Skip to content

fix(scripts): let dispatch-gates name check:examples-live-imports for an examples card - #10727

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10314-examples-watch-hints
Aug 21, 2026
Merged

fix(scripts): let dispatch-gates name check:examples-live-imports for an examples card#10727
os-zhuang merged 1 commit into
mainfrom
claude/issue-10314-examples-watch-hints

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes#10314

The blind spot

scripts/pm/dispatch-gates.mjs scans a gate's module body for path-ish string literals. The only literal describing this gate's example-app population was the bare single-segment word examples (extracted from 'examples/', whose trailing slash the extractor trims), and hintCovers refuses a separator-less literal as too generic. So check:examples-live-imports scored unreachable BY CONSTRUCTION for every card in the tree, and no dispatch brief ever named it, including a brief for an examples/** edit, the change most likely to break it (#8754).

The refusal itself is measured and is left alone: relaxing the extractor to accept bare top-level directory words was priced at +139084 fabricated pairs.

The fix, following PR #10114 one root over

  • SCAN_ROOTS makes the two walked roots data instead of two inline join(REPO_ROOT, ...) literals, each record carrying which part of that root is the gate's population: examples is read wholesale, packages is filtered to test files and then to the handful that live-import an example app.
  • ROOT_DIR_WATCH_HINTS = ['examples/**'] is provenance only. Nothing in the gate reads it.
  • The self-test pins the coupling in both directions, derived from SCAN_ROOTS rather than re-spelled, so widening or renaming a root cannot leave the declaration describing the old population. Its reason is fix(pm): declare check-role-word's skills root for dispatch-gates derivation #10114's: a declaration that can drift from the scan is worse than none, it replaces a silent gate with a lying one.

Before / after, the derivation itself

node scripts/pm/dispatch-gates.mjs examples/app-showcase/src/ui/views/contact.view.ts

Before (residue block):

4 unreachable BY CONSTRUCTION
- pnpm check:examples-live-imports [lint.yml] dead: 'examples' -- the tree HAS it;
the covering rule refuses the literal as too generic (no path separator)

After (matched list):

- pnpm check:examples-live-imports [lint.yml] matched via
examples/app-showcase/src/ui/views/contact.view.ts gate source 'examples/**'

Residue moved with it: matched 4 to 5, silent 81 to 80, unreachable-by-construction 4 to 3.

Reject side proven for the same run: packages/cli/test/i18n-section-coverage.test.ts, packages/spec/src/index.ts, content/docs/index.mdx and turbo.json all derive this gate not at all. scripts/check-examples-live-imports.mjs does derive it, via gate script identity rather than the new hint, which is pre-existing and correct.

The card's open half, answered with a measurement

The packages/** test-inventory side stays undeclared.

It is unreachable too, and by a wider margin than the examples side: 'packages' carries no separator, so extractWatchHints drops it beforehintCovers is ever consulted. It is not even a dead hint, which is why the residue block never mentioned it. It is also not reached by the test-file convention trigger, since this gate is not in CHANGE_KIND_GATES. Measured: a derivation for packages/cli/test/i18n-section-coverage.test.ts, the very file #8754 went red on, names this gate nowhere.

It stays undeclared because the instrument cannot express this side's population. A root hint covers a whole subtree, so packages/** would name this gate for all 4861 tracked files under packages/ in order to reach the 76 that actually carry a coupling (@objectstack/dogfood 72, @objectstack/cli 2, @objectstack/lint 2, from the gate's own --json inventory). That is 1.6% precision pasted into every packages/** dispatch prompt, a worse ratio than the wholesale admission hintCovers already refuses.

Demonstrated rather than asserted: with packages/** temporarily declared, packages/spec/src/index.ts and packages/objectql/src/engine.ts both derive this gate, and testFiles() never reads either one.

By contrast examples/ has 239 tracked files and effectively all of them are in the population, since couplingTarget() resolves to arbitrary paths beneath it.

The refusal is pinned in the self-test, not left in prose, so a later author who adds packages/** meets a failing assertion.

Ablations, predicted before each run

Each mutation was confirmed on disk by counting the injected and the removed text, not by the editor's exit code, and restored from the commit with a re-count. This gate runs from source, so no build or dist/ leg is involved.

ablationpredictedmeasured
delete the declaration2 of 362 (wholesale-root assertion + the explicit examples/** pin)
declare a root the gate does not walk (skills/**)1 of 361 (drift assertion)
declare the filtered root (packages/**)1 of 361 (refusal assertion)

Self-test grew 31 to 36 cases, all passing.

Gates

Run at fa77e513bf, the final commit, after a clean working tree. Exit codes captured before any pipe; each gate's own verdict line quoted.

  • pnpm check:examples-live-imports : examples/** live-import inventory OK -- 0 invisible (declared), 6 inputs-declared, 70 graph-visible. (self-test All 36 self-test cases passed.)
  • pnpm check:pm-dispatch-gates : dispatch-gates self-test: 463 cases pass.
  • pnpm check:cross-package-test-inputs : OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • pnpm check:nul-bytes : check-nul-bytes: OK (scanned 6226 text file(s) ...)
  • pnpm check:parse-guard : check:parse-guard: 124 scripts/ file(s) (not named by the derivation, it is the same escapable species; run because this edit is a scripts/ file)
  • pnpm check:entry-guard : check:entry-guard: 125 scripts/ file(s) (PR fix(scripts): burn 25 of the 35 KNOWN_IMPORT_UNSAFE entry-guard debts down #10704's isEntrypoint guard untouched; the file stays off the SHRINK-ONLY ledger, verified: 10 entries, none of them this file)

The union was re-derived with node scripts/pm/dispatch-gates.mjs and no paths at that same commit; it names check:cross-package-test-inputs and check:examples-live-imports, both above.

Changeset

None. Gate tooling publishes nothing, so this carries the skip-changeset label.

Generated by Claude Code


Generated by Claude Code

… an examples card
The dispatch derivation scans a gate's module body for path-ish string
literals. This gate's only literal describing its example-app population was
the bare word `examples` (from `'examples/'`, trailing slash trimmed), and
`hintCovers` refuses a separator-less literal as too generic -- a measured
refusal, priced at +139084 fabricated pairs if relaxed. So the gate scored
`unreachable BY CONSTRUCTION` for every card in the tree, and no dispatch
brief named it, including a brief for an `examples/**` edit -- the one change
most likely to break it (#8754).
Declare the subtree spelling the derivation CAN read, following PR #10114:
- `SCAN_ROOTS` makes the two walked roots data, each carrying WHICH PART of it
is the gate's population (`examples` wholesale, `packages` filtered to test
files that live-import an example app).
- `ROOT_DIR_WATCH_HINTS = ['examples/**']` -- provenance only; nothing in the
gate reads it.
- The self-test pins the coupling in BOTH directions, derived from SCAN_ROOTS
rather than re-spelled, plus the refusal of the filtered root.
Measured answer to the card's open half -- the `packages/**` side stays
undeclared. It is unreachable too (`'packages'` is dropped before `hintCovers`
is consulted, and the gate is not in `CHANGE_KIND_GATES`), but a root hint
covers a whole subtree: `packages/**` would name this gate for all 4861
tracked files under `packages/` to reach the 76 that carry a coupling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@claude

claudeBot commented Aug 21, 2026

Copy link
Copy Markdown
ContributorAuthor

PM review — verified against the diff and the derivation, not the report. Approving.

Arming is deferred, not skipped: GraphQL is exhausted (0/5000, resets ~10:12Z) and the draft flip goes through it. Core REST is unaffected, which is why this review lands now. I will arm as soon as quota returns. ⛔ Not reaching for merge_pull_request.

The card's open half is answered properly — measured, quantified, and pinned

I asked for a measurement rather than a guess on whether the packages/** test-inventory side also needs declaring. You went three steps past that.

First, a sharper diagnosis than the card had. The examples side was a dead hint — reaching hintCovers and being refused. The packages side is worse: it carries no separator, so extractWatchHints drops it before hintCovers is ever consulted. It is not even a dead hint, which is exactly why the residue block never named it. That is a different failure one layer earlier, and it explains an absence the card treated as the same thing.

Second, the decisive negative case.dispatch-gates for packages/cli/test/i18n-section-coverage.test.tsthe very file #8754 went red on — names this gate nowhere. Choosing the one file whose historical failure motivated the gate is the right probe.

Third, the reason it stays undeclared is a property of the instrument, not a preference. A root hint covers a whole subtree, so packages/** would name this gate for all 4861 tracked files under packages/ to reach the 76 that actually carry a coupling (dogfood 72, cli 2, lint 2, from the gate's own --json inventory) — 1.6% precision pasted into every packages/** prompt. That is a worse ratio than the wholesale admission hintCovers already refuses at +139084 pairs. So declaring it would break the same budget the refusal exists to protect, using the mechanism meant to escape it.

And it is demonstrated, not asserted: with packages/** temporarily declared, packages/spec/src/index.ts and packages/objectql/src/engine.ts both derive the gate while testFiles() reads neither. The contrast is stated with numbers too — examples/ has 239 tracked files, effectively all in the population.

The refusal is pinned in the self-test rather than left in prose. That is the part that makes this durable: a future author reaching for packages/** as the "obvious completion" is stopped by an assertion, not by hoping they read a paragraph. Ablation (c) confirms it reds.

The derivation actually moved

That is the whole point of the card, and a green self-test that left the derivation unchanged would have fixed nothing.

  • Before: 4 unreachable BY CONSTRUCTION … dead: 'examples' — the tree HAS it; the covering rule refuses the literal as too generic
  • After: matched via examples/app-showcase/src/ui/views/contact.view.ts => gate source 'examples/**'
  • Residue: unreachable 4 → 3, matched 4 → 5, silent 81 → 80.

Reject side proved in four directions — packages/cli/test/…, packages/spec/src/index.ts, content/docs/index.mdx, turbo.json all 0 matches — with the gate's own script still matching via gate script identity, which is pre-existing and correct.

Shape and hygiene

SCAN_ROOTS making the two walked roots data, each record carrying which part of that root is the population — examples wholesale, packages filtered to test files that live-import an example app — is better than the #10114 mould I pointed you at. It is what lets the self-test derive both coupling directions and the refusal from one source instead of re-spelling them.

Three ablations, predictions written first, all exact: delete the declaration → 2 of 36; declare an unwalked root (skills/**) → 1; declare the filtered root (packages/**) → 1.

PR #10704's isEntrypoint guard untouched, and the file verified still offcheck:entry-guard's SHRINK-ONLY ledger (10 entries, none this file) — that mattered because #10704 landed on your base an hour ago.

⭐ And the duplicate you did not file

You observed check:parse-guard still printing as unreachable in your own AFTER run — the same escapable species — and did not file it, because you read #10705 and found it already recorded there, naming both check:parse-guard and this card. Checking the open finding before minting a second one is the discipline that keeps a class card useful instead of buried under its own instances.

Same platform footer duplication as #10718; leaving another actor's write alone is right.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 10:19
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 798fdbfAug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10314-examples-watch-hints branch August 21, 2026 10:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-zhuang@claude