From a63b40f460c096e0630dff430dab27a26e5ad1b6 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:31:04 +0900 Subject: [PATCH 01/11] ticket: accept resume cli routing --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KVJX7VZT/item.md | 4 +- .yoi/tickets/00001KVJX7VZT/thread.md | 97 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KVJX7VZT/artifacts/orchestration-plan.jsonl diff --git a/.yoi/tickets/00001KVJX7VZT/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KVJX7VZT/artifacts/orchestration-plan.jsonl new file mode 100644 index 00000000..b3ccbca6 --- /dev/null +++ b/.yoi/tickets/00001KVJX7VZT/artifacts/orchestration-plan.jsonl @@ -0,0 +1 @@ +{"id":"orch-plan-20260620-163100-1","ticket_id":"00001KVJX7VZT","kind":"accepted_plan","accepted_plan":{"summary":"Implement explicit `yoi resume [--all]` CLI, remove top-level bare Pod-name inference, make default resume workspace-scoped by Pod metadata, preserve explicit `--pod `, update help and focused parser/TUI tests.","branch":"impl/00001KVJX7VZT-cli-resume-subcommand","worktree":"/home/hare/Projects/yoi/.worktree/00001KVJX7VZT-cli-resume-subcommand","role_plan":"Orchestrator creates child worktree and records acceptance. Coder gets narrow write scope for implementation worktree. Reviewer will be spawned read-only after Coder reports implementation commit(s), then Orchestrator integrates approved branch into `orchestration`, validates, records closure, and cleans only child worktree/branch."},"author":"yoi-orchestrator","at":"2026-06-20T16:31:00Z"} diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index f94e98b8..d0c0fadd 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -1,8 +1,8 @@ --- title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' -state: 'queued' +state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:29:26Z' +updated_at: '2026-06-20T16:31:00Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index 5ac901c6..a0d6f873 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -13,4 +13,101 @@ LocalTicketBackend によって作成されました。 Ticket を `workspace-panel` が queued にしました。 +--- + + + +## Decision + +Routing decision: `implementation_ready` + +Reason: +- Ticket body は `yoi resume` サブコマンド化、top-level bare Pod name 推測廃止、workspace-scoped resume default、明示 `resume --all`、`--pod ` 維持、help/parser/picker tests 更新まで具体化されている。 +- Binding decisions / invariants と implementation latitude が分離されており、`-r` / `--resume` alias を残さない方針、host-wide enumeration を `resume --all` に限定する方針、legacy Pod metadata 判断時の escalation 条件も明示されている。 +- Relation metadata と orchestration plan に blocker はない。 +- Orchestrator worktree は clean で、対象 Ticket 用の既存 implementation worktree / branch はない。 +- Visible Pods にこの Ticket の coder/reviewer は存在せず、現在 queued Ticket はこの Ticket のみ。 + +Evidence checked: +- Ticket body / thread / artifacts via `TicketShow` and direct `item.md` read。 +- `TicketRelationQuery(00001KVJX7VZT)`: no relations / blockers。 +- `TicketOrchestrationPlanQuery(00001KVJX7VZT)`: no records。 +- `TicketList(state=queued)`: queued Ticket はこの Ticket のみ。 +- `ListPods`: current visible Pods に対象 Ticket の child Pod はない。 +- Orchestrator worktree git state: clean on `orchestration` at `8684344e`。 +- Worktree list / branch list: 対象 Ticket 用 worktree / branch はない。 +- Bounded code map check: + - `crates/yoi/src/main.rs` に `LaunchMode::PodName` / `LaunchMode::Resume` / old help and parser tests がある。 + - `crates/tui/src/lib.rs` currently dispatches `LaunchMode::Resume` to `console::run_resume(runtime_command)`。 + - `crates/tui/src/console/mod.rs` resume path calls `picker::run()` without workspace filtering。 + - `crates/tui/src/pod_list.rs` has `PodList::from_workspace_sources(...)` and workspace metadata filtering tests。 + +IntentPacket: + +Intent: +- CLI resume UX を explicit subcommand model に変更し、bare word Pod name inference を廃止する。 +- Default resume picker は workspace-scoped、`resume --all` は human CLI explicit opt-in の host-wide listing とする。 + +Binding decisions / invariants: +- Top-level bare word を Pod 名として推測しない。 +- Pod 名を直接開く導線は explicit `--pod ` のみ維持する。 +- `-r` / `--resume` は互換 alias として残さず、unknown/deprecated error にする。 +- `resume --all` なしで host-wide Pod list を表示しない。 +- Workspace-scoped resume は Pod metadata の `workspace_root` を authority にし、Dashboard と同じ方向の semantics を使う。 +- Human CLI の `resume --all` と LLM-facing Pod tool visibility/scope を混同しない。 +- `yoi` 引数なし default Console 起動は変更しない。 +- Existing explicit command paths (`yoi pod`, `yoi ticket`, `yoi plugin`, `yoi memory lint`, `yoi panel`, `--session`, `--profile`) を壊さない。 + +Requirements / acceptance criteria: +- `yoi resume` が resume picker を開く。 +- `yoi resume --workspace ` が指定 workspace の Pod だけを表示する。 +- `yoi resume --all` が host/data-dir wide Pod 一覧を表示する。 +- `yoi -r` / `yoi --resume` は legacy resume mode にならない。 +- `yoi ` は `PodName` mode ではなく unknown command / usage error になる。 +- `yoi --pod ` は明示 Pod open/attach/restore/create path として残る。 +- `yoi --help` から `[POD_NAME]` と `-r, --resume` guidance を消し、`yoi resume [--workspace ] [--all]` を案内する。 +- Parser tests と TUI/pod-list/picker tests が新挙動を固定する。 + +Implementation latitude: +- `LaunchMode::Resume` に options を持たせるか、別 options 型を作るかは Coder 判断でよい。 +- picker API は options 化してよい。 +- Workspace filtering は `PodList::from_workspace_sources(...)` reuse を優先し、必要なら小 helper extraction を許可する。 +- Error/help wording は `--pod` と `resume` が発見しやすい範囲で Coder 判断可。 +- CLI smoke は non-interactive に検証できる範囲でよい。 + +Escalate if: +- `workspace_root` metadata がない legacy Pod を workspace-scoped resume に含めるべきか実装中に判断不能になる。 +- `-r` / `--resume` alias を残す必要が出る。 +- `resume --all` が Pod scope / permission / LLM-visible tool visibility と混ざる設計になりそうになる。 +- Parser change が existing explicit command paths を壊す。 +- 要件達成に public CLI surface beyond this Ticket を追加する必要が出る。 + +Validation: +- `cargo fmt --check` +- Focused parser tests in `-p yoi` +- Focused TUI/pod-list/picker tests in `-p tui` +- `cargo check -p yoi -p tui` +- `git diff --check` +- `yoi ticket doctor` +- CLI smoke as practical: `target/debug/yoi --help`, `target/debug/yoi resume --help`, `target/debug/yoi unknown`, and parser coverage for `--pod`。 + +Current code map: +- Primary files: `crates/yoi/src/main.rs`, `crates/tui/src/lib.rs`, `crates/tui/src/console/mod.rs`, `crates/tui/src/picker.rs`, `crates/tui/src/pod_list.rs`。 +- Related panel semantics reference: `crates/tui/src/workspace_panel.rs` / dashboard PodList filtering, but avoid broad dashboard churn。 + +Critical risks / reviewer focus: +- Backward compatibility intentionally changes: ensure old positional Pod name tests are removed/replaced, not silently preserved。 +- Ensure `resume --all` is explicit and default resume cannot leak host-wide Pod list。 +- Ensure `--pod ` remains the explicit direct Pod path。 +- Ensure help text and parser error behavior make the new command model discoverable。 +- Ensure workspace filtering depends on metadata `workspace_root`, not live socket heuristics alone。 + +--- + + + +## State changed + +Human authorized queue routing from Workspace Dashboard. Ticket has concrete acceptance criteria and no recorded blockers, so Orchestrator accepts implementation and will create a child implementation worktree before spawning sibling Coder/Reviewer roles. + --- From 50b261e7e23f06dac871a00079fb1ea766e150e7 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:31:58 +0900 Subject: [PATCH 02/11] ticket: start resume cli implementation --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index d0c0fadd..929e90b9 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:31:00Z' +updated_at: '2026-06-20T16:31:44Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index a0d6f873..87153665 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -111,3 +111,28 @@ Critical risks / reviewer focus: Human authorized queue routing from Workspace Dashboard. Ticket has concrete acceptance criteria and no recorded blockers, so Orchestrator accepts implementation and will create a child implementation worktree before spawning sibling Coder/Reviewer roles. --- + + + +## Implementation report + +Implementation start report: + +- Created child implementation worktree: + - `/home/hare/Projects/yoi/.worktree/00001KVJX7VZT-cli-resume-subcommand` +- Created branch: + - `impl/00001KVJX7VZT-cli-resume-subcommand` +- Base commit: + - `a63b40f4 ticket: accept resume cli routing` +- Confirmed tracked Ticket project records are visible in the child worktree. +- Confirmed `.yoi/memory` has no tracked/untracked entries in the child worktree check. +- Spawned sibling Coder Pod: + - `yoi-coder-00001KVJX7VZT` +- Coder scope: + - read: `/home/hare/Projects/yoi` + - write: `/home/hare/Projects/yoi/.worktree/00001KVJX7VZT-cli-resume-subcommand` + +Next action: +- Wait for Coder implementation report, then spawn Reviewer read-only for the implementation diff. Orchestrator will not merge/close until reviewer approval and validation evidence are available。 + +--- From 861c351a96cd52a92cd28ff4137e15c49cb19bbd Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:45:28 +0900 Subject: [PATCH 03/11] feat: add explicit resume command --- crates/tui/src/console/mod.rs | 9 +- crates/tui/src/lib.rs | 15 +- crates/tui/src/picker.rs | 113 +++++++++++-- crates/yoi/src/main.rs | 289 +++++++++++++++++++--------------- 4 files changed, 283 insertions(+), 143 deletions(-) diff --git a/crates/tui/src/console/mod.rs b/crates/tui/src/console/mod.rs index 1c622f32..dccd8a58 100644 --- a/crates/tui/src/console/mod.rs +++ b/crates/tui/src/console/mod.rs @@ -275,10 +275,17 @@ async fn connect_live_pod( pub(crate) async fn run_resume( runtime_command: PodRuntimeCommand, + workspace_root: PathBuf, + all: bool, ) -> Result<(), Box> { // Pick a Pod in its own inline viewport, dropping the viewport before // attaching/restoring so each phase gets fresh vertical room. - let (pod_name, socket_override) = match picker::run().await? { + let picker_options = if all { + picker::PickerOptions::all() + } else { + picker::PickerOptions::workspace(workspace_root) + }; + let (pod_name, socket_override) = match picker::run(picker_options).await? { PickerOutcome::Picked { pod_name, socket_override, diff --git a/crates/tui/src/lib.rs b/crates/tui/src/lib.rs index b19694ba..b14be24d 100644 --- a/crates/tui/src/lib.rs +++ b/crates/tui/src/lib.rs @@ -48,16 +48,17 @@ pub enum LaunchMode { pod_name: Option, profile: Option, }, - /// `yoi ` / `yoi --pod `: attach to a live Pod by name if - /// possible; otherwise launch the Pod runtime command with `--pod ` so it + /// `yoi --pod `: attach to a live Pod by name if possible; + /// otherwise launch the Pod runtime command with `--pod ` so it /// resumes from name-keyed state or creates a fresh same-name Pod. PodName { pod_name: String, socket_override: Option, }, - /// `yoi -r` / `yoi --resume`: open the Pod picker, then attach to the - /// selected live Pod or restore the selected stopped Pod by name. - Resume, + /// `yoi resume`: open the Pod picker, then attach to the selected live Pod + /// or restore the selected stopped Pod by name. Without `--all`, the picker + /// is scoped to the current runtime workspace. + Resume { all: bool }, /// `yoi --session `: skip the picker, go straight to the /// resume name dialog with `id` baked in. ResumeWithSession { @@ -101,7 +102,9 @@ pub async fn launch(options: LaunchOptions) -> ExitCode { pod_name, socket_override, } => console::run_pod_name(pod_name, socket_override, runtime_command).await, - LaunchMode::Resume => console::run_resume(runtime_command).await, + LaunchMode::Resume { all } => { + console::run_resume(runtime_command, workspace_root.clone(), all).await + } LaunchMode::ResumeWithSession { id, pod_name } => { console::run_spawn(Some(id), pod_name, None, runtime_command).await } diff --git a/crates/tui/src/picker.rs b/crates/tui/src/picker.rs index 4c607d4f..2b3dfdd8 100644 --- a/crates/tui/src/picker.rs +++ b/crates/tui/src/picker.rs @@ -20,7 +20,7 @@ use ratatui::{Frame, TerminalOptions, Viewport}; use session_store::FsStore; use crate::pod_list::{ - PodList, PodListEntry, PodVisibilitySource, StoredMetadataState, + LivePodInfo, PodList, PodListEntry, PodVisibilitySource, StoredMetadataState, StoredPodInfo, live_socket_for_pod as pod_list_live_socket_for_pod, read_reachable_live_pod_infos, read_stored_pod_infos, }; @@ -73,6 +73,31 @@ pub enum PickerOutcome { Cancelled, } +#[derive(Debug, Clone)] +pub(crate) struct PickerOptions { + scope: PickerScope, +} + +impl PickerOptions { + pub(crate) fn workspace(workspace_root: PathBuf) -> Self { + Self { + scope: PickerScope::Workspace(workspace_root), + } + } + + pub(crate) fn all() -> Self { + Self { + scope: PickerScope::All, + } + } +} + +#[derive(Debug, Clone)] +enum PickerScope { + Workspace(PathBuf), + All, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum PodRowState { Live, @@ -100,7 +125,31 @@ impl PodRowState { } } -pub async fn run() -> Result { +fn list_for_options( + options: &PickerOptions, + stored_pods: Vec, + live_pods: Vec, +) -> PodList { + match &options.scope { + PickerScope::Workspace(workspace_root) => PodList::from_workspace_sources( + PodVisibilitySource::ResumePicker, + stored_pods, + live_pods, + None, + MAX_ROWS, + workspace_root, + ), + PickerScope::All => PodList::from_sources( + PodVisibilitySource::ResumePicker, + stored_pods, + live_pods, + None, + MAX_ROWS, + ), + } +} + +pub async fn run(options: PickerOptions) -> Result { let store_dir = default_store_dir()?; let store = FsStore::new(&store_dir)?; let pod_store = FsPodStore::new(default_pod_store_dir()?).map_err(io::Error::other)?; @@ -108,13 +157,7 @@ pub async fn run() -> Result { let live_pods = read_reachable_live_pod_infos(&store) .await .unwrap_or_default(); - let mut list = PodList::from_sources( - PodVisibilitySource::ResumePicker, - stored_pods, - live_pods, - None, - MAX_ROWS, - ); + let mut list = list_for_options(&options, stored_pods, live_pods); if list.entries.is_empty() { return Err(PickerError::NoPods); } @@ -361,6 +404,58 @@ mod tests { assert_eq!(picker_title(), "resume pod pick a pod"); } + #[test] + fn picker_workspace_options_filter_by_workspace_metadata() { + let list = list_for_options( + &PickerOptions::workspace(PathBuf::from("/workspace/current")), + vec![ + stored_pod("current", Some("/workspace/current"), 3), + stored_pod("other", Some("/workspace/other"), 2), + stored_pod("legacy", None, 1), + ], + vec![], + ); + + let names: Vec<_> = list + .entries + .iter() + .map(|entry| entry.name.as_str()) + .collect(); + assert_eq!(names, vec!["current"]); + } + + #[test] + fn picker_all_options_include_host_wide_and_legacy_pods() { + let list = list_for_options( + &PickerOptions::all(), + vec![ + stored_pod("current", Some("/workspace/current"), 3), + stored_pod("other", Some("/workspace/other"), 2), + stored_pod("legacy", None, 1), + ], + vec![], + ); + + let names: Vec<_> = list + .entries + .iter() + .map(|entry| entry.name.as_str()) + .collect(); + assert_eq!(names, vec!["current", "other", "legacy"]); + } + + fn stored_pod(name: &str, workspace_root: Option<&str>, updated_at: u64) -> StoredPodInfo { + StoredPodInfo { + pod_name: name.to_string(), + metadata_state: StoredMetadataState::Present, + active_session_id: None, + active_segment_id: None, + updated_at, + workspace_root: workspace_root.map(PathBuf::from), + preview: None, + } + } + #[test] fn picker_row_shows_live_pending_preview_and_runtime_segment_id() { let segment_id = session_store::new_segment_id(); diff --git a/crates/yoi/src/main.rs b/crates/yoi/src/main.rs index 4a2e4e81..40f27524 100644 --- a/crates/yoi/src/main.rs +++ b/crates/yoi/src/main.rs @@ -17,6 +17,7 @@ use tui::{LaunchMode, LaunchOptions}; #[derive(Debug)] enum Mode { Help, + ResumeHelp, MemoryLintHelp, MemoryLint(LintCliOptions), Mcp(mcp_cli::McpCliCommand), @@ -60,6 +61,10 @@ async fn main() -> ExitCode { print_help(); ExitCode::SUCCESS } + Mode::ResumeHelp => { + print_resume_help(); + ExitCode::SUCCESS + } Mode::MemoryLintHelp => { print_memory_lint_help(); ExitCode::SUCCESS @@ -168,13 +173,13 @@ fn parse_args_slice(args: &[String]) -> Result { pod_name: None, profile: None, }, - workspace_root: std::env::current_dir() - .map_err(|e| ParseError(format!("failed to resolve current directory: {e}")))?, + workspace_root: current_dir()?, }); } match args[0].as_str() { "--help" | "-h" => return Ok(Mode::Help), + "resume" => return parse_resume_args(&args[1..]), "pod" => return Ok(Mode::PodRuntime(args[1..].to_vec())), "objective" => { let objective_cli = objective_cli::parse_objective_args(&args[1..]) @@ -229,35 +234,30 @@ fn parse_args_slice(args: &[String]) -> Result { return Ok(Mode::MemoryLint(options)); } "memory" => { - return Ok(Mode::Tui { - mode: LaunchMode::PodName { - pod_name: "memory".to_string(), - socket_override: None, - }, - workspace_root: std::env::current_dir() - .map_err(|e| ParseError(format!("failed to resolve current directory: {e}")))?, - }); + return Err(ParseError( + "yoi memory requires the `lint` subcommand".to_string(), + )); + } + other if !other.starts_with('-') => { + return Err(ParseError(format!("unknown command `{other}`"))); } _ => {} } - let mut workspace_root = std::env::current_dir() - .map_err(|e| ParseError(format!("failed to resolve current directory: {e}")))?; - let mut resume = false; + parse_console_options(args) +} + +fn parse_console_options(args: &[String]) -> Result { + let mut workspace_root = current_dir()?; let mut session = None; let mut pod_name = None; let mut socket_override = None; let mut profile = None; - let mut positional = None; let mut i = 0; while i < args.len() { let arg = &args[i]; match arg.as_str() { - "--resume" | "-r" => { - resume = true; - i += 1; - } "--session" => { let value = args .get(i + 1) @@ -349,51 +349,21 @@ fn parse_args_slice(args: &[String]) -> Result { return Err(ParseError(format!("unknown argument: {arg}"))); } value => { - if positional.replace(value.to_string()).is_some() { - return Err(ParseError( - "only one positional Pod name is supported".to_string(), - )); - } - i += 1; + return Err(ParseError(format!( + "unknown command `{value}`; use --pod to open a Pod by name" + ))); } } } - if pod_name.is_some() && positional.is_some() { - return Err(ParseError( - "--pod and a positional Pod name are mutually exclusive".to_string(), - )); - } - - if profile.is_some() - && (resume || session.is_some() || positional.is_some() || socket_override.is_some()) - { + if profile.is_some() && (session.is_some() || socket_override.is_some()) { return Err(ParseError( "--profile can only be used for fresh spawn".to_string(), )); } - if pod_name.is_some() && resume { - return Err(ParseError( - "--pod and --resume are mutually exclusive".to_string(), - )); + if socket_override.is_some() && pod_name.is_none() { + return Err(ParseError("--socket requires --pod".to_string())); } - if positional.is_some() && resume { - return Err(ParseError( - "--resume cannot be used with a positional Pod name".to_string(), - )); - } - if socket_override.is_some() && pod_name.is_none() && positional.is_none() { - return Err(ParseError( - "--socket requires --pod or a positional Pod name".to_string(), - )); - } - if resume && session.is_some() { - return Err(ParseError( - "--resume and --session are mutually exclusive".to_string(), - )); - } - - let pod_name = pod_name.or(positional); if socket_override.is_some() && session.is_some() { return Err(ParseError( "--socket can only be used with --pod attach mode".to_string(), @@ -424,12 +394,6 @@ fn parse_args_slice(args: &[String]) -> Result { workspace_root, }); } - if resume { - return Ok(Mode::Tui { - mode: LaunchMode::Resume, - workspace_root, - }); - } Ok(Mode::Tui { mode: LaunchMode::Spawn { pod_name: None, @@ -439,6 +403,75 @@ fn parse_args_slice(args: &[String]) -> Result { }) } +fn parse_resume_args(args: &[String]) -> Result { + let mut workspace_root = current_dir()?; + let mut workspace_set = false; + let mut all = false; + + let mut i = 0; + while i < args.len() { + let arg = &args[i]; + match arg.as_str() { + "--help" | "-h" => { + if args.len() == 1 { + return Ok(Mode::ResumeHelp); + } + return Err(ParseError( + "yoi resume --help does not accept other arguments".to_string(), + )); + } + "--all" => { + all = true; + i += 1; + } + "--workspace" => { + let value = args + .get(i + 1) + .ok_or_else(|| ParseError("--workspace requires a value".to_string()))?; + if value.starts_with('-') { + return Err(ParseError("--workspace requires a value".to_string())); + } + workspace_root = PathBuf::from(value); + workspace_set = true; + i += 2; + } + arg if arg.starts_with("--workspace=") => { + let value = arg.trim_start_matches("--workspace="); + if value.is_empty() { + return Err(ParseError("--workspace requires a value".to_string())); + } + workspace_root = PathBuf::from(value); + workspace_set = true; + i += 1; + } + arg if arg.starts_with('-') => { + return Err(ParseError(format!("unknown yoi resume option `{arg}`"))); + } + value => { + return Err(ParseError(format!( + "yoi resume does not accept positional argument `{value}`" + ))); + } + } + } + + if all && workspace_set { + return Err(ParseError( + "yoi resume --all and --workspace are mutually exclusive".to_string(), + )); + } + + Ok(Mode::Tui { + mode: LaunchMode::Resume { all }, + workspace_root, + }) +} + +fn current_dir() -> Result { + std::env::current_dir() + .map_err(|e| ParseError(format!("failed to resolve current directory: {e}"))) +} + fn parse_plugin_args(args: &[String]) -> Result { let Some((subcommand, rest)) = args.split_first() else { return Err(ParseError( @@ -777,7 +810,13 @@ fn parse_session_id(value: &str) -> Result { fn print_help() { println!( - "yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi panel [--workspace ]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi objective [OPTIONS]\n yoi session analyze --json\n yoi ticket [OPTIONS]\n yoi plugin new rust-component-tool [--json]\n yoi plugin check [--json]\n yoi plugin pack [--output ] [--json]\n yoi plugin list [--workspace ] [--profile ] [--json]\n yoi plugin show [--workspace ] [--profile ] [--json]\n yoi mcp list [--workspace ] [--profile ] [--json]\n yoi mcp show [--workspace ] [--profile ] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace ] [--profile ] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Pod chat/client surface (default, --pod, --resume)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n -r, --resume Open the Pod Console picker and resume/attach a Pod\n --workspace Runtime workspace root (defaults to cwd)\n --pod Open the Pod Console by name (attach/restore/create)\n --socket Attach a Pod Console to a specific socket with --pod\n --session Resume a specific session segment in the Pod Console\n --profile Select a reusable Profile recipe\n -h, --help Print help\n" + "yoi\n\nUsage:\n yoi [OPTIONS]\n yoi resume [--workspace ] [--all]\n yoi panel [--workspace ]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi objective [OPTIONS]\n yoi session analyze --json\n yoi ticket [OPTIONS]\n yoi plugin new rust-component-tool [--json]\n yoi plugin check [--json]\n yoi plugin pack [--output ] [--json]\n yoi plugin list [--workspace ] [--profile ] [--json]\n yoi plugin show [--workspace ] [--profile ] [--json]\n yoi mcp list [--workspace ] [--profile ] [--json]\n yoi mcp show [--workspace ] [--profile ] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace ] [--profile ] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Pod chat/client surface (default, --pod, yoi resume)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n --workspace Runtime workspace root for default Console/--pod (defaults to cwd)\n --pod Open the Pod Console by name (attach/restore/create)\n --socket Attach a Pod Console to a specific socket with --pod\n --session Resume a specific session segment in the Pod Console\n --profile Select a reusable Profile recipe\n -h, --help Print help\n" + ); +} + +fn print_resume_help() { + println!( + "yoi resume\n\nUsage:\n yoi resume [--workspace ] [--all]\n\nOptions:\n --workspace Open the Pod Console picker scoped to this workspace (defaults to cwd)\n --all Open the Pod Console picker across this host/data dir\n -h, --help Print help\n" ); } @@ -810,38 +849,50 @@ mod tests { } #[test] - fn parse_positional_name_uses_pod_name_mode() { - match parse_args_from(["agent"]).unwrap() { + fn parse_bare_word_is_unknown_command() { + let err = parse_args_from(["agent"]).unwrap_err(); + assert_eq!(err.to_string(), "unknown command `agent`"); + } + + #[test] + fn parse_memory_without_lint_is_usage_error() { + let err = parse_args_from(["memory"]).unwrap_err(); + assert_eq!(err.to_string(), "yoi memory requires the `lint` subcommand"); + } + + #[test] + fn parse_resume_subcommand_defaults_to_workspace_scope() { + match parse_args_from(["resume"]).unwrap() { Mode::Tui { - mode: - LaunchMode::PodName { - pod_name, - socket_override, - }, + mode: LaunchMode::Resume { all }, .. - } => { - assert_eq!(pod_name, "agent"); - assert_eq!(socket_override, None); - } - _ => panic!("expected PodName mode"), + } => assert!(!all), + _ => panic!("expected Resume mode"), } } #[test] - fn parse_memory_alone_remains_positional_pod_name() { - match parse_args_from(["memory"]).unwrap() { + fn parse_resume_workspace_scope() { + match parse_args_from(["resume", "--workspace", "/tmp/resume-workspace"]).unwrap() { Mode::Tui { - mode: - LaunchMode::PodName { - pod_name, - socket_override, - }, - .. + mode: LaunchMode::Resume { all }, + workspace_root, } => { - assert_eq!(pod_name, "memory"); - assert_eq!(socket_override, None); + assert!(!all); + assert_eq!(workspace_root, PathBuf::from("/tmp/resume-workspace")); } - _ => panic!("expected PodName mode"), + _ => panic!("expected Resume mode"), + } + } + + #[test] + fn parse_resume_all_scope() { + match parse_args_from(["resume", "--all"]).unwrap() { + Mode::Tui { + mode: LaunchMode::Resume { all }, + .. + } => assert!(all), + _ => panic!("expected Resume mode"), } } @@ -1038,14 +1089,9 @@ mod tests { } #[test] - fn memory_lint_with_other_second_word_remains_positional_pod_name() { - match parse_args_from(["memory", "other"]).unwrap() { - Mode::Tui { - mode: LaunchMode::PodName { pod_name, .. }, - .. - } => assert_eq!(pod_name, "memory"), - _ => panic!("expected PodName mode"), - } + fn memory_lint_with_other_second_word_is_usage_error() { + let err = parse_args_from(["memory", "other"]).unwrap_err(); + assert_eq!(err.to_string(), "yoi memory requires the `lint` subcommand"); } #[test] @@ -1075,19 +1121,13 @@ mod tests { } #[test] - fn parse_rejects_resume_and_pod_name_selection() { + fn parse_rejects_legacy_resume_flags() { let cases = [ - ( - vec!["-r".to_string(), "--pod".to_string(), "agent".to_string()], - "--pod and --resume are mutually exclusive", - ), + (vec!["-r".to_string()], "unknown argument: -r"), + (vec!["--resume".to_string()], "unknown argument: --resume"), ( vec!["--pod".to_string(), "agent".to_string(), "-r".to_string()], - "--pod and --resume are mutually exclusive", - ), - ( - vec!["-r".to_string(), "agent".to_string()], - "--resume cannot be used with a positional Pod name", + "unknown argument: -r", ), ]; @@ -1097,6 +1137,15 @@ mod tests { } } + #[test] + fn parse_resume_rejects_workspace_with_all() { + let err = parse_args_from(["resume", "--workspace", "/tmp/ws", "--all"]).unwrap_err(); + assert_eq!( + err.to_string(), + "yoi resume --all and --workspace are mutually exclusive" + ); + } + #[test] fn parse_profile_spawn_mode() { match parse_args_from([ @@ -1125,14 +1174,6 @@ mod tests { fn parse_profile_rejects_resume_attach_modes() { let segment_id = session_store::new_segment_id().to_string(); let cases = [ - ( - vec![ - "--profile".to_string(), - "p.lua".to_string(), - "--resume".to_string(), - ], - "--profile can only be used for fresh spawn", - ), ( vec![ "--profile".to_string(), @@ -1151,14 +1192,6 @@ mod tests { ], "--profile can only be used for fresh spawn", ), - ( - vec![ - "--profile".to_string(), - "p.lua".to_string(), - "agent".to_string(), - ], - "--profile can only be used for fresh spawn", - ), ]; for (args, message) in cases { @@ -1179,15 +1212,9 @@ mod tests { } #[test] - fn parse_dashboard_word_remains_a_pod_console_name_not_an_alias() { - let config = parse_args_from(["dashboard"]).unwrap(); - match config { - Mode::Tui { - mode: LaunchMode::PodName { pod_name, .. }, - .. - } => assert_eq!(pod_name, "dashboard"), - other => panic!("expected PodName TUI mode, got {other:?}"), - } + fn parse_dashboard_word_is_not_an_alias_or_pod_name() { + let err = parse_args_from(["dashboard"]).unwrap_err(); + assert_eq!(err.to_string(), "unknown command `dashboard`"); } #[test] @@ -1204,6 +1231,14 @@ mod tests { } } + #[test] + fn parse_resume_help() { + match parse_args_from(["resume", "--help"]).unwrap() { + Mode::ResumeHelp => {} + _ => panic!("expected ResumeHelp mode"), + } + } + #[test] fn parse_memory_lint_help() { match parse_args_from(["memory", "lint", "--help"]).unwrap() { From 8fcfa6e016ce6957bbebdd1ce534572182f57882 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:46:27 +0900 Subject: [PATCH 04/11] ticket: record resume cli implementation --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 52 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index 929e90b9..6d20f55c 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:31:44Z' +updated_at: '2026-06-20T16:46:22Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index 87153665..86808a4e 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -136,3 +136,55 @@ Next action: - Wait for Coder implementation report, then spawn Reviewer read-only for the implementation diff. Orchestrator will not merge/close until reviewer approval and validation evidence are available。 --- + + + +## Implementation report + +Coder implementation report received from `yoi-coder-00001KVJX7VZT`. + +Implementation commit: +- `861c351a feat: add explicit resume command` + +Changed files: +- `crates/yoi/src/main.rs` +- `crates/tui/src/lib.rs` +- `crates/tui/src/console/mod.rs` +- `crates/tui/src/picker.rs` + +Coder summary: +- Added explicit `yoi resume` subcommand。 +- Added `yoi resume --workspace ` workspace-scoped picker behavior。 +- Added `yoi resume --all` host/data-dir-wide picker behavior。 +- Removed legacy top-level `-r` / `--resume` resume mode。 +- Removed bare positional Pod-name inference (`yoi agent` now errors as unknown command)。 +- Preserved explicit `--pod ` routing。 +- Updated help to remove `[POD_NAME]` and `-r, --resume`, and document `yoi resume [--workspace ] [--all]`。 +- Reused `PodList::from_workspace_sources(...)` for workspace-scoped resume filtering。 +- Added parser and picker/workspace tests for new behavior。 + +Coder validation reported: +- `cargo fmt --check`: passed +- `cargo test -p yoi parse_`: passed (`32 passed`) +- `cargo test -p tui picker_`: passed (`7 passed`) +- `cargo test -p tui workspace`: passed (`37 passed`) +- `cargo check -p yoi -p tui`: passed +- `git diff --check`: passed +- `target/debug/yoi ticket doctor`: passed (`doctor: ok`) +- CLI smoke: + - `target/debug/yoi --help`: passed + - `target/debug/yoi resume --help`: passed + - `target/debug/yoi agent`: nonzero with `unknown command \`agent\`` + - `target/debug/yoi -r`: nonzero with `unknown argument: -r` + - `target/debug/yoi --pod cli-smoke --socket /tmp/yoi-cli-smoke.sock`: reached TUI launch path and failed raw-mode due non-TTY environment as expected; parser tests cover explicit `--pod` routing。 + +Orchestrator evidence checked: +- Implementation worktree is clean on `impl/00001KVJX7VZT-cli-resume-subcommand`。 +- HEAD is `861c351a`。 +- Diff `a63b40f4..HEAD` touches 4 expected files with 282 insertions / 142 deletions。 +- `git diff --check a63b40f4..HEAD` produced no diagnostics。 + +Next action: +- Route to read-only Reviewer Pod for acceptance review against the Ticket and IntentPacket。 + +--- From 2081fd5bdaf7fd966f88ce72e5adee5af3bb8dc8 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:46:56 +0900 Subject: [PATCH 05/11] ticket: start resume cli review --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 31 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index 6d20f55c..b000f373 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:46:22Z' +updated_at: '2026-06-20T16:46:52Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index 86808a4e..4dd6e17d 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -188,3 +188,34 @@ Next action: - Route to read-only Reviewer Pod for acceptance review against the Ticket and IntentPacket。 --- + + + +## Comment + +Review routing: + +Spawned read-only Reviewer Pod: +- `yoi-reviewer-00001KVJX7VZT-r1` + +Review target: +- Worktree: `/home/hare/Projects/yoi/.worktree/00001KVJX7VZT-cli-resume-subcommand` +- Branch: `impl/00001KVJX7VZT-cli-resume-subcommand` +- Baseline: `a63b40f4` +- Implementation commit: `861c351a feat: add explicit resume command` +- Diff: `a63b40f4..861c351a` + +Reviewer scope: +- read-only `/home/hare/Projects/yoi` + +Reviewer focus: +- No remaining `-r` / `--resume` compatibility alias。 +- No remaining top-level bare Pod-name inference。 +- Default `yoi resume` is workspace-scoped and `resume --all` is required for host-wide listing。 +- Workspace filtering uses persisted Pod metadata `workspace_root` semantics。 +- Explicit `--pod ` and existing subcommands remain parseable。 +- Help/errors are discoverable and no longer advertise `[POD_NAME]` or legacy resume flag。 + +Orchestrator will wait for reviewer verdict before integration。 + +--- From 79ee5c4388ff1a83d4b200603b9c3d805a609766 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:50:52 +0900 Subject: [PATCH 06/11] ticket: request changes on resume cli guidance --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 82 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index b000f373..9b74a255 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:46:52Z' +updated_at: '2026-06-20T16:50:49Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index 4dd6e17d..159c1ce4 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -219,3 +219,85 @@ Reviewer focus: Orchestrator will wait for reviewer verdict before integration。 --- + + + +## Review: request changes + +Verdict: `request_changes` + +確認範囲: +- Ticket authority: + - `.yoi/tickets/00001KVJX7VZT/item.md` + - `.yoi/tickets/00001KVJX7VZT/thread.md` +- Implementation diff `a63b40f4..861c351a`: + - `crates/yoi/src/main.rs` + - `crates/tui/src/lib.rs` + - `crates/tui/src/console/mod.rs` + - `crates/tui/src/picker.rs` +- Related inspected file for stale user-facing guidance: + - `crates/tui/src/dashboard/mod.rs` +- Related workspace filtering authority: + - `crates/tui/src/pod_list.rs` + +Core implementation assessment: +- `resume` is parsed as an explicit subcommand。 +- Legacy `-r` / `--resume` no longer parse as resume。 +- Bare top-level words are no longer inferred as Pod names。 +- `--pod ` remains the explicit direct path。 +- Default resume uses `PickerOptions::workspace(...)` / `PodList::from_workspace_sources(...)`。 +- `resume --all` uses host/data-dir-wide sources。 + +Blocking issue: + +1. Stale user-facing guidance still tells users to run removed `yoi -r`。 + +Evidence: + +```rust +// crates/tui/src/dashboard/mod.rs:91-94 +Self::NoPods => write!( + f, + "no Tickets or Pods found — create a Ticket with `yoi ticket create` or restore a Pod with `yoi -r`" +), +``` + +This is misleading because `yoi -r` intentionally fails: + +```text +$ target/debug/yoi -r +yoi: unknown argument: -r +try `yoi --help` for usage. +exit=1 +``` + +This conflicts with the Ticket’s explicit CLI replacement intent and the reviewer focus that parser/help/user-facing errors should be discoverable and not misleading. Update the guidance to `yoi resume`, or if this Dashboard no-pods case intends host/data-dir-wide discovery, use explicit `yoi resume --all` wording。 + +Non-blocking concern: +- `PickerError::NoPods` still says `no pods found — start a fresh pod with `yoi` and try again`。 +- For default workspace-scoped `yoi resume`, this can be ambiguous if Pods exist outside the current workspace。Consider a scope-aware message such as `no pods found in this workspace; use `yoi resume --all` to list all host/data-dir Pods` either in this fix or as follow-up。This is not blocking by itself。 + +Reviewer validation: +- `cargo fmt --check`: passed +- `git diff --check a63b40f4..HEAD`: passed +- `cargo test -p yoi parse_`: passed, 32 tests +- `cargo test -p tui picker_`: passed, 7 tests +- `cargo test -p tui workspace`: passed, 37 tests +- `cargo check -p yoi -p tui`: passed +- `target/debug/yoi ticket doctor`: passed (`doctor: ok`) +- CLI smoke: + - `target/debug/yoi --help`: exit 0; no `[POD_NAME]`, no `-r, --resume`, includes `yoi resume [--workspace ] [--all]` + - `target/debug/yoi resume --help`: exit 0; documents `--workspace` and `--all` + - `target/debug/yoi agent`: exit 1; `unknown command 'agent'` + - `target/debug/yoi -r`: exit 1; `unknown argument: -r` + - `target/debug/yoi --resume`: exit 1; `unknown argument: --resume` + - `target/debug/yoi memory other`: exit 1; usage error, not Pod-name inference + - `target/debug/yoi resume --all --workspace /tmp/ws`: exit 1; mutually exclusive error +- Stale-alias search: + - `rg 'yoi -r|--resume|\[POD_NAME\]' . --glob '!target/**' --glob '!Cargo.lock'` + - Found blocking active product-code stale guidance at `crates/tui/src/dashboard/mod.rs:93`; historical docs/report mentions were not treated as product guidance。 + +Worktree status: +- Clean on `impl/00001KVJX7VZT-cli-resume-subcommand`。 + +--- From d25ca6ff3cdec0e3ee0a62f7ea0cffd3b73bfb1e Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:53:32 +0900 Subject: [PATCH 07/11] fix: update resume guidance --- crates/tui/src/dashboard/mod.rs | 2 +- crates/tui/src/picker.rs | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/tui/src/dashboard/mod.rs b/crates/tui/src/dashboard/mod.rs index 87c6e974..e1e25a98 100644 --- a/crates/tui/src/dashboard/mod.rs +++ b/crates/tui/src/dashboard/mod.rs @@ -90,7 +90,7 @@ impl std::fmt::Display for DashboardError { Self::Store(e) => write!(f, "session store error: {e}"), Self::NoPods => write!( f, - "no Tickets or Pods found — create a Ticket with `yoi ticket create` or restore a Pod with `yoi -r`" + "no Tickets or Pods found — create a Ticket with `yoi ticket create` or restore a Pod with `yoi resume`" ), } } diff --git a/crates/tui/src/picker.rs b/crates/tui/src/picker.rs index 2b3dfdd8..bd3c538c 100644 --- a/crates/tui/src/picker.rs +++ b/crates/tui/src/picker.rs @@ -32,7 +32,7 @@ const VIEWPORT_LINES: u16 = MAX_ROWS as u16 + 4; pub enum PickerError { Io(io::Error), Store(session_store::StoreError), - NoPods, + NoPods { all: bool }, } impl std::fmt::Display for PickerError { @@ -40,10 +40,14 @@ impl std::fmt::Display for PickerError { match self { Self::Io(e) => write!(f, "io error: {e}"), Self::Store(e) => write!(f, "session store error: {e}"), - Self::NoPods => write!( + Self::NoPods { all: true } => write!( f, "no pods found — start a fresh pod with `yoi` and try again" ), + Self::NoPods { all: false } => write!( + f, + "no pods found in this workspace — use `yoi resume --all` to list all host/data-dir Pods" + ), } } } @@ -159,7 +163,9 @@ pub async fn run(options: PickerOptions) -> Result { .unwrap_or_default(); let mut list = list_for_options(&options, stored_pods, live_pods); if list.entries.is_empty() { - return Err(PickerError::NoPods); + return Err(PickerError::NoPods { + all: matches!(options.scope, PickerScope::All), + }); } let mut terminal = make_inline_terminal()?; @@ -404,6 +410,20 @@ mod tests { assert_eq!(picker_title(), "resume pod pick a pod"); } + #[test] + fn picker_no_pods_message_mentions_all_for_workspace_scope() { + let message = PickerError::NoPods { all: false }.to_string(); + assert!(message.contains("no pods found in this workspace")); + assert!(message.contains("yoi resume --all")); + } + + #[test] + fn picker_no_pods_message_keeps_fresh_pod_hint_for_all_scope() { + let message = PickerError::NoPods { all: true }.to_string(); + assert!(message.contains("start a fresh pod with `yoi`")); + assert!(!message.contains("yoi resume --all")); + } + #[test] fn picker_workspace_options_filter_by_workspace_metadata() { let list = list_for_options( From bf4bf4da09b15c6a24f037f3f80f0970ecbfd0e8 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:54:29 +0900 Subject: [PATCH 08/11] ticket: record resume guidance fix --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 53 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index 9b74a255..0b2ea184 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:50:49Z' +updated_at: '2026-06-20T16:54:23Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index 159c1ce4..e4264e82 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -301,3 +301,56 @@ Worktree status: - Clean on `impl/00001KVJX7VZT-cli-resume-subcommand`。 --- + + + +## Implementation report + +Coder r1-fix report received from `yoi-coder-00001KVJX7VZT`. + +New fix commit: +- `d25ca6ff fix: update resume guidance` + +Branch commits now: +- `861c351a feat: add explicit resume command` +- `d25ca6ff fix: update resume guidance` + +Changed files in r1 fix: +- `crates/tui/src/dashboard/mod.rs` +- `crates/tui/src/picker.rs` + +Fix summary: +- Updated Dashboard no-pods guidance from removed `yoi -r` to `yoi resume`。 +- Searched active product guidance for `yoi -r`, `--resume`, and `[POD_NAME]` excluding historical docs/reports and Ticket records; no remaining active stale guidance found。 +- Addressed Reviewer non-blocking concern: + - Workspace-scoped resume empty result now says: `no pods found in this workspace — use yoi resume --all to list all host/data-dir Pods` + - Host-wide `--all` empty result keeps the fresh Pod hint。 + - Added picker tests for these messages。 + +Coder validation reported: +- `cargo fmt --check`: passed +- `git diff --check`: passed +- `cargo test -p tui picker_`: passed +- `cargo test -p yoi parse_`: passed +- `cargo test -p tui workspace`: passed +- `cargo check -p yoi -p tui`: passed +- `target/debug/yoi ticket doctor`: passed +- `cargo build -p yoi`: passed +- CLI smoke: + - `target/debug/yoi --help`: passed + - `target/debug/yoi resume --help`: passed + - `target/debug/yoi agent`: nonzero with `unknown command \`agent\`` + - `target/debug/yoi -r`: nonzero with `unknown argument: -r` + - `target/debug/yoi --resume`: nonzero with `unknown argument: --resume` + +Orchestrator evidence checked: +- Implementation worktree clean on `impl/00001KVJX7VZT-cli-resume-subcommand`。 +- HEAD is `d25ca6ff`。 +- r1-fix diff `861c351a..HEAD` touches only `crates/tui/src/dashboard/mod.rs` and `crates/tui/src/picker.rs`。 +- Active product-guidance grep under `crates` / `resources` for `yoi -r|--resume|[POD_NAME]` returned only the parser test expectation for `unknown argument: --resume`, not active user guidance。 +- `git diff --check a63b40f4..HEAD` produced no diagnostics。 + +Next action: +- Route back to Reviewer for r2 verification and final acceptance review。 + +--- From 35fba2b6fbc346fec809c8fc2ebe9bf042e102e6 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 01:58:34 +0900 Subject: [PATCH 09/11] ticket: approve resume cli changes --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 69 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index 0b2ea184..343023fb 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'inprogress' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:54:23Z' +updated_at: '2026-06-20T16:58:30Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index e4264e82..d56e7e75 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -354,3 +354,72 @@ Next action: - Route back to Reviewer for r2 verification and final acceptance review。 --- + + + +## Review: approve + +Verdict: `approve` + +確認範囲: +- Ticket authority: + - `.yoi/tickets/00001KVJX7VZT/item.md` + - `.yoi/tickets/00001KVJX7VZT/thread.md` +- r1 blocker fix diff `861c351a..d25ca6ff`: + - `crates/tui/src/dashboard/mod.rs` + - `crates/tui/src/picker.rs` +- Overall diff `a63b40f4..d25ca6ff`: + - `crates/yoi/src/main.rs` + - `crates/tui/src/lib.rs` + - `crates/tui/src/console/mod.rs` + - `crates/tui/src/picker.rs` + - `crates/tui/src/dashboard/mod.rs` +- Workspace filtering reference: + - `crates/tui/src/pod_list.rs` + +Blocking issues: +- None。 + +Previous blocker verification: +- Dashboard no-pods guidance now says `yoi resume`, not removed `yoi -r`。 +- Workspace-scoped picker empty result now points users to explicit `yoi resume --all`。 +- Host-wide `--all` empty result keeps the fresh-Pod hint。 +- Active stale-guidance search found only the parser test expectation for rejecting `--resume`, not active user guidance。 + +Acceptance / critical focus verification: +- Legacy top-level `-r` / `--resume` no longer parse as resume; both fail as unknown arguments。 +- Top-level bare word such as `agent` fails as unknown command; no positional Pod-name inference remains。 +- `yoi resume` is an explicit subcommand and defaults to workspace-scoped picker behavior。 +- `yoi resume --workspace ` is supported and mutually exclusive with `--all`。 +- `yoi resume --all` is the explicit host/data-dir-wide listing path。 +- Default workspace filtering uses stored Pod metadata `workspace_root` semantics, with live entries included only when tied to matching stored metadata names。 +- `yoi --pod ` and explicit subcommands remain covered by parser tests。 +- Help advertises `yoi resume [--workspace ] [--all]` and no longer advertises `[POD_NAME]` or `-r, --resume`。 + +Non-blocking concerns: +- None for this Ticket。 + +Reviewer validation: +- `cargo fmt --check`: passed +- `git diff --check a63b40f4..HEAD`: passed +- `cargo test -p tui picker_`: passed, 9 tests +- `cargo test -p yoi parse_`: passed, 32 tests +- `cargo test -p tui workspace`: passed, 38 tests +- `cargo check -p yoi -p tui`: passed +- `target/debug/yoi ticket doctor`: passed (`doctor: ok`) +- `cargo build -p yoi`: passed +- CLI smoke: + - `target/debug/yoi --help`: exit 0; includes resume usage, excludes `[POD_NAME]` and `-r, --resume` + - `target/debug/yoi resume --help`: exit 0; documents `--workspace ` and `--all` + - `target/debug/yoi agent`: exit 1; `unknown command 'agent'` + - `target/debug/yoi -r`: exit 1; `unknown argument: -r` + - `target/debug/yoi --resume`: exit 1; `unknown argument: --resume` + - `target/debug/yoi resume --all --workspace /tmp/ws`: exit 1; mutually exclusive error +- Stale active guidance search: + - `rg -n 'yoi -r|--resume|\[POD_NAME\]' crates resources --glob '!target/**' --glob '!Cargo.lock'` + - Only hit: parser test expectation for `unknown argument: --resume`。 + +Worktree status: +- Clean at `d25ca6ff3cdec0e3ee0a62f7ea0cffd3b73bfb1e` on `impl/00001KVJX7VZT-cli-resume-subcommand`。 + +--- From 19c3ec45eb0c7228b2df704d2b7ad7415adeb0e5 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 02:00:29 +0900 Subject: [PATCH 10/11] ticket: complete resume cli command --- .yoi/tickets/00001KVJX7VZT/item.md | 4 +- .yoi/tickets/00001KVJX7VZT/resolution.md | 23 +++++++ .yoi/tickets/00001KVJX7VZT/thread.md | 84 ++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KVJX7VZT/resolution.md diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index 343023fb..b8182013 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -1,8 +1,8 @@ --- title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' -state: 'inprogress' +state: 'closed' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T16:58:30Z' +updated_at: '2026-06-20T17:00:25Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/resolution.md b/.yoi/tickets/00001KVJX7VZT/resolution.md new file mode 100644 index 00000000..47b31431 --- /dev/null +++ b/.yoi/tickets/00001KVJX7VZT/resolution.md @@ -0,0 +1,23 @@ +CLI resume UX を explicit `yoi resume` subcommand model に変更し、top-level bare Pod name inference と legacy `-r` / `--resume` resume path を廃止した。 + +主な成果: +- `yoi resume` を explicit subcommand として追加。 +- `yoi resume --workspace ` で指定 workspace scoped picker を開くようにした。 +- `yoi resume --all` で host/data-dir-wide Pod listing を明示 opt-in にした。 +- Default `yoi resume` は workspace-scoped picker にし、stored Pod metadata `workspace_root` semantics を使う。 +- Top-level bare word (`yoi agent` など) は Pod name inference ではなく unknown command になる。 +- `yoi -r` / `yoi --resume` は legacy resume alias ではなく unknown argument になる。 +- Explicit `yoi --pod ` path は維持。 +- Help から `[POD_NAME]` と `-r, --resume` guidance を削除し、`yoi resume [--workspace ] [--all]` を案内。 +- Dashboard / picker no-pods guidance を新 CLI に合わせて更新し、workspace-scoped empty result から `yoi resume --all` を案内。 +- Parser / picker / workspace tests を更新。 + +統合・検証: +- Merge commit: `bdc11c77 merge: explicit resume command` +- Implementation commits: `861c351a`, `d25ca6ff` +- Reviewer final verdict: approve +- Validation passed: `cargo fmt --check`, `git diff --check HEAD^1..HEAD`, `cargo test -p yoi parse_`, `cargo test -p tui picker_`, `cargo test -p tui workspace`, `cargo check -p yoi -p tui`, `cargo build -p yoi`, `target/debug/yoi ticket doctor`, and CLI smoke for help/resume help/unknown bare word/-r/--resume/mutually-exclusive resume args。 + +範囲外: +- Legacy `-r` / `--resume` compatibility alias は意図的に追加していない。 +- LLM-facing Pod scope/tool visibility には変更を加えていない。 \ No newline at end of file diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index d56e7e75..d9e6e6d6 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -423,3 +423,87 @@ Worktree status: - Clean at `d25ca6ff3cdec0e3ee0a62f7ea0cffd3b73bfb1e` on `impl/00001KVJX7VZT-cli-resume-subcommand`。 --- + + + +## Implementation report + +Reviewer r2 approvalを受けて、実装ブランチを Orchestrator worktree の `orchestration` branch に統合した。 + +統合: +- Merge commit: `bdc11c77 merge: explicit resume command` +- Merged branch: `impl/00001KVJX7VZT-cli-resume-subcommand` +- Implementation commits: + - `861c351a feat: add explicit resume command` + - `d25ca6ff fix: update resume guidance` + +Orchestrator worktree validation: +- `cargo fmt --check`: passed +- `git diff --check HEAD^1..HEAD`: passed +- `cargo test -p yoi parse_`: passed +- `cargo test -p tui picker_`: passed +- `cargo test -p tui workspace`: passed +- `cargo check -p yoi -p tui`: passed +- `cargo build -p yoi`: passed +- `target/debug/yoi ticket doctor`: passed (`doctor: ok`) +- CLI smoke: + - `target/debug/yoi --help`: passed and includes `yoi resume [--workspace ] [--all]` + - `target/debug/yoi resume --help`: passed and documents `--workspace ` and `--all` + - `target/debug/yoi agent`: nonzero and reports `unknown command \`agent\`` + - `target/debug/yoi -r`: nonzero and reports `unknown argument: -r` + - `target/debug/yoi --resume`: nonzero and reports `unknown argument: --resume` + - `target/debug/yoi resume --all --workspace /tmp/ws`: nonzero and reports mutual exclusion +- Active guidance grep: + - `rg -n 'yoi -r|--resume|\[POD_NAME\]' crates resources --glob '!target/**' --glob '!Cargo.lock'` + - only hit is parser test expectation for `unknown argument: --resume`, not active user guidance。 + +Final Orchestrator worktree status after validation: clean on `orchestration` at `bdc11c77`。 + +--- + + + +## State changed + +Reviewer approval、Orchestrator worktree への統合、focused parser / TUI tests、cargo check/build、CLI smoke、Ticket doctor が完了したため `done` に遷移する。 + +--- + + + +## State changed + +Ticket を closed にしました。 + + +--- + + + +## 完了 + +CLI resume UX を explicit `yoi resume` subcommand model に変更し、top-level bare Pod name inference と legacy `-r` / `--resume` resume path を廃止した。 + +主な成果: +- `yoi resume` を explicit subcommand として追加。 +- `yoi resume --workspace ` で指定 workspace scoped picker を開くようにした。 +- `yoi resume --all` で host/data-dir-wide Pod listing を明示 opt-in にした。 +- Default `yoi resume` は workspace-scoped picker にし、stored Pod metadata `workspace_root` semantics を使う。 +- Top-level bare word (`yoi agent` など) は Pod name inference ではなく unknown command になる。 +- `yoi -r` / `yoi --resume` は legacy resume alias ではなく unknown argument になる。 +- Explicit `yoi --pod ` path は維持。 +- Help から `[POD_NAME]` と `-r, --resume` guidance を削除し、`yoi resume [--workspace ] [--all]` を案内。 +- Dashboard / picker no-pods guidance を新 CLI に合わせて更新し、workspace-scoped empty result から `yoi resume --all` を案内。 +- Parser / picker / workspace tests を更新。 + +統合・検証: +- Merge commit: `bdc11c77 merge: explicit resume command` +- Implementation commits: `861c351a`, `d25ca6ff` +- Reviewer final verdict: approve +- Validation passed: `cargo fmt --check`, `git diff --check HEAD^1..HEAD`, `cargo test -p yoi parse_`, `cargo test -p tui picker_`, `cargo test -p tui workspace`, `cargo check -p yoi -p tui`, `cargo build -p yoi`, `target/debug/yoi ticket doctor`, and CLI smoke for help/resume help/unknown bare word/-r/--resume/mutually-exclusive resume args。 + +範囲外: +- Legacy `-r` / `--resume` compatibility alias は意図的に追加していない。 +- LLM-facing Pod scope/tool visibility には変更を加えていない。 + +--- From 5fa0846dcfcd564e0987ce3ec8e77bcde6b3b9e6 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 21 Jun 2026 02:00:53 +0900 Subject: [PATCH 11/11] ticket: record resume cli cleanup --- .yoi/tickets/00001KVJX7VZT/item.md | 2 +- .yoi/tickets/00001KVJX7VZT/thread.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KVJX7VZT/item.md b/.yoi/tickets/00001KVJX7VZT/item.md index b8182013..55798edd 100644 --- a/.yoi/tickets/00001KVJX7VZT/item.md +++ b/.yoi/tickets/00001KVJX7VZT/item.md @@ -2,7 +2,7 @@ title: 'CLI: `resume` サブコマンド化と Pod 名の暗黙解釈廃止' state: 'closed' created_at: '2026-06-20T16:18:52Z' -updated_at: '2026-06-20T17:00:25Z' +updated_at: '2026-06-20T17:00:46Z' assignee: null readiness: 'implementation_ready' risk_flags: ['cli-ux', 'pod-metadata', 'workspace-scope', 'backward-compatibility'] diff --git a/.yoi/tickets/00001KVJX7VZT/thread.md b/.yoi/tickets/00001KVJX7VZT/thread.md index d9e6e6d6..0fdbf34d 100644 --- a/.yoi/tickets/00001KVJX7VZT/thread.md +++ b/.yoi/tickets/00001KVJX7VZT/thread.md @@ -507,3 +507,22 @@ CLI resume UX を explicit `yoi resume` subcommand model に変更し、top-leve - LLM-facing Pod scope/tool visibility には変更を加えていない。 --- + + + +## Implementation report + +Post-close cleanup completed。 + +- Stopped child Pods and reclaimed scope: + - `yoi-coder-00001KVJX7VZT` + - `yoi-reviewer-00001KVJX7VZT-r1` +- Removed implementation worktree: + - `/home/hare/Projects/yoi/.worktree/00001KVJX7VZT-cli-resume-subcommand` +- Deleted implementation branch: + - `impl/00001KVJX7VZT-cli-resume-subcommand` +- Orchestrator worktree remains clean on `orchestration` at `19c3ec45`。 + +Root/original workspace was not used for merge/validation/cleanup operations。 + +---