Output Routing and CLI UX¶
Date: 2026-03-09 Status: Implemented
Problem¶
The original CLI defaulted to printing raw changeset JSON to stdout (binoc diff a b → JSON), with human-readable Markdown only appearing as an automatic sidecar file when --output was specified. This was backwards for the common case: a human at a terminal wants to see what changed, not parse JSON. Meanwhile, the single --output <path> flag conflated two concerns — choosing where to write and choosing what format to write — and couldn't express "save JSON to a file and save Markdown to another file" in one invocation.
The sidecar model (write changeset.json, automatically get changeset.md alongside it) was also surprising: it created files the user didn't explicitly ask for, and there was no way to control which sidecar formats were produced without editing the dataset config's renderers list.
Options Considered¶
A: Keep JSON-to-stdout, add --format markdown (rejected)¶
Least disruptive change. Add --format to switch stdout output. Keep --output writing JSON with sidecars.
Rejected because it preserves the unintuitive default. The tool's value proposition is human-readable changelogs; the default experience should reflect that. Machine consumers can opt in to JSON.
B: Separate --save for JSON, --output for formatted (rejected)¶
Two flags: --save changeset.json writes the raw changeset, -o changelog.md writes formatted output.
Rejected for adding an unnecessary concept split. JSON is just another output format — the distinction between "raw IR" and "formatted output" matters internally but shouldn't require the user to learn two flags.
C: Markdown to stdout by default, repeatable -o [format:]path (chosen)¶
Stdout prints the human-readable format by default. --format switches what goes to stdout (e.g. --format json for piping). Repeatable -o writes to files with format inferred from extension or set explicitly via format:path prefix. -q suppresses stdout for CI use.
Decision¶
Option C. The implementation:
-
Stdout defaults to the first configured renderer (Markdown).
--format jsonswitches to raw changeset JSON.--format <name>accepts any registered renderer name, withbinoc.prefix optional (somarkdownandbinoc.markdownboth work).-q/--quietsuppresses stdout entirely. -
-o/--outputis repeatable. Each value is parsed as anOutputSpec— eitherformat:path(explicit) or a bare path (inferred). The split happens on the first colon, but only if the prefix contains no path separators (so/tmp/file.jsonisn't misread as format"/tmp/file", path"json"). -
Extension inference checks two sources: the special
jsonformat (for.jsonfiles, meaning raw changeset IR), and the renderer registry'sfile_extension()values for everything else. If no renderer claims the extension, the CLI errors with a message suggestingformat:pathsyntax. If multiple renderers claim the same extension, it errors listing the conflicting names. -
jsonis not a renderer. It's a reserved format name handled by the CLI viaoutput::to_json(). This keeps theRenderertrait focused on human-readable rendering — JSON serialization doesn't useOutputConfig, doesn't do significance bucketing, and is conceptually different (it's the IR, not a view of it). -
Both
diffandchangelogshare the same output routing, extracted into awrite_outputs()function. Thechangelogcommand accepts the same flags.
Examples¶
binoc diff a b # markdown to stdout
binoc diff a b --format json # raw JSON to stdout
binoc diff a b -o changeset.json # JSON to file, markdown to stdout
binoc diff a b -o changeset.json -o CHANGES.md # both to files, markdown to stdout
binoc diff a b -o changeset.json -q # JSON to file, no stdout
binoc diff a b -o json:output.dat # explicit format for non-standard extension
binoc changelog changesets/*.json -o CHANGES.md # render saved changesets to file
Consequences¶
- The default
binoc diffexperience is now immediately useful to humans. Machine consumers use--format jsonor-o file.json. - The sidecar model is gone. Every file output is explicitly requested. This is more predictable but means migrating from the old
--outputflag to-o file.json -o file.mdif both were wanted. - Custom renderers that produce JSON (e.g. a structured changelog in JSON format) can use the explicit prefix to avoid the
.json→ raw inference:-o my-changelog:output.json. ResolvedPluginsgainedrenderer_for_extension()andrenderer_by_name()methods to support the lookup. These are generally useful for any code that needs to find renderers by something other than their full registered name.