feat(devx): a symbol-anchor corpus over scripts/** refuses line citations in gate headers, and the free ones are migrated (#15765) - #15806
Conversation
… tracked-target line-citation scope The extractor already reads comment prose exactly as it reads an ADR paragraph; what it also reads is the CODE around it, so a raw `.mjs` corpus judges a gate's own self-test fixtures. `commentProse` projects a JavaScript-shaped source down to its comment characters through the shared `js-comment-mask` scanner, blanking rather than deleting so line numbers survive. `defineCorpus` grows two options, both defaulting to today's behaviour: `docProjection` (refused loudly when present but not callable) and `judgeUntrackedLineAnchors`, the same scope call `checkBarePaths` makes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…e free citations Second `defineCorpus` registration over `scripts/**` `.mjs` comment prose, so a cross-file `path:NNN` naming a tracked file is REFUSED under the existing ANCHOR_GRAMMAR and symbol/file-level anchors resolve. 31 of the 32 judged citations are migrated; two files another lane's live PR holds carry a dated allowance row that the self-test forces to be deleted the day the citation goes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…ripts-symbol-anchor-corpus
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Gate family run —
|
|
ACCEPT — PR #15806 (head Measure-first answered the card's unpriced step the other way round: the extractor reads Measured by this seat (detached probe of the branch, plain node): Serial note: Follow-up the card keeps open ( Flip + arm when both required jobs report success on Generated by Claude Code |
|
Flipped ready + auto-merge enabled (09:04Z, Generated by Claude Code |
…re its held allowance row (#15765) The `scripts/**` symbol-anchor corpus (#15806) landed with two dated `HELD_FILE_ALLOWANCES` rows for files live PRs held. PR #15770 landed, so the row for `scripts/check-react-page-adapter-contract.mjs` is free — and an allowance nobody retires is an exemption wearing a date. The four dated readings in that gate's rot-record paragraph are now stated as DATA — a small table with the file and the line number in separate cells — never in `path:NNN` anchor form. The NUMBERS ARE NOT REPAIRED OR REPOINTED: they are readings taken on a date, the paragraph's argument is unchanged, and its ⛔ line still declares every number a record rather than a pointer. The one substantive addition is that the paragraph no longer claims nothing resolves citations in `scripts/**` — since #15806 something does, which is why the anchor spelling had to go. The self-test battery floor moves 34 → 30 because four of its cases are registered PER allowance row; the reason is recorded next to the number so a later reader cannot mistake the drop for cases that stopped running. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…st carriers `check-durability-degradation-log-level` (23), `symbol-anchors` (15), `doc-line-anchors` (11) and `pr-labels` (8) carried more than half the citations that name no tracked file, and each shape gets the repair its own kind admits: - DATED CENSUS ROWS name the file as a file-level anchor and keep the number beside it as data, losing no digit — the `#15806` method. The durability gate's two seam tables gain a `try line` column of their own, and `objectql engine.ts` / `metadata-protocol protocol.ts` become the full tracked paths the gate's own output actually prints. One of them binds tighter still: `packages/objectql/src/engine.ts#cascadeDeleteRelations` is now a resolved symbol anchor. - ILLUSTRATIONS take the angle-bracket placeholder spelling `ANCHOR_GRAMMAR` already uses. `symbol-anchors.mjs` and `doc-line-anchors.mjs` are the two files that DEFINE what a line citation looks like, so every example in them was a citation of its own; they now exhibit the shape without writing one, and say why in a⚠️ line so a later author does not "fix" it back. - THIRD-PARTY SOURCES (`codelytv/pr-size-labeler`, `actions/labeler`) name the repo, the file and the pinned line as prose. No in-repo resolver could ever check them, and a pinned upstream line is provenance rather than a pointer. ⛔ No number is repaired or repointed anywhere in this commit: every digit that was in an anchor is still on the page, as data. Corpus reading: 96 → 39 unresolvable citations. Part of #15809. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Part of #15765 — two files another lane's live PR holds still carry a citation, so this does not close the card. See Held below.
scripts/**was outside every symbol-anchor corpus, in both directions: apath:NNNthere was neither resolved nor refused. This registers the seconddefineCorpusoverscripts/**.mjscomment prose and migrates the free citations. ⛔ No second resolver — the grammar, the extractor and the resolution rule are still the one copy inscripts/symbol-anchors.mjs.1. The measurement the card said a fix owes first
It reads comment blocks perfectly, and that half needed no change.
extractAnchorsis line-based and knows nothing about syntax, so comment prose is read exactly as an ADR paragraph is. Fed one real gate header (scripts/check-react-page-adapter-contract.mjs) unmodified, on the merge base:The defect is the other half: it also reads the CODE. Those
commentish=falserows are the gate's own self-test fixtures — string literals like'p.ts:2'and'content/docs/other.mdx:1', written to provoke that gate's own line-reporting. A raw.mjscorpus would red a gate for testing itself. Measured over the 216 trackedscripts/**.mjsfiles: 251 live citations raw, 128 through comment prose — 123 of the raw count are code.So the extractor change is one thing and only one: a per-corpus doc projection,
scripts/symbol-anchors.mjs#commentProse. It asksscripts/js-comment-mask.mjs#scanSourcethe same question every source-scanning gate asks it and projects the answer the other way round frommaskComments— that one keeps code and blanks prose; a doc corpus wants the prose. It blanks, never deletes (scripts/js-comment-mask.mjs#blank), so a finding's line number is still the line the author opens. ⛔ Not a private stripper:check-comment-mask-adoptionexists precisely to stop a ninth copy.Pinned in
symbol-anchors --self-test, one fixture per comment form and the negatives, with a control:// … packages/a/line.ts:11/* … packages/a/block.ts:22 */* … packages/a/doc.ts:33`packages/a/doc.ts#realThing`const fixture = 'packages/a/string.ts:44'const tpl = `packages/a/template.ts:55`=== 5, and the line count is unchangedBattery floor raised 51 → 63.
2. The census, with its instrument stated
Three instruments give three answers on the same tree (
5315098df), and the instrument is the reading:extractAnchorsover the whole file — counts a gate's own fixturescommentProse— comment prose onlyThe card said 27 across 14 files, triage 22 with a coarser regex; neither binds. The third row reproduces the card's instrument to within one citation — requiring the path to be directory-qualified as well gives 28 across 14 files, exactly the card's shape. This gate does not add that extra rule, because a tracked file is a tracked file and
AGENTS.md:126is the same defect ascontent/docs/…:240.What the corpus declines to judge, and why it is a scope call rather than a softening.
judgeUntrackedLineAnchors: falseleaves 96 citations unjudged: 66 bare filenames inside census tables (engine.tsat some line — an abbreviation no resolver can bind to one of this tree's several files of that name), 22 continuations inheriting no path, 11 directory-qualified illustrations or sibling-repo paths, 1 tilde form. They are seen and counted (unresolvableLineCitation, asserted in the self-test so "declined" is told apart from "blind spot") and recorded as a follow-up — exactly asdocs/adr/**recorded its 1,056 bare paths. This gate cannot tell their author how to fix them, and a gate whose only remedy is "stop writing that" is the permanently-red gate this repo retired.checkBarePaths: falseon the same evidence: judging every bare path span here produces 1,617 findings.3. Migrated — 31 of the 32, and 12 of them are now checked symbol anchors
Symbol anchors, which the gate resolves on every run:
scripts/audits/14423-databaseloader-cost-probe.mjsdatabase-loader.ts:936,:1062#readTypeRows,#listscripts/audits/14423-unbound-declarations-before-count.mjsplugin.ts:2475#runGovernanceInventoryscripts/check-tenant-audit-census.mjscheck-system-context-census.mjs:77-84#UNENFORCED_TEXT_COUNTSscripts/measure-durability-swallow-family.mjsdb-job-adapter.ts:139#cancelscripts/pm/dispatch-gates.mjscheck-test-completeness.mjs:576,measure-position-name-fold-census.mjs:689,workspace-enumerator.mjs:328#selfTest×3scripts/pm/dispatch-gates.mjsbuild-docs.ts:61,build-skill-references.ts:35#SRC_DIR,#SPEC_SRCscripts/symbol-anchors.mjsengine.ts#registerApp,#installPackage(absent)#extractAnchors,#defineCorpus⭐
plugin.ts:2475was already 21 lines off —runGovernanceInventoryis declared at 2496 today. The card's argument reproduced on a second instance, found by the gate rather than by a reader.File-level anchors with the number kept beside them as data — every digit preserved, the anchor form dropped. These are dated census rows and rot records, not pointers; rewriting them to a live-looking
file:NNNis what made them lie in the first place:scripts/check-agent-test-spelling.mjs(5) ·scripts/check-doc-anchors.mjs(4) ·scripts/check-org-identifier.mjs(4) ·scripts/check-overlay-whitelist-table.mjs(2) ·scripts/check-init-service-contract.mjs·scripts/check-published-list-mirrors.mjs·scripts/docs-audit/check-docs-transcript-drift.mjs·scripts/eslint-fatal-guard.mjs·scripts/pm/check-half-states.mjs(2) ·scripts/pm/dispatch-gates.mjsPath-shaped placeholders rewritten to the angle-bracket words
ANCHOR_GRAMMARalready uses for exactly this reason — an illustration written as a real-looking path is read by a resolver as an anchor into a file the tree does not have:scripts/symbol-anchors.mjs,scripts/check-platform-checklist.mjs,scripts/check-published-readme-links.mjs,scripts/check-turbo-task-graph.mjs.4. Held — the follow-up the PM files
⛔ Not touched. Each carries a dated allowance row naming the PR that lifts it. The row is not an exemption: the finding is still produced, still printed (
⏳), still counted — it just does not fail the build while a reviewed PR is open on that file.scripts/check-react-page-adapter-contract.mjspackages/client/src/index.ts:310scripts/check-adr-0087-registration.mjspath/to/file.ts#SymbolplaceholderThe row is exact in both directions and the self-test holds it there — a stale row reds until it is deleted. Demonstrated against the live tree, not only against a fixture (leg 3 below).
5. Verification
Exit codes captured before any pipe. Verdict lines quoted as the gate printed them.
The corpus, before → after.
docs/adr/**is byte-identical, which is the point — the core gained options that default to today's behaviour:The new corpus's own line:
21 symbol anchors, up from 9 on the base — the 12 the migration added are checked from now on.
Ablation — every leg proved on disk before it was believed, restored by
git checkout HEAD -- ABSPATHand verified by blob hash and an emptygit diff HEAD, never by an exit code.trap … EXIT INT TERMthroughout.Leg 1 — the card's claim, reproduced. Plant
scripts/eslint-stack-headroom.mjs:212back into a migrated header (ON-DISK PROOF: planted-form count=1 migrated-form count=0):Leg 2 — the projection is load-bearing. Delete
docProjection: commentProse(ON-DISK PROOF: remaining=0) → exit 1, 39 findings fabricated out of gates' own string fixtures, e.g.check-init-service-contract.mjs:119and:797both citing the same fixture. Restored → green.Leg 3 — the allowance row is exact. Migrate the citation the held row names → the sweep goes green with 1 allowance finding instead of 2, and the self-test reds:
Comment-only, proved mechanically. Running each gate before/after across two checkouts was tried and is confounded three ways — some gates echo the tree path, this PR adds a file that file-counting gates count, and a gate with absent deps prints its own absolute path. The sound instrument is the projection:
stripCommentsboth revisions, drop whitespace-only lines, compare. 16 of the 18.mjsfiles this branch edits are CODE IDENTICAL (same sha256 of the executable text, different prose). The two that are not are declared:scripts/symbol-anchors.mjs— the extractor change itself.scripts/pm/dispatch-gates.mjs— its six citations are comment-only; the one executable change is the residue-ledger reason string, prose that happens to live in a string literal, updated to name both corpus registrations instead of one.maskCommentsis the wrong projection for this question and was tried first: it blanks to spaces to preserve offsets, so rewording a comment to a different length changes the space count and every file reads as "code moved". The bug was in the instrument.Runtime corroboration, one worktree and one variable (run at HEAD, revert only the migrated files to the merge base, run again, restore —
ON-DISK PROOF: 6 of 6 differ,RESTORED-OK: git diff HEAD empty):Wiring.
check-self-test-wired— every one of the 174 script(s) CI runs that ship a--self-testhas that self-test run by CI — andcheck-step-collectors— 405run:steps across 31 workflow(s); 4 step(s) run 2+ independent self-tests, all of them through a collector — both green with the new gate's collector entry and its separate production sweep. The sweep is a separate step deliberately: a production run inside the collector block re-invokes the stub that harness plants and the count comes out 4/3.Other gates,
dispatch-gates --changed --commands --repo objectstack-ai/objectstack(exit 0, 73 commands): see the comment below for the full run table.Changeset: none,
skip-changeset. AGENTS.md, verbatim: "A bug fix in a released package takes apatchchangeset — never none, and ⛔ neverskip-changeset: that label is for a diff that publishes nothing from any released package." This diff isscripts/**,.github/workflows/lint.ymland one script entry in the private rootpackage.json("private": true). Nothing publishes.pnpm check:nul-bytes— OK (scanned 7626 text file(s) … no raw ASCII control bytes); and an independent sweep of the diff for non-NUL control bytes,grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', exit 1 (no matches).check-governed-merges --teston the final file list: ✅ NOT governed — 0 of 29 path(s) hit the register (5 surfaces, repo-agnostic).6. Follow-ups this leaves, stated rather than dropped
docs/adr/**recorded its 1,056 bare paths.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code