Repository navigation
Epic: Command line interface — one output convention, every app #740
Description
Specs: 10-command-line-interface.md
Plans: 740-cli-output-conventions.md
Every binary in the suite presents the same command-line surface. Three streams: a result to stdout, JSON
by default; narration to stderr; documents — definitions and traces — to the execution store. One
shared flag set decides how the result is rendered and where the store lives, and no command invents its own.
The reserved set, settled against aws, az, docker, gcloud, and kubectl:
--filter, --jq, --output / -o, --store. Renderings are csv, json, none, table,
template=BODY, value, and yaml.
csv and value are the delimited pair, split by consumer rather than by separator: csv quotes so a
parser round-trips it, value does not so a shell reads what was composed. A tsv rendering sat between
them until 2026-08-30 and served neither — cut and awk have no quote awareness, so quoting a field
containing a tab does not stop the row splitting. gcloud and aws each ship one raw delimited format and no
tsv, for the same reason.
A format that needs an argument carries it in the value, split on the first = — kubectl's -o go-template=
shape. So there is no separate --template flag, and no mutual-exclusion rule between a format and its
argument to state or enforce. There is no --output <file> either — stdout already is one — and no boolean
--json.
Landed: the mechanism. cmd/internal/cli/output.go binds the four flags and BuildPipeline composes a
result.Pipeline of filter, formatter, and sink over pkg/result + pkg/sink. pkg/result/selection.go
holds the format names and is the authority on them.
Adopted so far: devlore-test in full — its invented stream=dest routing is gone, the artifacts carry what
their names say, and an execution trace is persisted (#738). writ's flags — the boolean --json on
status and verify is deleted rather than aliased, verify.Execute returns []Report for the command to
emit, and the common set is registered on the root.
Remaining, in order:
writ's renderings. 22fmt.Printcalls instatus/report.go, fourSerializeGraphs(os.Stdout, …)
dry-run dumps acrossdeploy/decommission/upgrade/secret,migrate's own--format, and one
stdout write inmigrate/session.goto classify as narration or result.lore.runSearch's table (a bug fix as much as a refactor — lore search truncates descriptions by byte count, cutting multi-byte characters mid-rune #741), the hand-rolled flags on
bundle/onboard/list, and thirteenfmt.Printcalls to triage.star. Register the common set and deletecmd/star/cli, which duplicates eighteen exported names
fromcmd/internal/cliand now contradicts it (cmd/star/cli duplicates cmd/internal/cli, and the two now disagree on the flag surface #743).- Enforcement. A test that fails when a command registers an output flag of its own, and one that fails
on a directos.Stdoutwrite from a command package.
The scope was measured wrong when this epic was written: it said "seven direct os.Stdout writes" in writ,
counted by grepping the literal os.Stdout, which misses every fmt.Print — and those write to stdout just
the same. The real figure is 30 call sites.
The enforcement phase exists because adoption went backwards rather than merely lagging.
docs/plans/extract-output-package.md recorded two call sites in March — lore inspect and writ snapshot.
writ snapshot was removed, taking half the convention's adopters with it, and nothing recorded that it had.
Invariants that are not tested decay silently.
Activity
feat(writ): the common set, and --json retires (#739, #740)
feat(result): csv and tsv presets; the format value carries its argum…
feat(result): the format value carries its argument; value renders raw (
feat(result): one delimited formatter with three presets, and one tab…
docs(cli): bring the reference and the plans current (#739, #740)
test(cli): the store test blocks MkdirAll on every platform (#740)
docs(cli): the plan's status matches its phases (#740)
feat(result)!: csv and value are the delimited pair; tsv is dropped (#…
docs(cli): the status document matches what landed (#740)
One output convention for every app: result to stdout, documents to t…
Merge develop: the output convention, and the devlore-test rewrite (#740
doc(cli): suite membership is infrastructure, and two version numbers (…
5 remaining items
David-Noble-at-work commentedon Sep 3, 2026
Open question from #743 phase 4: a prose result under the json-always default.
star docs starlarkreturns its authoring guide — one string of prose — as its result, so it now goes throughthe pipeline like every other result. Under the json default that is one quoted JSON string, correct and
useless to read;
-o valueprints the prose. Two ways to settle it:-o valueis how to read onevalueThe site holds the invariant either way. Recorded here so the epic decides it once, for every program.
star's last eight stdout sites: narration narrates, a result emits, a…
star adopts the output convention: the shared root, and extension com…
Close the convention box: the tests named where the rules are, and th…
David-Noble-at-work commentedon Sep 4, 2026
A second instance of the prose-result question, from #788:
config pathreturns one string, the path,and under the json default a user sees it quoted, with a Windows path's backslashes escaped:
"C:\\Users\\...\\config.yaml". Correct JSON, and not what a person typingwrit config pathwanted.Same shape as
star docs starlark: a scalar result under a default built for records. If the rulingis that a scalar result defaults to
value,TestCLI_ConfigPathchanges to expect the raw path.The output convention is enforced: the shared root owns the common se…
David-Noble-at-work commentedon Sep 11, 2026
Closure ledger — frozen 2026-09-10
The done-done audit of this epic, every source read to the end: the nine open issues, the sixteen closed
members and their residuals, the specification in full, every plan naming the convention, the invariants and
their tests, the scenario assertions, the help text of every command of the four programs, and every open issue
in the repository judged for shared-CLI ownership. Numbers are permanent. A new finding appends as 44; nothing
is renumbered. Every plan under this epic cites rows by number. "Applies" names the ruling already on the record
that decides the row; a row with no ruling names the one decision it needs.
cmd/:pkg/op/runtime_environment.go:211builds a stdout pipeline;pkg/processimportspkg/result; the walk readscmd/only--pathonman;--targeton eight star devlore commands; star's--dry_runstring shadows the root's boolmanin #832's plan; star's in #461 (placement)manhidden from every root's command listmanis the one routewrit repoandstar setupact when invoked barewrit verifywrit document verifyinternal/console→cmd/internal; credentials, manifest, registry each moved or justified in writing--silentbefore argv parsesresult-text-formatter.md:textis charteredlore listdefault-rendering exception, open in three plans--jqinstalled-decoration box untickedinstalledstays a field!prefix, §10 "not yet designed", no issueextract-output-packagecomplete with nine open boxes; 772, 768 in-progress with every phase tickedcmd/devlore-test/cli_test.go:144--scopespecified in §4.1, not built; writ still binds--target--non-interactivewhere §10 rules--unattended/--from; gate on stdout not stdinself installman pages: shared root, unfiledcmd/internal/cli: #763, #673, #337, #83, #518, #436; partials #385, #339, #798, #341, #827, #841, #825, #777--conflict=backup/overwrite, decommission--force, phantom config keys,lorepackage/PkgPath, #881writ secret's patterncmd.OutOrStdout()self uninstallprompts with no TTY check; non-TTY fails without naming--forceman --pathdefault publishes/Users/david-noble/…to the sitestar gh labels; Available Commands linesAddOutputFlagssix times, unexported; broken godoc link; two plans name a renamed testdocs/clidrift checkOrder of execution: #835, #832, #833, #834, then the epic close. One plan per feature, each mapping its rows to the
rulings above; genuinely new decisions are two, rows 8 and 15, and both arrive as proposals in their plans.
David-Noble-at-work commentedon Sep 11, 2026
Row 8, ruled 2026-09-11 in #835's plan review. The noun is
workflow, notdocument, and the group is theshared root's, not writ's:
<program> workflow listover the run index, and<program> workflow verifyselectingby
--tool,--scope,--kind definition|trace,--latest, with a path operand kept for a document from outsidethe store.
writ verifyretires by deletion. Written into §2, §6 and design decision 10 of the specification onbranch
feature/782-writ-workflow-verify; plandocs/plans/feature/782-writ-workflow-verify.md, chartered.The workflow group on the shared root: list and verify address the st…
A subcommand group takes no action: writ repo prints help, and a chec…
console and credentials move under cmd/internal; manifest and registr…
David-Noble-at-work commentedon Sep 12, 2026
Rows 7, 8 and 9: done in #835's pull request (
feature/782-writ-workflow-verify). Row 7's star half waits on #841, which callsCheckGroupsTakeNoActionfrom star's root test when an extension can declare a group.The workflow group on the shared root, groups that take no action, an…
David-Noble-at-work commentedon Sep 23, 2026
Closed 2026-09-23 by the owner. Schedule #887 closed all nineteen lanes across PRs #898, #901, #902, #905, #911 and #912; the report reads 25 of 25 leaves done across four features, and the three feature containers (#832, #834, #835) are closed with it.