diff --git a/ROADMAP.md b/ROADMAP.md index 4537de8c..edb36a5d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7639,3 +7639,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 735. **`claw /compact --output-format json` (and other interactive-only slash commands invoked outside a session) emitted `error_kind:"unknown"` instead of `error_kind:"interactive_only"` — `classify_error_kind` matched `"is a slash command"` and `"interactive_only:"` prefix but missed the `"slash command /X is interactive-only"` sentence pattern emitted by the interactive-only guard; automation branching on `error_kind` got `"unknown"` and couldn't distinguish "you called an interactive command outside a session" from a genuine unknown failure** — dogfooded 2026-05-26 on `d4494a8a`. Added `message.starts_with("slash command") && message.contains("interactive-only")` branch to `classify_error_kind` alongside the existing two matchers. Source: Jobdori dogfood on `d4494a8a`, 2026-05-26. 736. **`claw doctor --output-format json` `boot_preflight` check `details[]` had `value: null` for `Required binary`, `Last failed boot`, `MCP eligible`, and `Plugin eligible` entries — all four used format strings with no double-space separator, so the prose-splitter that builds `{key, value}` objects (introduced in #701) could not split key from value and emitted the entire string as `key` with `value: null`** — dogfooded 2026-05-26 on `b3242e8c`. Fix: insert the two-space separator between the label and its value in each format string: `"Required binary {} available={}"` → `key="Required binary claw"` / `value="available=true"`; `"Last failed boot {}"` → `key="Last failed boot"` / `value=""`; MCP/Plugin eligible compound values use `" · "` intra-value separator since `splitn(2, " ")` splits only on the first double-space run. Source: Jobdori dogfood on `b3242e8c`, 2026-05-26. + +737. **Test coverage gap: `doctor --output-format json` `boot_preflight` `details[]` had no assertion that entries are `{key,value}` objects with non-null `value` fields — the #736 double-space separator fix had no regression guard, so a revert or accidental prose-format change would silently re-introduce `value:null` entries** — filed 2026-05-26 on `ad982d20`. Added assertions to `doctor_and_resume_status_emit_json_when_requested` in `output_format_contract.rs`: iterate all `boot_preflight.details[]` entries and assert each has a string `key` and a non-null `value`. Source: Jobdori dogfood on `ad982d20`, 2026-05-26. 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 315afa25..e598cbe8 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -663,6 +663,22 @@ fn doctor_and_resume_status_emit_json_when_requested() { assert!(boot_preflight["boot_preflight"]["repo"]["exists"].is_boolean()); assert!(boot_preflight["boot_preflight"]["mcp_startup"]["eligible"].is_boolean()); assert!(boot_preflight["boot_preflight"]["required_binaries"].is_array()); + // #736: details[] must be {key,value} objects with non-null values; + // regression guard for the double-space separator fix on boot_preflight prose strings. + let bp_details = boot_preflight["details"] + .as_array() + .expect("boot_preflight details must be array"); + for entry in bp_details { + assert!( + entry["key"].is_string(), + "boot_preflight detail entry missing string key: {entry:?}" + ); + assert!( + !entry["value"].is_null(), + "boot_preflight detail entry has null value (prose-splitter failed): key={:?}", + entry["key"] + ); + } let sandbox = checks .iter()