Skip to content

fix(devx): check-declaration-mirrors declares the .d.mts/.mjs population it discovers, so a mirror edit derives it - #15601

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-15553-dmts-derives-declaration-mirrors
Sep 5, 2026
Merged

fix(devx): check-declaration-mirrors declares the .d.mts/.mjs population it discovers, so a mirror edit derives it#15601
baozhoutao merged 3 commits into
mainfrom
claude/issue-15553-dmts-derives-declaration-mirrors

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15553

check-declaration-mirrors discovers its corpus by EXTENSION — "every scripts/**/*.d.mts … Discovered, never listed" — and spells no path anywhere. dispatch-gates derives families from declared path literals, so the one class of change this gate exists for, a hand-written .d.mts moving out of step with the module it mirrors, was the class the derivation never routed to it. The specimen is PR #15532: 32 derived commands, all green, and then CI red here on an arity mismatch.

This declares the population the gate really reads, both sides of it, and pins the declaration in the gate's own self-test battery. dispatch-gates.mjs gets a docblock correction only — no logic, no self-test case edited.

The change

scripts/check-declaration-mirrors.mjs, beside SCRIPTS_DIR:

const ROOT_DIR_WATCH_HINTS = ['scripts/**/*.d.mts', 'scripts/**/*.mjs'];

Both sides, because either side moving breaks the mirror: the .d.mts files are what mirrorFiles() walks, and the .mjs modules are what checkPair() imports to read Function.length off. A change set naming only js-comment-mask.mjs can turn this gate red with no declaration edited at all.

Not a hand list of the four pairs — the corpus stays discovered, so a fifth pair added tomorrow is walked and covered by the same hint.

Six cases were added to the existing check-declaration-mirrors self-test battery (floor 23 → 29): the declaration is non-empty, every hint is multi-segment, none of them is the bare subtree or the repo root, every declaration the live walk discovers is admitted by a declared hint, so is the module each one mirrors, and the whole thing is ONE literal array of quoted strings read back from this file's own comment-masked source, statement-scoped.

Before / after derivation

Both readings taken on one tree (ab5c01379), the second with the declaration emptied by ablation, so the difference is attributable to the declaration and not to the tree moving under the measurement:

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- \
  scripts/js-comment-mask.d.mts scripts/js-comment-mask.mjs
declaration commands derived lines naming this gate
emptied (ablation) 31 0
as landed here 33 2node scripts/check-declaration-mirrors.mjs and … --self-test

The same zero was measured on the branch point ca46f8f12 before any edit: 31 commands, none of them this gate.

The cost of the scripts/**/*.mjs half, measured

hint tracked files it reaches files the gate reads precision
scripts/**/*.d.mts 4 4 100%
scripts/**/*.mjs 214 4 1.9%

The second hint is the price of keeping the module side declared without a hand list. What it buys is charged at:

Against a defect class whose alternative is a CI red a cycle late on every card that touches a mirror. Under hintCovers' recorded ruling — over-naming is loud and self-limiting, under-naming is silent — the trade runs the right way.

⚠️ scripts/pm/bare-root-worklist.mjs still records scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts as REFUSE-UNSPELLABLE on the stated ground that "what cannot be spelled here is the EXTENSION filter". That ground no longer holds — the vocabulary spells extensions (hintCovers judges scripts/**/*.d.mts as a pattern, and check-self-test-wired.mjs has declared scripts/**/*.mjs for some time). The row does not red: contradicted fires only on a row the sweep finds REACHABLE, and neither hint covers an arbitrary file at the top of the root, so the sweep stays green (verified, output quoted below). Re-deciding a verdict on that shrink-only map is a separate, ceremonied edit in a file this card does not own, so it is filed rather than ridden along.

The docblock correction (scripts/pm/dispatch-gates.mjs)

Comment-only, one paragraph, no logic and no self-test case touched — check:pm-dispatch-gates still passes with no case edited.

The paragraph said the floor probe "cannot read this file … the injection lands inside a template literal and only ever produces a SyntaxError". Since #14963 (PR #15580, landed as e39619d6f, which is in this branch's merge base) the probe anchors over a comment-and-literal mask and at a line start, so it lands on the real definition. Re-measured on the merge base:

