From ac45bbec1581daa4069e07eff0907bb4ebacb8bf Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Thu, 16 Apr 2026 03:13:50 +0000 Subject: [PATCH] Make ACP/Zed status obvious before users go source-diving ROADMAP #21, #22, and #23 were already closed on current main, so the next real repo-local backlog item was the ACP/Zed discoverability gap. This adds a local `claw acp` status surface plus aliases, updates help/docs, and separates the shipped discoverability fix from the still-open daemon/protocol follow-up so editor-first users get a crisp answer immediately. Constraint: No ACP/Zed daemon or protocol server exists in claw-code yet, so the new surface must be explicit status guidance rather than a fake implementation Rejected: Add a pretend `acp serve` daemon path | would imply supported protocol behavior that does not exist Rejected: Docs-only clarification | still leaves `claw --help` unable to answer the editor-launch question directly Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep ROADMAP discoverability fixes separate from future ACP daemon/protocol work so help text and backlog IDs stay unambiguous Tested: cargo fmt --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; cargo run -q -p rusty-claude-cli -- acp; cargo run -q -p rusty-claude-cli -- --output-format json acp; architect review APPROVED Not-tested: Real ACP/Zed daemon launch because no protocol-serving surface exists yet --- README.md | 2 + ROADMAP.md | 7 +- rust/README.md | 3 + rust/crates/rusty-claude-cli/src/main.rs | 101 ++++++++++++++++++ .../tests/output_format_contract.rs | 18 ++++ 5 files changed, 129 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76504c5..dcf4038 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ The canonical implementation lives in [`rust/`](./rust), and the current source > [!IMPORTANT] > Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. Make `claw doctor` your first health check after building, use [`rust/README.md`](./rust/README.md) for crate-level details, read [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint, and see [`docs/container.md`](./docs/container.md) for the container-first workflow. +> +> **ACP / Zed status:** `claw-code` does not ship an ACP/Zed daemon entrypoint yet. Run `claw acp` (or `claw --acp`) for the current status instead of guessing from source layout; `claw acp serve` is currently a discoverability alias only, and real ACP support remains tracked separately in `ROADMAP.md`. ## Current repository shape diff --git a/ROADMAP.md b/ROADMAP.md index 51e834b..4c82560 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1145,9 +1145,10 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes 63. **Droid session completion semantics broken: code arrives after "status: completed"** — dogfooded 2026-04-12. Ultraclaw droid sessions (use-droid via acpx) report `session.status: completed` before file writes are fully flushed/synced to the working tree. Discovered +410 lines of "late-arriving" droid output that appeared after I had already assessed 8 sessions as "no code produced." This creates false-negative assessments and duplicate work. **Fix shape:** (a) droid agent should only report completion after explicit file-write confirmation (fsync or existence check); (b) or, claw-code should expose a `pending_writes` status that indicates "agent responded, disk flush pending"; (c) lane orchestrators should poll for file changes for N seconds after completion before final assessment. **Blocker:** none. Source: Jobdori ultraclaw dogfood 2026-04-12. -64. **ACP/Zed editor integration entrypoint is too implicit** — dogfooded 2026-04-13 from a user request for a `-acp` parameter to support ACP protocol integration in editor-first workflows such as Zed. The gap is not generic "please add another integration" churn; it is a **discoverability and launch-contract problem**. Right now the product surface does not make it obvious whether ACP is already supported, how an editor should invoke claw-code, or whether a dedicated flag/mode exists at all. That forces evaluators into repo archaeology instead of giving them a crisp editor-facing invocation contract. **Fix shape:** either (a) add an explicit ACP/editor entrypoint such as `--acp` / `acp serve` with help text that states the contract, or (b) if the protocol path already exists, surface it prominently in CLI help/README with a concrete Zed/editor integration example so users do not have to guess. **Acceptance bar:** an editor-first user can answer "how do I launch claw-code for ACP/Zed?" from `claw --help` or the first screen of docs without reading source. **Blocker:** none; currently recorded as a roadmap follow-up because the repo-local entrypoint was not obvious during dogfood. +64a. **ACP/Zed editor integration entrypoint is too implicit** — **done (verified 2026-04-16):** `claw` now exposes a local `acp` discoverability surface (`claw acp`, `claw acp serve`, `claw --acp`, `claw -acp`) that answers the editor-first question directly without starting the runtime, and `claw --help` / `rust/README.md` now surface the ACP/Zed status in first-screen command/docs text. The current contract is explicit: claw-code does **not** ship an ACP/Zed daemon entrypoint yet; `claw acp serve` is only a status alias, while real ACP protocol support is tracked separately as #76. Fresh proof: parser coverage for `acp`/`acp serve`/flag aliases, help rendering coverage, and JSON output coverage for `claw --output-format json acp`. +Original filing (2026-04-13): user requested a `-acp` parameter to support ACP protocol integration in editor-first workflows such as Zed. The gap was a **discoverability and launch-contract problem**: the product surface did not make it obvious whether ACP was supported, how an editor should invoke claw-code, or whether a dedicated flag/mode existed at all. -64. **Artifact provenance is post-hoc narration, not structured events** — **done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now attaches structured `artifactProvenance` metadata to `lane.finished`, including `sourceLanes`, `roadmapIds`, `files`, `diffStat`, `verification`, and `commitSha`, while keeping the existing `lane.commit.created` provenance event intact. Regression coverage locks a successful completion payload that carries roadmap ids, file paths, diff stat, verification states, and commit sha without relying on prose re-parsing. **Original filing below.** +64b. **Artifact provenance is post-hoc narration, not structured events** — **done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now attaches structured `artifactProvenance` metadata to `lane.finished`, including `sourceLanes`, `roadmapIds`, `files`, `diffStat`, `verification`, and `commitSha`, while keeping the existing `lane.commit.created` provenance event intact. Regression coverage locks a successful completion payload that carries roadmap ids, file paths, diff stat, verification states, and commit sha without relying on prose re-parsing. **Original filing below.** 65. **Backlog-scanning team lanes emit opaque stops, not structured selection outcomes** — **done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now recognizes backlog-scan selection summaries and records structured `selectionOutcome` metadata on `lane.finished`, including `chosenItems`, `skippedItems`, `action`, and optional `rationale`, while preserving existing non-selection and review-lane behavior. Regression coverage locks the structured backlog-scan payload alongside the earlier quality-floor and review-verdict paths. **Original filing below.** @@ -1169,3 +1170,5 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes 74. **Poisoned test locks cascade into unrelated Rust regressions** — **done (verified 2026-04-12):** test-only env/cwd lock acquisition in `rust/crates/tools/src/lib.rs`, `rust/crates/plugins/src/lib.rs`, `rust/crates/commands/src/lib.rs`, and `rust/crates/rusty-claude-cli/src/main.rs` now recovers poisoned mutexes via `PoisonError::into_inner`, and new regressions lock that behavior so one panic no longer causes later tests to fail just by touching the shared env/cwd locks. Source: Jobdori dogfood 2026-04-12. 75. **`claw init` leaves `.clawhip/` runtime artifacts unignored** — **done (verified 2026-04-12):** `rust/crates/rusty-claude-cli/src/init.rs` now treats `.clawhip/` as a first-class local artifact alongside `.claw/` paths, and regression coverage locks both the create and idempotent update paths so `claw init` adds the ignore entry exactly once. The repo `.gitignore` now also ignores `.clawhip/` for immediate dogfood relief, preventing repeated OMX team merge conflicts on `.clawhip/state/prompt-submit.json`. Source: Jobdori dogfood 2026-04-12. + +76. **Real ACP/Zed daemon contract is still missing after the discoverability fix** — follow-up filed 2026-04-16. ROADMAP #64 made the current status explicit via `claw acp`, but editor-first users still cannot actually launch claw-code as an ACP/Zed daemon because there is no protocol-serving surface yet. **Fix shape:** add a real ACP entrypoint (for example `claw acp serve`) only when the underlying protocol/transport contract exists, then document the concrete editor wiring in `claw --help` and first-screen docs. **Acceptance bar:** an editor can launch claw-code for ACP/Zed from a documented, supported command rather than a status-only alias. **Blocker:** protocol/runtime work not yet implemented; current `acp serve` spelling is intentionally guidance-only. diff --git a/rust/README.md b/rust/README.md index e8269de..804e5bc 100644 --- a/rust/README.md +++ b/rust/README.md @@ -135,6 +135,7 @@ Top-level commands: version status sandbox + acp [serve] dump-manifests bootstrap-plan agents @@ -144,6 +145,8 @@ Top-level commands: init ``` +`claw acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, claw-code does **not** ship an ACP/Zed daemon entrypoint yet, and `claw acp serve` is only a status alias until the real protocol surface lands. + The command surface is moving quickly. For the canonical live help text, run: ```bash diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 6e0b307..ded1749 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -95,6 +95,8 @@ const CLI_OPTION_SUGGESTIONS: &[&str] = &[ "--allowedTools", "--allowed-tools", "--resume", + "--acp", + "-acp", "--print", "--compact", "--base-commit", @@ -248,6 +250,7 @@ fn run() -> Result<(), Box> { cli.run_turn_with_output(&effective_prompt, output_format, compact)?; } CliAction::Doctor { output_format } => run_doctor(output_format)?, + CliAction::Acp { output_format } => print_acp_status(output_format)?, CliAction::State { output_format } => run_worker_state(output_format)?, CliAction::Init { output_format } => run_init(output_format)?, CliAction::Export { @@ -337,6 +340,9 @@ enum CliAction { Doctor { output_format: CliOutputFormat, }, + Acp { + output_format: CliOutputFormat, + }, State { output_format: CliOutputFormat, }, @@ -368,6 +374,7 @@ enum LocalHelpTopic { Status, Sandbox, Doctor, + Acp, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -547,6 +554,10 @@ fn parse_args(args: &[String]) -> Result { rest.push(flag[9..].to_string()); index += 1; } + "--acp" | "-acp" => { + rest.push("acp".to_string()); + index += 1; + } "--allowedTools" | "--allowed-tools" => { let value = args .get(index + 1) @@ -661,6 +672,7 @@ fn parse_args(args: &[String]) -> Result { } } "system-prompt" => parse_system_prompt_args(&rest[1..], output_format), + "acp" => parse_acp_args(&rest[1..], output_format), "login" | "logout" => Err(removed_auth_surface_error(rest[0].as_str())), "init" => Ok(CliAction::Init { output_format }), "export" => parse_export_args(&rest[1..], output_format), @@ -715,6 +727,7 @@ fn parse_local_help_action(rest: &[String]) -> Option> "status" => LocalHelpTopic::Status, "sandbox" => LocalHelpTopic::Sandbox, "doctor" => LocalHelpTopic::Doctor, + "acp" => LocalHelpTopic::Acp, _ => return None, }; Some(Ok(CliAction::HelpTopic(topic))) @@ -785,6 +798,16 @@ fn removed_auth_surface_error(command_name: &str) -> String { ) } +fn parse_acp_args(args: &[String], output_format: CliOutputFormat) -> Result { + match args { + [] => Ok(CliAction::Acp { output_format }), + [subcommand] if subcommand == "serve" => Ok(CliAction::Acp { output_format }), + _ => Err(String::from( + "unsupported ACP invocation. Use `claw acp`, `claw acp serve`, `claw --acp`, or `claw -acp`.", + )), + } +} + fn try_resolve_bare_skill_prompt(cwd: &Path, trimmed: &str) -> Option { let bare_first_token = trimmed.split_whitespace().next().unwrap_or_default(); let looks_like_skill_name = !bare_first_token.is_empty() @@ -5175,6 +5198,13 @@ fn render_help_topic(topic: LocalHelpTopic) -> String { Output local-only health report; no provider request or session resume required Related /doctor · claw --resume latest /doctor" .to_string(), + LocalHelpTopic::Acp => "ACP / Zed + Usage claw acp [serve] + Aliases claw --acp · claw -acp + Purpose explain the current editor-facing ACP/Zed launch contract without starting the runtime + Status discoverability only; `serve` is a status alias and does not launch a daemon yet + Related ROADMAP #64a (discoverability) · ROADMAP #76 (real ACP support) · claw --help" + .to_string(), } } @@ -5182,6 +5212,39 @@ fn print_help_topic(topic: LocalHelpTopic) { println!("{}", render_help_topic(topic)); } +fn print_acp_status(output_format: CliOutputFormat) -> Result<(), Box> { + let message = "ACP/Zed editor integration is not implemented in claw-code yet. `claw acp serve` is only a discoverability alias today; it does not launch a daemon or Zed-specific protocol endpoint. Use the normal terminal surfaces for now and track ROADMAP #76 for real ACP support."; + match output_format { + CliOutputFormat::Text => { + println!( + "ACP / Zed\n Status discoverability only\n Launch `claw acp serve` / `claw --acp` / `claw -acp` report status only; no editor daemon is available yet\n Today use `claw prompt`, the REPL, or `claw doctor` for local verification\n Tracking ROADMAP #76\n Message {message}" + ); + } + CliOutputFormat::Json => { + println!( + "{}", + serde_json::to_string_pretty(&json!({ + "kind": "acp", + "status": "discoverability_only", + "supported": false, + "serve_alias_only": true, + "message": message, + "launch_command": serde_json::Value::Null, + "aliases": ["acp", "--acp", "-acp"], + "discoverability_tracking": "ROADMAP #64a", + "tracking": "ROADMAP #76", + "recommended_workflows": [ + "claw prompt TEXT", + "claw", + "claw doctor" + ], + }))? + ); + } + } + Ok(()) +} + fn render_config_report(section: Option<&str>) -> Result> { let cwd = env::current_dir()?; let loader = ConfigLoader::default_for(&cwd); @@ -8148,6 +8211,11 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> { out, " Diagnose local auth, config, workspace, and sandbox health" )?; + writeln!(out, " claw acp [serve]")?; + writeln!( + out, + " Show ACP/Zed editor integration status (currently unsupported; aliases: --acp, -acp)" + )?; writeln!(out, " Source of truth: {OFFICIAL_REPO_SLUG}")?; writeln!( out, @@ -9200,6 +9268,34 @@ mod tests { ); } + #[test] + fn parses_acp_command_surfaces() { + assert_eq!( + parse_args(&["acp".to_string()]).expect("acp should parse"), + CliAction::Acp { + output_format: CliOutputFormat::Text, + } + ); + assert_eq!( + parse_args(&["acp".to_string(), "serve".to_string()]).expect("acp serve should parse"), + CliAction::Acp { + output_format: CliOutputFormat::Text, + } + ); + assert_eq!( + parse_args(&["--acp".to_string()]).expect("--acp should parse"), + CliAction::Acp { + output_format: CliOutputFormat::Text, + } + ); + assert_eq!( + parse_args(&["-acp".to_string()]).expect("-acp should parse"), + CliAction::Acp { + output_format: CliOutputFormat::Text, + } + ); + } + #[test] fn local_command_help_flags_stay_on_the_local_parser_path() { assert_eq!( @@ -9217,6 +9313,10 @@ mod tests { .expect("doctor help should parse"), CliAction::HelpTopic(LocalHelpTopic::Doctor) ); + assert_eq!( + parse_args(&["acp".to_string(), "--help".to_string()]).expect("acp help should parse"), + CliAction::HelpTopic(LocalHelpTopic::Acp) + ); } #[test] @@ -10125,6 +10225,7 @@ mod tests { assert!(help.contains("claw status")); assert!(help.contains("claw sandbox")); assert!(help.contains("claw init")); + assert!(help.contains("claw acp [serve]")); assert!(help.contains("claw agents")); assert!(help.contains("claw mcp")); assert!(help.contains("claw skills")); diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 1c9bc93..9fbbdcb 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -46,6 +46,24 @@ fn status_and_sandbox_emit_json_when_requested() { assert!(sandbox["filesystem_mode"].as_str().is_some()); } +#[test] +fn acp_guidance_emits_json_when_requested() { + let root = unique_temp_dir("acp-json"); + fs::create_dir_all(&root).expect("temp dir should exist"); + + let acp = assert_json_command(&root, &["--output-format", "json", "acp"]); + assert_eq!(acp["kind"], "acp"); + assert_eq!(acp["status"], "discoverability_only"); + assert_eq!(acp["supported"], false); + assert_eq!(acp["serve_alias_only"], true); + assert_eq!(acp["discoverability_tracking"], "ROADMAP #64a"); + assert_eq!(acp["tracking"], "ROADMAP #76"); + assert!(acp["message"] + .as_str() + .expect("acp message") + .contains("discoverability alias")); +} + #[test] fn inventory_commands_emit_structured_json_when_requested() { let root = unique_temp_dir("inventory-json");