Skip to content

commands-resolve: a verb a document names must be one the command dispatches on - #69

Closed
HackingGate wants to merge 2 commits into
owner-and-visibility-fromfrom
doc-commands-resolve
Closed

commands-resolve: a verb a document names must be one the command dispatches on#69
HackingGate wants to merge 2 commits into
owner-and-visibility-fromfrom
doc-commands-resolve

Conversation

@HackingGate

Copy link
Copy Markdown
Owner

Closes #55. Stacked on #68 -- base retargets to main when that merges.

What it is

The third resolver. links-resolve resolves a path a reader would CLICK,
anchors-resolve a value a reader would BELIEVE, this a command a reader would
RUN.

[rule.doc-commands-resolve]
builtin = "commands-resolve"
message = "Name a verb the command dispatches on."
command_sources = ["cmd/{}/*.go"]

[rule.doc-commands-resolve.files]
glob = ["*.md"]
doc-commands-resolve: 1 command(s) discovered, 1 judged, 0 skipped
policy check failed: doc-commands-resolve
README.md:4: tells a reader to run `fg-registry credentials`, and fg-registry
dispatches on: services, sync

The three decisions

A grammar, not a line matcher. The implementation this ports used a regex
over lines plus an allow-list of eight subject spellings read off one
workspace's dispatches, and both halves of its measured false-finding rate come
from that. tree-sitter was already in this binary for the comment checks, so Go
is one new grammar crate and Rust was already here. A dispatch is now recognised
structurally, and each condition is a false finding the line matcher produced:

