fix(scripts): the citation extractor reads hyphen-suffixed, slash-joined and URL-spelled citations, pinned in one spelling table - #20989
Merged
objectstack-fleet[bot] merged 2 commits intoOct 1, 2026
Conversation
…spelling, and pins them in one table The extractor hid real citations behind exclusions wider than anything they protected. Measured over the declared surfaces at 3693a1b against one enumerated board: - a hyphen after the number hid 58 `#N-word` sites and protected none; the one non-citation shape it covered repo-wide (a markdown in-page heading anchor) keeps a narrower `](` head row; - a `/` before the `#` hid 528 sites (523 slash-joined second numbers); it stays only on the candidate arm, where it keeps URL path fragments out, and a slash-joined continuation reads as its chain (qualifier or ordinal); - the `re-charter`, `acceptance`, `clause`, `option` and section-mark head rows each protected 0 sites repo-wide and hid board citations; retired; - the URL spelling (`https://github.com/OWNER/REPO/issues/N`, `/pull/N`) is now a citation, qualified by its own OWNER/REPO, counted once inside a `[#N](URL)` link. The self-test gains the one enumeration table (48 spellings, each "extracted as" or "not a citation, because"), completeness rules over the head rows and the required spellings, and three live-corpus floors for the new arms. Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…a number spelled once on its line An ablation that restored the slash exclusion left the continuation floor green: a line citing the same number twice let a plain citation stand in for the slash-joined one. Each floor now counts only a row whose number appears once in its line. Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-20636-citation-extractor-closeout
branch
October 1, 2026 01:16
This was referenced Oct 1, 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 free
to 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 #20636
Clause-②: no
The family closeout for the citation extractor in
scripts/check-issue-citations.mjs: every spelling a seat measured as invisible to the diff gate and the census is now read, every exclusion keeps only the shapes it was measured protecting, and the self-test carries the one enumeration table the triage asked for (48 spellings, each "extracted as" or "not a citation, because"). Landing site:scripts/check-issue-citations.mjsonly.scripts/check-doc-authoring.mjsis untouched; H2 below says why.What changed
-, so#13398-class,#5347-Aandui#6206-Bread as citations of their number. It still refuses a word character, so a hex colour stays out.#. A/is now valid context before a bare#. It stays refused only before a qualifier candidate, so a URL path fragment such ashttps://example.com/docs/page#12never readspageas prose. A/right after a digit is the exception, so#3076/objectui#2614still reads its own qualifier.#A/#B, the second number takes the chain head's reading: bare after a bare or prose head, the head's repository after a qualified head, and an ordinal after an ordinal. Only a joined/continues a chain.objectui#1 / #2andobjectui#1 + #2stay two separate readings.https://github.com/OWNER/REPO/issues/Nand.../pull/Nare now citations, qualified by their ownOWNER/REPO. That putsobjectstack-ai/frameworkin this repository and makes every other repository's URL cross-repo, never a finding. Inside a markdown link[#N](URL), the citation counts once.re-charter,clauseandoption(named in the thread), plusacceptanceand the section mark (found by the same measurement). Each protects 0 sites repo-wide at two or more digits and hides board citations. The grammar's two-digit floor already keeps one-digit ordinals out. I added a](row, the narrower guard the hyphen exclusion leaves behind for markdown in-page heading anchors.spellingsbattery (56 cases) reads the table row by row.live-corpusbattery gains three floors, one per new arm, each counting only a number spelled once on its line.scripts/pm/dispatch-gates.mjs's self-test pinsscripts/check-issue-citations.mjs:204 local-env. The marker is still on line 204, and that pin passes (see Gates).H1: what each exclusion protected and hid
Measured on
3693a1b50over the declared surfaces. Every arm was judged against one enumerated board: 188 pages, frontier 20959, 2026-09-30T22:55Z. The instrument mirrors the gate's extractor and matched it file for file on all 2,640 files (0 mismatches).docs/design/**andskills/**, plus one range,docs/audits/...md, whose first number is a citation](row keeps the anchor out/before the##A/#Bsecond numbers and 5TOKEN/#Nsuch asADR-0049/#1888re-charterre-charter #13135were rewritten by PR #20750)clauseoptionoption #14088, live); 1 more underscripts/**acceptancethe silent acceptance #6132 closed, live)§6 #11176's decisions)PD85 (max 13), batch 276 (69 distinct, 11 to 227),OQ10,PKCS1The 8 slash chains headed by another repository are not a case where the two populations cannot be told apart. The 5 on objectui's public board each name objectui's record, the issue and then the pull request that fixed it, read one by one against both boards:
objectui#2715/#2717#2711/#2722#2725/#2732#2967/#2904#4648/#4901This repository's records with the same numbers are unrelated. The other 3 (
cloud,hotcrm-heimao) are boards one credential cannot read, so they stay unjudged, as they were before.H2: where the URL spelling belongs
The extractor. At
3693a1b50, 57 URL sites sit in the gate's projection: 56 in package comments and 1 link on a release page. 4 of them are dead. Only 2 URL sites in package sources are inside string literals, both internalnote:strings inpackages/runtime/src/route-ledger.ts.check:doc-authoringasks a different question: may a runtime string carry a tracker reference at all? It reads string literals, skills and spec refusal messages. The two projections are disjoint, so adding the URL to the extractor double-counts nothing there. Inside the extractor, the one double-spelled site ([#15325](.../issues/15325)onv17/17-3.mdx) counts once.check-doc-authoring.mjsis not touched.H3: open PRs' added lines
All 13 open PRs at 2026-09-30T23:2xZ: their heads were fetched into a private ref namespace (deleted afterwards). For each, the BASE extractor and this one were run over the lines it adds, against its merge base. Result: 85 added-line citations under both extractors, 0 newly extracted, 0 lost. No PR's verdict changes. Lines a PR does not add are never judged, which is unchanged and pinned in the
diff-scopebattery.Census, before and after
The gate's own
--census --json, once with the3693a1b50script and once with this one, over the same tree:That is 644 more judged sites and 15 more dead ones, with 0 findings lost. By arm: hyphen 1 dead, slash 10 dead, URL 4 dead.
Newly visible dead sites per lane. I rewrote none of them; they belong to the lane cards:
domain:clipackages (689 sites, 166 numbers, 95 files): the ruling C+D stage for this lane (from #20556) #20594 (domain:cli): 1.packages/rest/src/rest-server.ts:7456, [裁决]MetadataProtocol是否应声明publishMetaItem成员 —— 半声明的门(响应已声明、请求未声明) #11006.domain:enginepackages (645 sites, 160 numbers, 104 files): the ruling C+D stage for this lane (from #20556) #20595 (domain:engine): 6.driver-sql:sql-driver.ts:3933(URL, driver-sql: the $contains MEMBERSHIP spelling on any multi-valued / JSON column is a DATABASE_ERROR 500 on live PostgreSQL (SQLSTATE 42883, operator does not exist: json ~~ text) — it has only ever been executed on SQLite #17590),:12110([finding] 15 more PASSING@objectstack/runtimetests print the samerefused a read onfeature — 134 lines after #10380 quiets its two, led bynotifications.hono.integration.test.ts(52) #10629),:16106(driver-sql: amultiple: trueBOOLEAN column loses its$containsmembership filter — the #14079 type-gate compiles it to1 = 0, where the same shape on amultiple: truenumber keeps its GLOB #17343).metadata:loaders/ambiguous-metadata-stem.ts:40([finding] Two residual audit ↔ router asymmetries after #14123: the audit's third source isloadManywhile the router's isloadby name, andunboundDeclarationsstill reads two sources where the undeclared-handler half now reads three #14423).metadata-protocol:migrations/partial-index-probe.ts:395(Raw-exec consumers that surfaceerror.messageas an operator-facingdetailnow read the composed DATABASE_ERROR sentence — readcausethere (follow-up to #16019) #16657).objectql:plugin.ts:1496([finding] 15 more PASSING@objectstack/runtimetests print the samerefused a read onfeature — 134 lines after #10380 quiets its two, led bynotifications.hono.integration.test.ts(52) #10629).domain:servicespackages (629 sites, 112 numbers, 152 files): the ruling C+D stage for this lane (from #20556) #20596 (domain:services): 0.domain:specpackages (84 sites, 21 numbers, 23 files): the ruling C+D stage for this lane (from #20556) #20597 (domain:spec,packages/lint): 0.packages/spec/src): 7.data/datasource.zod.ts:701(mongoconfig.options.auth.passwordis a fourth spelling of an inline credential — authorable, persisted cleartext, unredacted, and read by the client #9040).data/filter.zod.ts:1040(URL, driver-sql: the $contains MEMBERSHIP spelling on any multi-valued / JSON column is a DATABASE_ERROR 500 on live PostgreSQL (SQLSTATE 42883, operator does not exist: json ~~ text) — it has only ever been executed on SQLite #17590) and:1042(URL, driver-memory: the stored-ARRAY value axis is still unrepaired outside the equality arm — $in/$nin, the text family and the ordering family answer one filter two ways, and the two exclusion arms answer it in the WIDENING direction #17286).data/value-roundtrip-conformance.ts:100(URL, [finding] driver-sql's SQLite json codec is not round-trip faithful for a string whose content is valid JSON — '123' reads back as a number, and Postgres does not agree #12380).ui/component.zod.ts:593and:669(element:record_picker的sort/limit扁平简写:renderer 兑现、schema 未声明(#5775 实施中新测出,A 表之外的第 7 处) #6276), and:3771([finding]page:tabsitems[].iconis the exact sibling of #9881 — live objectui reader, no.describe(), no accept-pin #9972).domain:devx, no lane card): 1.content/docs/releases/v17/index.mdx:168([finding][drivers] 五个驱动的find/count/…仍声明query: QueryAST,而调用方已可省略object—— 双变让它编译,但声明开始说谎 #6075).This census was run on the PR head's tree, not after landing. A re-run after landing reads the same corpus plus whatever
mainhas gained by then.In-place fixes beyond the three named rows
I retired the
acceptanceand section-mark rows here rather than filing them. All four conditions hold:optionandclause: a head row hiding a board citation.NON_CITATION_HEADS).Evidence:
acceptance #6132(live) inpackages/formula/src/cel-pushdown-limits.ts:82, and the section-mark sites in deferred test files. Neither row has any two-or-more-digit ordinal anywhere in the repository.Gates (final head
25d96fcc0)I re-derived the list with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, which gave 32 commands. The same derivation on a throwaway tree atorigin/main(05be35259) with this diff applied gave an identical list. I ran all 32, pluspnpm check:doc-authoringand the self-test, and every one exited 0. Reconciliation verdict line:✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3).Verdict lines worth quoting:
node scripts/check-issue-citations.mjs --self-test:✅ ... every spelling enumerated ... (173 cases, 9 batteries). It also passes onorigin/main05be35259with this diff applied.node scripts/check-issue-citations.mjs(diff mode):✅ check-issue-citations: no issue citations added against 3693a1b50 (0 file(s) read).The script lives in the deferredscripts/**surface.pnpm check:pm-dispatch-gates:✓ dispatch-gates self-test: 1976 cases pass.(1237.9s). Its pinscripts/check-issue-citations.mjs:204 local-envholds.pnpm check:doc-authoring:✓ doc authoring guard: ... hold the baseline — 549 pinned site(s).pnpm check:nul-bytes:check-nul-bytes: OK (scanned 9582 text file(s) ...).node scripts/check-scripts-symbol-anchors.mjs:✅ ... 3706 anchors across 282 scripts resolve.Ablations (one-shot, from committed
25d96fcc0, viascripts/ablation-replace.mjs)Every leg was expected to go red, and every leg did. Each one restored to blob
c732ce2e21c7(equal to HEAD) with an emptygit diff HEAD. No permanent ablation file is left.the live corpus must yield a #N-word citation/refused again before the#the live corpus must yield a slash-joined #A/#B second numberthe live corpus must yield a URL-spelled citationoptionhead row restoredspelling option #N in "the option #14088 gave" must read #14088; got nothingspelling repo#A/#B ... must read objectstack-ai/objectui#2711, objectstack-ai/objectui#2722](row disableda markdown link's in-page heading anchor is not a citationspelling [#N](URL) ... must read #15325The first slash ablation, run before the floors were tightened, went red in the table but left the live-corpus continuation floor green. A line citing the same number twice let a plain citation stand in for the slash-joined one. Commit
25d96fcc0makes each new floor count only a number spelled once on its line. The re-run above is red at that floor.Acceptance notes
check:doc-authoringdoes not read it. At3693a1b50the population is 2 internal route-ledgernote:strings (packages/runtime/src/route-ledger.ts:459,:465), and no author-facing door shows them. Noted, not filed; carrier: none.#712-714, the second number carries no#and is not read. There is 1 site repo-wide, indocs/audits/**, outside the declared surfaces. Pinned in the table with its reason.clauseand section-mark rows hid are in the deferred test surface. They become visible only when that surface is swept.dispatch-gates.mjspins this file'slocal-envmarker by line number (:204), so any future header growth above it has to move that pin in the same PR.No changeset: a root
scripts/file publishes nothing (the rootpackage.jsonis private, and no package'sfilesshipsscripts/), so this PR takesskip-changeset.Generated by Claude Code