Skip to content

The output convention is enforced: the shared root owns the common set, and the checkers hold it - #788

Merged
David-Noble-at-work merged 7 commits into
developfrom
feature/776-output-enforcement
Sep 4, 2026
Merged

David-Noble-at-work merged 7 commits into
developfrom
feature/776-output-enforcement

Conversation

@David-Noble-at-work

@David-Noble-at-work David-Noble-at-work commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Closes #776, closes #757, closes #786. Plan: docs/plans/feature/776-output-enforcement.md — every phase
complete; it closes the convention box of #740 and the 740 plan with it.

Four commits, plus the charter:

  1. The checkers and the seam, committed red by design. Two tree checkers and two source walks in
    cmd/internal/cli, each shown red on a fixture: no command shadows a flag it inherits, by long name or
    shorthand — cobra calls the first an override and panics on the second, and neither is protection — none
    binds a reserved output flag, every subcommand inherits the four, a root's set is the shared root's, no
    in-scope package writes to stdout without the sink, nothing outside cmd/internal/cli imports
    pkg/result. RunInteractive is the one seam through which an editor or pager gets the terminal,
    TTY-gated. Invariant 1 landed red on the shared package's own thirteen writes.
  2. The shared root owns the common set. NewRootCmd registers the four flags once; cli.Emit is the
    one render path; addOutputFlags is unexported. Lore, star and writ drop their call, variable and
    emitResult. devlore-test moves onto the shared root (devlore-test builds its own root, so shared-infrastructure fixes do not reach it #757), its help wraps, and DEVLORE_TEST_* keeps
    working. config get/list/path/schema/validate emit results; man and the prompt narrate; invariant 1
    goes green. The shadow checker found four real shadows on its first run and each is resolved.
  3. Get-EpicReport --by epic|thread --view tree|table --output markdown|json (Get-EpicReport gains --by, --view and --output: the thread status document #786), the thread status
    document.
  4. The close. §14 of the design names the test behind each invariant; the status document's enforcement
    box is ticked; the 740 plan is complete. Found on the way: devlore-docs generates lore's and writ's
    pages only — devlore-docs generates pages for lore and writ only; star and devlore-test have no CLI reference #787, its own worktree.

Rulings recorded (2026-09-03): the interaction model — data children captured always, a graph never
needs a TTY, interactive children through one seam, interactive commands ask one question at a time and
record every answer into their artifact; the shared root owns the common set; no command shadows an
inherited flag.

Acceptance criteria

The canonical checklist lives in the plan; this is its copy. Every box is checked before merge, and the
one that cannot be checked from this branch says what checks it.

Goals

  • Three invariants have a test each, and each was shown red — invariant 1 on the tree, the others on
    fixtures — with the red output in the commit that added it — phase 1
  • The shared commands keep the convention: config get/list/path/schema/validate through -o, man
    and the prompt on stderr — phase 2
  • Every in-scope root is cli.NewRootCmd, which registers the common set once; no program calls
    AddOutputFlags, which no longer exists by that name; every root has a root test calling the
    checkers (devlore-test builds its own root, so shared-infrastructure fixes do not reach it #757 closes here) — phase 2
  • make docs succeeds; the status doc's enforcement box and the 740 plan are closed — phase 3

Test rows

  • 1 — no direct stdout write in an in-scope package (source walk) — red at phase 1, green at phase 2
  • 2 — no shadowed inherited flag, no reserved name, every subcommand inherits the four, per root (tree walk)
  • 3 — the root's set is the shared root's; no private pipeline (tree walk, source walk)
  • 4 — devlore-test wraps help at COLUMNS=70 and renders through -o (unit)
  • 5 — each checker red on its fixture (unit)
  • 6 — the shared config subcommands render through -o; man narrates (unit)
  • 7 — make docs generates every page it knows, lore's and writ's (build, in the commit's script)
  • 8 — the suite passes on all five platforms: darwin-arm64 locally, and on the pull request
    test (darwin-arm64), test (linux-amd64), test (linux-arm64), test (windows-amd64), test (windows-arm64) — all green; merged from the browser, ticked after

https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW

David Noble added 5 commits September 3, 2026 11:00
…, and #757 folds in

Reviewed 2026-09-03. All three predecessors landed before this plan was chartered, so the sequencing story
changed: invariant 1 arrives red on cmd/internal/cli itself, where the shared config and man commands print
13 results and notes with fmt.Print, and this plan does that work rather than filing it. Invariants 2 and 3
arrive green and are shown red on fixtures. Invariant 3 is reframed: not every leaf reaches BuildPipeline,
but every root's set is the shared root's and nothing builds a pipeline beside it.

Two rulings. The interaction model: a data child is captured, always, and a graph never needs a TTY; an
interactive child -- $EDITOR and man, no other -- goes through one seam, cli.RunInteractive, TTY-gated,
naming the alternative; interactive commands ask one question at a time with a default, record every
answer into the artifact they produce, and run the same unattended. The shared root owns the common set:
NewRootCmd registers the four flags once, cli.Emit is the one render path, addOutputFlags is unexported,
and the programs drop their call, variable and emitResult. #757 folds in so all four roots are the shared
root; the environment prefix maps - to _ so DEVLORE_TEST_* keeps working.

Refs #776, #757

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
…ild gets the terminal

Two tree checkers and two source walks in cmd/internal/cli, each shown red on a fixture: no command
shadows a flag it inherits, by long name or shorthand -- cobra calls the first an override and says
nothing, and panics on the second the first time the command runs; neither is protection, this is
(ruled 2026-09-03) -- none binds a reserved output flag, and every subcommand inherits the four; a root's
set is the shared root's, by usage text; no in-scope package writes to stdout without the sink; nothing
outside cmd/internal/cli imports pkg/result. The checker reads raw flag sets, because cobra's merged
views are what panics. ReservedOutputFlagNames moves here from star, which imports it.

RunInteractive is the seam: an editor or pager the user drives gets the three streams there and nowhere
else, and with no terminal it refuses naming the alternative. config edit and man call it. §10 of the
design records the interaction model ruled 2026-09-03: data children are captured, always; a graph never
needs a TTY; an interactive command asks one question at a time, records every answer into its artifact,
and runs the same unattended.

Invariant 1 over the real tree is COMMITTED RED, by design (776-output-enforcement.md, Requirement 4).
TestNoDirectStdout_InScope reports thirteen writes, all in the shared package itself:

  cmd/internal/cli/config.go:109  fmt.Println   config get
  cmd/internal/cli/config.go:366  fmt.Println   config validate
  cmd/internal/cli/config.go:390  fmt.Println   config validate
  cmd/internal/cli/config.go:392  fmt.Printf    config validate
  cmd/internal/cli/config.go:397  fmt.Printf    config validate
  cmd/internal/cli/config.go:413  fmt.Println   config schema
  cmd/internal/cli/config.go:419  fmt.Println   config path
  cmd/internal/cli/config.go:421  fmt.Println   config path
  cmd/internal/cli/config.go:510  fmt.Printf    config list
  cmd/internal/cli/man.go:171     fmt.Printf    man --install
  cmd/internal/cli/man.go:172     fmt.Println   man --install
  cmd/internal/cli/man.go:173     fmt.Printf    man --install
  cmd/internal/cli/selfinstall.go:188  fmt.Print  the self install prompt

Phase 2 turns it green. #776 phase 1; the plan's Phase 1 is complete.

Refs #776

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
…nd the shared commands keep it

NewRootCmd registers the four flags once and owns their options per root; cli.Emit is the one render path,
for the shared config commands and every program alike; addOutputFlags is unexported so no program can
forget it or call it twice. lore, star and writ drop their call, their variable and their emitResult.
devlore-test moves onto the shared root (#757): its run command renders through cli.Emit, its own --store
resolution and its local --dry-run go because the root does both, and its help wraps at COLUMNS=70 like the
other three. The environment prefix maps - to _, so DEVLORE_TEST_* keeps working.

config get, list, path, schema and validate emit results, so -o json works on each; validate's result is a
report {path, present, valid, warnings}; man's install notes and the self install prompt narrate on
stderr. TestNoDirectStdout_InScope, committed red one commit ago on those thirteen sites, is green.

The checker that refuses every shadow of an inherited flag found four on its first run over the tree:
lore onboard --verbose and star devlore test run --dry-run duplicated the root's and now read it; star
devlore model build --model is --base-model and star lint go --config is --golangci-config, because each
meant something else. Star's loader now refuses any flag that shadows a root flag. Every program's root
test calls the shared checkers; writ and devlore-test gain the root test they lacked.

#776 phase 2; the plan's Phase 2 is complete.

Refs #776, #757

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
--view table renders one table per epic in the tree's order: ID, Issue, Title, Status (a check when closed)
and a Comment carrying the kind, the parent feature, severity and priority, and any classification fault;
--view tree is the report as it was, and the default. --by thread sections the report by the four threads
in execution order and defaults the state to all; --by epic, the default, is every epic or the one --epic
names; the two are orthogonal. --output markdown|json is the reduced set of
the output convention: markdown for a person, json for the rows every view is built from. A WIP label, if
one is ever used, travels in the comment.

Closes #786

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
…e epic's plan complete

Section 14 of the CLI design names the test behind each invariant; the status document's enforcement box
is ticked, the last of the epic's convention boxes. The 740 plan's goals, its Phase 3 writ boxes -- landed
by #774 and unticked since -- and its Phase 5 are ticked, and its status is complete. make docs runs in
this commit's gates. devlore-docs generates lore's and writ's pages only, filed as #787.

#776 phase 3; every phase of the plan is complete.

Refs #776, #740

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
@David-Noble-at-work David-Noble-at-work self-assigned this Sep 4, 2026
…the checkers pass lint

Both Windows test legs failed on TestCLI_ConfigPath: config path emits through the pipeline since #776
phase 2, and under the json default a Windows path comes back as a quoted string with its backslashes
escaped, as JSON requires. The test searched the raw text for the path, which only ever worked where a
path had nothing to escape. It now decodes the JSON and compares the path, equal rather than contains,
on every platform.

quality-gate: CheckNoOwnOutputFlag and stdoutWrite were over the cognitive-complexity ceiling and are
split; the three exec.Command calls in the tests carry a context.

Refs #776

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
The version was written twice, in the Makefile and in the CI install URL. The Makefile is now the one
place, make print-golangci-lint-version prints it, and CI installs that. The pin moves to v2.13.2: this
machine runs it, make lint had refused on the mismatch for a day, and the whole tree is clean on it. With
the gate runnable, every commit script runs make check before it commits.

Closes #789

Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
@David-Noble-at-work
David-Noble-at-work merged commit 966bdf9 into develop Sep 4, 2026
12 checks passed
@David-Noble-at-work
David-Noble-at-work deleted the feature/776-output-enforcement branch September 4, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant