From 912c7aa07dedb5a6c71b9594fb831e627cc3db16 Mon Sep 17 00:00:00 2001 From: th-kim0823 Date: Thu, 7 May 2026 12:28:41 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(kebab-cli):=20emit=20error.v1?= =?UTF-8?q?=20ndjson=20on=20stderr=20in=20--json=20mode=20(fb-27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps the existing `Err(e)` arm with a `cli.json` branch: - `--json`: stderr ndjson `error.v1` via wire_error_v1 - non-`--json`: legacy `error: ` text path (unchanged) exit_code() unchanged — RefusalSignal/NoHitSignal/DoctorUnhealthy still drive 1/1/3. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/kebab-cli/src/main.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs index fca4756..90c49dd 100644 --- a/crates/kebab-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -281,10 +281,18 @@ fn main() -> ExitCode { // Refusals at exit code 1 print to stdout (already done by the // caller); errors go to stderr. if code != 1 { - eprintln!("error: {e}"); - if cli.verbose { - for cause in e.chain().skip(1) { - eprintln!(" caused by: {cause}"); + if cli.json { + let v1 = error_classify::classify(&e, cli.verbose); + let v = wire::wire_error_v1(&v1); + eprintln!("{}", serde_json::to_string(&v).unwrap_or_else(|_| { + "{\"schema_version\":\"error.v1\",\"code\":\"generic\",\"message\":\"serialize failed\"}".to_string() + })); + } else { + eprintln!("error: {e}"); + if cli.verbose { + for cause in e.chain().skip(1) { + eprintln!(" caused by: {cause}"); + } } } }