fix(devx): check-declaration-mirrors declares the .d.mts/.mjs population it discovers, so a mirror edit derives it - #15601
Conversation
…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
…ts-derives-declaration-mirrors
…he pre-#14963 one (#15553) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
|
ACCEPT — PM seat Reviewed against the three-dot diff
Dev's verification quoted (on 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 |
Fixes #15553
check-declaration-mirrorsdiscovers its corpus by EXTENSION — "everyscripts/**/*.d.mts… Discovered, never listed" — and spells no path anywhere.dispatch-gatesderives families from declared path literals, so the one class of change this gate exists for, a hand-written.d.mtsmoving 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.mjsgets a docblock correction only — no logic, no self-test case edited.The change
scripts/check-declaration-mirrors.mjs, besideSCRIPTS_DIR:Both sides, because either side moving breaks the mirror: the
.d.mtsfiles are whatmirrorFiles()walks, and the.mjsmodules are whatcheckPair()imports to readFunction.lengthoff. A change set naming onlyjs-comment-mask.mjscan 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-testbattery (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/check-declaration-mirrors.mjsand… --self-testThe same zero was measured on the branch point
ca46f8f12before any edit: 31 commands, none of them this gate.The cost of the
scripts/**/*.mjshalf, measuredscripts/**/*.d.mtsscripts/**/*.mjsThe second hint is the price of keeping the module side declared without a hand list. What it buys is charged at:
lint.ymlruns costs ~0.18 s wall clock (three runs: 0.194 s, 0.162 s, 0.178 s; shared box, so read the magnitude, not the digits). The plain invocation alone is ~0.09 s..d.mtshint (fix(devx): js-comment-mask.d.mts declares the interpolation flags and the self-test verdict the module really returns #15593, a real mirror edit) and 9 through the.mjsone (fix(devx): check-self-test-workflow-commands declares the scripts/** population it scans #15590, fix(devx): anchor the self-test floor probe on the real definition, not the first text that reads like one #15580, fix(scripts): the swallow census reads its own empty worklist as a landed handover #15579, docs(pm-skill): rules-only rewrite of the seven core pm-dispatch references #15485, docs(pm): rules-only rewrite of the pm-dispatch SKILL.md — 红线 first, one rule per line, templates last #15460, fix(service-settings): re-point value-domains.ts onto @objectstack/spec/shared and refuse withvalue_domain#15434, docs(agents): state the app-vs-platform boundary once, so it stops being re-derived #15427, fix(docs): stamp the release index's "current series" at version time (#15332) #15392, fix(release): retry the post-publish npm check, and verify all 69 packages instead of 1 #15331). ~1.6 s of extra fleet compute per 36 cards.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.mjsstill recordsscripts/check-declaration-mirrors.mjs SCRIPTS_DIR scriptsasREFUSE-UNSPELLABLEon the stated ground that "what cannot be spelled here is the EXTENSION filter". That ground no longer holds — the vocabulary spells extensions (hintCoversjudgesscripts/**/*.d.mtsas a pattern, andcheck-self-test-wired.mjshas declaredscripts/**/*.mjsfor some time). The row does not red:contradictedfires 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-gatesstill 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: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.node scripts/check-declaration-mirrors.mjs --self-testAll 29 self-test cases passed.node scripts/check-declaration-mirrors.mjsOK: 4 hand-written declaration(s) agree …— byte-identical tomain's outputpnpm check:watch-hint-literal57 declaration(s) across 4 rostered name(s) … every one an array of quoted literals inside its own statementpnpm check:declared-population-live213 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-gatesdispatch-gates self-test: 1445 cases pass.(no case edited; the count ismain's — the dispatch order predicted 1415 from an older tree)node scripts/pm/bare-root-worklist.mjs --self-test54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradictedpnpm check:nul-bytesscanned 7558 text file(s) … no raw ASCII control bytesThe whole derived family for this change set, from
node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(27 commands, run underscripts/pm/os-verify-lock.sh, slotissue-15553): all 27 exit 0, wrapperVERDICT 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 lintis not in the family.Ablations, each trap-guarded with an absolute path and restored by blob hash (
git diff HEADempty and the on-disk hash equal to theHEADbloba017fc90cf851e4b76f661503457708414f244ebafter every leg; each mutation confirmed on disk by anchor grep counts and a changed blob hash before anything was read):4/29 self-test case(s) failed, exit 1, naming the four cases that depend on it.HINT_EXTS.map(…), runtime value unchanged) → exactly the literal-array case reds, andextractWatchHintson the mutated source returns["scripts"]— the two real hints gone, leaving only a bare wordhintCoversrefuses. That is the silent-drop mechanism this case exists to make loud.Lint, narrowed and declared:
npx eslint --no-inline-config --format jsonover 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, andeslint.config.mjs:328records 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-repopnpm lintis CI's run.No changeset:
scripts/**only, nothing published —skip-changesetapplied.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code