Skip to content

Commit 6f75c2f

Browse files
claude[bot]os-steveclaude
authored
ci(gates): refuse a bare -- between pnpm and vitest in committed commands (#11667)
* ci(gates): refuse a bare `--` between pnpm and vitest in committed commands `pnpm --filter <pkg> test` with its arguments after a bare separator runs the WHOLE package suite. pnpm forwards the separator verbatim into the child argv and vitest's cac parser then discards everything after it, options and positionals alike, in silence — the file pattern and `--maxWorkers` die together, the suite passes, and it reads exactly like the narrow run that was asked for. #11425 landed the corrected spelling in the instruction files. That is discipline; this is the mechanical half. Both mechanism layers were re-measured on this tree rather than recalled, each with a control: pnpm and npx forward the separator, npm and turbo strip it, and vitest 4.1.10 turns `CACError: Unknown option` (exit 1) into no output at all (exit 0) when the same flag moves behind the separator. The violating population is EMPTY today, so the gate is built to say that out loud rather than print a bare "0 violations": it reports how many separators it JUDGED (6 on this tree), refuses a tree whose roots, corpus or workspace derivation went blind, and carries its non-vacuity in `--self-test`, which drives this same sweep RED over a temp tree on disk. Five of those six judged separators are forms a naive bare-`--` selector would have RED and every one is correct as written — this workflow's own turbo dogfood shard and the three documented `pnpm dev -- --fresh` spellings — so the rule is vitest-BOUND, with the vitest-backed script names derived from the workspace instead of typed in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx * ci(gates): declare this gate's bare roots so the dispatch derivation can see it `INSTRUCTION_ROOTS` and `EXECUTED_ROOTS` spelled two of their four roots as bare single-segment words. `extractWatchHints` builds no hint from a literal without a separator, so a card touching `scripts/**` or `skills/**` derived this gate NOWHERE — including the cards most likely to add the spelling it exists to judge. `scripts/pm/bare-root-worklist.mjs --self-test` caught it on the commit that introduced it. Declared rather than refused, and the choice is measured from this gate's own walker rather than taken from the shape: numerator is what `scannedFiles()` admits, denominator the tracked files under the subtree the declaration names — `skills/**` 50 of 50 (100%), `scripts/**` 239 of 242 (98.8%, the 3 misses are `.txt`). This is the `subtree` case, not the `filtered` one: the walk descends the whole of each root and the only losses are non-code files the extension filter drops. Both precedents are already in this tree at the same or lower coverage — `check-pnpm-filter-targets.mjs` declares `scripts/**` at 97.0%, and `check-role-word.mjs` declares `skills/**`. `AGENTS.md/**` and `CLAUDE.md/**` take the escapable repo-root-FILE case the derivation's own residue names: a bare filename is refused as too generic and reaches its file through the subtree spelling. Measured — `AGENTS.md/**` covers `AGENTS.md` and does NOT cover `examples/AGENTS.md`, so it claims the one file this gate reads at that root and does not overclaim the nested ones. The workspace-manifest read stays UNDECLARED: the only spellable claim is `packages/**` + `apps/**` + `examples/**`, which would name this gate for ~5200 files to reach ~80 manifests. Refused, and pinned in `--self-test`. ⚠️ The declaration is pinned STRUCTURALLY, derived from the scan roots, not by importing `hintCovers`. An earlier draft did import it, and `discoverFamilies` then treated dispatch-gates.mjs as this gate's own source and hoisted ITS path literals — `packages/spec/src/**`, `packages/plugins`, `packages/drivers`, `packages/services` — into this gate's hints. That is the fabricated lead the declaration exists to avoid, arriving through the assertion meant to prevent it. Measured before and after: 19 hints with the import, 11 without, 0 fabricated. `scripts/pm/bare-root-worklist.mjs` itself is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx --------- Co-authored-by: Steve Jobs <steve@objectstack.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent c6a62d0 commit 6f75c2f

3 files changed

Lines changed: 1068 additions & 0 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,48 @@ jobs:
24112411
- name: Every committed pnpm --filter names a real package
24122412
run: pnpm check:pnpm-filter-targets
24132413

2414+
# The sibling of the gate above, and the OTHER half of the family its
2415+
# comment names (#10166). There the `--filter` selector matched nothing;
2416+
# here the filter matched, the script ran, and the ARGUMENTS were lost.
2417+
#
2418+
# ⛔ Hand a vitest-backed script its arguments after a bare separator and
2419+
# the WHOLE package suite runs. pnpm forwards the separator verbatim into
2420+
# the child argv (npm and turbo both strip it — pnpm is the odd one out),
2421+
# and vitest's cac parser then discards EVERYTHING after it, options and
2422+
# positionals alike, in silence. The file pattern and `--maxWorkers` die
2423+
# together, so the concurrency discipline is inert in the same breath.
2424+
# Measured on the card: 415 files / 11045 tests / 358s under the shared
2425+
# verify lock, where the intended run was one file in 655ms — green, exit
2426+
# 0, reading exactly like the narrow run that was asked for.
2427+
#
2428+
# ⚠️ The broken spelling is written out in the GATE'S header, not here.
2429+
# The first draft of this comment spelled it, and the gate red on this
2430+
# file at its own wiring commit — which is the objection a corpus gate was
2431+
# rejected on once before ("it would fire on the counter-example the
2432+
# corrected instruction MUST contain"). That objection is real; the answer
2433+
# is `COUNTER_EXAMPLE_FILES` in the gate, and the reason this file is not
2434+
# in it is that a whole workflow is too coarse a thing to exempt — a
2435+
# broken spelling here would EXECUTE.
2436+
#
2437+
# ⚠️ The violating population is EMPTY today (#11425 landed the corrected
2438+
# spelling), so this step's job is to keep it empty — and the gate says so
2439+
# in its own verdict line rather than printing a bare "0 violations". It
2440+
# reports how many separators it JUDGED, not just how many it failed: 5 on
2441+
# the tree it landed against, four of which a naive bare-`--` selector
2442+
# would have RED (this workflow's own turbo dogfood shard, and the three
2443+
# documented `pnpm dev -- --fresh` spellings in AGENTS.md). Its rule is
2444+
# vitest-BOUND, and the vitest-backed script names are derived from the
2445+
# workspace rather than typed here.
2446+
#
2447+
# Reads text under `.claude/**`, `skills/**`, `scripts/**`,
2448+
# `.github/workflows/**` and every tracked AGENTS.md / CLAUDE.md, plus the
2449+
# workspace manifests for the derivation. No build, no network,
2450+
# sub-second. The `check:` key runs `--self-test` first: it drives this
2451+
# same sweep RED over a temp tree on disk, which is what carries
2452+
# non-vacuity while the real population is zero.
2453+
- name: No committed command forwards args to vitest through a bare --
2454+
run: pnpm check:agent-test-spelling
2455+
24142456
# Lane 1 of 4 behind the required `TypeScript Type Check` context. The
24152457
# aggregator job at the bottom of this file explains the split, holds the
24162458
# contract, and is the thing the merge queue actually requires — read it

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs",
5353
"check:skill-compatibility": "node scripts/check-skill-compatibility-version.mjs --self-test && node scripts/check-skill-compatibility-version.mjs",
5454
"check:agent-model-declared": "node scripts/check-agent-model-declared.mjs --self-test && node scripts/check-agent-model-declared.mjs",
55+
"check:agent-test-spelling": "node scripts/check-agent-test-spelling.mjs --self-test && node scripts/check-agent-test-spelling.mjs",
5556
"check:ratchet-remedy-authority": "node scripts/check-ratchet-remedy-authority.mjs --self-test && node scripts/check-ratchet-remedy-authority.mjs",
5657
"check:pm-skill-ratchet": "node scripts/pm/check-skill-line-ratchet.mjs --self-test && node scripts/pm/check-skill-line-ratchet.mjs",
5758
"check:pm-skill-id-lint": "node scripts/pm/check-skill-id-lint.mjs --self-test && node scripts/pm/check-skill-id-lint.mjs",

0 commit comments

Comments
 (0)