Skip to content

Epic: Command line interface — one output convention, every app #740

Closed

Description

@David-Noble-at-work

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:

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

  1. 5 remaining items

  2. David-Noble-at-work commented on Sep 3, 2026

    @David-Noble-at-work
    ContributorAuthor

    Open question from #743 phase 4: a prose result under the json-always default.

    star docs starlark returns its authoring guide — one string of prose — as its result, so it now goes through
    the pipeline like every other result. Under the json default that is one quoted JSON string, correct and
    useless to read; -o value prints the prose. Two ways to settle it:

    1. leave it: a prose result defaults to json like any other, and the docs say -o value is how to read one
    2. rule an exception: a command whose result is prose defaults to value

    The site holds the invariant either way. Recorded here so the epic decides it once, for every program.

  3. David-Noble-at-work commented on Sep 4, 2026

    @David-Noble-at-work
    ContributorAuthor

    A second instance of the prose-result question, from #788: config path returns 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 typing writ config path wanted.
    Same shape as star docs starlark: a scalar result under a default built for records. If the ruling
    is that a scalar result defaults to value, TestCLI_ConfigPath changes to expect the raw path.

  4. David-Noble-at-work commented on Sep 11, 2026

    @David-Noble-at-work
    ContributorAuthor

    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.

    # Item Applies Disposition
    1 Exit codes: §9 said 0/1/2, constants had gaps, every failure exited 1 ruled 2026-09-10: the sysexits set #884, #832's plan
    2 Invariant 3 false outside cmd/: pkg/op/runtime_environment.go:211 builds a stdout pipeline; pkg/process imports pkg/result; the walk reads cmd/ only §14 row 3 fix, #832's plan
    3 devlore-docs, devlore-index, devlore-inventory: "not yet" in §2; excluded from the walk; untracked §2 ruling 2026-08-31, membership is infrastructure file under #832; fix in its plan
    4 lore and star have no functional assertions on the output flags §14 rows 4–5; 743/775 plans' deferred rows fix, #832's plan
    5 --path on man; --target on eight star devlore commands; star's --dry_run string shadows the root's bool §4 destination rule; §14 invariant 2 man in #832's plan; star's in #461 (placement)
    6 man hidden from every root's command list §12, man is the one route fix, #832's plan
    7 writ repo and star setup act when invoked bare §3, a group takes no action fix, #835's plan
    8 #782 rename writ verify §3 grammar; name proposed in #835's plan: writ document verify #835's plan
    9 #742 internal/console → cmd/internal; credentials, manifest, registry each moved or justified in writing #742's own body #835's plan
    10 #828 --silent before argv parses #828's body: fix 1 now, the diagnostic half to #507 fix, #832's plan
    11 #826 text rendering for a scalar string result-text-formatter.md: text is chartered fix, #833's plan
    12 lore list default-rendering exception, open in three plans §7 json-always; a domain rendering is an app flag close the question in #833's plan
    13 #774's S4 sectioned-object rendering question §8 S4 row: one row, nested as compact JSON; reshape with --jq close the question in #833's plan
    14 #741 closed with the installed-decoration box unticked §7: installed stays a field retract the box, #833's plan
    15 #759 wrap Long; Available Commands and usage lines outside its carve-out convention proposed in #834's plan: indented is verbatim, unindented is prose #834's plan
    16 The ! prefix, §10 "not yet designed", no issue 776's plan: to be talked through, not part of this file as a task under #834, deferred by design
    17 Plan status drift: extract-output-package complete with nine open boxes; 772, 768 in-progress with every phase ticked development-process.md, documents on every commit doc fixes in the epic-close PR; a lint is #365's
    18 #665 fixed in the tree and pinned by a test; still open the test at cmd/devlore-test/cli_test.go:144 close with evidence
    19 --scope specified in §4.1, not built; writ still binds --target #762's charter placement: #762
    20 writ migrate: --non-interactive where §10 rules --unattended/--from; gate on stdout not stdin §10 placement: #762 (writ's vocabulary settlement)
    21 #774's reconcile selector deferred to #772, which closed with no successor 774's plan placement: #762
    22 #780 self install man pages: shared root, unfiled issue-standards: a bug lives with its code placement: move to #832
    23 Fix entirely in cmd/internal/cli: #763, #673, #337, #83, #518, #436; partials #385, #339, #798, #341, #827, #841, #825, #777 issue-standards placement table in #832's plan
    24 Help states retired or unregistered things: repo add/remove (#791), --conflict=backup/overwrite, decommission --force, phantom config keys, lorepackage/PkgPath, #881 §12 the shared config help in #834's plan; the rest to their owners, listed there
    25 15 lore commands and 3 star key leaves exit "not yet implemented" behind help that reads as working §12; writ secret's pattern placement: #73/#71/#461; the help pattern in #834's plan
    26 #738 closed with five unticked boxes; "zero expectations is not a pass" untracked #738's escalation comment placement: #534
    27 #769's un-renamed plans, deferred per thread, no ledger 769's plan placement: ops process
    28 #794 stdout walk learns cmd.OutOrStdout() #794 fix, #832's plan
    29 #795 version emits a record #795 fix, #832's plan
    30 Invariant 5 test: narration absent from stdout under every format §14 row 5 fix, #832's plan
    31 #776's third invariant, every leaf of a root consumes the set, never shipped 776's plan fix, #832's plan
    32 self uninstall prompts with no TTY check; non-TTY fails without naming --force §10 fix, #832's plan
    33 man --path default publishes /Users/david-noble/… to the site §12; the #739 shape fix, #832's plan
    34 #830 pager #830 fix, #833's plan
    35 #787 CLI reference for star and devlore-test #787 fix, #834's plan
    36 Width: 130-column Shorts under star gh labels; Available Commands lines §12 fix, #834's plan
    37 Spec contradicts itself on star and devlore-test adoption (:723, :761, :796); status.md stale §15 doc, #834's plan
    38 Spec names AddOutputFlags six times, unexported; broken godoc link; two plans name a renamed test — doc, #834's plan
    39 Plans say "no scenario touches star/lore"; the self-install scenario drives both — doc, #834's plan
    40 #739's boxes stale though fixed; no scheduled docs/cli drift check #739 doc, #834's plan
    41 740's plan: complete with a stale box and two open questions — epic-close PR
    42 Epic body's "Remaining, in order" stale — epic-close PR
    43 #762 carries two doc boxes PR #767 already did — note on #762

    Order 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.

  5. David-Noble-at-work commented on Sep 11, 2026

    @David-Noble-at-work
    ContributorAuthor

    Row 8, ruled 2026-09-11 in #835's plan review. The noun is workflow, not document, and the group is the
    shared root's, not writ's: <program> workflow list over the run index, and <program> workflow verify selecting
    by --tool, --scope, --kind definition|trace, --latest, with a path operand kept for a document from outside
    the store. writ verify retires by deletion. Written into §2, §6 and design decision 10 of the specification on
    branch feature/782-writ-workflow-verify; plan docs/plans/feature/782-writ-workflow-verify.md, chartered.

  6. David-Noble-at-work commented on Sep 12, 2026

    @David-Noble-at-work
    ContributorAuthor

    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 calls CheckGroupsTakeNoAction from star's root test when an extension can declare a group.

  7. David-Noble-at-work commented on Sep 23, 2026

    @David-Noble-at-work
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Epic:CommandLineOne output convention across every CLI in the suiteepicTop-level initiative

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions