Skip to content

cli: a listing command refuses a name it cannot answer about - #97

Merged
donislawdev merged 1 commit into
mainfrom
cli/listing-commands-refuse-extra-names
Sep 9, 2026
Merged

cli: a listing command refuses a name it cannot answer about#97
donislawdev merged 1 commit into
mainfrom
cli/listing-commands-refuse-extra-names

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Closes O196 and O197.

O196 - the silence

Measured on the built binary before touching anything:

command was is
tfg formats png svg described png, ignored svg, exit 0 exit 2, names svg
tfg preset list some-name printed the whole list, ignored the name, exit 0 exit 2, names it
tfg preset show <id> extra exit 2 unchanged
tfg verify a b exit 2 unchanged

preset list is not in the observation. It came out of measuring the one
that was, and fixing only the named half would have left O196's own sentence -
"two commands answer one question two ways" - true about a different pair.
Flagging it because only formats was authorised.

tfg preset list takes no name at all, so anything after it was always a
mistake, most likely somebody reaching for tfg preset show. The refusal says
so.

O197 - the hand written list

The flag spelling guard named seven directories by hand and internal/damage
arrived on layer 2, under both surfaces, without joining it. The fix derives
the directories from the layer map, so this closes the class rather than the
instance: every later package below the surfaces is covered with nothing to
remember.

The layer map is the right source rather than a walk of internal, for two
reasons. TestLayeringHoldsForEveryPackage already refuses a package that is
neither on the ladder nor declared test only. And it leaves internal/oracle
out on purpose - that one carries three real command lines for inkscape and
ffprobe in raw strings, and a walk would redden on them, which is how a guard
gets switched off inside a week.

Measured: 113 files scanned became 128 (damage 4, legal 7, site 3,
version 1), zero findings - the hole was empty, and it was still a hole.

What it cost

Refusing the extra name pushed internal/cli/preset.go to 439 lines against a
ceiling of 433. Carving beat raising the number: presetcmd.go answers somebody
who typed tfg preset, preset.go turns a named question into targets. The
ratchet then took the ceiling down - longestFile 433 to 408,
crowdedFiles 1 to 0.

The split invalidated three mutation entries and the ceiling change a fourth,
and only staleness.py said so.

Guards

One new guard asking four commands at once, including the two that already
refused - the defect was not a missing check in one place, it was four commands
answering one question two ways. It requires the refusal to name the rejected
word
, because an exit code alone is satisfied by a build that refuses
everything, and the control half checks these commands still answer.

Three new mutations plus four retargeted, all eight run and caught.

Verification

  • full go test ./... green, gofmt, go vet, staticcheck@v0.8.1 clean
  • tools/journey.py 300 of 300
  • tools/mutate/staleness.py: 943 patterns, each occurring exactly once
  • no generated file moves a byte - this touches argument handling only

🤖 Generated with Claude Code

Measured on the built binary: "tfg formats png svg" described png, said
nothing at all about svg and ended with zero, and "tfg preset list some-name"
printed the whole list and ignored the name though that operation takes no
name at all. Both are silence about an argument somebody typed, and in a
script a confident answer about the wrong thing is worse than a refusal.

The second one is not in the observation that asked for this. It came out of
measuring the first, and fixing only the named half would have left that
observation's own sentence true about a different pair of commands.

Both now end with 2 and name the word they could not use. The guard asks four
commands at once, including the two that already refused, and requires the
refusal to name the rejected word - an exit code alone is satisfied by a build
that refuses everything, so the other half checks these commands still answer.

The flag spelling guard stops naming its directories by hand and derives them
from the layer map. internal/damage arrived on layer 2, under both surfaces,
without joining that list, and the hole would have taken every later package
too. The layer map is the right source because the layering guard already
refuses a package that is neither on the ladder nor declared test only, and
because it leaves out internal/oracle on purpose - that one carries real
command lines for inkscape and ffprobe in raw strings. 113 files scanned
became 128, with zero findings.

Refusing the extra name pushed internal/cli/preset.go past the file ceiling,
so it is split by what the parts do: presetcmd.go answers somebody who typed
tfg preset, preset.go turns a named question into targets. The ratchet then
took the ceiling down, 433 to 408 and crowdedFiles 1 to 0.

Three new mutations, and four existing ones retargeted after the split - only
staleness.py said they had gone stale. All eight run and caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 74b4b37 into main Sep 9, 2026
18 checks passed
@donislawdev
donislawdev deleted the cli/listing-commands-refuse-extra-names branch September 9, 2026 12:11
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