Skip to content

cli: one declaration for the commands, and the documents held to it - #101

Merged
donislawdev merged 1 commit into
mainfrom
cli/one-declaration-for-commands
Sep 9, 2026
Merged

cli: one declaration for the commands, and the documents held to it#101
donislawdev merged 1 commit into
mainfrom
cli/one-declaration-for-commands

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Closes O201. Three lists said the same thing, no two of them were compared, and all three were green.

  • the block tfg --help prints was a raw string beside the switch that dispatched
  • the website copied that block by hand
  • the flag table in README was typed out separately from the flag set generate builds

README was missing --damage, added to the program the same day, and nothing said so.

Measured before writing anything

The naive fix would not have worked, and this is why:

list count contents
dispatch, verbs taking arguments 8 generate validate verify cleanup recipe preset formats damage
the Commands: block 10 those eight, but recipe fmt, plus version and license

All four differences are legitimate - recipe branches on one word and prints as two, and version/license do not take args[1:]. A guard comparing the two lists would have raised four false alarms. That is what shaped the declaration into separate fields rather than one name.

The change

Run and the help both come from one table in internal/cli/commands.go.

The output is byte for byte what it was - the same sha256 across --help, --version and an unknown command. This is a refactor, not something a user of the tool can see.

The fields are apart because they answer different questions. Verb is what the dispatch matches. Aliases reach the same place and are not printed, which is how four spellings of licence exist without the help turning into a spelling lesson. Shown replaces the verb in the help where they differ, and only recipe does. An empty summary means reachable but unlisted, which is help itself.

Guards

The other two lists are documents, which no declaration can generate. Each guard asks a rule, not a list of exceptions:

  • every command the help lists is a verb the tool answers to - asked with the first word of the printed name, because that is what a reader types
  • every command it does not list does nothing except print that help, compared against the help itself - which tells help apart from a verb somebody forgot to describe, without naming either
  • the README table is compared against the flag set generate really builds, both directions, with a shape check first so a change in flag.PrintDefaults cannot leave the map empty and every comparison trivially true
  • the English page repeats the program's own sentences word for word, which is all that can be asked where the Polish page describes the same command in Polish on purpose

TestEveryCommandTakingItsOwnArgumentsIsWatchedForHelp read the dispatch with go/ast and refused to pass when that switch disappeared - exactly what it was built to do. It asks behaviour now: a command with its own flag set refuses an unknown flag with ExitUsage, one that ignores what follows answers normally. All eleven verbs, eight to three, no exception to write down.

What this cost

Five mutations were invalidated at once - four by code moving out of cli.go, one by my own pattern written against a line I then reflowed. All five reported SKIP, which reads as proven, and only staleness.py said otherwise. Repointed and rerun.

Three came back NOT CAUGHT or BROKEN, each for a different reason, and one was a real hole in a new guard: it asked the declaration about its own verb rather than about the printed name, so an entry printing one word and dispatching on another passed. The fix is a repair, not a mutation tweak.

internal/guard/site_test.go crossed into the crowding band, answered with a cut rather than a larger number - the command knowledge moved to the new file where it belongs.

Verification

Full suite green, gofmt from $(go env GOROOT)/bin and go vet clean, journey.py 300 of 300, staleness.py reports every pattern occurring exactly once across 958 entries, ten mutations run and all caught.

🤖 Generated with Claude Code

The block tfg --help prints was a raw string sitting beside the switch that
dispatched, and nothing compared the two. The website copied that block by
hand. The flag table in README was typed out separately from the flag set
generate builds. Three lists saying one thing, no two of them compared, and all
three green - README was missing --damage, added to the program the same day.

Run and the help now come from one table in internal/cli/commands.go, so the
first of those cannot drift at all. The output is byte for byte what it was:
the same sha256 across the help, the version and an unknown command.

The declaration keeps the parts apart because they are different questions.
Verb is what the dispatch matches; Aliases reach the same place and are not
printed, which is how four spellings of licence exist without the help becoming
a spelling lesson; Shown replaces the verb where they differ, and only recipe
does, since the dispatch branches on "recipe" while a person types "recipe
fmt"; an empty summary means reachable but unlisted, which is help itself.

That shape came from measuring first. The dispatch offered eight verbs taking
arguments and the help printed ten, with all four differences legitimate - a
guard comparing the two lists would have raised four false alarms.

The other two lists are documents, which no declaration can generate, so they
get guards asking a rule rather than carrying a list of exceptions. Every
command the help lists must be a verb the tool answers to, and every command it
does not list must do nothing except print that help - which tells help apart
from a verb somebody forgot to describe without naming either. The README table
is compared against the flag set generate really builds, both directions. The
English page must repeat the program's own sentences word for word.

TestEveryCommandTakingItsOwnArgumentsIsWatchedForHelp read the dispatch out of
cli.go with go/ast, and refused to pass when that switch disappeared - which is
what it was built to do. It asks behaviour now: a command with its own flag set
refuses an unknown flag with ExitUsage and one that ignores what follows
answers normally, splitting all eleven verbs eight to three with no exception
to write down.

Five mutations were invalidated by the move and are repointed. Three came back
NOT CAUGHT or BROKEN first, and one of those was a real hole in a new guard: it
asked the declaration about its own verb rather than about the first word of
the name the help prints, so an entry printing one word and dispatching on
another passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 9258c3e into main Sep 9, 2026
18 checks passed
@donislawdev
donislawdev deleted the cli/one-declaration-for-commands branch September 9, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant