Repository navigation
The output convention is enforced: the shared root owns the common set, and the checkers hold it - #788
Merged
Conversation
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
…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
This was referenced Sep 4, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #776, closes #757, closes #786. Plan:
docs/plans/feature/776-output-enforcement.md— every phasecomplete; it closes the convention box of #740 and the 740 plan with it.
Four commits, plus the charter:
cmd/internal/cli, each shown red on a fixture: no command shadows a flag it inherits, by long name orshorthand — 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/cliimportspkg/result.RunInteractiveis 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.
NewRootCmdregisters the four flags once;cli.Emitis theone render path;
addOutputFlagsis unexported. Lore, star and writ drop their call, variable andemitResult. 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, andDEVLORE_TEST_*keepsworking.
config get/list/path/schema/validateemit results;manand the prompt narrate; invariant 1goes green. The shadow checker found four real shadows on its first run and each is resolved.
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 statusdocument.
box is ticked; the 740 plan is complete. Found on the way:
devlore-docsgenerates lore's and writ'spages 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
fixtures — with the red output in the commit that added it — phase 1
config get/list/path/schema/validatethrough-o,manand the prompt on stderr — phase 2
cli.NewRootCmd, which registers the common set once; no program callsAddOutputFlags, which no longer exists by that name; every root has a root test calling thecheckers (devlore-test builds its own root, so shared-infrastructure fixes do not reach it #757 closes here) — phase 2
make docssucceeds; the status doc's enforcement box and the 740 plan are closed — phase 3Test rows
devlore-testwraps help atCOLUMNS=70and renders through-o(unit)configsubcommands render through-o;mannarrates (unit)make docsgenerates every page it knows, lore's and writ's (build, in the commit's script)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