injected at line 11855:  function selfTest() {
                           return; /*OS_SELF_TEST_FLOOR_PROBE*/
node --check COPY   → exit 0     (raw anchor on the pre-merge tree: exit 1, SyntaxError)
node COPY --self-test → exit 1, "selfTest() returned without reaching its verdict"

The entry stays hand-read in ENTRY_BY_HAND (four self-test-shaped names in raw source) and the row's remaining NOT MEASURED is the probe's own artefact (#15515). The rewritten paragraph says exactly that.

Verification

Exit codes captured before any pipe; verdict lines quoted as the gates print them. Everything below ran on ab5c01379.

command exit verdict
node scripts/check-declaration-mirrors.mjs --self-test 0 All 29 self-test cases passed.
node scripts/check-declaration-mirrors.mjs 0 OK: 4 hand-written declaration(s) agree … — byte-identical to main's output
pnpm check:watch-hint-literal 0 57 declaration(s) across 4 rostered name(s) … every one an array of quoted literals inside its own statement
pnpm check:declared-population-live 0 213 of 258 famil(ies) declare a path population, and every one of them reaches this tree's 7565 tracked file(s)
pnpm check:pm-dispatch-gates 0 dispatch-gates self-test: 1445 cases pass. (no case edited; the count is main's — the dispatch order predicted 1415 from an older tree)
node scripts/pm/bare-root-worklist.mjs --self-test 0 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:nul-bytes 0 scanned 7558 text file(s) … no raw ASCII control bytes

The whole derived family for this change set, from node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack (27 commands, run under scripts/pm/os-verify-lock.sh, slot issue-15553): all 27 exit 0, wrapper VERDICT command-exit 0 · held the lock 578s. Three families are ⊘ NOT MEASURED by the tool's own account — they carry a $RUNNER_TEMP / matrix value that exists only in a CI run: check-cross-package-test-inputs.mjs --union-into …, check-shard-attestation.mjs --emit …, check-test-completeness.mjs …. pnpm lint is not in the family.

Ablations, each trap-guarded with an absolute path and restored by blob hash (git diff HEAD empty and the on-disk hash equal to the HEAD blob a017fc90cf851e4b76f661503457708414f244eb after every leg; each mutation confirmed on disk by anchor grep counts and a changed blob hash before anything was read):

  1. declaration emptied4/29 self-test case(s) failed, exit 1, naming the four cases that depend on it.
  2. declaration computed (HINT_EXTS.map(…), runtime value unchanged) → exactly the literal-array case reds, and extractWatchHints on the mutated source returns ["scripts"] — the two real hints gone, leaving only a bare word hintCovers refuses. That is the silent-drop mechanism this case exists to make loud.
  3. declaration emptied, derivation re-run → the before/after table above.

Lint, narrowed and declared: npx eslint --no-inline-config --format json over the two changed files, exit 0, 0 errors / 0 warnings. The narrowing is a measurement rather than a skip — the population comes from eslint's own config resolution, the file count (2) is read from the JSON output, and eslint.config.mjs:328 records that no config here enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules), so this diff cannot move the verdict on any file it does not touch. The whole-repo pnpm lint is CI's run.

No changeset: scripts/** only, nothing published — skip-changeset applied.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…ion it discovers, so a mirror edit derives it (#15553)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…he pre-#14963 one (#15553)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff origin/main...claude/issue-15553-dmts-derives-declaration-mirrors (head ab5c01379; scripts/check-declaration-mirrors.mjs +121/−1, scripts/pm/dispatch-gates.mjs docblock-only +11/−6), not the self-report.

Dev's verification quoted (on ab5c01379): --self-test All 29 self-test cases passed.; gate OK: 4 hand-written declaration(s) agree … byte-identical to main; before/after on ONE tree for scripts/js-comment-mask.d.mts scripts/js-comment-mask.mjs: 31 commands / 0 naming this gate with the declaration emptied → 33 / 2 as landed; three trap-guarded ablations (emptied → 4/29 … failed; computed → the literal-array case reds and extractWatchHints returns ["scripts"], the silent-drop mechanism shown; emptied + re-derived → 31/0), restores by blob a017fc90 == HEAD; derived family 27 commands all exit 0 (1445 cases pass, no case edited; 213 of 258 famil(ies)), three CI-only families named NOT MEASURED. Cost: ~0.18 s; 10 of 36 open PRs newly derive it (1 via .d.mts, 9 via .mjs); precision 4/4 on .d.mts, 4/214 on .mjs — stated, accepted (a 0.1 s gate).

Out-of-scope finding filed by the dev: #15602 (bare-root-worklist's REFUSE-UNSPELLABLE row for this gate is stale on a withdrawn ground).

Flipping ready + enabling auto-merge. Fixes #15553. Closes the derivation-gap class this seat filed from PR #15532's rework.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants