From de0f533bc3c1771de9ce31dff0612cbab6e62fd0 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 17:45:39 +0900 Subject: [PATCH 1/9] ticket: route panel queue sync --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KTWPE3KQ/item.md | 4 +- .yoi/tickets/00001KTWPE3KQ/thread.md | 83 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTWPE3KQ/artifacts/orchestration-plan.jsonl diff --git a/.yoi/tickets/00001KTWPE3KQ/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KTWPE3KQ/artifacts/orchestration-plan.jsonl new file mode 100644 index 00000000..1429fa53 --- /dev/null +++ b/.yoi/tickets/00001KTWPE3KQ/artifacts/orchestration-plan.jsonl @@ -0,0 +1 @@ +{"id":"orch-plan-20260612-084329-1","ticket_id":"00001KTWPE3KQ","kind":"accepted_plan","note":"Role Pods は今回起動しない。明示 follow-up まで queued のまま保持する。","accepted_plan":{"summary":"Routing では implementation_ready と判断した。ただし今回の launch instruction は role Pod spawn を explicit follow-up まで待つ指定のため、現時点では queued のまま保持し、queued -> inprogress、worktree 作成、coder/reviewer spawn、merge/close は行わない。実装開始時は side effect 前に TicketShow / relation / orchestration plan / git/worktree state を再確認し、問題なければ queued -> inprogress を記録してから進める。実装対象は Panel Queue action を root/dev 側 Ticket mutation + Queue commit + orchestration worktree ff-only sync + sync 確認後 Orchestrator notify/kick という durable handoff にすること。","branch":"ticket/panel-queue-orchestrator-sync","worktree":"/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync","role_plan":"次の明示 follow-up 後に Orchestrator が worktree-workflow で実装 worktree を作り、coder はその worktree に narrow write scope、reviewer は read-only scopeで sibling として起動する。Queue commit の対象差分限定、root/orchestration worktree identity checks、ff-only sync、dirty/divergent workspace block、workspace_root と cwd の分離維持を reviewer focus とする。"},"author":"orchestrator","at":"2026-06-12T08:43:29Z"} diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index 3ef40a5c..38c1decc 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -1,8 +1,8 @@ --- title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' -state: 'queued' +state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T02:39:25Z' +updated_at: '2026-06-12T08:45:20Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index b40388e7..95a36083 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -30,4 +30,87 @@ Marked ready by `yoi ticket state`. Ticket を `yoi ticket` が queued にしました。 +--- + + + +## Decision + +Routing decision: implementation_ready + +Reason: +- Ticket body は Panel Queue action の目的、binding invariants、失敗時の block 条件、非目標、受け入れ条件を observable に記録している。 +- `ready -> queued` は既に記録済みで、relation blocker はない。 +- 設計境界は「Ticket tools は cwd 基準」「Panel は root/dev 側の Queue commit を authority とし、Orchestrator kick 前に orchestration worktree を ff-only sync する」「自動 conflict resolution / stash / rebase はしない」として十分に固定されている。 +- 残る不確実性は既存 Panel/Ticket/git helper 実装への bounded implementation investigation と local tactic selection に閉じている。 + +Evidence checked: +- Ticket body / thread / artifacts: `00001KTWPE3KQ` の本文、state/event、artifact なしを確認。 +- TicketRelationQuery: outgoing/incoming relation なし、blocker なし。 +- TicketOrchestrationPlanQuery: 既存 record なし。今回 `orch-plan-20260612-084329-1` として accepted plan を記録。 +- Workspace state: Orchestrator worktree は `## ticket/orchestrator-progress-companion-notify`、root workspace は `## develop`、どちらも dirty 出力なし。 +- Code map: `crates/tui/src/workspace_panel.rs` と `crates/tui/src/multi_pod.rs` 周辺に Panel Queue / workspace panel / orchestrator launch の実装面があることを確認。 +- Visible Pods: 現在 visible Pod はこの Orchestrator のみで、coder/reviewer は未起動。 + +IntentPacket: + +Intent: +- Panel Queue action を、root/dev 側の `ready -> queued` commit と orchestration worktree への ff-only sync を完了してから Orchestrator notify/kick する durable handoff にする。 + +Binding decisions / invariants: +- root workspace の canonical top-level、orchestration worktree の canonical top-level、共通 git dir、期待 branch、dirty 状態、対象 Ticket の `ready` 状態、orchestration_head が root_head の ancestor であることを mutation 前に検証する。 +- Queue commit は root/dev 側で対象 Ticket record だけを stage/commit する。 +- orchestration worktree への自動同期は `git -C merge --ff-only ` のみに限定する。 +- merge commit、rebase、stash、patch apply、conflict resolution、dirty cleanup は Panel Queue action では行わない。 +- Orchestrator notify/restore/kick は orchestration worktree HEAD が Queue commit を含み、orchestration worktree 側 Ticket backend で対象 Ticket が `queued` として読めることを確認した後だけ行う。 +- Ticket tools が cwd 基準で動く設計と、workspace_root / cwd / orchestration worktree / merge target の分離を崩さない。 + +Requirements / acceptance criteria: +- Queue 成功時、Panel は queued Ticket id、dev 側 Queue commit sha、orchestration worktree sync 結果、Orchestrator notify/kick の有無を表示する。 +- root dirty、orchestration dirty、branch divergence、non-ff sync required、Ticket state mismatch、worktree identity mismatch では Queue を block し、失敗した check 名と対象 path / branch / Ticket id を表示する。 +- `nix build .#yoi` が通る。 + +Implementation latitude: +- 既存 Panel action / Git helper / Ticket backend 呼び出しのどこへ checks と sync 処理を分割するかは coder が調査して選んでよい。 +- 表示文言や内部 helper 名は、失敗条件が具体的に伝わり、既存 UX と整合する範囲で調整してよい。 +- Focused tests / unit coverage の追加位置は既存の TUI/client/test 構造に合わせてよい。 + +Escalate if: +- Queue action が root/dev 側 Ticket commit 以外の変更を commit/stage する必要が出る。 +- ff-only 以外の sync、stash/rebase/conflict resolution、dirty cleanup を自動化しないと成立しない。 +- Ticket tools の cwd 基準設計、Pod workspace_root/cwd 分離、dedicated Orchestrator worktree の authority boundary を変える必要が出る。 +- Panel が Orchestrator routing/acceptance を代行する必要が出る。 + +Validation: +- 変更に対応する focused test / existing relevant cargo test。 +- `cargo fmt --check`。 +- `git diff --check`。 +- `target/debug/yoi ticket doctor`。 +- runtime resource / packaging / prompt へ触れる場合、または最終確認として `nix build .#yoi`。 + +Current code map: +- 主な調査対象: `crates/tui/src/workspace_panel.rs`, `crates/tui/src/multi_pod.rs`。 +- Ticket backend / state mutation / CLI helper を変更する場合は `crates/ticket` と `crates/yoi` 側の既存 typed Ticket path に合わせる。 +- Orchestrator/main `.yoi` project records、memory/local/runtime/secret-like `.yoi` paths は implementation worktree 側で勝手に生成・編集しない。 + +Critical risks / reviewer focus: +- root workspace と orchestration worktree を取り違えないこと。 +- dirty/divergent 状態で mutation/commit/notify しないこと。 +- Queue commit の差分が対象 Ticket record 以外へ広がらないこと。 +- ff-only 限定を破らないこと。 +- notify/kick が sync 完了前に走らないこと。 +- cwd 基準 Ticket tools と workspace_root 分離を壊さないこと。 + +Next action: +- 今回の launch instruction は role Pod spawn を explicit follow-up まで待つ指定なので、ここでは `queued -> inprogress`、worktree 作成、coder/reviewer spawn、merge/close は行わない。 +- 明示 follow-up があれば、side effect 前に TicketShow / relation / orchestration plan / git/worktree state を再確認し、問題がなければ `queued -> inprogress` を記録してから `multi-agent-workflow` に接続する。 + +--- + + + +## State changed + +Routing accepted for implementation. Ticket body/thread, relation blockers, orchestration plan, visible Pods, and root/orchestration git state were rechecked. No unresolved dependency/blocker or missing planning decision was found. Implementation side effects will start only after this accepted `queued -> inprogress` transition is recorded; worktree and sibling coder/reviewer routing will use the accepted plan `orch-plan-20260612-084329-1`. + --- From ae55260174e92640f6f9a9f01fad2ef0ef7374a2 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 17:46:06 +0900 Subject: [PATCH 2/9] ticket: start panel queue sync implementation --- .yoi/tickets/00001KTWPE3KQ/thread.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index 95a36083..d32b77cb 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -114,3 +114,17 @@ Next action: Routing accepted for implementation. Ticket body/thread, relation blockers, orchestration plan, visible Pods, and root/orchestration git state were rechecked. No unresolved dependency/blocker or missing planning decision was found. Implementation side effects will start only after this accepted `queued -> inprogress` transition is recorded; worktree and sibling coder/reviewer routing will use the accepted plan `orch-plan-20260612-084329-1`. --- + + + +## Plan + +Implementation worktree created for multi-agent handoff. + +- Ticket: `00001KTWPE3KQ` +- Branch: `ticket/panel-queue-orchestrator-sync` +- Worktree: `/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync` +- Base: Orchestrator workspace HEAD `de0f533b` (`orchestration/yoi-orchestrator`), including the accepted routing record and `queued -> inprogress` transition. +- Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available. + +--- From 571b0ce53c24d492c5b7df1665b4fdb620be822e Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 17:46:11 +0900 Subject: [PATCH 3/9] ticket: update panel queue sync timestamp --- .yoi/tickets/00001KTWPE3KQ/item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index 38c1decc..6b911849 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -2,7 +2,7 @@ title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T08:45:20Z' +updated_at: '2026-06-12T08:45:59Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' From 25487c93252dcd4c3bc55d196263ddbe1dfe3f17 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 17:47:26 +0900 Subject: [PATCH 4/9] ticket: delegate panel queue sync coder --- .yoi/tickets/00001KTWPE3KQ/item.md | 2 +- .yoi/tickets/00001KTWPE3KQ/thread.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index 6b911849..241304b0 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -2,7 +2,7 @@ title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T08:45:59Z' +updated_at: '2026-06-12T08:47:16Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index d32b77cb..71aaea9e 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -128,3 +128,17 @@ Implementation worktree created for multi-agent handoff. - Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available. --- + + + +## Plan + +Coder delegated. + +- Coder Pod: `yoi-coder-panel-queue-sync` +- Worktree: `/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync` +- Branch: `ticket/panel-queue-orchestrator-sync` +- Scope: read `/home/hare/Projects/yoi`, write `/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync` +- Task: implement Panel Queue durable handoff with root/dev Queue commit, orchestration worktree ff-only sync, post-sync Ticket verification, and notify/kick ordering. Coder was instructed not to edit Orchestrator/main `.yoi` records or generated memory/runtime/secret-like paths. + +--- From 04a3c6e03cfbbafea10f38fe4b4d295a345d5af3 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 18:00:06 +0900 Subject: [PATCH 5/9] tui: make panel queue handoff durable --- crates/tui/src/multi_pod.rs | 693 ++++++++++++++++++++++++++++++++++-- 1 file changed, 658 insertions(+), 35 deletions(-) diff --git a/crates/tui/src/multi_pod.rs b/crates/tui/src/multi_pod.rs index a69cb625..f862b97a 100644 --- a/crates/tui/src/multi_pod.rs +++ b/crates/tui/src/multi_pod.rs @@ -2404,26 +2404,14 @@ async fn dispatch_ticket_action( match request.action { NextUserAction::Queue => { - if current_ticket.workflow_state != TicketWorkflowState::Ready { - return Err(TicketActionError::Stale( - "Queue is only valid while state is ready; reload and retry".to_string(), - )); - } - backend - .queue_ready( - TicketIdOrSlug::Id(request.ticket_id.clone()), - "workspace-panel", - ) - .map_err(|error| TicketActionError::Ticket(error.to_string()))?; - let notification = - notify_workspace_orchestrator(request.orchestrator, current_ticket).await; - Ok(TicketActionOutcome { - notice: format!( - "Queued Ticket {}; {}. Orchestrator routing is authorized; implementation side effects still require queued -> inprogress acceptance.", - current_ticket.id, - notification.sentence() - ), - }) + dispatch_panel_queue( + &request.workspace_root, + &backend, + &request.ticket_id, + request.orchestrator, + current_ticket, + ) + .await } NextUserAction::Close => unreachable!("Close action is handled before row dispatch"), NextUserAction::Clarify @@ -2439,6 +2427,566 @@ async fn dispatch_ticket_action( } } +async fn dispatch_panel_queue( + workspace_root: &Path, + backend: &LocalTicketBackend, + ticket_id: &str, + orchestrator: Option, + current_ticket: &crate::workspace_panel::TicketPanelEntry, +) -> Result { + if current_ticket.workflow_state != TicketWorkflowState::Ready { + return Err(TicketActionError::Stale(format!( + "Queue handoff check `root-ticket-state` failed for Ticket {ticket_id} at {}: state is {}, expected ready; reload and retry", + backend.root().display(), + current_ticket.workflow_state.as_str() + ))); + } + + let preflight = prepare_panel_queue_handoff(workspace_root, backend, ticket_id)?; + backend + .queue_ready(TicketIdOrSlug::Id(ticket_id.to_owned()), "workspace-panel") + .map_err(|error| TicketActionError::Ticket(error.to_string()))?; + let commit = commit_panel_queue_ticket_record(&preflight)?; + let sync = sync_panel_queue_to_orchestration(&preflight, &commit)?; + verify_panel_queue_synced(&preflight, &commit)?; + let notification = notify_workspace_orchestrator(orchestrator, current_ticket).await; + Ok(TicketActionOutcome { + notice: format!( + "Queued Ticket {}; root Queue commit {}; orchestration sync {}; {}. Orchestrator routing is authorized; implementation side effects still require queued -> inprogress acceptance.", + ticket_id, + commit.sha, + sync.sentence(), + notification.sentence() + ), + }) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct PanelQueueHandoffPreflight { + ticket_id: String, + root_top_level: PathBuf, + orchestration: OrchestrationWorktreeLayout, + ticket_record_dir: PathBuf, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct PanelQueueCommit { + sha: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct PanelQueueSync { + path: PathBuf, + branch: String, + head: String, +} + +impl PanelQueueSync { + fn sentence(&self) -> String { + format!( + "ff-only synced {} ({}) to {}", + self.path.display(), + self.branch, + self.head + ) + } +} + +fn prepare_panel_queue_handoff( + workspace_root: &Path, + backend: &LocalTicketBackend, + ticket_id: &str, +) -> Result { + let root_top_level = git_top_level(workspace_root).map_err(|message| { + queue_check_failed("root-worktree-identity", ticket_id, workspace_root, message) + })?; + let expected_root = workspace_root.canonicalize().map_err(|error| { + queue_check_failed( + "root-worktree-identity", + ticket_id, + workspace_root, + format!("could not canonicalize root workspace path: {error}"), + ) + })?; + if root_top_level != expected_root { + return Err(queue_check_failed( + "root-worktree-identity", + ticket_id, + workspace_root, + format!( + "Git top-level is {}, expected root workspace {}", + root_top_level.display(), + expected_root.display() + ), + )); + } + + let root_branch = git_current_branch(&root_top_level).map_err(|message| { + queue_check_failed("root-branch", ticket_id, &root_top_level, message) + })?; + let root_branch = root_branch.ok_or_else(|| { + queue_check_failed( + "root-branch", + ticket_id, + &root_top_level, + "root workspace is detached; expected merge target branch".to_string(), + ) + })?; + ensure_git_effective_user(&root_top_level).map_err(|message| { + queue_check_failed("root-git-user", ticket_id, &root_top_level, message) + })?; + ensure_git_clean("root-clean", ticket_id, &root_top_level)?; + + let orchestration = orchestration_worktree_layout(&root_top_level); + if !orchestration.path.exists() { + return Err(queue_check_failed( + "orchestration-worktree-identity", + ticket_id, + &orchestration.path, + "dedicated orchestration worktree is missing; open the Panel with Orchestrator support before Queue".to_string(), + )); + } + validate_existing_orchestration_worktree(&root_top_level, &orchestration).map_err( + |message| { + queue_check_failed( + "orchestration-worktree-identity", + ticket_id, + &orchestration.path, + message, + ) + }, + )?; + ensure_git_clean("orchestration-clean", ticket_id, &orchestration.path)?; + + let orchestration_branch = git_current_branch(&orchestration.path).map_err(|message| { + queue_check_failed( + "orchestration-branch", + ticket_id, + &orchestration.path, + message, + ) + })?; + if orchestration_branch.as_deref() != Some(orchestration.branch.as_str()) { + return Err(queue_check_failed( + "orchestration-branch", + ticket_id, + &orchestration.path, + format!( + "orchestration branch is {:?}, expected {}", + orchestration_branch, orchestration.branch + ), + )); + } + + let root_common = git_common_dir(&root_top_level).map_err(|message| { + queue_check_failed("shared-common-dir", ticket_id, &root_top_level, message) + })?; + let orchestration_common = git_common_dir(&orchestration.path).map_err(|message| { + queue_check_failed("shared-common-dir", ticket_id, &orchestration.path, message) + })?; + if root_common != orchestration_common { + return Err(queue_check_failed( + "shared-common-dir", + ticket_id, + &orchestration.path, + format!( + "orchestration common dir {} differs from root common dir {}", + orchestration_common.display(), + root_common.display() + ), + )); + } + + ensure_ticket_state( + backend, + ticket_id, + TicketWorkflowState::Ready, + "root-ticket-state", + &root_top_level, + )?; + + let orchestration_head = git_rev_parse(&orchestration.path, "HEAD").map_err(|message| { + queue_check_failed("branch-divergence", ticket_id, &orchestration.path, message) + })?; + let root_head = git_rev_parse(&root_top_level, "HEAD").map_err(|message| { + queue_check_failed("branch-divergence", ticket_id, &root_top_level, message) + })?; + ensure_git_ancestor(&root_top_level, &orchestration_head, &root_head).map_err(|message| { + queue_check_failed( + "branch-divergence", + ticket_id, + &orchestration.path, + format!( + "orchestration HEAD {orchestration_head} is not an ancestor of root branch {root_branch} HEAD {root_head}: {message}" + ), + ) + })?; + + let ticket_record_dir = backend.root().join(ticket_id); + if !ticket_record_dir.join("item.md").is_file() { + return Err(queue_check_failed( + "target-ticket-record", + ticket_id, + &ticket_record_dir, + "target Ticket item.md is missing".to_string(), + )); + } + + Ok(PanelQueueHandoffPreflight { + ticket_id: ticket_id.to_string(), + root_top_level, + orchestration, + ticket_record_dir, + }) +} + +fn commit_panel_queue_ticket_record( + preflight: &PanelQueueHandoffPreflight, +) -> Result { + let ticket_rel = path_relative_to_root( + &preflight.root_top_level, + &preflight.ticket_record_dir, + "target-ticket-record", + &preflight.ticket_id, + )?; + let mut add = Command::new("git"); + add.arg("-C") + .arg(&preflight.root_top_level) + .arg("add") + .arg("--") + .arg(&ticket_rel); + run_git_command(add, "stage Queue Ticket record").map_err(|message| { + queue_check_failed( + "queue-commit-stage", + &preflight.ticket_id, + &preflight.ticket_record_dir, + message, + ) + })?; + + let staged = git_capture( + &preflight.root_top_level, + &["diff", "--cached", "--name-only"], + "list staged files", + ) + .map_err(|message| { + queue_check_failed( + "queue-commit-pathscope", + &preflight.ticket_id, + &preflight.root_top_level, + message, + ) + })?; + let staged_paths = staged + .lines() + .filter(|line| !line.trim().is_empty()) + .collect::>(); + if staged_paths.is_empty() { + return Err(queue_check_failed( + "queue-commit-pathscope", + &preflight.ticket_id, + &preflight.ticket_record_dir, + "Queue mutation produced no staged Ticket record changes".to_string(), + )); + } + let ticket_rel_string = git_path_string(&ticket_rel); + let outside = staged_paths + .iter() + .find(|path| !git_status_path_is_inside(path, &ticket_rel_string)); + if let Some(path) = outside { + return Err(queue_check_failed( + "queue-commit-pathscope", + &preflight.ticket_id, + &preflight.root_top_level, + format!( + "staged path {path} is outside target Ticket record {}", + ticket_rel.display() + ), + )); + } + + let message = format!("ticket: queue {}", preflight.ticket_id); + let mut commit = Command::new("git"); + commit + .arg("-C") + .arg(&preflight.root_top_level) + .arg("commit") + .arg("--no-verify") + .arg("-m") + .arg(message) + .arg("--") + .arg(&ticket_rel); + run_git_command(commit, "commit Queue Ticket record").map_err(|message| { + queue_check_failed( + "queue-commit-create", + &preflight.ticket_id, + &preflight.root_top_level, + message, + ) + })?; + let sha = git_rev_parse(&preflight.root_top_level, "HEAD").map_err(|message| { + queue_check_failed( + "queue-commit-create", + &preflight.ticket_id, + &preflight.root_top_level, + message, + ) + })?; + Ok(PanelQueueCommit { sha }) +} + +fn sync_panel_queue_to_orchestration( + preflight: &PanelQueueHandoffPreflight, + commit: &PanelQueueCommit, +) -> Result { + ensure_git_clean( + "orchestration-clean-before-sync", + &preflight.ticket_id, + &preflight.orchestration.path, + )?; + let mut merge = Command::new("git"); + merge + .arg("-C") + .arg(&preflight.orchestration.path) + .arg("merge") + .arg("--ff-only") + .arg(&commit.sha); + run_git_command( + merge, + "ff-only sync Queue commit into orchestration worktree", + ) + .map_err(|message| { + queue_check_failed( + "orchestration-ff-only-sync", + &preflight.ticket_id, + &preflight.orchestration.path, + message, + ) + })?; + let head = git_rev_parse(&preflight.orchestration.path, "HEAD").map_err(|message| { + queue_check_failed( + "orchestration-ff-only-sync", + &preflight.ticket_id, + &preflight.orchestration.path, + message, + ) + })?; + Ok(PanelQueueSync { + path: preflight.orchestration.path.clone(), + branch: preflight.orchestration.branch.clone(), + head, + }) +} + +fn verify_panel_queue_synced( + preflight: &PanelQueueHandoffPreflight, + commit: &PanelQueueCommit, +) -> Result<(), TicketActionError> { + let head = git_rev_parse(&preflight.orchestration.path, "HEAD").map_err(|message| { + queue_check_failed( + "orchestration-sync-verify", + &preflight.ticket_id, + &preflight.orchestration.path, + message, + ) + })?; + ensure_git_ancestor(&preflight.orchestration.path, &commit.sha, &head).map_err(|message| { + queue_check_failed( + "orchestration-sync-verify", + &preflight.ticket_id, + &preflight.orchestration.path, + format!( + "orchestration HEAD {head} does not contain Queue commit {}: {message}", + commit.sha + ), + ) + })?; + let config = TicketConfig::load_workspace(&preflight.orchestration.path).map_err(|error| { + queue_check_failed( + "orchestration-ticket-state", + &preflight.ticket_id, + &preflight.orchestration.path, + error.to_string(), + ) + })?; + let backend = LocalTicketBackend::new(config.backend_root()) + .with_record_language(config.ticket_record_language()); + ensure_ticket_state( + &backend, + &preflight.ticket_id, + TicketWorkflowState::Queued, + "orchestration-ticket-state", + &preflight.orchestration.path, + ) +} + +fn ensure_ticket_state( + backend: &LocalTicketBackend, + ticket_id: &str, + expected: TicketWorkflowState, + check: &'static str, + path: &Path, +) -> Result<(), TicketActionError> { + let ticket = backend + .show(TicketIdOrSlug::Id(ticket_id.to_string())) + .map_err(|error| queue_check_failed(check, ticket_id, path, error.to_string()))?; + if ticket.meta.workflow_state != expected { + return Err(queue_check_failed( + check, + ticket_id, + path, + format!( + "state is {}, expected {}", + ticket.meta.workflow_state.as_str(), + expected.as_str() + ), + )); + } + Ok(()) +} + +fn ensure_git_clean( + check: &'static str, + ticket_id: &str, + path: &Path, +) -> Result<(), TicketActionError> { + let status = git_status_porcelain(path) + .map_err(|message| queue_check_failed(check, ticket_id, path, message))?; + if status.is_empty() { + return Ok(()); + } + let detail = status.into_iter().take(6).collect::>().join("; "); + Err(queue_check_failed( + check, + ticket_id, + path, + format!("worktree is dirty: {detail}"), + )) +} + +fn ensure_git_effective_user(path: &Path) -> Result<(), String> { + let name = git_capture(path, &["config", "user.name"], "read git user.name")?; + let email = git_capture(path, &["config", "user.email"], "read git user.email")?; + if name.trim().is_empty() || email.trim().is_empty() { + return Err("git user.name and user.email must be configured before the Panel creates a Queue commit".to_string()); + } + Ok(()) +} + +fn git_rev_parse(path: &Path, rev: &str) -> Result { + git_capture(path, &["rev-parse", rev], "resolve Git revision") +} + +fn git_status_porcelain(path: &Path) -> Result, String> { + let output = git_capture( + path, + &["status", "--porcelain", "--untracked-files=normal"], + "read Git status", + )?; + Ok(output.lines().map(|line| line.to_string()).collect()) +} + +fn ensure_git_ancestor(path: &Path, ancestor: &str, descendant: &str) -> Result<(), String> { + let status = Command::new("git") + .arg("-C") + .arg(path) + .arg("merge-base") + .arg("--is-ancestor") + .arg(ancestor) + .arg(descendant) + .status() + .map_err(|error| format!("could not run git merge-base --is-ancestor: {error}"))?; + if status.success() { + Ok(()) + } else { + Err(format!( + "git merge-base --is-ancestor {ancestor} {descendant} exited with {status}" + )) + } +} + +fn git_capture(path: &Path, args: &[&str], action: &str) -> Result { + let output = Command::new("git") + .arg("-C") + .arg(path) + .args(args) + .output() + .map_err(|error| { + format!( + "could not run git to {action} at {}: {error}", + path.display() + ) + })?; + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let detail = if stderr.is_empty() { stdout } else { stderr }; + Err(format!( + "git failed to {action} at {}: {detail}", + path.display() + )) +} + +fn path_relative_to_root( + root: &Path, + path: &Path, + check: &'static str, + ticket_id: &str, +) -> Result { + let canonical = path.canonicalize().map_err(|error| { + queue_check_failed( + check, + ticket_id, + path, + format!("could not canonicalize path: {error}"), + ) + })?; + canonical + .strip_prefix(root) + .map(PathBuf::from) + .map_err(|_| { + queue_check_failed( + check, + ticket_id, + path, + format!( + "path {} is outside root Git top-level {}", + canonical.display(), + root.display() + ), + ) + }) +} + +fn git_path_string(path: &Path) -> String { + path.components() + .map(|component| component.as_os_str().to_string_lossy()) + .collect::>() + .join("/") +} + +fn git_status_path_is_inside(path: &str, parent: &str) -> bool { + path == parent + || path + .strip_prefix(parent) + .is_some_and(|rest| rest.starts_with('/')) +} + +fn queue_check_failed( + check: &'static str, + ticket_id: &str, + path: &Path, + message: impl Into, +) -> TicketActionError { + TicketActionError::Stale(format!( + "Queue handoff check `{check}` failed for Ticket {ticket_id} at {}: {}", + path.display(), + message.into() + )) +} + fn dispatch_panel_close( backend: &LocalTicketBackend, ticket_id: &str, @@ -3675,21 +4223,11 @@ mod tests { fn init_test_repo(root: &Path) { std::fs::create_dir_all(root).unwrap(); run_test_git(root, &["init"]).unwrap(); + run_test_git(root, &["config", "user.email", "test@example.invalid"]).unwrap(); + run_test_git(root, &["config", "user.name", "Yoi Test"]).unwrap(); std::fs::write(root.join("README.md"), "repo").unwrap(); run_test_git(root, &["add", "README.md"]).unwrap(); - run_test_git( - root, - &[ - "-c", - "user.email=test@example.invalid", - "-c", - "user.name=Yoi Test", - "commit", - "-m", - "init", - ], - ) - .unwrap(); + run_test_git(root, &["commit", "-m", "init"]).unwrap(); } #[test] @@ -3728,6 +4266,16 @@ mod tests { ) -> (TempDir, String, LocalTicketBackend) { let temp = TempDir::new().unwrap(); fs::create_dir_all(temp.path().join(".yoi")).unwrap(); + fs::write( + temp.path().join(".gitignore"), + ".worktree/\n.yoi/tickets/.ticket-backend.lock\n", + ) + .unwrap(); + fs::write( + temp.path().join(".yoi/.gitignore"), + "tickets/.ticket-backend.lock\n", + ) + .unwrap(); fs::write( temp.path().join(".yoi/ticket.config.toml"), "[backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n", @@ -3746,6 +4294,21 @@ mod tests { ticket_workspace(title, TicketWorkflowState::Ready, |_| {}) } + fn ready_ticket_git_workspace(title: &str) -> (TempDir, String, LocalTicketBackend) { + let (temp, ticket_id, backend) = ready_ticket_workspace(title); + run_test_git(temp.path(), &["init"]).unwrap(); + run_test_git( + temp.path(), + &["config", "user.email", "test@example.invalid"], + ) + .unwrap(); + run_test_git(temp.path(), &["config", "user.name", "Yoi Test"]).unwrap(); + run_test_git(temp.path(), &["add", "."]).unwrap(); + run_test_git(temp.path(), &["commit", "-m", "seed tickets"]).unwrap(); + ensure_orchestration_worktree(temp.path()).unwrap(); + (temp, ticket_id, backend) + } + fn done_ticket_workspace(title: &str) -> (TempDir, String, LocalTicketBackend) { ticket_workspace(title, TicketWorkflowState::Done, |_| {}) } @@ -3765,14 +4328,23 @@ mod tests { #[tokio::test] async fn ticket_queue_action_transitions_ready_ticket_and_authorizes_orchestrator_routing() { - let (temp, ticket_id, backend) = ready_ticket_workspace("panel-queue"); + let (temp, ticket_id, backend) = ready_ticket_git_workspace("panel-queue"); + let root_head_before = git_rev_parse(temp.path(), "HEAD").unwrap(); let outcome = dispatch_ticket_action(request_for(&temp, ticket_id.clone(), NextUserAction::Queue)) .await .unwrap(); + let root_head_after = git_rev_parse(temp.path(), "HEAD").unwrap(); + let layout = orchestration_worktree_layout(temp.path()); + let orchestration_head = git_rev_parse(&layout.path, "HEAD").unwrap(); + assert_ne!(root_head_after, root_head_before); + assert_eq!(orchestration_head, root_head_after); assert!(outcome.notice.contains("Queued Ticket")); + assert!(outcome.notice.contains(&root_head_after)); + assert!(outcome.notice.contains("root Queue commit")); + assert!(outcome.notice.contains("ff-only synced")); assert!( outcome .notice @@ -3780,7 +4352,7 @@ mod tests { ); assert!(outcome.notice.contains("queued -> inprogress acceptance")); assert!(!outcome.notice.contains("No implementation was started")); - let ticket = backend.show(TicketIdOrSlug::Id(ticket_id)).unwrap(); + let ticket = backend.show(TicketIdOrSlug::Id(ticket_id.clone())).unwrap(); assert_eq!(ticket.meta.workflow_state, TicketWorkflowState::Queued); assert_eq!(ticket.meta.queued_by.as_deref(), Some("workspace-panel")); assert!(ticket.meta.queued_at.is_some()); @@ -3795,6 +4367,56 @@ mod tests { }) .expect("queue state_changed event is recorded"); assert_eq!(state_change.author.as_deref(), Some("workspace-panel")); + let orchestration_backend = LocalTicketBackend::new(layout.path.join(".yoi/tickets")); + let orchestration_ticket = orchestration_backend + .show(TicketIdOrSlug::Id(ticket_id)) + .unwrap(); + assert_eq!( + orchestration_ticket.meta.workflow_state, + TicketWorkflowState::Queued + ); + } + + #[tokio::test] + async fn ticket_queue_action_blocks_dirty_root_without_mutation() { + let (temp, ticket_id, backend) = ready_ticket_git_workspace("panel-dirty-root"); + fs::write(temp.path().join("dirty.txt"), "dirty").unwrap(); + + let error = + dispatch_ticket_action(request_for(&temp, ticket_id.clone(), NextUserAction::Queue)) + .await + .unwrap_err(); + let message = error.to_string(); + + assert!(message.contains("root-clean")); + assert!(message.contains(&ticket_id)); + assert!(message.contains(&temp.path().display().to_string())); + assert!(message.contains("dirty.txt")); + let ticket = backend.show(TicketIdOrSlug::Id(ticket_id)).unwrap(); + assert_eq!(ticket.meta.workflow_state, TicketWorkflowState::Ready); + assert!(ticket.meta.queued_by.is_none()); + } + + #[tokio::test] + async fn ticket_queue_action_blocks_orchestration_branch_divergence() { + let (temp, ticket_id, backend) = ready_ticket_git_workspace("panel-diverged"); + let layout = orchestration_worktree_layout(temp.path()); + fs::write(layout.path.join("orchestrator-only.txt"), "diverged").unwrap(); + run_test_git(&layout.path, &["add", "orchestrator-only.txt"]).unwrap(); + run_test_git(&layout.path, &["commit", "-m", "orchestrator-only"]).unwrap(); + + let error = + dispatch_ticket_action(request_for(&temp, ticket_id.clone(), NextUserAction::Queue)) + .await + .unwrap_err(); + let message = error.to_string(); + + assert!(message.contains("branch-divergence")); + assert!(message.contains(&ticket_id)); + assert!(message.contains(&layout.path.display().to_string())); + let ticket = backend.show(TicketIdOrSlug::Id(ticket_id)).unwrap(); + assert_eq!(ticket.meta.workflow_state, TicketWorkflowState::Ready); + assert!(ticket.meta.queued_by.is_none()); } #[tokio::test] @@ -5173,6 +5795,7 @@ mod tests { launch: TicketRoleLaunchResult { plan: client::ticket_role::TicketRoleLaunchPlan { workspace_root: PathBuf::from("/tmp/workspace"), + cwd: None, original_workspace_root: PathBuf::from("/tmp/workspace"), target_workspace_root: PathBuf::from("/tmp/workspace"), implementation_worktree_root: PathBuf::from("/tmp/workspace/.worktree"), From 28180cc337c330c521ded4948ee7e415f0db703c Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 18:05:38 +0900 Subject: [PATCH 6/9] ticket: record panel queue sync implementation --- .yoi/tickets/00001KTWPE3KQ/item.md | 2 +- .yoi/tickets/00001KTWPE3KQ/thread.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index 241304b0..b387dd6e 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -2,7 +2,7 @@ title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T08:47:16Z' +updated_at: '2026-06-12T09:05:32Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index 71aaea9e..5a79c352 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -142,3 +142,26 @@ Coder delegated. - Task: implement Panel Queue durable handoff with root/dev Queue commit, orchestration worktree ff-only sync, post-sync Ticket verification, and notify/kick ordering. Coder was instructed not to edit Orchestrator/main `.yoi` records or generated memory/runtime/secret-like paths. --- + + + +## Implementation report + +Coder completed initial implementation. + +- Coder Pod: `yoi-coder-panel-queue-sync` +- Implementation branch: `ticket/panel-queue-orchestrator-sync` +- Implementation commit: `04a3c6e0` (`tui: make panel queue handoff durable`) +- Worktree status checked clean. + +Orchestrator validation performed after coder handoff: +- `git diff --check HEAD^..HEAD` — pass +- `cargo test -p tui ticket_queue_action -- --nocapture` — pass +- `cargo fmt --check` — pass +- `cargo check -p tui --all-targets` — pass +- `nix build .#yoi` — pass +- `./result/bin/yoi ticket doctor` — `doctor: ok` + +Next: delegate independent reviewer against the recorded Ticket intent, accepted plan, implementation commit, and validation evidence. + +--- From 190f5964136114f1c93446d13949060f2666cd0d Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 18:06:06 +0900 Subject: [PATCH 7/9] ticket: delegate panel queue sync review --- .yoi/tickets/00001KTWPE3KQ/item.md | 2 +- .yoi/tickets/00001KTWPE3KQ/thread.md | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index b387dd6e..db66440d 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -2,7 +2,7 @@ title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T09:05:32Z' +updated_at: '2026-06-12T09:06:03Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index 5a79c352..8cd9c7ff 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -165,3 +165,16 @@ Orchestrator validation performed after coder handoff: Next: delegate independent reviewer against the recorded Ticket intent, accepted plan, implementation commit, and validation evidence. --- + + + +## Plan + +Reviewer delegated. + +- Reviewer Pod: `yoi-reviewer-panel-queue-sync` +- Review target: `/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync`, branch `ticket/panel-queue-orchestrator-sync`, commit `04a3c6e0` +- Scope: read-only `/home/hare/Projects/yoi` +- Reviewer focus: durable Queue handoff ordering, root/orchestration worktree identity and cleanliness checks, ff-only-only sync, pathscope of Queue commit, no auto conflict/stash/rebase, notify only after sync + Ticket verification, and whether `--no-verify` is acceptable. + +--- From 2e29f91bd407b71a0562b6714a879c1585e05c7c Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 18:11:35 +0900 Subject: [PATCH 8/9] ticket: defer role launch input routing --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KTVPS6K3/item.md | 2 +- .yoi/tickets/00001KTVPS6K3/thread.md | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .yoi/tickets/00001KTVPS6K3/artifacts/orchestration-plan.jsonl diff --git a/.yoi/tickets/00001KTVPS6K3/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KTVPS6K3/artifacts/orchestration-plan.jsonl new file mode 100644 index 00000000..6e6fad54 --- /dev/null +++ b/.yoi/tickets/00001KTVPS6K3/artifacts/orchestration-plan.jsonl @@ -0,0 +1 @@ +{"id":"orch-plan-20260612-091119-1","ticket_id":"00001KTVPS6K3","kind":"waiting_capacity_note","note":"Workspace Panel Queue notification was received, but this Orchestrator cannot safely accept implementation yet. The Orchestrator Ticket backend still reads the Ticket as `ready`, while the root workspace has unsynced/uncommitted queued Ticket changes for `00001KTVPS6K3`; root workspace is dirty (`.yoi/tickets/00001KTVPS6K3/*` plus `crates/tui/src/multi_pod.rs`). Current active work `00001KTWPE3KQ` is also in review and is specifically fixing the Panel Queue durable handoff/sync path. Re-route this Ticket after the Queue handoff is safely synced/committed and active review/merge state is clear.","author":"orchestrator","at":"2026-06-12T09:11:19Z"} diff --git a/.yoi/tickets/00001KTVPS6K3/item.md b/.yoi/tickets/00001KTVPS6K3/item.md index d30755e6..b6279397 100644 --- a/.yoi/tickets/00001KTVPS6K3/item.md +++ b/.yoi/tickets/00001KTVPS6K3/item.md @@ -2,7 +2,7 @@ title: 'Ticket role launch inputを短縮し、role behaviorをInstruction/Workflowへ分離する' state: 'ready' created_at: '2026-06-11T16:03:28Z' -updated_at: '2026-06-11T16:03:33Z' +updated_at: '2026-06-12T09:11:30Z' assignee: null risk_flags: ['prompt-context', 'workflow-boundary', 'role-launch'] --- diff --git a/.yoi/tickets/00001KTVPS6K3/thread.md b/.yoi/tickets/00001KTVPS6K3/thread.md index e7731c68..17cab8cc 100644 --- a/.yoi/tickets/00001KTVPS6K3/thread.md +++ b/.yoi/tickets/00001KTVPS6K3/thread.md @@ -21,3 +21,30 @@ Ticket role launch の初回 user message を短縮し、情報所管を Instruc 要件・受け入れ条件・境界判断が揃ったため、Orchestrator routing 可能。実装はユーザーが panel で queue した後に開始する。 --- + + + +## Decision + +Routing decision: blocked_by_dependency_or_missing_authority + +Reason: +- Workspace Panel Queue notification was received, but this Orchestrator backend still reads the Ticket as `ready`, not `queued`. +- The root workspace has unsynced/uncommitted queue-related changes for this Ticket and is dirty, including `.yoi/tickets/00001KTVPS6K3/*` and `crates/tui/src/multi_pod.rs`. +- Active in-progress work `00001KTWPE3KQ` is currently fixing the Panel Queue durable handoff/sync path and is in reviewer handoff; accepting this new Ticket before that path is reviewed/merged would require manual sync/queue recovery and could mix queue-side effects with unrelated dirty root changes. + +Evidence checked: +- TicketShow `00001KTVPS6K3`: Orchestrator backend state is `ready`. +- TicketRelationQuery: no relation blockers. +- TicketOrchestrationPlanQuery: no prior plan records before this routing note. +- Root/orchestrator git state: Orchestrator branch has local routing record changes; root workspace is dirty with this Ticket's `.yoi` files and `crates/tui/src/multi_pod.rs`. +- Visible Pods: active reviewer `yoi-reviewer-panel-queue-sync` for `00001KTWPE3KQ`. + +Next action: +- Leave this Ticket unaccepted for implementation in this Orchestrator pass. +- Re-route after the Panel Queue durable handoff work is resolved and the root/orchestration Ticket state is synchronized cleanly, or after a human explicitly instructs manual recovery for the queued root-side changes. + +Escalate if: +- The queued root-side changes should be manually committed/synced despite the current dirty workspace and active Queue-handoff fix. + +--- From 870b6446b1731b149d47d27260eec2bc38002e46 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 18:12:31 +0900 Subject: [PATCH 9/9] ticket: record panel queue sync dossier --- .yoi/tickets/00001KTWPE3KQ/item.md | 2 +- .yoi/tickets/00001KTWPE3KQ/thread.md | 79 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTWPE3KQ/item.md b/.yoi/tickets/00001KTWPE3KQ/item.md index db66440d..97149e74 100644 --- a/.yoi/tickets/00001KTWPE3KQ/item.md +++ b/.yoi/tickets/00001KTWPE3KQ/item.md @@ -2,7 +2,7 @@ title: 'Panel Queue時にdevとOrchestrator worktreeを同期する' state: 'inprogress' created_at: '2026-06-12T01:16:39Z' -updated_at: '2026-06-12T09:06:03Z' +updated_at: '2026-06-12T09:12:25Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-06-12T02:39:25Z' diff --git a/.yoi/tickets/00001KTWPE3KQ/thread.md b/.yoi/tickets/00001KTWPE3KQ/thread.md index 8cd9c7ff..d0b600bf 100644 --- a/.yoi/tickets/00001KTWPE3KQ/thread.md +++ b/.yoi/tickets/00001KTWPE3KQ/thread.md @@ -178,3 +178,82 @@ Reviewer delegated. - Reviewer focus: durable Queue handoff ordering, root/orchestration worktree identity and cleanliness checks, ff-only-only sync, pathscope of Queue commit, no auto conflict/stash/rebase, notify only after sync + Ticket verification, and whether `--no-verify` is acceptable. --- + + + +## Implementation report + +Merge-ready dossier (human/parent merge authority required before merge/close/cleanup) + +Ticket: +- `00001KTWPE3KQ` — Panel Queue時にdevとOrchestrator worktreeを同期する + +Branch / worktree: +- Branch: `ticket/panel-queue-orchestrator-sync` +- Worktree: `/home/hare/Projects/yoi/.worktree/panel-queue-orchestrator-sync` +- Implementation commit: `04a3c6e0` (`tui: make panel queue handoff durable`) + +Intent / invariant check: +- Panel Queue action is now implemented as root/dev-side Ticket `ready -> queued` mutation plus Queue commit, ff-only sync into the dedicated orchestration worktree, then Orchestrator notify/kick only after sync and Ticket-state verification. +- Automatic sync path is constrained to `git -C merge --ff-only `. +- No automatic merge commit, rebase, stash, patch apply, conflict resolution, or dirty cleanup path was found. +- Runtime workspace root / cwd / orchestration worktree / merge target separation remains path-explicit. + +Implementation summary: +- `crates/tui/src/multi_pod.rs` changed. +- Added Panel Queue preflight for root/orchestration Git identity, branch state, shared common dir, clean worktrees, root Ticket `ready` state, and orchestration-head ancestry. +- Added root-side Queue commit creation scoped to the target Ticket record. +- Added ff-only orchestration worktree sync and post-sync verification that orchestration HEAD contains the Queue commit and orchestration Ticket backend reads the Ticket as `queued`. +- Added focused tests for successful Queue handoff, dirty root blocking, and orchestration branch divergence blocking. + +Coder evidence: +- Coder Pod: `yoi-coder-panel-queue-sync` +- Commit: `04a3c6e0` +- Coder reported `cargo fmt --check`, `cargo check -p tui`, `cargo test -p tui ticket_queue_action -- --nocapture`, `git diff --check`, `nix build .#yoi`, and `./result/bin/yoi ticket doctor` passed. Full `cargo test -p tui` had unrelated existing failures reported by coder. + +Orchestrator validation evidence: +- `git diff --check HEAD^..HEAD` — pass +- `cargo test -p tui ticket_queue_action -- --nocapture` — pass +- `cargo fmt --check` — pass +- `cargo check -p tui --all-targets` — pass +- `nix build .#yoi` — pass +- `./result/bin/yoi ticket doctor` — `doctor: ok` + +Reviewer evidence: +- Reviewer Pod: `yoi-reviewer-panel-queue-sync` +- Verdict: approve. +- Reviewer reran `git diff --check HEAD^..HEAD` and `cargo test -p tui ticket_queue_action -- --nocapture`; both passed. +- Reviewer found pre-mutation checks, root-side mutation/commit, ff-only sync, notify-after-verify ordering, path-explicit workspace separation, and failure-message specificity acceptable. +- Reviewer did not treat `--no-verify` as a blocker for the automatic target-Ticket-record metadata commit; noted the tradeoff that local commit hooks do not run. + +Blockers fixed / rejected findings: +- No reviewer blocker. +- `--no-verify` retained as non-blocking per reviewer rationale: hook-side effects are less acceptable for this narrow automatic Ticket metadata commit than skipping local hooks, with pathscope checks and post-validation covering the handoff. + +Residual risks: +- If sync or verify fails after the root Queue commit is created, the root/dev side remains durably queued and committed; manual follow-up may be required. +- Clean-worktree checks are preflight checks, not a global lock against concurrent filesystem/git changes during the action. +- Current root workspace is dirty from separate Panel/user activity (`00001KTVPS6K3` queue attempt and `crates/tui/src/multi_pod.rs` local changes), so merge target safety must be rechecked before any merge. + +Dirty state: +- Implementation worktree was checked clean after commit. +- Orchestrator worktree has only Ticket dossier changes before this record is committed. +- Root/merge-target workspace is dirty and must not be used for merge until understood/cleaned or explicitly authorized. + +Decision needs: +- Explicit merge-completion authority is required before merging `ticket/panel-queue-orchestrator-sync` into the recorded merge target, marking final review/done/close, stopping cleanup, or deleting the worktree/branch. +- If authority is granted later, recheck branch/worktree/commit identity, independent approval, target dirty state, and rerun post-merge validation before Ticket completion. + +--- + + + +## Implementation report + +Coder/reviewer Pods stopped after merge-ready dossier was recorded. + +- Stopped: `yoi-coder-panel-queue-sync` +- Stopped: `yoi-reviewer-panel-queue-sync` +- Implementation worktree/branch are retained for explicit merge-completion authority. + +---