mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-28 02:25:00 +08:00
feat: #142 structured fields in claw init --output-format json
Previously `claw init --output-format json` emitted a valid JSON envelope but packed the entire human-formatted output into a single `message` string. Claw scripts had to substring-match human language to tell `created` from `skipped`. Changes: - Add InitStatus::json_tag() returning machine-stable "created"|"updated"|"skipped" (unlike label() which includes the human " (already exists)" suffix). - Add InitReport::NEXT_STEP constant so claws can read the next-step hint without grepping the message string. - Add InitReport::artifacts_with_status() to partition artifacts by state. - Add InitReport::artifact_json_entries() for the structured artifacts[] array. - Rewrite run_init + init_json_value to emit first-class fields alongside the legacy message string (kept for text consumers): project_path, created[], updated[], skipped[], artifacts[], next_step, message. - Update the slash-command Init dispatch to use the same structured JSON. - Add regression test artifacts_with_status_partitions_fresh_and_idempotent_runs asserting both fresh + idempotent runs produce the right partitioning and that the machine-stable tag is bare 'skipped' not label()'s phrasing. Verified output: - Fresh dir: created[] has 4 entries, skipped[] empty - Idempotent call: created[] empty, skipped[] has 4 entries - project_path, next_step as first-class keys - message preserved verbatim for backward compat Full workspace test green except pre-existing resume_latest flake (unrelated). Closes ROADMAP #142.
This commit is contained in:
@@ -2948,11 +2948,15 @@ fn run_resume_command(
|
||||
json: Some(render_memory_json()?),
|
||||
}),
|
||||
SlashCommand::Init => {
|
||||
let message = init_claude_md()?;
|
||||
// #142: run the init once, then render both text + structured JSON
|
||||
// from the same InitReport so both surfaces stay in sync.
|
||||
let cwd = env::current_dir()?;
|
||||
let report = crate::init::initialize_repo(&cwd)?;
|
||||
let message = report.render();
|
||||
Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
message: Some(message.clone()),
|
||||
json: Some(init_json_value(&message)),
|
||||
json: Some(init_json_value(&report, &message)),
|
||||
})
|
||||
}
|
||||
SlashCommand::Diff => {
|
||||
@@ -5666,20 +5670,31 @@ fn init_claude_md() -> Result<String, Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn run_init(output_format: CliOutputFormat) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let message = init_claude_md()?;
|
||||
let cwd = env::current_dir()?;
|
||||
let report = initialize_repo(&cwd)?;
|
||||
let message = report.render();
|
||||
match output_format {
|
||||
CliOutputFormat::Text => println!("{message}"),
|
||||
CliOutputFormat::Json => println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&init_json_value(&message))?
|
||||
serde_json::to_string_pretty(&init_json_value(&report, &message))?
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_json_value(message: &str) -> serde_json::Value {
|
||||
/// #142: emit first-class structured fields alongside the legacy `message`
|
||||
/// string so claws can detect per-artifact state without substring matching.
|
||||
fn init_json_value(report: &crate::init::InitReport, message: &str) -> serde_json::Value {
|
||||
use crate::init::InitStatus;
|
||||
json!({
|
||||
"kind": "init",
|
||||
"project_path": report.project_root.display().to_string(),
|
||||
"created": report.artifacts_with_status(InitStatus::Created),
|
||||
"updated": report.artifacts_with_status(InitStatus::Updated),
|
||||
"skipped": report.artifacts_with_status(InitStatus::Skipped),
|
||||
"artifacts": report.artifact_json_entries(),
|
||||
"next_step": crate::init::InitReport::NEXT_STEP,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user