merge: ticket role launch input split
This commit is contained in:
commit
bdbd955bc6
|
|
@ -4,7 +4,6 @@
|
|||
//! host-side Pod spawning behind the `client` crate so UI callers do not need to
|
||||
//! depend on `pod` internals.
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
|
@ -39,13 +38,12 @@ impl TicketRef {
|
|||
non_empty(self.id.as_deref())
|
||||
}
|
||||
|
||||
fn append_prompt_lines(&self, out: &mut String, prompts: &TicketRolePromptTemplates) {
|
||||
fn append_submit_lines(&self, out: &mut String) {
|
||||
match non_empty(self.id.as_deref()) {
|
||||
None => out.push_str("Target Ticket: not specified\n"),
|
||||
Some(id) => {
|
||||
out.push_str("Target Ticket:\n");
|
||||
push_bounded_bullet(out, "id", id);
|
||||
push_prompt_fragment(out, &prompts.ticket_id_guidance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,11 +64,10 @@ impl TicketIntakeHandoff {
|
|||
}
|
||||
}
|
||||
|
||||
fn append_prompt_lines(&self, out: &mut String, prompts: &TicketRolePromptTemplates) {
|
||||
fn append_submit_lines(&self, out: &mut String) {
|
||||
out.push_str("\nPanel handoff:\n");
|
||||
push_bounded_bullet(out, "workspace", &self.workspace_label);
|
||||
push_bounded_bullet(out, "workspace_orchestrator_pod", &self.orchestrator_pod);
|
||||
push_prompt_fragment(out, &prompts.intake_handoff);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +283,7 @@ pub fn plan_ticket_role_launch_with_config(
|
|||
None => default_pod_name(context.role, context.ticket.as_ref()),
|
||||
};
|
||||
validate_ticket_role_profile(context.role, &profile, &context.workspace_root, &pod_name)?;
|
||||
let prompt = build_launch_prompt(&context, &profile, &workflow, launch_prompt_ref.as_deref());
|
||||
let prompt = build_launch_prompt(&context);
|
||||
|
||||
let original_workspace_root = context.original_workspace_root().to_path_buf();
|
||||
let target_workspace_root = context.target_workspace_root().to_path_buf();
|
||||
|
|
@ -499,154 +496,31 @@ async fn wait_for_run_acceptance(
|
|||
.map_err(|_| TicketRoleLaunchError::RunAcceptanceTimeout)?
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TicketRolePromptTemplates {
|
||||
launch_preamble: String,
|
||||
ticket_id_guidance: String,
|
||||
record_language_configured: String,
|
||||
record_language_unconfigured: String,
|
||||
intake_handoff: String,
|
||||
orchestrator_worktree_routing: String,
|
||||
orchestrator_merge_completion: String,
|
||||
coder_worktree_routing: String,
|
||||
reviewer_worktree_routing: String,
|
||||
}
|
||||
|
||||
impl TicketRolePromptTemplates {
|
||||
fn load(workspace_root: &Path) -> Self {
|
||||
Self {
|
||||
launch_preamble: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"launch_preamble",
|
||||
include_str!("../../../resources/prompts/ticket_role/launch_preamble.md"),
|
||||
),
|
||||
ticket_id_guidance: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"ticket_id_guidance",
|
||||
include_str!("../../../resources/prompts/ticket_role/ticket_id_guidance.md"),
|
||||
),
|
||||
record_language_configured: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"record_language_configured",
|
||||
include_str!(
|
||||
"../../../resources/prompts/ticket_role/record_language_configured.md"
|
||||
),
|
||||
),
|
||||
record_language_unconfigured: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"record_language_unconfigured",
|
||||
include_str!(
|
||||
"../../../resources/prompts/ticket_role/record_language_unconfigured.md"
|
||||
),
|
||||
),
|
||||
intake_handoff: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"intake_handoff",
|
||||
include_str!("../../../resources/prompts/ticket_role/intake_handoff.md"),
|
||||
),
|
||||
orchestrator_worktree_routing: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"orchestrator_worktree_routing",
|
||||
include_str!(
|
||||
"../../../resources/prompts/ticket_role/orchestrator_worktree_routing.md"
|
||||
),
|
||||
),
|
||||
orchestrator_merge_completion: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"orchestrator_merge_completion",
|
||||
include_str!(
|
||||
"../../../resources/prompts/ticket_role/orchestrator_merge_completion.md"
|
||||
),
|
||||
),
|
||||
coder_worktree_routing: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"coder_worktree_routing",
|
||||
include_str!("../../../resources/prompts/ticket_role/coder_worktree_routing.md"),
|
||||
),
|
||||
reviewer_worktree_routing: load_ticket_role_prompt(
|
||||
workspace_root,
|
||||
"reviewer_worktree_routing",
|
||||
include_str!("../../../resources/prompts/ticket_role/reviewer_worktree_routing.md"),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_ticket_role_prompt(workspace_root: &Path, name: &str, builtin: &str) -> String {
|
||||
let relative = Path::new("ticket_role").join(format!("{name}.md"));
|
||||
for candidate in [
|
||||
Some(workspace_root.join(".yoi/prompts").join(&relative)),
|
||||
manifest::paths::user_prompts_dir().map(|dir| dir.join(&relative)),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Ok(text) = fs::read_to_string(candidate) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
builtin.to_string()
|
||||
}
|
||||
|
||||
fn push_prompt_fragment(out: &mut String, fragment: &str) {
|
||||
out.push_str(fragment.trim_end());
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
fn build_launch_prompt(
|
||||
context: &TicketRoleLaunchContext,
|
||||
profile: &str,
|
||||
workflow: &str,
|
||||
launch_prompt_ref: Option<&str>,
|
||||
) -> String {
|
||||
let prompts = TicketRolePromptTemplates::load(&context.workspace_root);
|
||||
fn build_launch_prompt(context: &TicketRoleLaunchContext) -> String {
|
||||
let mut out = String::new();
|
||||
push_prompt_fragment(&mut out, &prompts.launch_preamble);
|
||||
out.push('\n');
|
||||
push_bounded_field(&mut out, "Role", context.role.as_str());
|
||||
push_bounded_field(&mut out, "Profile selector", profile);
|
||||
push_bounded_field(&mut out, "Workflow", workflow);
|
||||
match launch_prompt_ref {
|
||||
Some(prompt_ref) => push_bounded_field(
|
||||
&mut out,
|
||||
"Configured launch_prompt ref (unresolved)",
|
||||
prompt_ref,
|
||||
),
|
||||
None => out.push_str("Configured launch_prompt ref: none\n"),
|
||||
}
|
||||
out.push('\n');
|
||||
match non_empty(context.ticket_record_language.as_deref()) {
|
||||
Some(language) => {
|
||||
push_bounded_field(&mut out, "Ticket record language", language);
|
||||
push_prompt_fragment(&mut out, &prompts.record_language_configured);
|
||||
}
|
||||
None => push_prompt_fragment(&mut out, &prompts.record_language_unconfigured),
|
||||
}
|
||||
out.push('\n');
|
||||
|
||||
if let Some(ticket) = &context.ticket {
|
||||
ticket.append_prompt_lines(&mut out, &prompts);
|
||||
ticket.append_submit_lines(&mut out);
|
||||
} else {
|
||||
out.push_str("Target Ticket: not specified\n");
|
||||
}
|
||||
|
||||
append_workspace_routing_context(&mut out, context);
|
||||
|
||||
match non_empty(context.user_instruction.as_deref()) {
|
||||
Some(instruction) => push_bounded_section(&mut out, "User/action instruction", instruction),
|
||||
None => out.push_str("\nUser/action instruction: not specified\n"),
|
||||
if let Some(instruction) = non_empty(context.user_instruction.as_deref()) {
|
||||
push_bounded_section(&mut out, "Action instruction", instruction);
|
||||
}
|
||||
|
||||
if let Some(handoff) = &context.intake_handoff {
|
||||
handoff.append_prompt_lines(&mut out, &prompts);
|
||||
handoff.append_submit_lines(&mut out);
|
||||
}
|
||||
|
||||
if let Some(intent_packet) = non_empty(context.intent_packet.as_deref()) {
|
||||
push_bounded_section(&mut out, "Intent packet", intent_packet);
|
||||
}
|
||||
|
||||
append_operation_targets(&mut out, context);
|
||||
|
||||
if context.worktree_path.is_some() || non_empty(context.branch.as_deref()).is_some() {
|
||||
out.push_str("\nWorktree context:\n");
|
||||
out.push_str("\nWorktree target:\n");
|
||||
if let Some(path) = &context.worktree_path {
|
||||
push_bounded_bullet(&mut out, "path", &path.display().to_string());
|
||||
}
|
||||
|
|
@ -666,72 +540,29 @@ fn build_launch_prompt(
|
|||
);
|
||||
}
|
||||
|
||||
append_role_execution_guidance(&mut out, context.role, &prompts);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn append_workspace_routing_context(out: &mut String, context: &TicketRoleLaunchContext) {
|
||||
let original_workspace_root = context.original_workspace_root();
|
||||
let implementation_worktree_root = context.implementation_worktree_root();
|
||||
let should_emit = context.original_workspace_root.is_some()
|
||||
|| context.target_workspace_root.is_some()
|
||||
|| context.role == TicketRole::Orchestrator;
|
||||
if !should_emit {
|
||||
fn append_operation_targets(out: &mut String, context: &TicketRoleLaunchContext) {
|
||||
if context.role != TicketRole::Orchestrator {
|
||||
return;
|
||||
}
|
||||
if context.original_workspace_root.is_none() && context.target_workspace_root.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
out.push_str("\nWorkspace routing context:\n");
|
||||
push_bounded_bullet(
|
||||
out,
|
||||
"role_workspace_root",
|
||||
&context.workspace_root.display().to_string(),
|
||||
);
|
||||
if let Some(cwd) = &context.cwd {
|
||||
push_bounded_bullet(out, "role_cwd", &cwd.display().to_string());
|
||||
}
|
||||
push_bounded_bullet(
|
||||
out,
|
||||
"original_workspace_root",
|
||||
&original_workspace_root.display().to_string(),
|
||||
);
|
||||
out.push_str("\nOrchestrator operation targets:\n");
|
||||
push_bounded_bullet(
|
||||
out,
|
||||
"implementation_worktree_root",
|
||||
&implementation_worktree_root.display().to_string(),
|
||||
&context.implementation_worktree_root().display().to_string(),
|
||||
);
|
||||
if context.role == TicketRole::Orchestrator {
|
||||
out.push_str(
|
||||
"- Treat `role_workspace_root` / `role_cwd` as the Orchestrator workspace on the orchestration branch. Use `implementation_worktree_root` only as the placement root for child implementation worktrees; do not operate on `original_workspace_root` itself. Root/original workspace reads, writes, validation, cleanup, and git operations are prohibited. Create implementation branches from the Orchestrator workspace current HEAD / orchestration branch HEAD and integrate reviewed work back into that orchestration branch automatically.\n",
|
||||
if context.target_workspace_root.is_some() {
|
||||
push_bounded_bullet(
|
||||
out,
|
||||
"merge_target_workspace_root",
|
||||
&context.target_workspace_root().display().to_string(),
|
||||
);
|
||||
} else {
|
||||
out.push_str(
|
||||
"- Treat `role_workspace_root` as the launched role runtime workspace/Ticket backend root. Create implementation worktrees under `implementation_worktree_root`, not relative to the role cwd, and run role work against the recorded workspace routing context.\n",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn append_role_execution_guidance(
|
||||
out: &mut String,
|
||||
role: TicketRole,
|
||||
prompts: &TicketRolePromptTemplates,
|
||||
) {
|
||||
match role {
|
||||
TicketRole::Orchestrator => {
|
||||
out.push('\n');
|
||||
push_prompt_fragment(out, &prompts.orchestrator_worktree_routing);
|
||||
out.push('\n');
|
||||
push_prompt_fragment(out, &prompts.orchestrator_merge_completion);
|
||||
}
|
||||
TicketRole::Coder => {
|
||||
out.push('\n');
|
||||
push_prompt_fragment(out, &prompts.coder_worktree_routing);
|
||||
}
|
||||
TicketRole::Reviewer => {
|
||||
out.push('\n');
|
||||
push_prompt_fragment(out, &prompts.reviewer_worktree_routing);
|
||||
}
|
||||
TicketRole::Intake => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -776,13 +607,6 @@ fn sanitise_pod_name_component(value: &str) -> String {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn push_bounded_field(out: &mut String, label: &str, value: &str) {
|
||||
out.push_str(label);
|
||||
out.push_str(": ");
|
||||
out.push_str(&bounded(value));
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
fn push_bounded_bullet(out: &mut String, label: &str, value: &str) {
|
||||
out.push_str("- ");
|
||||
out.push_str(label);
|
||||
|
|
@ -1096,7 +920,7 @@ profile = "project:no-such-ticket-role-profile"
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn configured_ticket_record_language_is_included_in_role_prompt() {
|
||||
fn ticket_record_language_stays_out_of_first_run_text() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_config(
|
||||
temp.path(),
|
||||
|
|
@ -1113,11 +937,9 @@ profile = "builtin:default"
|
|||
let plan = plan_ticket_role_launch(context).unwrap();
|
||||
let text = text_segment(&plan);
|
||||
|
||||
assert!(text.contains("Ticket record language: Japanese"));
|
||||
assert!(text.contains("write durable Ticket item/thread/resolution text"));
|
||||
assert!(text.contains("does not change normal worker response language"));
|
||||
assert!(text.contains("memory/Knowledge generation language"));
|
||||
assert!(text.contains("Do not translate protocol literals"));
|
||||
assert!(!text.contains("Ticket record language"));
|
||||
assert!(!text.contains("Japanese"));
|
||||
assert!(!text.contains("write durable Ticket item/thread/resolution text"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1168,7 +990,7 @@ profile = "builtin:default"
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn configured_role_refs_are_exposed_in_plan_and_prompt() {
|
||||
fn configured_role_refs_are_plan_metadata_not_submit_text() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_config(
|
||||
temp.path(),
|
||||
|
|
@ -1198,12 +1020,16 @@ workflow = "ticket-review-workflow"
|
|||
&plan.run_segments[0],
|
||||
Segment::WorkflowInvoke { slug } if slug == "ticket-review-workflow"
|
||||
));
|
||||
assert!(text.contains(
|
||||
"Configured launch_prompt ref (unresolved): $workspace/ticket/reviewer/launch"
|
||||
));
|
||||
assert!(text.contains("Workflow: ticket-review-workflow"));
|
||||
assert!(text.contains("Profile selector: builtin:default"));
|
||||
assert!(!text.contains("Configured launch_prompt"));
|
||||
assert!(!text.contains("$workspace/ticket/reviewer/launch"));
|
||||
assert!(!text.contains("Workflow: ticket-review-workflow"));
|
||||
assert!(!text.contains("Profile selector: builtin:default"));
|
||||
assert!(!text.contains("Role: reviewer"));
|
||||
assert!(!text.contains("system_instruction"));
|
||||
assert!(text.contains("Target Ticket:"));
|
||||
assert!(text.contains("id: 20260605-190330-ticket-role-pod-launcher"));
|
||||
assert!(text.contains("Action instruction:"));
|
||||
assert!(text.contains("Review the submitted implementation."));
|
||||
let spawn = plan
|
||||
.spawn_config(PodRuntimeCommand::for_executable("/bin/yoi"))
|
||||
.unwrap();
|
||||
|
|
@ -1214,7 +1040,7 @@ workflow = "ticket-review-workflow"
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn generated_prompt_covers_intake_orchestrator_coder_and_reviewer_context() {
|
||||
fn submit_text_contains_only_ticket_action_and_per_launch_context() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_builtin_role_config(
|
||||
temp.path(),
|
||||
|
|
@ -1230,9 +1056,11 @@ workflow = "ticket-review-workflow"
|
|||
intake.user_instruction = Some("Clarify and materialize this request as a Ticket.".into());
|
||||
let intake_plan = plan_ticket_role_launch(intake).unwrap();
|
||||
let intake_text = text_segment(&intake_plan);
|
||||
assert!(intake_text.contains("Role: intake"));
|
||||
assert!(intake_text.contains("Action instruction:"));
|
||||
assert!(intake_text.contains("Clarify and materialize"));
|
||||
assert!(intake_text.contains("Workflow: ticket-intake-workflow"));
|
||||
assert!(!intake_text.contains("Workflow:"));
|
||||
assert!(!intake_text.contains("Profile selector:"));
|
||||
assert!(!intake_text.contains("Role:"));
|
||||
|
||||
let mut handoff_intake = TicketRoleLaunchContext::new(temp.path(), TicketRole::Intake);
|
||||
handoff_intake.intake_handoff = Some(TicketIntakeHandoff::new(
|
||||
|
|
@ -1244,13 +1072,9 @@ workflow = "ticket-review-workflow"
|
|||
assert!(handoff_text.contains("Panel handoff:"));
|
||||
assert!(handoff_text.contains("workspace_orchestrator_pod: panel-orchestrator-demo"));
|
||||
assert!(handoff_text.contains("workspace: Demo workspace"));
|
||||
assert!(handoff_text.contains("created_or_updated_ticket_id"));
|
||||
assert!(handoff_text.contains("state"));
|
||||
assert!(handoff_text.contains("Ticket tool surface"));
|
||||
assert!(handoff_text.contains("ready -> queued"));
|
||||
assert!(handoff_text.contains("queued` as schedulable"));
|
||||
assert!(!handoff_text.contains("user_go_required"));
|
||||
assert!(!handoff_text.contains("human Go gates"));
|
||||
assert!(!handoff_text.contains("created_or_updated_ticket_id"));
|
||||
assert!(!handoff_text.contains("Ticket tool surface"));
|
||||
assert!(!handoff_text.contains("ready -> queued"));
|
||||
|
||||
let mut orchestrator = TicketRoleLaunchContext::new(temp.path(), TicketRole::Orchestrator);
|
||||
orchestrator.ticket = Some(TicketRef::id("launcher"));
|
||||
|
|
@ -1258,27 +1082,15 @@ workflow = "ticket-review-workflow"
|
|||
orchestrator.validation = vec!["cargo check --workspace --all-targets".into()];
|
||||
let orchestrator_plan = plan_ticket_role_launch(orchestrator).unwrap();
|
||||
let orchestrator_text = text_segment(&orchestrator_plan);
|
||||
assert!(orchestrator_text.contains("Role: orchestrator"));
|
||||
assert!(orchestrator_text.contains("id: launcher"));
|
||||
assert!(orchestrator_text.contains("Route to implementation after planning sync."));
|
||||
assert!(orchestrator_text.contains("cargo check --workspace --all-targets"));
|
||||
assert!(orchestrator_text.contains("state = inprogress"));
|
||||
assert!(orchestrator_text.contains("worktree-workflow"));
|
||||
assert!(orchestrator_text.contains("keep tracked `.yoi` project records visible"));
|
||||
assert!(orchestrator_text.contains("exclude `.yoi/memory`"));
|
||||
assert!(
|
||||
orchestrator_text
|
||||
.contains("prohibit creating generated memory/local/runtime/secret-like files")
|
||||
);
|
||||
assert!(orchestrator_text.contains("multi-agent-workflow"));
|
||||
assert!(orchestrator_text.contains("coder and reviewer are siblings"));
|
||||
assert!(orchestrator_text.contains("branch-local reviewer verdicts"));
|
||||
assert!(orchestrator_text.contains("binding decisions/invariants"));
|
||||
assert!(orchestrator_text.contains("not unrecorded preferred tactics"));
|
||||
assert!(orchestrator_text.contains("integration outcome"));
|
||||
assert!(orchestrator_text.contains(
|
||||
"integrate the implementation branch into the orchestration branch automatically"
|
||||
));
|
||||
assert!(orchestrator_text.contains("Root/original workspace reads, writes, validation, cleanup, and git operations are prohibited"));
|
||||
assert!(!orchestrator_text.contains("state = inprogress"));
|
||||
assert!(!orchestrator_text.contains("worktree-workflow"));
|
||||
assert!(!orchestrator_text.contains("multi-agent-workflow"));
|
||||
assert!(!orchestrator_text.contains("root/original workspace reads"));
|
||||
assert!(!orchestrator_text.contains("role_workspace_root"));
|
||||
assert!(!orchestrator_text.contains("role_cwd"));
|
||||
|
||||
let mut coder = TicketRoleLaunchContext::new(temp.path(), TicketRole::Coder);
|
||||
coder.ticket = Some(TicketRef::id("20260605-190330-ticket-role-pod-launcher"));
|
||||
|
|
@ -1288,17 +1100,13 @@ workflow = "ticket-review-workflow"
|
|||
coder.report_expectations = vec!["implementation report with validation".into()];
|
||||
let coder_plan = plan_ticket_role_launch(coder).unwrap();
|
||||
let coder_text = text_segment(&coder_plan);
|
||||
assert!(coder_text.contains("Role: coder"));
|
||||
assert!(coder_text.contains("path: /tmp/yoi-code"));
|
||||
assert!(coder_text.contains("branch: work/ticket-role-pod-launcher"));
|
||||
assert!(coder_text.contains("cargo test -p client ticket_role"));
|
||||
assert!(coder_text.contains("provided child worktree/branch"));
|
||||
assert!(coder_text.contains("do not edit main-workspace `.yoi`"));
|
||||
assert!(coder_text.contains("child-worktree `.yoi` project records may be visible"));
|
||||
assert!(coder_text.contains("Do not create `.yoi/memory`"));
|
||||
assert!(coder_text.contains("implementation latitude"));
|
||||
assert!(coder_text.contains("choose local tactics"));
|
||||
assert!(coder_text.contains("Do not merge, push, close Tickets, or delete worktrees"));
|
||||
assert!(coder_text.contains("implementation report with validation"));
|
||||
assert!(!coder_text.contains("provided child worktree/branch"));
|
||||
assert!(!coder_text.contains("choose local tactics"));
|
||||
assert!(!coder_text.contains("Do not merge, push"));
|
||||
|
||||
let mut reviewer = TicketRoleLaunchContext::new(temp.path(), TicketRole::Reviewer);
|
||||
reviewer.ticket = Some(TicketRef::id("20260605-190330-ticket-role-pod-launcher"));
|
||||
|
|
@ -1307,22 +1115,16 @@ workflow = "ticket-review-workflow"
|
|||
reviewer.report_expectations = vec!["approve or request changes".into()];
|
||||
let reviewer_plan = plan_ticket_role_launch(reviewer).unwrap();
|
||||
let reviewer_text = text_segment(&reviewer_plan);
|
||||
assert!(reviewer_text.contains("Role: reviewer"));
|
||||
assert!(reviewer_text.contains("path: /tmp/yoi-review"));
|
||||
assert!(reviewer_text.contains("branch: work/ticket-role-pod-launcher"));
|
||||
assert!(reviewer_text.contains("approve or request changes"));
|
||||
assert!(reviewer_text.contains("read-only by default"));
|
||||
assert!(reviewer_text.contains("recorded intent, binding decisions/invariants"));
|
||||
assert!(reviewer_text.contains("not unrecorded preferred tactics"));
|
||||
assert!(reviewer_text.contains("Orchestrator-side integration"));
|
||||
assert!(
|
||||
reviewer_text
|
||||
.contains("Do not merge, close, push, operate on the root/original workspace")
|
||||
);
|
||||
assert!(!reviewer_text.contains("read-only by default"));
|
||||
assert!(!reviewer_text.contains("Orchestrator-side integration"));
|
||||
assert!(!reviewer_text.contains("Do not merge, close"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orchestrator_prompt_covers_orchestration_branch_integration_boundary() {
|
||||
fn orchestrator_submit_exposes_operation_targets_without_runtime_workspace_context() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_builtin_role_config(temp.path(), &[TicketRole::Orchestrator]);
|
||||
let mut orchestrator = TicketRoleLaunchContext::new(temp.path(), TicketRole::Orchestrator);
|
||||
|
|
@ -1348,50 +1150,17 @@ workflow = "ticket-review-workflow"
|
|||
assert_eq!(spawn_config.workspace_root, temp.path());
|
||||
assert_eq!(spawn_config.cwd, None);
|
||||
|
||||
assert!(text.contains("Workspace routing context:"));
|
||||
assert!(text.contains("role_workspace_root"));
|
||||
assert!(text.contains("Orchestrator operation targets:"));
|
||||
assert!(text.contains("implementation_worktree_root"));
|
||||
assert!(text.contains("Orchestrator workspace on the orchestration branch"));
|
||||
assert!(text.contains("Root/original workspace reads, writes, validation, cleanup, and git operations are prohibited"));
|
||||
assert!(text.contains("Create implementation branches from the Orchestrator workspace current HEAD / orchestration branch HEAD"));
|
||||
assert!(
|
||||
text.contains(
|
||||
"integrate reviewed work back into that orchestration branch automatically"
|
||||
)
|
||||
);
|
||||
assert!(text.contains("Orchestrator implementation integration guidance"));
|
||||
assert!(
|
||||
text.contains("Integrate only within the Orchestrator workspace/orchestration branch")
|
||||
);
|
||||
assert!(text.contains("root/original workspace is not an integration target"));
|
||||
assert!(text.contains("merge or otherwise integrate that implementation branch into the orchestration branch automatically"));
|
||||
assert!(text.contains(
|
||||
"Run post-integration validation from the Orchestrator workspace/orchestration branch"
|
||||
));
|
||||
assert!(text.contains("Cleanup is limited to the child implementation worktree/branch"));
|
||||
assert!(text.contains("merge_target_workspace_root"));
|
||||
assert!(!text.contains("Workspace routing context:"));
|
||||
assert!(!text.contains("role_workspace_root"));
|
||||
assert!(!text.contains("role_cwd"));
|
||||
assert!(!text.contains("original_workspace_root"));
|
||||
assert!(!text.contains("Orchestrator workspace on the orchestration branch"));
|
||||
assert!(!text.contains("Root/original workspace reads, writes, validation, cleanup, and git operations are prohibited"));
|
||||
assert!(!text.contains("Orchestrator implementation integration guidance"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_prompt_override_replaces_ticket_role_fragment() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_builtin_role_config(temp.path(), &[TicketRole::Orchestrator]);
|
||||
let override_dir = temp.path().join(".yoi/prompts/ticket_role");
|
||||
std::fs::create_dir_all(&override_dir).unwrap();
|
||||
std::fs::write(
|
||||
override_dir.join("orchestrator_worktree_routing.md"),
|
||||
"Orchestrator worktree + agent routing guidance:\n- WORKSPACE OVERRIDE MARKER\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut orchestrator = TicketRoleLaunchContext::new(temp.path(), TicketRole::Orchestrator);
|
||||
orchestrator.ticket = Some(TicketRef::id("prompt-resource-override"));
|
||||
let plan = plan_ticket_role_launch(orchestrator).unwrap();
|
||||
let text = text_segment(&plan);
|
||||
|
||||
assert!(text.contains("WORKSPACE OVERRIDE MARKER"));
|
||||
assert!(!text.contains("Use `multi-agent-workflow`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_provided_pod_name_is_used_exactly() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
|
|
|||
|
|
@ -1588,6 +1588,10 @@ mod tests {
|
|||
Some(expected)
|
||||
);
|
||||
assert_eq!(resolved.manifest.pod.name, "role-pod");
|
||||
if matches!(expected, "intake" | "orchestrator" | "coder" | "reviewer") {
|
||||
let expected_instruction = format!("$yoi/role/{expected}");
|
||||
assert_eq!(resolved.manifest.worker.instruction, expected_instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,16 @@ mod tests {
|
|||
assert!(!source.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_ticket_role_instructions_resolve() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
for role in ["intake", "orchestrator", "coder", "reviewer"] {
|
||||
let (reference, source) = loader.resolve(&format!("$yoi/role/{role}"), None).unwrap();
|
||||
assert_eq!(reference.to_qualified_string(), format!("$yoi/role/{role}"));
|
||||
assert!(source.contains("first committed user message"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_subdirectory_lookup() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ return yoi.profile.extend("builtin:default", {
|
|||
|
||||
scope = yoi.scope.workspace_write(),
|
||||
|
||||
worker = {
|
||||
instruction = "$yoi/role/coder",
|
||||
},
|
||||
|
||||
feature = {
|
||||
task = { enabled = true },
|
||||
memory = { enabled = true },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ return yoi.profile.extend("builtin:default", {
|
|||
|
||||
scope = yoi.scope.workspace_read(),
|
||||
|
||||
worker = {
|
||||
instruction = "$yoi/role/intake",
|
||||
},
|
||||
|
||||
feature = {
|
||||
task = { enabled = false },
|
||||
memory = { enabled = true },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ return yoi.profile.extend("builtin:default", {
|
|||
|
||||
scope = yoi.scope.workspace_read(),
|
||||
|
||||
worker = {
|
||||
instruction = "$yoi/role/orchestrator",
|
||||
},
|
||||
|
||||
feature = {
|
||||
task = { enabled = false },
|
||||
memory = { enabled = true },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ return yoi.profile.extend("builtin:default", {
|
|||
|
||||
scope = yoi.scope.workspace_read(),
|
||||
|
||||
worker = {
|
||||
instruction = "$yoi/role/reviewer",
|
||||
},
|
||||
|
||||
feature = {
|
||||
task = { enabled = false },
|
||||
memory = { enabled = true },
|
||||
|
|
|
|||
7
resources/prompts/role/coder.md
Normal file
7
resources/prompts/role/coder.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
You are the Ticket Coder role.
|
||||
|
||||
Keep role behavior here and treat the first committed user message as concrete Ticket/action context only. Implement only within the delegated worktree/branch and authority scope. Treat the Ticket, intent packet, binding decisions/invariants, implementation latitude, validation expectations, and report expectations as the contract.
|
||||
|
||||
Choose local implementation tactics within that contract. Escalate to the Orchestrator instead of expanding scope when design, permission, dependency, prompt-boundary, or Ticket-boundary questions appear. Do not merge, push, close Tickets, delete worktrees, or create generated memory/local/runtime/log/lock/cache/socket/secret-like `.yoi` state.
|
||||
|
||||
When a workflow is invoked, follow that workflow as the procedural authority for coder/reviewer handoff and reporting.
|
||||
5
resources/prompts/role/intake.md
Normal file
5
resources/prompts/role/intake.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
You are the Ticket Intake role.
|
||||
|
||||
Keep role behavior here and treat the first committed user message as concrete Ticket/action context only. Clarify ambiguous user requests, create or update the appropriate Ticket through typed Ticket tools, and leave implementation side effects to the user/Orchestrator queue flow. Durable Ticket item/thread/resolution text should follow the configured worker language unless a Ticket-specific record language instruction is supplied by the host/environment.
|
||||
|
||||
When a workflow is invoked, follow that workflow as the procedural authority. Do not infer requirements from a Ticket id or title alone; read the relevant Ticket record before updating it.
|
||||
7
resources/prompts/role/orchestrator.md
Normal file
7
resources/prompts/role/orchestrator.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
You are the Ticket Orchestrator role.
|
||||
|
||||
Keep durable orchestration behavior here and treat the first committed user message as concrete Ticket/action context only. Use typed Ticket tools and current repository state as authority. Record `inprogress` before implementation side effects, route concrete work to sibling Coder/Reviewer Pods when appropriate, and stop for human authority when merge/closure is not explicitly delegated.
|
||||
|
||||
Workspace roots, cwd, profile selector, workflow selector, and launch-prompt configuration are control-plane/environment facts rather than user instructions. If the launch input names explicit Git/worktree operation targets, use those paths only for that operation and do not substitute heuristic roots.
|
||||
|
||||
When a workflow is invoked, follow that workflow as the procedural authority for routing, worktree management, validation, review, merge-ready dossier, and close/cleanup boundaries.
|
||||
5
resources/prompts/role/reviewer.md
Normal file
5
resources/prompts/role/reviewer.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
You are the Ticket Reviewer role.
|
||||
|
||||
Keep role behavior here and treat the first committed user message as concrete Ticket/action context only. Review the implementation against the Ticket intent, binding decisions/invariants, acceptance criteria, and project design boundaries. Prefer read-only inspection and focused validation; do not merge, close, clean up worktrees, or take over implementation unless explicitly asked.
|
||||
|
||||
Report clear approve/request-changes evidence with risks, validation performed, and any unresolved requirement or design-boundary concern. When a workflow is invoked, follow that workflow as the procedural authority for reviewer handoff and report shape.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
Coder worktree routing guidance:
|
||||
- Implement only in the provided child worktree/branch. SpawnPod should set `cwd` to that worktree so Bash/tool defaults already start there; do not treat `cwd` as authority, and do not edit main-workspace `.yoi`, Ticket, workflow, docs, or memory records; child-worktree `.yoi` project records may be visible when they are part of the branch.
|
||||
- Do not create `.yoi/memory`, local/runtime state, logs, locks, caches, sockets, or secret-like files in the child worktree.
|
||||
- Treat the intent packet, binding decisions/invariants, implementation latitude, validation expectations, and report expectations as the contract. Investigate and choose local tactics only within the recorded implementation latitude; escalate to Orchestrator rather than expanding scope when design, permission, history, prompt-context, dependency, or Ticket-boundary questions appear.
|
||||
- Report worktree path, branch, commits/status, changed files, implementation summary, validation run, unresolved notes, and whether the branch is ready for external review. Do not merge, push, close Tickets, or delete worktrees.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
- When Intake has clarified the request and created/updated the Ticket, use the typed Ticket tool surface to append `intake_summary` and set `state = ready` when the Ticket is ready to queue; use planning language for Tickets that still need clarification/preparation.
|
||||
- Handoff report fields: created_or_updated_ticket_id, state, open_questions_or_risk_flags, intake_summary.
|
||||
- Do not start implementation automatically; the user queues a ready Ticket via panel (`ready -> queued`), and Orchestrator treats `queued` as schedulable before moving it to `inprogress` when starting.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Ticket role launch
|
||||
|
||||
Profile supplies durable system/role behavior. The workflow segment supplies the procedural flow. This generated launch prompt supplies only the concrete Ticket/action context for the first committed user task.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
Orchestrator implementation integration guidance:
|
||||
- Integrate only within the Orchestrator workspace/orchestration branch. The root/original workspace is not an integration target and must not be read, written, validated, cleaned, or touched with git.
|
||||
- Treat the child implementation branch as work targeting the orchestration branch. After coder completion, reviewer approval, and blocker resolution, merge or otherwise integrate that implementation branch into the orchestration branch automatically.
|
||||
- Before integration, verify the dossier identities: Ticket id/title/state, child worktree path, child branch name, commits/diff, reviewer verdict, validation evidence, and any unresolved blockers.
|
||||
- Run post-integration validation from the Orchestrator workspace/orchestration branch. Do not run validation from the root/original workspace and do not rely on root workspace state for the decision.
|
||||
- Record the integration result, validation evidence, and any remaining risks in the Ticket thread visible in the Orchestrator worktree. If the Ticket requirements are satisfied, advance the Ticket lifecycle in that worktree.
|
||||
- Cleanup is limited to the child implementation worktree/branch and related child Pods. Do not remove, reset, merge, fast-forward, close, or otherwise mutate the root/original workspace.
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
Orchestrator worktree + agent routing guidance:
|
||||
- Treat `ticket-orchestrator-routing` as the routing gate. Read the Ticket and Orchestrator workspace state first; `ready -> queued` authorizes routing, not implementation side effects.
|
||||
- Work only in the Orchestrator workspace/orchestration branch and child implementation worktrees. Do not operate on the original/root workspace: no reads for decision evidence, no writes, no validation, no Ticket edits, no cleanup, and no git operations there.
|
||||
- If the launch context includes `original_workspace_root` or `implementation_worktree_root`, use those paths only to choose child implementation worktree placement under `.worktree`. The root workspace itself is not a work target.
|
||||
- Create implementation branches from the Orchestrator workspace current HEAD / orchestration branch HEAD, not from root/develop. After reviewer approval and blocker resolution, integrate the implementation branch into the orchestration branch automatically; do not wait for root-side promotion.
|
||||
- Create worktrees or spawn coder/reviewer Pods only after `state = inprogress` is already recorded and accepted. If the Ticket is still queued and unblocked, record `queued -> inprogress` before any worktree/SpawnPod side effect.
|
||||
- Use `worktree-workflow` for the mechanical worktree plan: create the child implementation worktree under the recorded implementation worktree root, keep tracked `.yoi` project records visible in the child worktree, and exclude `.yoi/memory` plus local/runtime/log/lock/secret-like `.yoi` paths.
|
||||
- Use `multi-agent-workflow` for the sibling loop: coder and reviewer are siblings under this Orchestrator; coder gets narrow write scope to the child worktree; reviewer is read-only by default.
|
||||
- Give the coder an intent packet that distinguishes binding decisions/invariants, implementation latitude, escalation conditions, child worktree/branch, validation commands, and report expectations; set SpawnPod `cwd` to the child worktree while delegating explicit scope separately, prohibit editing root workspace records, and prohibit creating generated memory/local/runtime/secret-like files in the child worktree.
|
||||
- Give the reviewer the recorded Ticket intent, binding decisions/invariants, implementation latitude, acceptance criteria, explicit escalation conditions, diff/commits, validation evidence, and blocker/non-blocker criteria; reviewer judgment is against recorded requirements and decisions, not unrecorded preferred tactics. Keep branch-local reviewer verdicts in the review report or orchestration dossier.
|
||||
- Ticket thread progress may record worktree plan, coder delegated/completed/blocked, reviewer delegated, blocker/fix-loop summaries, integration outcome, validation evidence, and cleanup outcome in the Orchestrator workspace.
|
||||
|
|
@ -1 +0,0 @@
|
|||
Ticket record language guidance: write durable Ticket item/thread/resolution text and Ticket tool bodies in this language. This does not change normal worker response language or memory/Knowledge generation language. Do not translate protocol literals, file paths, commands, logs, identifiers, or quoted external text solely because this language is configured.
|
||||
|
|
@ -1 +0,0 @@
|
|||
Ticket record language: not configured; preserve existing/default Ticket record language behavior.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Reviewer worktree routing guidance:
|
||||
- Review as a sibling of the coder under Orchestrator, read-only by default. Read the Ticket/intent packet, branch diff or commits, and validation evidence before judging. Judge implementation against recorded intent, binding decisions/invariants, implementation latitude, acceptance criteria, and explicit escalation conditions, not unrecorded preferred tactics.
|
||||
- Classify findings as blockers, non-blocking follow-ups, or parent-decision items against the recorded intent, binding decisions/invariants, implementation latitude, acceptance criteria, and explicit escalation conditions; include concrete file/line evidence where useful.
|
||||
- Keep the branch-local reviewer verdict in the review report for Orchestrator-side integration. Do not merge, close, push, operate on the root/original workspace, or instruct the coder directly.
|
||||
|
|
@ -1 +0,0 @@
|
|||
- Treat the Ticket id as an opaque storage id. Read TicketShow body/thread/artifacts before routing or implementation; do not infer requirements from id or title alone.
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
---
|
||||
description: Public sibling coder/reviewer workflow
|
||||
model_invokation: false
|
||||
description: worktree と sibling の coder / reviewer Pod を使い、下位 orchestrator が concrete Ticket 群の実装・外部レビュー・修正・完了準備を管理する orchestration フロー
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: [workflow-resource-boundary]
|
||||
---
|
||||
# Multi-agent Workflow
|
||||
|
||||
Use sibling implementation and review roles for a bounded Ticket. The Orchestrator owns intent, acceptance boundaries, blocker decisions, final merge-completion authority, and cleanup. The coder implements within delegated scope; the reviewer checks the recorded Ticket intent and acceptance criteria rather than unrecorded preferences.
|
||||
# Multi-agent workflow
|
||||
|
||||
Produce a merge-ready dossier with Ticket id, branch/worktree, commits, implementation summary, reviewer verdict, validation evidence, residual risks, dirty state, and any remaining human decision needs.
|
||||
1. Orchestrator は concrete Ticket intent、binding decisions/invariants、implementation latitude、validation expectations、report expectations、worktree path、branch を Coder に渡す。Coder は delegated child worktree/branch のみを編集し、main/root workspace、merge、push、Ticket close、worktree cleanup、generated `.yoi` runtime/local/memory/log/lock/cache/socket/secret-like state を触らない。
|
||||
2. Coder は local tactics を選んで実装し、変更ファイル・summary・validation・commit/status・unresolved notes・review readiness を報告する。design/permission/history/prompt-context/dependency/Ticket-boundary の未決事項は Orchestrator に escalate する。
|
||||
3. Reviewer は Ticket/intent packet、binding decisions/invariants、acceptance criteria、diff/commits、validation evidence を読んでから判断する。実装を recorded requirements と project design boundary に照らして review し、blocker / non-blocking follow-up / parent-decision item を分類する。
|
||||
4. Reviewer report は approve/request-changes evidence、file/line evidence where useful、validation performed、risks、unresolved concerns を含める。Reviewer は merge、close、push、cleanup、root/original workspace operation、Coder への直接指示を行わない。
|
||||
5. Orchestrator は blocker fix loop、review approval、merge-ready dossier、integration/cleanup/close authority を管理する。下位 Pod の completion notification は transient hint とし、durable output/session/worktree state で確認する。
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
---
|
||||
description: Public Ticket intake requirements-sync workflow
|
||||
description: ユーザーの曖昧な依頼を要件同期し、合意済みの Ticket として作成・更新する Intake workflow
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: [workflow-resource-boundary]
|
||||
---
|
||||
# Ticket Intake Workflow
|
||||
|
||||
Clarify a user request until it can be represented as a concrete Ticket. Preserve user intent, write bounded requirements and acceptance criteria, and avoid creating duplicate or umbrella Tickets when a more concrete Ticket is appropriate.
|
||||
# Ticket intake workflow
|
||||
|
||||
Do not perform implementation side effects. If an existing Ticket is refined, make scope/readiness changes explicit and keep broad changes as drafts until user agreement is clear.
|
||||
1. ユーザー依頼と既存 Ticket を同期し、重複作成を避ける。既存 Ticket を対象にする場合は body/thread/artifacts を読んでから更新する。
|
||||
2. 要件・背景・受け入れ条件・未決事項を Ticket に記録する。実装手順は必要になるまで増やしすぎない。
|
||||
3. Ticket が queue 可能な粒度と明確さになったら、typed Ticket tool surface で intake summary を残し、`state = ready` にする。未決事項がある場合は planning に留め、必要な質問やリスクを明示する。
|
||||
4. Handoff report は `created_or_updated_ticket_id`、`state`、`open_questions_or_risk_flags`、`intake_summary` を含める。
|
||||
5. Intake は実装を開始しない。ユーザーが panel 等で `ready -> queued` し、Orchestrator が queued Ticket を routing する。
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
---
|
||||
description: Public Ticket orchestrator routing workflow
|
||||
description: Ticket を読み、Orchestrator が planning return / spike / implementation / review / blocked / close へ明示的に routing する workflow
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: [workflow-resource-boundary]
|
||||
---
|
||||
# Ticket Orchestrator Routing Workflow
|
||||
|
||||
Read the Ticket, relation metadata, orchestration-plan records, and relevant workspace state before deciding the next action. Treat `queued -> inprogress` as the implementation acceptance marker and record it before worktree creation, role Pod spawn, or other implementation side effects.
|
||||
# Ticket orchestrator routing workflow
|
||||
|
||||
Classify the Ticket as planning return, blocked, spike, implementation-ready, review-needed, close-ready, or noop. If implementation-ready, record an IntentPacket with binding decisions, implementation latitude, acceptance criteria, escalation conditions, validation, and reviewer focus.
|
||||
1. Ticket body/thread/artifacts/resolution 状態を読み、current state と blocking relation を確認する。queued Ticket は実装 side effect 前に `inprogress` を記録する。
|
||||
2. 不足情報が具体的にある場合は planning return とし、何が足りないかを Ticket thread に残す。risk flag は context lookup・review focus・invariant・escalation として扱い、自動 stop gate にしない。
|
||||
3. 実装に進む場合は Orchestrator workspace/orchestration HEAD を基準にし、launch input の explicit Git/worktree operation target だけを使う。role workspace/cwd は環境情報であり、original/root workspace を読み書き・検証・cleanup・git 操作対象にしない。
|
||||
4. child implementation worktree は recorded implementation worktree root 配下に作成し、implementation branch は Orchestrator workspace current HEAD / orchestration branch HEAD から切る。Coder/Reviewer は sibling Pods として route し、Ticket intent・binding decisions・validation/report expectations を concrete handoff として渡す。
|
||||
5. Reviewer が request-changes した場合は、scope 内で Coder に修正を戻すか、Orchestrator が blocked/escalation を記録する。approve evidence が揃うまで merge-ready とみなさない。
|
||||
6. merge completion authority が無い場合は、Ticket/branch/commit identity、intent/invariant check、implementation summary、coder/reviewer evidence、blocker resolution、validation、risks、dirty state、decision needs を含む merge-ready dossier で停止する。
|
||||
7. 明示 authority と clean reviewer evidence がある場合のみ、merge target workspace への統合、validation、Ticket close/completion processing、関連 Pod 停止、scope reclamation、worktree/branch cleanup、evidence reporting を進める。
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user