Repository navigation
docs(spec): re-anchor the dead tracker citations in migrations/entries to the commits that decided them (stage 10) - #20750
Conversation
…s to the commits that decided them Every dead tracker number in the hand-written migration entries (131 sites over 21 numbers, all comments; one in entries/README.md) now cites the commit that decided it, in ruling C+D form C, with the decision in words where the number alone carried it. The anchors are the ones earlier stages chose for the same numbers. Lit citations on the same lines stay. Comment text only: no entry id, literal, order or code token moves. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…ntries gen:migration-registry output only. The 128 changed lines are the entry comment lines the generator carries, re-indented; no line outside the generated regions moves. spec-changes.json and the upgrade guide are unchanged, because entry comments are never projected into them. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…ents Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…grations-citations
…grations-citations
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3d26556ab74c39abd96cd3056f0605693de27cd3 && git checkout 3d26556ab74c39abd96cd3056f0605693de27cd3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 97005aed04a069abc2d2fa82fd6c1594aebd7cd1 1ee5841c092cdc7d2c446ab1e7cf933b940e3e25 && git checkout -B drift-repro 97005aed04a069abc2d2fa82fd6c1594aebd7cd1 && git merge --no-ff 1ee5841c092cdc7d2c446ab1e7cf933b940e3e25
node scripts/docs-audit/affected-docs.mjs --json 97005aed04a069abc2d2fa82fd6c1594aebd7cd1
|
Contract reviewServed-tier: Read: card #20234, body and all 41 comments (the C+D path decision ① Derived judgmentsThe claim says no accept-set or public-surface move. Each judgment below is from the diff and the git objects, not the dev's prose.
② Semver level
③ Boundary flags
Check-runs on the head, read once in the first CI wave (the newest concluded run at the read was Implemented-by: VERDICT: PASS Generated by Claude Code |
…ned and URL-spelled citations, pinned in one spelling table (objectstack-ai#20989) Fixes objectstack-ai#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.mjs` only. `scripts/check-doc-authoring.mjs` is untouched; H2 below says why. ## What changed - **Hyphen after the number.** The lookahead no longer refuses a `-`, so `objectstack-ai#13398-class`, `objectstack-ai#5347-A` and `ui#6206-B` read as citations of their number. It still refuses a word character, so a hex colour stays out. - **Slash before the `#`.** A `/` is now valid context before a bare `#`. It stays refused only before a qualifier candidate, so a URL path fragment such as `https://example.com/docs/page#12` never reads `page` as prose. A `/` right after a digit is the exception, so `objectstack-ai#3076/objectui#2614` still reads its own qualifier. - **Slash-joined continuation.** In `#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 / objectstack-ai#2` and `objectui#1 + objectstack-ai#2` stay two separate readings. - **URL spelling.** `https://github.com/OWNER/REPO/issues/N` and `.../pull/N` are now citations, qualified by their own `OWNER/REPO`. That puts `objectstack-ai/framework` in this repository and makes every other repository's URL cross-repo, never a finding. Inside a markdown link `[#N](URL)`, the citation counts once. - **Head rows.** I retired `re-charter`, `clause` and `option` (named in the thread), plus `acceptance` and 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. - **Self-test.** - A new `spellings` battery (56 cases) reads the table row by row. - Every head row must excuse at least one table row, and every required spelling (the card's list plus the thread's) must be present. - The `live-corpus` battery gains three floors, one per new arm, each counting only a number spelled once on its line. - The roster floor rises from 6 to 9 batteries. - Total: 114 cases in 8 batteries became 173 in 9. - I edited the header in place and kept its line count, because `scripts/pm/dispatch-gates.mjs`'s self-test pins `scripts/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 `3693a1b50` over 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). | exclusion | hid (sites, dead) | protected in the declared surfaces | disposition | |---|---|---|---| | hyphen after the number | 58, 1 dead (8 cross-repo) | 0: no numeric range, slug or hex-like token. Repo-wide: in-page heading anchors, 16 lines in `docs/design/**` and `skills/**`, plus one range, `docs/audits/...md`, whose first number is a citation | dropped; the `](` row keeps the anchor out | | `/` before the `#` | 528, 10 dead: 523 `#A/#B` second numbers and 5 `TOKEN/#N` such as `ADR-0049/objectstack-ai#1888` | 0 paths and 0 URL fragments | narrowed to the candidate arm; continuations read as their chain | | head `re-charter` | 0 left on this tree (the 26 dead `re-charter objectstack-ai#13135` were rewritten by PR objectstack-ai#20750) | 0; only the gate's own fixtures used it | retired | | head `clause` | 0 in the surfaces; 4 in deferred test files, all board citations | 0 | retired | | head `option` | 1 (`option objectstack-ai#14088`, live); 1 more under `scripts/**` | 0 | retired | | head `acceptance` | 1 (`the silent acceptance objectstack-ai#6132 closed`, live) | 0 at two or more digits | retired (in-place, below) | | head section mark | 0 in the surfaces; 4 in test files (`§6 objectstack-ai#11176's decisions`) | 0 at two or more digits | retired (in-place, below) | | heads kept | none measured hiding a citation | directive 203 (max 13), `PD` 85 (max 13), batch 276 (69 distinct, 11 to 227), `OQ` 10, `PKCS` 1 | kept | The 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/objectstack-ai#2717` - `objectstack-ai#2711/objectstack-ai#2722` - `objectstack-ai#2725/objectstack-ai#2732` - `objectstack-ai#2967/objectstack-ai#2904` - `objectstack-ai#4648/objectstack-ai#4901` This 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 internal `note:` strings in `packages/runtime/src/route-ledger.ts`. `check:doc-authoring` asks 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 (`[objectstack-ai#15325](...objectstack-ai/issues/15325)` on `v17/17-3.mdx`) counts once. `check-doc-authoring.mjs` is 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-scope` battery. ## Census, before and after The gate's own `--census --json`, once with the `3693a1b50` script and once with this one, over the same tree: | | judged | resolves | resolves as PR | cross-repo | allocated-but-absent | |---|---|---|---|---|---| | before (frontier 20965) | 37,152 | 33,403 | 1,985 | 1,024 | 740 | | after (frontier 20966) | 37,796 | 33,917 | 2,083 | 1,041 | 755 | 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: - **objectstack-ai#20594 (`domain:cli`): 1.** `packages/rest/src/rest-server.ts:7456`, objectstack-ai#11006. - **objectstack-ai#20595 (`domain:engine`): 6.** - `driver-sql`: `sql-driver.ts:3933` (URL, objectstack-ai#17590), `:12110` (objectstack-ai#10629), `:16106` (objectstack-ai#17343). - `metadata`: `loaders/ambiguous-metadata-stem.ts:40` (objectstack-ai#14423). - `metadata-protocol`: `migrations/partial-index-probe.ts:395` (objectstack-ai#16657). - `objectql`: `plugin.ts:1496` (objectstack-ai#10629). - **objectstack-ai#20596 (`domain:services`): 0.** - **objectstack-ai#20597 (`domain:spec`, `packages/lint`): 0.** - **objectstack-ai#20234 (`packages/spec/src`): 7.** - `data/datasource.zod.ts:701` (objectstack-ai#9040). - `data/filter.zod.ts:1040` (URL, objectstack-ai#17590) and `:1042` (URL, objectstack-ai#17286). - `data/value-roundtrip-conformance.ts:100` (URL, objectstack-ai#12380). - `ui/component.zod.ts:593` and `:669` (objectstack-ai#6276), and `:3771` (objectstack-ai#9972). - **Release pages (`domain:devx`, no lane card): 1.** `content/docs/releases/v17/index.mdx:168` (objectstack-ai#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 `main` has gained by then. ## In-place fixes beyond the three named rows I retired the `acceptance` and section-mark rows here rather than filing them. All four conditions hold: 1. They are the same defect class as `option` and `clause`: a head row hiding a board citation. 2. The fix is mechanical, and the shape is pinned by the table. 3. The file is this claim's own surface (`NON_CITATION_HEADS`). 4. The same gate's self-test covers them, so no new verification surface is added. Evidence: `acceptance objectstack-ai#6132` (live) in `packages/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 at `origin/main` (`05be35259`) with this diff applied gave an identical list. I ran all 32, plus `pnpm check:doc-authoring` and 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 on `origin/main` `05be35259` with this diff applied. - `node scripts/check-issue-citations.mjs` (diff mode): `✅ check-issue-citations: no issue citations added against 3693a1b (0 file(s) read).` The script lives in the deferred `scripts/**` surface. - `pnpm check:pm-dispatch-gates`: `✓ dispatch-gates self-test: 1976 cases pass.` (1237.9s). Its pin `scripts/check-issue-citations.mjs:204 local-env` holds. - `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`, via `scripts/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 empty `git diff HEAD`. No permanent ablation file is left. | mutation | first red | |---|---| | hyphen refused again after the number | `the live corpus must yield a #N-word citation` | | `/` refused again before the `#` | `the live corpus must yield a slash-joined #A/#B second number` | | URL arm disabled | `the live corpus must yield a URL-spelled citation` | | `option` head row restored | `spelling option #N in "the option objectstack-ai#14088 gave" must read objectstack-ai#14088; got nothing` | | continuation disabled | `spelling repo#A/#B ... must read objectstack-ai/objectui#2711, objectstack-ai/objectui#2722` | | `](` row disabled | `a markdown link's in-page heading anchor is not a citation` | | link de-duplication disabled | `spelling [#N](URL) ... must read objectstack-ai#15325` | The 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 `25d96fcc0` makes each new floor count only a number spelled once on its line. The re-run above is red at that floor. ## Acceptance notes - **URL spelling in runtime strings.** `check:doc-authoring` does not read it. At `3693a1b50` the population is 2 internal route-ledger `note:` strings (`packages/runtime/src/route-ledger.ts:459`, `:465`), and no author-facing door shows them. Noted, not filed; carrier: none. - **Range second numbers.** In a range such as `objectstack-ai#712-714`, the second number carries no `#` and is not read. There is 1 site repo-wide, in `docs/audits/**`, outside the declared surfaces. Pinned in the table with its reason. - **Dormant test-file citations.** The 8 test-file citations the retired `clause` and section-mark rows hid are in the deferred test surface. They become visible only when that surface is swept. - **Line-number pin.** `dispatch-gates.mjs` pins this file's `local-env` marker 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 root `package.json` is private, and no package's `files` ships `scripts/`), so this PR takes `skip-changeset`. --- _Generated by [Claude Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20234
Clause-②: no
Stage 10 of the dead-citation sweep: the migration registry's hand-written entries. Every tracker number in
packages/spec/src/migrations/entries/**that no longer exists on the board now cites the commit that decided it, in ruling C+D form C (ruling5749154545on #19123). Where the number alone carried the meaning, the line now says what was decided. That is 131 sites over 21 numbers. All of them are comments; the entries' string literals carry no dead number.migrations/registry.tsmoves only bygen:migration-registry. No entry id, literal, order,conversionIdsor code token moves, and no live citation is removed.Boundary
entries/**. The gate's census reads 117 sites there, under the claim's ~120 slicing threshold, so there is no slice. My raw walk finds 14 more comment sites that the census does not count (see Census below), which gives 131 in total. They are the same dead card on the line after a counted site, plus one README line, so they are rewritten with it.migrations/registry.ts, 128 lines.spec-changes.jsonanddocs/protocol-upgrade-guide.mddo not move, because entry comments are never projected into them. Bothcheck:scripts pass with no regeneration.registry.ts's hand-written parts. They still hold 13 dead sites, listed under Acceptance notes. The six18.*-unit-in-key.tsentries that PR chore(objectui): bump the console pin to db11afd4967c (carries objectui#11119, the objectui#11105 fix) #20706 edits carry no dead site, and I did not touch them. I did not touchconversions/or other lanes' sites.The anchors (one per number, reused from earlier stages where they anchored the same number)
re-charter #13135)RestServerConfigkeyscontributesmembersdeadrowsthemescarrier andThemeSchemaconnector.errorMappingcursoron both halvesz.partialRecordproposal that commit's changeset recorded; the retirement leaves no record to reshapeunreadCountfix"contributes.routes(Option B)oauth.*binding, which leftapplications.deleteout as a behaviour change (PR #15445 kept)Two lines change without a number, so the sentence still reads:
patterns' follow-on line and theoauthanchor block's continuation line. In total 133 lines are removed and 133 added in 86 files, and every file is balanced.Verification record (final head
1ee5841c09; basefbec216e2d)Census (the gate's own
check-issue-citations.mjs --census --json, board enumerated, 186 pages):entries/retired-keysentries/retired-defsentries/semanticregistry.ts, generated regionsregistry.ts, hand-written partschain.ts,types.ts,index.ts,spec-changes.ts, testsmigrations/packages/spec/srcplugin-audit's, frommain's docs(plugin-audit): re-anchor the dead tracker citations to the commits that decided them #20737. 0 sites are only at head.registry.ts's hand-written rationale (see Acceptance notes).issues/Nwithout following redirects, over all 302 distinct in-repo numbers of 100 or more inmigrations/. 277 answer 200 and 25 answer 404. Controls were read at start, every 50 and end: lit packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 200 (8/8), dead Does a DEACTIVATEDsys_positionstill receive sharing-rule shares?expandPositionUsersnever reads the catalogue row, so today it does #8710 404 (8/8).re-charter #13135, becauseNON_CITATION_HEADSreadsre-charter #Nas an ordinal.entries/README.md, which is outside the gate'spackages/**/src/**/*.tssurface.${…}placeholder syntax in memory persistence.path / persistence.key at publish (#8495) #8666, [finding] spec ships a three-layer metadata-customization protocol (MetadataOverlaySchema, field-level change tracking) with zero consumers — published reference docs describe an unimplemented customization architecture #12057,additionalTypesonMetadataPluginConfigis authorable, documented as THE way a plugin declares a metadata type, and has no reader — a plugin cannot declare a kind at all #8586, [finding] HotReloadManager.startWatching watches nothing and logs "File watching started" at info; watchPatterns has no reader and watchHandles is never populated #12428, Composed-branch twin of #9041:external.credentialsRefbound + discrete mongo fields naming nousernameis the same silent no-op, unrefused #9147 and fix(client)!: bind the oauth.* family to the wire shapes better-auth sends #15445. The diff-scoped gate judges them: "every citation this change adds resolves".Residue (scratch walker over the TypeScript parser, per file, base vs head, 86
.tsfiles):67c1f6db944e93ed.Regeneration (
gen:migration-registry):check:migration-registryexits 1 before and 0 after.check:spec-changesandcheck:upgrade-guideexit 0 with no regeneration.Build and tests (under
os-verify-lock):turbo run buildover./packages/*and./packages/*/*, 71 of 71, at845e90fea4and again at1ee5841c09.check:generated: all 15 artifacts up to date, at both heads.localproject: 576 files, 16,991 passed and 1 todo, at both heads.repoproject: 41 of its 45 files, 666 passed, at both heads.typecheck: exit 0; 53 files / 251 errors / 138 pinned signatures held.Gates:
dispatch-gates --commandsderives 82 gates, and the derivation is identical before and after the second merge. At1ee5841c09all 82 exit 0.--ranreconciles: 82 derived, 82 run, 0 NOT-MEASURED, a derived zero.Lint (a proven narrowing):
eslint --no-inline-config --format jsonover the 86 touched.tsfiles: 86 files, 0 errors, 0 warnings.isPathIgnoredis false for all 86.eslint.config.mjs:327-328states that type-aware linting is never enabled, so a comment edit cannot move an untouched file's verdict.Changeset:
patch.dist, the new wording (for example "the precedent of commit 4bfe1a5, PR feat(spec): refuse${…}placeholder syntax in memory persistence.path / persistence.key at publish (#8495) #8666") appears indist/migrations/index.jsand.mjs, and the old wording appears in 0 files.@objectstack/spec/migrationsentry carries these comments.Merges:
origin/mainwas merged twice throughscripts/pm/os-regen-merge.sh. Neither merge left a regeneration to commit.mainhas advanced to97005aed04, and none of those commits touchespackages/spec.merge-treeprobes (from a bare--sharedscratch clone with nomerge.*config) exit 0 against thatmainand against PR chore(objectui): bump the console pin to db11afd4967c (carries objectui#11119, the objectui#11105 fix) #20706's headd8bac3877d.Acceptance notes
registry.ts's hand-written parts. They are outside this claim, which saysregistry.tsis regenerated only.:5940[finding][spec] memory-driver persistence.path / persistence.key still accept unresolved${…}placeholders — the #8336 shape one surface over, deliberately outside its connection-material class #8495, the step-18 doc comment on the persistence placeholder refusal.:22819[spec] View-Management 五方法(listViews/getView/createView/updateView/deleteView)是零实现零路由的声明面 —— 已导致 #5948 把它的响应 schema 误当成线上路由的契约 #6239, theRETIRED_DEFS_BY_MAJOR[17]doc comment.:344cli/runtime: explicit driver(--database-driver/OS_DATABASE_DRIVER)在 CLI 与 standalone stack 之间仍有三处分叉(#6265 后续) #6345. It projects intodocs/protocol-upgrade-guide.md:68, so that stage regenerates the guide.:5121integration/ErrorMappingConfig+ErrorMappingRuleare 11 authorable keys with zero consumers — and one of them is nameduserMessage, colliding with the live #9934 channel #14676,:5455Narrow the per-optiondefaultkey OUT of the form-view options vocabulary — the object-field face keeps it (enforced, #7246), the form-view face parses-and-ignores it (ruled on objectui#6263) #12868,:5526spec(data): mappinglookuptransform params (object / fromField / toField / autoCreate) are authorable and read by nothing #10329,:5544[finding][spec] memory-driver persistence.path / persistence.key still accept unresolved${…}placeholders — the #8336 shape one surface over, deliberately outside its connection-material class #8495,:5555Retire the paper metadata-customization protocol with its full coupling set — spec module, authorable customizationPolicies/mergeStrategy keys, metadata API §5, optional IMetadataService overlay methods, unreachable packages/metadata limb (re-charter of #12057) #13135,:5742Retire the nine dead members of the plugin-manifestcontributesblock (events/menus/themes/translations/actions/drivers/fieldTypes/functions/commands) — tombstone route,kindssurvives #10724,:5745Plugin-manifestcontributes.themesdeclares a theme-contribution channel nothing reads — same declared-but-unenforced shape as the retired stackthemescarrier #10627,:5752contributes.routeshas zero readers, yet a published skill, an ADR row and the dispatcher's own doc all present it as the way to serve a code-handler endpoint — enforce-or-remove fork #10726,:5799defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485,:5816i18n: component-translationsubmitLabelcopy key lost its only declared carrier whenelement:formretired (#9249) — decide retire vs re-anchor #10926. These are not projected into either artifact yet.NON_CITATION_HEADSskipsre-charter #Neven where N is a tracker card..mdfiles underpackages/**/srcare outside the surface.scripts/build-schemas-check-mode.test.tshit the foreground cap alone (exit 124 after 560 s, no output). It parsesregistry.ts's source; the residue check above shows that source's code and string tokens are unchanged.repo-project files (def-key-collisions,publish-smoke-boot-failure,publish-smoke-port-collision) were not run, as in stages 8 and 9.scripts/doc-authoring-prose-id.baseline.jsonhas nopackages/specrow, because its leg excludespackages/spec.check:doc-authoringreads strings only, so a comment-only diff cannot move it. It read 808 sites across 229 files at845e90fea4and 794 across 227 at1ee5841c09. The difference ismain's own re-anchor commits; this diff touches no row.Generated by Claude Code