feat(cli): state each local engine's reach in the routing recipe - #155
feat(cli): state each local engine's reach in the routing recipe#155thecodedrift wants to merge 4 commits into
Conversation
|
@claude /review |
|
Claude finished @thecodedrift's task in 13m 57s —— View job Review completeReview mode: incremental — read 1 prior review thread(s) before reviewing.
SummaryThis is a well-scoped, carefully justified change. The core design — pinning engine reach as pure-data constants in
Two small things worth fixing, posted inline:
Neither is a functional bug — the recipe text, the substitution mechanism, and the two vendor-contract test suites (ast-grep set-equality, Vale tier-by-probe) all check out. Nice attention to detail in the Vale probing (measuring rather than assuming |
`route` chose an engine from the shape of the evidence alone and said
nothing about language reach, so a rule over a GitHub Actions workflow
was escalated to `create-runtime-rule` — which needs a login — because
nothing stated that ast-grep parses YAML. It does; `Yaml` is one of the
26 languages the pinned `@ast-grep/cli@0.41.0` lists in `sg run -h`.
Nothing in the repo could have answered that. The vendored ast-grep
schema types `$defs.Language` as a bare string with no enum, `verify`
never validates a rule's `language`, and `detect --json` reports the
repository's own languages in a different vocabulary. Vale self-reports
nothing at all, so its reach was measured by probing the shipped 3.17.1
binary tier by tier.
A new `src/rules/capabilities.ts` carries both engines' reach as pure
data pinned to the version it came from, and `buildVariables` renders it
into `route.txt` (topic v2) and `create-vale-rule.txt` (topic v3) as
`%(...)s` substitutions rather than prose typed into a `.txt`. Two
vendor-contract blocks pin the constants to the binaries, so an engine
bump that changes what a binary parses fails a test instead of leaving a
confident wrong sentence in front of an agent.
Also names Vale's converter-dependent formats and what they cost: Vale
supports reStructuredText, AsciiDoc, XML, DITA and MDX by shelling out
to a converter this CLI does not ship, and one such file exits 2 with an
`E100` and abandons the whole run. `create-vale-rule` had been offering
`[*.{md,mdx}]` as its example of widening a matcher.
Refs #151
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
The recipe and the constants both claimed a plausible `C++` or `yaml` passes `verify` and fails inside the binary. Measured against the pinned 0.41.0: `C++` and `cpp` are accepted aliases that resolve to Cpp, so an off-list spelling is not reliably an error at all. The two failures that are real, and neither is caught locally: a name ast-grep does not recognize (`C#` for `CSharp`) aborts config parsing, so every other rule goes unreported alongside it; and a valid name for the wrong parser (`TypeScript` over a `.tsx` tree) reports nothing and reads as a clean codebase. The advice to copy from the list is unchanged — the reason for it is now the accurate one. The assertions in the vendor contract were already correct: they pin the canonical list, not what the binary rejects. Their comment said otherwise and now says what they actually cover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
…pendent prose
The vendor contract asserted that Vale's `E100` text contains the converter
name, deriving the expected token from our own prose with
`converter.replace(/^an /, "").split(" ")[0]`. For `.xml` that yielded
"XSLT", which matched locally and failed in CI — the only failure across
all three stacked PRs.
The cause is that `.xml` fails two different ways. Vale reports
`xsltproc not found` where the program is absent and
`no XSLT transform provided` where it is present, and the split is by
platform: macOS ships /usr/bin/xsltproc, the Linux runner image does not.
So the assertion was reading host state through a vendor string while
presenting itself as a vendor contract.
It now asserts the checker tag — `[lintXML]`, `[lintAdoc]`, `[lintRST]`,
`[lintDITA]`, `[lintMDX]` — which is identical on both hosts, from a new
VALE_CONVERTER_CHECKERS map held to set-equality with the extension list.
The actionability claim the probe used to make moved to its own case,
asserted against our own data where no binary is involved.
Corrects the `.xml` entry while here. It is the one format whose converter
is not sufficient on its own: an XSLT transform is document-specific, so
there is no default to ship and installing xsltproc does not make `.xml`
lintable, where installing asciidoctor genuinely fixes `.adoc`. It now
names the stylesheet as well as the program, and that reaches the routing
recipe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
…ims them A comment here pointed at a `the language field` suite that did not exist on this branch — it was added one PR up the stack, so a maintainer following the pointer found nothing and might have assumed the C#-abort and wrong-parser behaviours were covered. Reported in review, and correct. Moved down rather than the comment softened. `route.txt` makes both claims on this branch, and the house rule is that an engine claim in a recipe is quoted from a pinned binary rather than described, so the claim and its pin belong in the same change. Adds a third case while here, pinning the fact this branch's own history got wrong: `C++` and `cpp` are ACCEPTED aliases resolving to Cpp, so an off-list spelling is not reliably an error. Without it the suite reads as "off-list spellings fail", which is the claim that had to be corrected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
3ecd0cd to
b1eaaa7
Compare
Stack (root → tip):
An agent reading
route.txtcould not tell whether a language was buildable locally or had to escalate toruntime, which needs a login. The reporter's case: two GitHub Actions YAML rules were routed toruntimebecause nothing said whether ast-grep handles YAML.It does. The pinned
@ast-grep/cli@0.41.0lists 26 languages includingYaml, and a realpr-workflow-no-branches-filterrule (language: Yaml) passesverifyandtest. Both Actions rules belong increate-sg-rulewith no login.Refs #151
Derived, not transcribed
There was no source of truth for either engine's reach.
$defs.Languagein the vendored schema is a bare string with no enum,verifynever validateslanguage, anddetect --jsonreports the repository's languages in a different vocabulary. The binaries were the only authority, androute.txtsaid nothing.Transcribing two lists into a
.txtwould drift on a version bump with nothing to catch it, and stale prose is worse than the current silence — silence at least makes the agent ask. So: pinned constants insrc/rules/capabilities.ts, substituted into recipes as%(KEY)s, with vendor-contract tests asserting the constants still match the binaries. That is the patternast-grep-vendor-contract.test.tsalready exists for — exact-pinned binary plus a test that fires on the bump.capabilities.tsis deliberately import-free:prompts/recipes.tsimports it, and theassert-prompts-graphvite plugin fails the build if that chunk reaches a host capability.Rejected alternative: extending
detect --jsonwith anenginesblock.cli-detect's spec framesdetectas an offline repo scan, engine reach is knowable at build time, and it would cost a third subprocess turn in a recipe already spending two.Vale's reach was measured, not read
Vale self-reports no format list, so every tier here comes from probing the shipped 3.17.1 binary — and the measurement corrected the starting assumptions in five places:
.cssis comment-tier, not plaintext. The first probe said otherwise because it fed CSS a//comment, which is not one..sassis comment-tier;.scssis not..Ris comment-aware,.PY/.JS/.TSare not;.cc/.cxx/.pyw/.pmare,.hh/.hxxare not..htm,.xhtml).--no-exitdoes not suppress the converter tier'sE100exit 2.Each tier is pinned by its discriminating property, because on ordinary prose all three readable tiers look identical: markup by a construct only a parser skips, comment-tier by the negative (a bare non-comment line must yield zero), converter-tier by non-zero exit plus
E100plus the converter's name. A test also asserts the fixture map covers the markup constant exactly, so an unprobed entry cannot be added to a constant.create-vale-rule.txtwas teaching the crashIts worked example globbed
[*.{md,mdx}]..mdxneedsmdx2vast, which this CLI does not ship, and one such file fails the entire Vale pass — so any rule following that example silently disabled every other Vale rule. Now[*.{md,markdown}], with the point stated plainly: a matcher like that is not a wider[*.md], it is a broken one. (The guard that makes this non-fatal is in the PR above this one.)A correction, included deliberately
The last commit walks back a claim this branch originally shipped. Both the recipe and the constants said a plausible
C++oryaml"passesverifyand fails inside the binary." Measured at 0.41.0,C++andcppare accepted aliases that resolve toCpp, so an off-list spelling is not reliably an error.The two failures that are real, neither caught locally: a name ast-grep does not recognize (
C#forCSharp) aborts config parsing, taking every other rule's report down with it; and a valid name for the wrong parser (TypeScriptover a.tsxtree) reports nothing and reads as a clean codebase. The advice to copy from the list is unchanged; the reason for it is now the accurate one. The vendor-contract assertions were already right — they pin the canonical list, not what the binary rejects — but their comment said otherwise.Also
route.txtnow states that a language on neither list does not route toruntimeby default:create-legacy-rulecomes first, since the repo's own linter may already speak it. And a new note separates reach (a property of the pinned version) from availability (a property of the host), which the existing "availability you can assert" bullet was conflating while giving the agent no way to assert it.OpenSpec delta on
cli-rule-routing— engine reach becomes a fourth thing the recipe is specified to state, with the normative core being that it is derived from the pinned versions rather than transcribed. Not archived; this is not the tip of the stack.