ref:main

JSON output done right: one Response contract across all commands (#32, #37, #38) #48

merged colechristensen cole.christensen@gmail.com wants to merge feat/json-output-contract into main

JSON output, done right — one contract across all 140 commands

Collapses the JSON-output trio (#32 coverage, #38 competing mechanisms, #37 misleading --json help) into a single architectural fix, plus enforcement so it can’t regress.

The contract (correct-by-construction)

Every leaf command returns an output::Response { json, exit }; commands::run is the single place that writes stdout under --json. The human helpers (detail/header/success/info/line/print_table) self-suppress under --json. So a command can’t forget to emit JSON (the compiler requires a Response) and can’t leak human text (helpers no-op + raw println! is a clippy error). Errors are a {"ok":false,"error":…} envelope from main — as a fallback, so a gate that prints-then-exits never double-writes.

Envelope: reads echo the server payload (or {"items":[…]} for client-built lists); mutations are {"ok":true,…}; deletes {"ok":true,"deleted":…}; gates emit their payload then exit nonzero.

Audit fixes folded in

  • deploy status no longer prints a human header and a raw pretty-JSON dump (was invalid JSON and ugly human output).
  • registry token create returns the one-time secret as a JSON field instead of a raw println!.
  • release drops its competing OutputFormat/--format enum — it routes through the global --json now.
  • board list on a feature-disabled repo exits nonzero with an error envelope instead of exit 0 masking the failure.
  • label/milestone stop double-nesting the server envelope ({"label":{"label":…}}).
  • epic view includes children under --json; requirement list --kind all emits one document, not two.
  • runner list/view/token/logs/status + service subcommands, and ci job-view, now emit JSON.
  • The --json help is now the concise, accurate contract instead of a 380-char requirement-specific paragraph reprinted on every subcommand.

Enforcement

  • [lib] target so tests introspect the real command surface; main.rs is a thin shell.
  • clippy.toml bans println!/print! outside the output.rs renderer — a raw stdout write in a command is a build error.
  • tests/json_contract.rs::every_leaf_is_covered walks the clap tree and pins all 140 leaves — a new subcommand fails CI until it’s wired through the contract.
  • Regression tests for the deploy-status, --kind all, registry-token, and board-feature-disabled fixes.

How it was built

WS-0 (the Response contract) landed first, then a 10-agent workflow migrated each file-group against the locked API, then central integration + the enforcement layer. cargo fmt/clippy -D warnings/cargo test (198 tests) all green.

🤖 Generated with Claude Code

Created Jul 24, 2026 at 01:52 UTC | Merged Jul 24, 2026 at 04:18 UTC by colechristensen cole.christensen@gmail.com