Uh oh!
There was an error while loading. Please reload this page.
fix(pm): the line ratchet declares its repo-root population, so an AGENTS.md card derives it - #9978
Merged
Merged
Conversation
…ENTS.md card derives it `extractWatchHints` requires a path separator, so the ratchet's eighteen CEILINGS keys yielded seventeen hints: the repo-root `AGENTS.md` has none. `node scripts/pm/dispatch-gates.mjs AGENTS.md` therefore derived ZERO gates, and a dev dispatched on the largest ceiling in that map (958 lines, headroom 0) first met `check:pm-skill-ratchet` as red CI. lint.yml has no path filter, so CI always enforced — what was missing was discoverability. The gate now declares its root-file population as `AGENTS.md/**`, the one form the extractor already accepts for a repo-root file: `collapseHint` reduces it back to `AGENTS.md` and it matches that path alone. Provenance only — it stays out of CEILINGS, which is the map `run` opens files through. Measured over 114 families x 6326 tracked files, reusing the methodology of the original genericity refusal (the control reproduces it: 26060 -> 175192 pairs, one packages/spec card 8 -> 37 families). This declaration: 26060 -> 26061 pairs, one family gaining coverage, one file. The extractor-widening alternative was measured and REFUSED: 26060 -> 26077 is cheap by volume but 8 of its 17 new pairs are fabricated, because gates spell README.md and CHANGELOG.md as basenames they join with a package directory — a README.md card would gain six leads of which five name a gate that never reads it. Self-tests: dispatch-gates 310 -> 314, line ratchet 15 -> 19. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
os-warren
marked this pull request as ready for review
August 19, 2026 11:48
os-warren
enabled auto-merge
August 19, 2026 11:49
This was referenced Aug 19, 2026
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9964
node scripts/pm/dispatch-gates.mjs AGENTS.mdderived zero gates.extractWatchHintstreats "looks like a path" as "carries a separator" (plus a short allowlist of dotted top-level dirs), so the line ratchet's eighteenCEILINGSkeys yielded seventeen watch hints — the repo-rootAGENTS.mdhas no separator to be found by. A dev dispatched on an AGENTS.md card got an empty local gate brief and first metcheck:pm-skill-ratchetas red CI, on the largest ceiling in that map (958 lines, headroom 0), where one added paragraph crosses it.lint.ymlcarries no path filter, so CI always enforced: this was discoverability, never enforcement.The measurement gate — both options priced on the original refusal's corpus
The card required option 2 and option 3 to be measured against the same corpus the original genericity refusal used, before either could be implemented. The methodology is stated in
hintCovers' docblock ("Measured over 107 discovered families against all 6181 tracked files: watch-hint (gate, file) pairs"); no measurement script was ever committed, so it was reconstructed offline: discover the check families exactly asderive()does, extract each family's hints, count the (family, tracked file) pairshintCoversaccepts. Corpus today: 114 families x 6326 tracked files.The control validates the harness. Re-running the generalisation the original refusal rejected (accept any bare literal naming a real top-level directory) reproduces its published shape on today's larger tree:
packages/spec/src/index.tscardOption 2 — extractor allowance for bare top-level literals ending in an instruction-file extension and naming a real tracked file:
README.mdandCHANGELOG.mdas basenames they join with a directory, not as files they read — a manifestfilesentry (CANONICALin check-published-files), a per-package markdown exclusion (MARKDOWN_EXCLUDEDin check-published-readme-exports), a.changeset/basename filter (check-changeset-no-major), a remote directory listing filter (check-objectui-pin-fresh), andjoin(target.dir, 'CHANGELOG.md')in check-release-body.README.mdcard would come back with six leads of which five name a gate that never reads that file. That is the exact mechanism the original refusal named ("everypackage.json/turbo.json/tsconfig.jsonbasename a gate joins with a package directory"), one extension over.extractWatchHintsandhintCovers' genericity refusal — since the latter refuses separator-less, non-dotted hints.Option 3 — the ratchet declares its root-file population in a form the extractor already accepts:
Criterion used, and it is the refusal's own, in two parts. (i) Volume — no order-of-magnitude blowup (the refused generalisation was x6.7; the two accepted changes were +4.3% and +22.6%). (ii) Provenance — "ZERO lost" and every new pair a genuine population declaration, because this file's stated error direction prices a fabricated lead far above a missing one ("pasted into EVERY dispatch prompt whose surface brushes it"). Option 2 passes (i) with room to spare and fails (ii). Option 3 passes both. Volume alone was never the whole threshold, and judging option 2 on its +17 would have inverted the refusal's own reasoning.
Chosen: option 3. Option 1's documentation half was therefore not implemented (the card scopes it to "both fail"); the existing residue sentence naming the top-level-FILE class with
README.mdstays accurate, since the class is still refused.What landed
scripts/pm/check-skill-line-ratchet.mjsgainsROOT_FILE_WATCH_HINTS = ['AGENTS.md/**']. The subtree spelling is the only form that reaches a repo-root file: the extractor requires the separator, andcollapseHintreduces it back toAGENTS.md, matching that path alone (nothing in the tree lives under it). It is provenance, not a lookup key — it stays out ofCEILINGS, which is the maprunopens files through; a key rewritten into the glob form would send the ratchet looking for a file that does not exist.scripts/pm/dispatch-gates.mjsgains the four self-test cases and a docblock paragraph recording the re-measurement, so the next reader finds theAGENTS.md/**hint as a decision rather than deleting it as a typo.Result:
Residue accounting for an AGENTS.md card moves by exactly one family:
0 matched · 35 undetermined · 79 silentbecomes1 matched · 35 undetermined · 78 silent.Reverse verification — direction predicted in writing first
Predicted: emptying
ROOT_FILE_WATCH_HINTSto[]while leaving the docblock's prose copy of the glob in place returns the derivation to zero (proving the hint comes from the module body, not from prose — the accident the #9639 narrowing exists to stop relying on); exactly 1 of 4 new dispatch-gates cases goes red, because the other three assert absence and cannot distinguish; exactly 2 of 4 new ratchet cases go red, the other two being vacuously true over an empty list.Observed, all three exactly as predicted:
dispatch-gates AGENTS.mdNo check family names the given paths...,0 matched · 35 undetermined · 79 silent✗ dispatch-gates self-test: 1 of 314 case(s) failed.— the positive case only✗ check-skill-line-ratchet self-test: 2 of 19 case(s) failed.git hash-objectequals theHEADblob)No rebuild applies to this ablation and none was needed: both files are plain
.mjsexecuted from source, and dispatch-gates reads the ratchet's source text off disk — nothing here resolves through a packageexportsfield or adist/, so there is no stale-artifact leg to defeat.Control paths gain no phantom hints (each still derives zero families, and residue totals are unchanged at
35 undetermined · 79 silent):README.md,CLAUDE.md, andexamples/AGENTS.md— the last being the segment-boundary control, a same-named file one directory down.Gates
Union derived from the real diff by
node scripts/pm/dispatch-gates.mjswith no path arguments (it reads its own change set from the merge base), run after the final commit, at63fce4a24. Exit codes captured before any pipe; each gate's own verdict line quoted:pnpm check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 314 cases pass.pnpm check:pm-skill-ratchet✓ check-skill-line-ratchet: AGENTS.md is 958 lines (ceiling 958; headroom 0).pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6321 text file(s) -- 6321 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).node scripts/check-cross-package-test-inputs.mjsalso appears in the derived union; it is the same script the first row runs.Self-test counts: dispatch-gates 310 to 314, line ratchet 15 to 19.
No changeset: internal PM tooling under
scripts/pm/, nothing published changes.skip-changesetapplied at PR creation.Not a governed path (
scripts/pm/), so this is a draft for the PM to flip ready.Generated by Claude Code