Repository navigation
writ reconcile: the command is renamed, and its renderings go through the sink - #778
Merged
Merged
Conversation
added 5 commits
September 1, 2026 15:14
Three of the four items left on #740 had no issue and no plan. Now #774, #775 and #776 each have both, cross-referenced in both directions -- each issue links its plan, each plan names its issue, and every plan links the other two and the epic's. The plans land together on #774's branch, following the precedent #768 set: plans for work that is not yet started ride with the first piece that is. This pull request will close #774 alone when its work is done; #775 and #776 close when theirs is. #774 -- writ reconcile. The rename and the 30 stdout call sites are ONE piece of work, not a rename followed by a refactor: the renderings being rewritten live in the package being renamed. This is also #762's phase 2, and both plans say so. The surface is the one #772 settled -- a report, no command flags, the globals used faithfully. #775 -- lore. The search table moves onto the shared TableFormatter, three hand-rolled flag sets go, and thirteen fmt.Print calls are triaged. #741 is kept separate so a user-visible defect is not buried in a refactor, and closes under #775 only if a multi-byte rune test -- written red first -- proves the fix. #776 -- enforcement. Three tests, sequenced LAST because they are red until the other items land and they certify those items rather than precede them. Each of #774, #775 and #743 turns exactly one test green, which makes each of those pull requests' "done" observable rather than asserted. The third test is the one that would have caught #753 and #754: a root that registers the flags and leaves that ignore them looks like compliance to the other two. 740's table of remaining items now names the issue and plan for each, and the paragraph that said three needed filing says they are filed. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
Phase 1 of #774, and phase 2 of #762: the two plans record that these are one piece of work. THE PACKAGE MOVES WITH ITS HISTORY. git mv cmd/writ/writ/status -> cmd/writ/writ/reconcile, then status.go -> reconcile.go and the integration test to match, so `git log --follow` crosses the rename. report.go keeps its name. The package clause, the doc comments, and the surface follow: Config, Execute, BuildReport; StatusConfig -> ReconcileConfig; parseStatusConfig -> parseReconcileConfig, whose doc comment already said parseReconcileConfig -- someone started this rename before and got one line in; newStatusCmd -> newReconcileCmd; runStatus -> runReconcile. THE COMMAND IS `writ reconcile`. Use, Long, and Example say so; "Status indicators" becomes "Entry states"; "Status is report-only" becomes "Reconcile produces a report", which is the surface #772 settled -- a report, no command flags, the globals used faithfully. No alias: `writ status` stops existing. The package doc records why the name went away and came back: step 47 named it status because "reconcile" promised a mutation the command did not perform; #762 returned the name once repair was chartered. THE SCENARIO TEST invokes the new name and asserts under it. Six Go comments outside the package that described defects in `writ status` now say `writ reconcile`; the one that says the command "shipped for months" answering -o yaml with a dashboard says it shipped as `writ status` then, because otherwise the sentence is false. THE DOCUMENTS. Seven architecture and guide documents and nine plans changed outright -- the two user-facing guides among them also lose a `--json` that no longer exists, in favor of `-o json`. Two documents are the record of the original step-47 rename, and "reconcile replaces reconcile" is nonsense, so those carry a note after their H1 and keep their text. The remaining `writ status` in the tree is in history notes and in the plans that name the retired command as the thing retired. FOUND ON THE WAY: three of #762's phase-1 boxes were completed by #767 and never ticked. Ticked now and recorded as stale rather than quietly corrected, per the process. The 30 stdout call sites are NOT touched here. That is phase 2, and it is the reason this is one piece of work rather than two: the 22 in report.go are rewritten next, in the package that now has its final name. make build, make vet, make test, gofmt -l, and Test-GuideFrontmatter.sh are all green. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
Phase 2 of #774. The 22 fmt.Print* calls in reconcile/report.go are deleted, not routed: the human rendering is `-o table` or `-o list` applied to the report's JSON, per the two-stage model, so there is no text renderer to route and no format decision left in the package. WHAT GOES. presentJSON, presentText, presentEntries, and State.String() -- the glyph the text report drew, which no output now carries. reconcile.Execute goes with them; BuildReport is the entry, and it returns the Report to the command. Config.JSON goes, and with it the one-bool bridge at config.go:160 that read outputOptions.Format == "json" so that a boolean could stand in for a format -- the thing #740 retired --json to stop. WHAT REMAINS. runReconcile builds the report and hands it to emitResult, exactly as verify does. Every --output value, --jq and --filter now apply to it the way they apply to any other command's result, because the report reaches BuildPipeline instead of bypassing it. The help text's "Entry states" lists the labels JSON emits -- linked, copied, missing -- rather than glyphs, and says that --jq '.entries' selects the delta alone. THE TEST, AT TWO LEVELS. TestReport_EveryFormatRenders pins the Report's shape under the shared pipeline: State marshals as its label, the four sections survive stage-1 normalization, none of the eight formats is silently rejected. TestWritDeployScenario_Deploy gains a loop over the seven non-JSON formats against the real binary and fails on the human dashboard or on silence -- and that is the assertion this phase exists to turn green, because the defect was in the command's wiring, not in the pipeline. make test skips the scenario suite; make test-scenario ran it, and it passed. On "write the failing test first": the scenario assertion is red on the previous tree by construction -- presentText's first statement was fmt.Println("Layers:"), and the assertion fails on that string. It was written and the wiring changed in one pass, so red was argued rather than observed. The plan says so, at the right size. Eight of the thirty stdout sites remain: four SerializeGraphs dry-run dumps and two in migrate, which are phase 3, and two in verify already gone. Also riding here, unrelated to writ and logged where it was asked: a question for the configuration design (#441), recorded in configuration.md's Open questions and in thread 4's plan -- should a config system be instantiated from the flags cobra has parsed and viper has bound, rather than binding its own? Source 5 of resolution says "the parsed pflag set, read once" without saying whose. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
Phase 3 of #774: the last eight of writ's thirty stdout sites. THE FOUR DRY-RUN DUMPS. deploy, decommission, upgrade and secret each wrote their graphs to os.Stdout with SerializeGraphs and returned. Each Execute now returns ([]*op.Graph, error) -- the plan under --dry-run, nil otherwise -- following verify.Execute, and the command hands a non-nil plan to emitResult. Every --output value, --jq and --filter apply to a dry run the way they apply to any other result, and `-o yaml` on a dry run is a parseable plan rather than a dump. No new helper was needed. Graph has its own MarshalJSON and MarshalYAML, so the Go value's document form is SerializeGraphs's -- as a JSON array or YAML sequence rather than a multi-document stream, which is the one visible difference and a better one: `--jq '.[0]'` works. 28 test call sites now discard the plan they never asserted on. Two dry-run tests assert it comes back, and the scenario suite runs `deploy --dry-run -o json` against the real binary and requires a non-empty JSON array -- red before this, when -o json yielded YAML. MIGRATE. Its own --format retires for -o. FormatMigrationPlan and the eleven text helpers under it are deleted; NewMigrationView builds the value the pipeline renders, and the human rendering is -o table or -o list on that JSON. Options.Format was read by nothing and goes with it. The session's one os.Stdout write was its result -- the analysis and graph at completion -- and is emitted through an Options.Emit hook the command supplies, since the session has no writer of its own. THE DOCUMENTS. 10-command-line-interface.status.md ticks writ's renderings box: all 30 sites, with the phase that took each. The conformance table in 10-command-line-interface.md moves writ to "none". FOUND ON THE WAY, logged where each belongs. manage-environments.md teaches `writ inspect --format`, a command that does not exist (#777, not this work). Two lore guides teach --format, recorded in #775's file list and resolved there. make build, make vet, make test (106 ok, 0 FAIL) and make test-scenario are green; gofmt -l is empty. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW
Phase 4 of #774, the last. Three more assertions in the scenario suite, against the real binary, so that every global writ registers is shown to be consumed rather than merely present -- a flag on a root that no leaf reads looks like compliance, which is how #753 and #754 shipped. --jq '.entries' returns the delta as a JSON array. --filter with a malformed expression is refused, and the refusal names the expression: were the flag ignored, the command would print the report and exit 0, a typo honored as a request for the default. --store pointed at a store that was never written is refused for its missing run index rather than reporting on the default store as though it had complied. With -o (phase 2, eight formats) and --dry-run (phase 3, the plan as a JSON array), that is all five. AND THE RENAME IS PINNED. TestReconcile_StatusIsGone executes the root with `status` and requires "unknown command", then executes `reconcile --help` so the test cannot pass by the root being broken. Until now the old name's absence was verified by grep; the plan's own test row 1 asked for a test. THE ACCEPTANCE CRITERIA are written into the plan as a checklist -- three goals, seven test rows, the gates -- and the pull request carries a copy. Every box is checked before merge. One cannot be checked from this branch: row 7, the scenario suite on all five platforms, is four-fifths CI, and it is checked on the pull request when every scenario leg is green. docs/cli regenerates cleanly: reconcile.md present, status.md gone, and no --format on migrate.md. It is gitignored here and published from develop by docs-publish.yaml, so there is nothing to commit for it. The plan is complete. 740's table marks this item landed; #775 is next. 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 #774
writ statusis retired andwrit reconciletakes its place. All 30 of writ's stdout call sites routethrough the sink, and the five globals the root registers are consumed, not merely present. This is
phase 2 of #762 as well as the first item of #740; both plans record that they are one piece of work.
Acceptance criteria
The canonical copy is the plan's own section —
774-writ-reconcile.md§ Acceptance criteria.Every box is checked before merge.
Goals
writ statusdoes not exist and has no alias —TestReconcile_StatusIsGonecmd/writgoes through the sink — the onlyos.Stdoutleft is a.Stat()TTYcheck; 30 of 30 sites gone
Test rows
writ statusdoes not exist:TestReconcile_StatusIsGone(unit)-ovalues renders onwrit reconcile:TestReport_EveryFormatRenders(8 subtests)+ the scenario loop
--jq '.entries'returns the delta alone: scenario--store <dir>reads that store: scenario — an empty store is refused for its missing run index--dry-runyields the plan, rendered by-o: scenariodeploy --dry-run -o jsonis a JSON array+ two unit tests
fmt.Print*incmd/writ/writ/reconcile: 0 in four files; the tree-wide test is Enforcement: two tests that fail when a command leaves the output convention #776's bydesign
request scenario (darwin-arm64), scenario (linux-amd64), scenario (linux-arm64), scenario (windows-amd64), scenario (windows-arm64) — all green
Gates, every phase:
make build,make vet,make test,make test-scenario,gofmt -lempty,Test-GuideFrontmatter.sh.The four phases
git mv cmd/writ/writ/status → reconcile/, the package surface, thescenario test, sixteen documents changed outright and two step-47 records given a note.
fmt.Print*deleted;BuildReporthands the Report toemitResult;-o,--jq,--filterapply to it like any result. The one-bool--jsonbridge goes.--dry-run. FourExecutes return([]*op.Graph, error);Graph'sown marshalers make the value its document, so no helper.
migrate --formatretired; its session emitsthrough an
Options.Emithook.Found on the way, logged where each belongs
manage-environments.mdteacheswrit inspect --format, a command that does not exist — manage-environments.md teacheswrit inspect --format, a command that does not exist #777loreguides teach--format— recorded in lore: the search table, three hand-rolled flag sets, and thirteen fmt.Print adopt the convention #775's file listconfiguration.md's Open questions and Epic: Unified configuration #441's planAlso carried
The plans for #775 and #776, committed here before their work starts, per the process. This PR closes
#774 alone.
https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW