v0.32.0 Migration Guide

commands -o json is an indexSection anchor link

sparkwing commands -o json emits index fields only. Every record is now three fields:

FieldMeaning
paththe command, e.g. sparkwing runs list
synopsisits one-line summary
subcommand_countdirect children in the listing; 0 means a leaf

Records for hidden commands additionally carry "hidden": true, and those only appear under --include-hidden.

description, flags, examples, positional_args, and the subcommands array are gone from this listing. Nothing else about the output changed: it is still NDJSON, still one record per line, still sorted by path, and still filtered by --path.

Before:

{"path":"sparkwing cache prune","synopsis":"Evict least recently used binaries down to the ceilings","description":"Removes the least recently used cached binaries until the ca [...]","flags":[{"name":"max-bytes","argument":"SIZE","description":"Byte ceiling, e.g. 512MiB","group":"Limits"}, ...],"examples":[{"description":"Trim to the configured ceilings","command":"sparkwing cache prune"}, ...]}

After:

{"path":"sparkwing cache prune","synopsis":"Evict least recently used binaries down to the ceilings","subcommand_count":0}

Steps:

  1. If you read path or synopsis, nothing changes.
  2. If you read description, flags, examples, or positional_args, get them from the command's own help instead: sparkwing <path> --help --json returns the full record for one command, in the same field names, and is unchanged by this release. Ask for the one command you are about to run rather than for all 150.
  3. If you walked the subcommands array to decide whether to descend, read subcommand_count instead and descend with --path <path>, which lists that subtree.
  4. If you counted subcommand names from the array, list them: sparkwing commands --path "<path>" -o plain is one path per line.

Why: the dropped fields are what <command> --help already prints, from the same command registry, so the listing carried a second copy of the help system that could disagree with the first. They were also 83% of it -- of 207KB across 150 records, description was 76KB, flags 55KB, and examples 40KB, against 6.6KB of synopsis and 3.6KB of path. An index exists to help a reader choose which page to open; it does not have to be the page. The listing is now 17KB.

Gotchas:

  • subcommand_count counts the children this listing shows, which is what --path will return. It is not Command.Subcommands from the help renderer, which is a hand-maintained display list and disagrees with the registry in a few places.
  • Hidden commands are still excluded, and that is deliberate: a hidden command is dispatchable but not offered -- its own help names the supported verb to use instead -- so listing it would put a "use something else" entry in the index a reader consults to decide what to use. --include-hidden lists them, marked "hidden": true, and a --path that matches only hidden commands still errors saying so rather than reporting an empty subtree.
  • -o pretty, -o plain, and -o markdown (including --split-dir, which generates docs/cli-reference.md and the docs/cli-*.md pages) are unchanged, and so is <command> --help --json.

List output is NDJSONSection anchor link

Every list-shaped -o json output is now newline-delimited JSON: one complete, independently parseable object per line, with no enclosing array and no pretty-printing. The migration is mechanical -- decode line by line instead of decoding the whole document -- and takes about a minute per consumer. pretty, plain, markdown, and --quiet non-JSON output are untouched, as are the single-object verbs (runs status, runs get, runs receipt, pipeline describe, queue, doctor, version, info, ...), which still emit one pretty-printed object.

Before:

[
  {
    "path": "sparkwing runs list",
    "synopsis": "Recent runs, newest first"
  },
  {
    "path": "sparkwing runs status",
    "synopsis": "One run's nodes and outcome"
  }
]

After:

{"path":"sparkwing runs list","synopsis":"Recent runs, newest first"}
{"path":"sparkwing runs status","synopsis":"One run's nodes and outcome"}

Steps:

  1. Read the stream a line at a time -- json.Decoder in a loop, jq -c . with no -s, while read line in shell -- instead of unmarshalling the whole output into a slice. In Go, json.Decoder already does this: call Decode in a loop until io.EOF and drop the surrounding slice type.
  2. Drop any [0]-style indexing into the top-level array. Each line is a record, and every field it used to carry inside the array is still on it, unrenamed.
  3. jq consumers: jq '.[] | .id' becomes jq '.id'. To get the old array back, pipe through jq -s ..
  4. Anything that counted records with len(...) or length counts lines instead.

Why: a caller's only defense against output too large for its context is head, and head is line-oriented. sparkwing commands -o json was 258KB across 6,439 pretty-printed lines, and AGENTS.md points an arriving agent straight at it -- so the repository's own orientation path handed a fresh agent a quarter-megabyte document whose first five lines parse as nothing at all. NDJSON makes a truncated read lossy but still valid: sparkwing commands -o json | head -5 is now five complete command records. This is house rule 12 of the CLI design standard (list output is one record per line, in every mode).

Gotchas:

  • An empty listing is now an empty stream -- zero bytes -- where it used to be [] or, at a few sites, null. A consumer that treated empty output as an error has to treat it as zero records; success is still carried by the exit code, which is where it always belonged.
  • Nothing was renamed and nothing was dropped. If a field was on a record inside the old array, it is on that record's line now.
  • --quiet -o json on runs list, runs find, and runs grep was a JSON array of id strings and is now one JSON-quoted id per line ("run-2026…"), which is the plain --quiet output with quotes.

Affected commands: commands, runs list (including --by-pipeline and --quiet), runs errors, runs failures (including --group-by), runs stats (including --capacity), runs find, runs grep, runs annotations list, runs approvals list, runs triggers list, pipeline list, pipeline discover, pipeline lint (findings and --rules), pipeline explain --all, pipeline publish, pipeline hooks survey, pipeline hooks fire, cluster tokens list, cluster agents list, cluster webhooks list, cluster webhooks deliveries, configure xrepo list, examples, docs list, docs guides, docs search, docs versions, docs migrations list, repos, and repos update.