condition what it rejects
dispatches on something a Go tagless switch { -- 22 of the original's 38 first-run findings
two or more string-literal branches a match over an enum, whose variants nobody types
a catch-all branch exists a lookup that never answers for an unknown word

A command agrees with itself before it judges anyone. Two readings of its own
sources -- the string labels of its dispatch, and the verbs its own usage block
names about itself -- must agree, or the command is counted, named and skipped.
The count prints every run, and zero judged is exit 1 rather than a pass.

A pattern, not a table. {} is captured out of the path, so the convention
stays in the repository that has one. It also bounds the union: a sibling binary
in the same repository cannot lend a command its verbs.

Not shipped in a bundled set

The rule needs a command_sources pattern describing one tree's layout, and a
rule arriving from a set cannot be handed a parameter. A set carrying a layout
would either impose one workspace's convention on every inheriting repository or
ship a rule that refuses to run.

Limits, asserted rather than left to be found

  • A flag taking a SEPARATE value hides the verb behind it:
    fg-registry --workspace here sync reads here. --flag=value passes
    through. Nothing in the text tells a flag's value from a verb; only the
    command's own flag table does, and reading that is a second parse with a
    second way to be wrong.
  • A binary whose name lives in a manifest rather than in its path is not
    discoverable by a path pattern. src/bin/{}.rs works; a single-binary crate
    named in Cargo.toml over src/main.rs does not.

Tests

8 unit cases in src/commands.rs, 9 CLI cases in tests/scan_cli.rs.
cargo deny check is clean with the new grammar crate.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c8cb78c9-0171-4751-991b-601b2ea0a325

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.14563% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.25%. Comparing base (ab4b7a3) to head (1535c9e).

Files with missing lines Patch % Lines
src/commands.rs 95.49% 16 Missing ⚠️
src/scan.rs 92.10% 9 Missing ⚠️

❌ Your patch status has failed because the patch coverage (95.14%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@                      Coverage Diff                      @@
##           owner-and-visibility-from      #69      +/-   ##
=============================================================
+ Coverage                      90.02%   90.25%   +0.23%     
=============================================================
  Files                             34       35       +1     
  Lines                          10496    11006     +510     
=============================================================
+ Hits                            9449     9934     +485     
- Misses                          1047     1072      +25     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

`links-resolve` resolves a path a reader would CLICK. `anchors-resolve` a value
a reader would BELIEVE. This one a command a reader would RUN, which is the last
of the three things a document asserts about a tree -- and before any of them
existed all three failed the same way: silently, forever, with every gate in
every repository still green.

The defect it was built from is measured rather than imagined. A README opened
with `fg-registry credentials` for as long as the file existed. That command has
two verbs and `credentials` was never one of them; the binary's own error names
the alternatives, so the answer was one invocation away. Nobody invoked it,
because a reader who trusts the README has no reason to and a reader who does
not is not reading the README.

WHY THE DISPATCH AND NOT `--help`. Running the binary needs a build, and a gate
that needs the network is a gate that gets skipped. Worse, it invites resolving
a verb by RUNNING it: `fg-registry sync` in a document would be verified by
fast-forwarding thirty-nine submodules. Only the help text is safe to execute
for, and help text is prose too -- a doc comment drifts from the switch below it
exactly as the README drifted. The switch IS the verb list.

WHY A GRAMMAR. The implementation this ports read dispatches with a regex over
lines and an allow-list of eight subject spellings taken off dispatches in one
workspace. Both halves of its measured false-finding rate come from that: a
tagless `switch {` is a line the matcher cannot read, and a subject list read
off one tree describes one tree. tree-sitter was already in this binary for the
comment checks, so Go is one grammar crate and Rust is already here. A dispatch
is now recognised structurally -- it dispatches on something, two or more
branches match string literals, a catch-all exists -- and each of those three
conditions is a false finding the line matcher produced.

WHY A COMMAND MUST AGREE WITH ITSELF FIRST. A verb list read wrong is worse than
no verb list: it produces confident findings against documents that were right.
The first run of the ported implementation reported 38 findings and one binary
supplied 22 of them, every one false and every one reading as real. So a command
judges documents only when two readings of its own sources agree -- the string
labels of its dispatch, and the verbs its own usage block names about itself --
and when they disagree it is counted, named and skipped. The count is printed
every run, because a check that read four commands out of a hundred otherwise
reads exactly like one that read them all. Zero judged is exit 1 rather than a
pass, for the same reason.

WHY A PATTERN AND NOT A TABLE. `command_sources = ["cmd/{}/*.go"]` says what a
command looks like in this tree and captures the name out of the path. A list of
command names would be a second copy of the tree, free to go stale, which is the
class of defect this rule refuses in documents. The capture also bounds the
union: a command's verbs come from the files its own pattern selected, so a
sibling binary in the same repository cannot lend it verbs -- a repository-wide
widening in the original resolved a flag-only command to its neighbour's verbs,
and a document naming one of them would have passed.

NO BUNDLED SET SHIPS IT. The rule needs a pattern describing one tree's layout,
and a rule arriving from a set cannot be handed a parameter. A set carrying a
layout would either impose one workspace's convention on every inheriting
repository or ship a rule that refuses to run.

Two limits are stated in the reference and asserted by a test rather than left
to be found: a flag taking a separate value hides the verb behind it, and a
binary whose name lives in a manifest rather than in its path is not
discoverable by a path pattern.

Closes #55.
…n sentences

Two defects, both found by pointing the rule at a real workspace of nineteen
commands rather than at its own fixtures.

A TAGLESS SWITCH IS NOT AUTOMATICALLY A CHAIN OF BOOLEANS. "Dispatches on
something" was never the property worth testing. Go spells a dispatch that also
guards its own argument count as `switch { case len(args) > 0 && args[0] ==
"serve": }`, and refusing to read it left a real command judged against a
SUB-dispatch found in another file of the same package -- the `record|last` of
one of its own verbs -- which produced three confident findings against a README
that was right. That is the exact failure this rule exists to prevent, arriving
through the guard against it.

The condition is now: every branch names a literal. A tagless switch whose arms
are `ready()` and `waiting()` still contributes nothing, which is the case the
guard was written for.

A SENTENCE THAT BEGINS WITH THE COMMAND'S NAME IS NOT A USAGE CLAIM. The two
readings fail in opposite directions: a false document finding accuses a file
that was right, while a false USAGE verb costs coverage in silence -- the
readings disagree and the command then judges nothing at all. A command called
`session` collected `for` and `in` out of three ordinary comments ("session in
an already-running browser", "session for the ref"), disagreed with its own
dispatch over words that are not verbs, and skipped itself out of every document
in its repository. Any command whose name is also an English word has that
waiting for it.

So the usage reading now takes only lines whose remainder still reads as an
invocation: a flag, a placeholder, an alternation, or nothing. Applied to the
usage half and deliberately not to the documents, where narrowing would drop
real findings.
@HackingGate

Copy link
Copy Markdown
Owner Author

Closed by GitHub when its stacked base branch was deleted on the merge of #68, and it cannot be reopened once that base is gone. Continued as #70: same branch, rebased onto main, plus two fixes found by pointing the rule at a live workspace of nineteen commands.

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.

2 participants