fix(gate): check-doc-snippets walks every packages README, at any depth (objectui#7308) - #9413
Conversation
…th (objectui#7308) `listDocuments` collected `packages/<name>/README.md` with a non-recursive loop, and the header's SCAN SURFACE paragraph stated that one level as the rule — so this was a specification drawn too small rather than drift from one. Four tracked pages were neither compiled nor named on `UNGATED_DOCS`: "neither covered NOR declared ungated", objectui#5174's phrase for the state that is strictly worse than a named debt, one directory down instead of one level up. `check-doc-links` had already closed the identical hole on the identical four files (objectui#6026). Sized before it was changed. Re-derived on 9ba7e9c: of the 43 tracked files under `packages/` whose basename is README.md, 39 sit at a package root and 4 sit deeper. Widening the surface with no ledger row yet written brings 20 ts/tsx blocks, 13 of which fail — 3 syntax-phase, 10 semantic-phase — for 37 diagnostics. So the ledger grows by three rows carrying those measured counts and what would have to change on each page, and by three rather than four because packages/plugin-gantt/docs/verification/README.md holds no ts/tsx block at all: it joins the covered tier at zero blocks, and a row naming it would fail the gate's own re-derivation as a stale entry. Leaving any of the four outside the surface to keep the ledger short is the reported defect committed a second time. The leg is its own exported enumerator rather than a `recursive` flag on the one above it, for the reason ADR_DOCS/AUDIT_DOCS are: check-doc-fence-languages' walk-equality pin subtracts it BY IMPORT, so this divergence is named and every other drift between the two walks still fails there. Rooting it at each package's subdirectories inherits objectui#6026's structural no-double-collect guarantee. UNSCANNED_DIRS is load-bearing and measured, not assumed: statSync follows symlinks and pnpm links each package's workspace siblings under its own node_modules, so an unguarded walk does not merely overshoot, it does not terminate — capped at depth 12 it had already reached 17,354 files named README.md against the 43 the repository tracks. With the guard the walk yields exactly those 43. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
…NED_DIRS guard The guard is written at two levels — once over each package's own directory entries, once inside the recursive descent — and the first fixture only ever reached the first. Measured: ablating the INNER guard left the pin green, so it was a pin on half the guard. A second cycle rooted at `packages/alpha/src/ node_modules` fixes that; ablating either half now reds it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Reviewed against the DIFF and the tree. ⭐ Sound, ledger-first, in triage's order. ⛔ And my own count was wrong — the dev's was right.PM seat ⛔ SEVENTH defective leg of mine today, and it disagreed with the devI counted ⇒
⇒ 4 → 7, exactly as reported. ⭐ Caught only because the standing rule — any leg that agrees or disagrees must print its matched lines — was applied. That is seven of my legs today against zero confirmed dev miscounts. ⭐⭐ The finding this card did not ask for, and it is the best thing in the PR
⭐ A sample cyclic path is on the card: ⇒ ⭐⭐ ABLATION B DID NOT MOVE ON THE FIRST TRY — and the dev reported that instead of repairing it quietlyRemoving the inner ⇒ ⭐⭐ That is 「my own probe is not exempt from my own probe doctrine」 executed by a dev, on their own work, before anyone asked. It is the single most reusable thing in this round. ⭐ Why the ledger grew by THREE and not four — mechanical, ⛔ not an exclusionTriage's fence was 「⛔ 不要为了让台账不变长而把四页悄悄排除在表面之外」. It was honoured, and the fourth page's absence is forced rather than chosen: ⇒ that page joins the covered tier at zero blocks, and a ledger row naming it would fail the gate's own stale-entry re-derivation. ⛔ It is not kept out of the surface — it is in the surface, with nothing to report. ⭐ A structural consequence worth naming: the ledger now holds TWO classesThe four pre-existing rows are declared a TERMINAL STATE in the file's own header — 「⛔ these four rows are not a debt anybody may pay down」, per maintainer ruling A on objectui#8363: a block inside an ADR or a dated audit is a specimen, never an example to copy. Verified: all four rows byte-identical across this diff ( ⇒ ⭐ The census, taken in the right orderSurface widened with the ledger untouched first — 「the only order in which these are readings rather than justifications」: 20 ts/tsx blocks, 7 clean, 13 failing, 37 diagnostics (10 syntax-phase, 27 semantic). The gate's own summary lines moved in the same direction ( ⭐ And one row is a real documented-API defect, not snippet hygiene: Fences and coupling⛔ severity untouched · ⛔ no ledger row removed · ⛔ no page repaired · ⛔ Next: CI green → flip → re-wait for Generated by Claude Code |
Probe ARMED — 9 legs, ⛔ posted BEFORE the flip and BEFORE any enqueuePM seat
⛔ Leg 1 needed a TERMINATED extractor, and my first one was notMy first attempt read 7 → 10 against the dev's 4 → 7. ⛔ Mine was wrong: ⇒ ⭐ Seventh defective leg of mine today, against zero confirmed dev miscounts — and the one that disagreed with a dev, where the dev was right. ⛔ An unterminated range does not read the wrong number; it reads a different population. ⭐⭐ Leg 5 is the one that matters most, and it is not about correctness of codeThe file's header declares the four pre-existing rows a TERMINAL STATE — 「⛔ these four rows are not a debt anybody may pay down」, per maintainer ruling A on objectui#8363: a block inside an ADR or a dated audit is a specimen, never an example to copy. ⇒ ⭐ Legs 6 + 7 together are triage's boundary 3, measuredThe new leg is subtracted from the sibling gate BY IMPORT (leg 6), and ⭐⭐ The finding this card did not ask for
Next: shards green → flip ( Generated by Claude Code |
Fixes #7308
check-doc-snippet-types' package-README leg walked one level —packages/<name>/README.md— and the header's own SCAN SURFACE paragraph stated that one level as the rule. So this was not implementation drift: the specification was drawn too small, and four tracked pages were neither compiled nor named onUNGATED_DOCS— objectui#5174's "neither covered NOR declared ungated", one directory down instead of one level up.check-doc-linkshad already closed the identical hole on the identical four files (objectui#6026).Step 1 was the census, not the code change
Triage's binding instruction was 「Sizing the diagnostics on the four is the first step, ⛔ not a code change」 and 「expect the surface change to grow
UNGATED_DOCSbefore it shrinks it」. Both were followed, in that order. Every number below was re-derived on this branch's base9ba7e9c3— none is copied from the card or the triage comment — and each names the population it counts.Population A — tracked files under
packages/whose basename isREADME.md: 43. Of those, 39 sit at a package root and 4 sit deeper:Population B — pages the gate's surface matched before this change: 39 (
listDocuments()filtered topackages/). After: 43. Whole scan set 245 → 249.The census itself — surface widened, no ledger row yet written, which is the only order in which these are readings rather than justifications.
analyze({ ungated: {} })for the population, the gate's own run for the phases, against the closure--build-filternames (35/35 turbo tasks successful):ts/tsxblockspackages/components/src/__tests__/README.mdpackages/core/src/adapters/README.mdpackages/plugin-gantt/docs/verification/README.mdsh)packages/types/src/zod/README.mdStep 2: the ledger grows by THREE, and the missing fourth is mechanical
Three
UNGATED_DOCSrows, each carrying its measured counts, its phases and what would have to change. The fourth page gets no row — and that is not an exclusion, it is this gate's own rule:packages/plugin-gantt/docs/verification/README.mdholds nots/tsxblock, so it joins the covered tier at zero blocks, and a row naming it would fail re-derivation as a stale entry ("an entry naming a file that … holds nots/tsxblock at all, fails as a stale entry").⛔ The fence triage set is honoured literally: 「不要为了让台账不变长而把四页悄悄排除在表面之外」. No page was kept out of the surface to keep the ledger short. Gate severity is untouched, and no existing row was removed.
⭐ One row is a documented-API defect rather than snippet hygiene, and it is what the widening bought immediately:
packages/core/src/adapters/README.md's custom-adapter template declaresimplements DataSource<T>while omitting the interface's requiredgetObjectSchema(TS2420), with six// Your implementationbodies under non-voidreturn annotations (TS2355 x6). A reader who copies it gets a class that does not satisfy the interface it claims. Paying these three rows down is filed as #9412.The mechanism
The leg is its own exported enumerator (
nestedPackageReadmePages/NESTED_PACKAGE_READMES) rather than arecursiveflag on the leg above it, for the two reasons the file already has precedent for:check-doc-fence-languages.test.tscompares the two gates' walks element by element. Movingcheck:doc-fences' own surface is not this card's (triage boundary 3), so the divergence is subtracted BY IMPORT, exactly as objectui#7856's threedocs/legs are — every other drift between the two walks still fails there, and a fifth nested README landing tomorrow travels into both the walk and the subtraction by itself.README.mdis not inside any of them. Nothing here has a notion of "top level". Asserted over the real tree: 0 duplicates.UNSCANNED_DIRSis load-bearing, and that is measured rather than assumedEvery other recursive leg in this file crosses an authored tree with nothing generated inside it.
packages/is not that tree:statSyncfollows symlinks, and pnpm links each package's workspace siblings under its ownnode_modules, sopackages/a/node_modules/@object-ui/bleads back intopackages/band onward. Measured on9ba7e9c3with the workspace installed — an unguarded walk does not merely overshoot, it does not terminate:README.mdreachedUNSCANNED_DIRSThe set is the same one
check-doc-links.mjswalks its disk surfaces with.Verification, on
d8d39205node scripts/check-doc-snippet-types.mjs@9ba7e9c3pnpm type-check:scriptstsc -p tsconfig.scripts.jsonvitest run scripts/__tests__/check-doc-snippet-types.test.ts scripts/__tests__/check-doc-fence-languages.test.tscheck-doc-links·check-doc-example-types·check-doc-snippet-emitted-census·check-doc-component-types·check-readme-exports·check-doc-expression-carriage·check-skill-examples·check-links-workflowcheck:doc-fences·check:doc-types·check:doc-examples·check:control-bytespnpm lint:root— the full root project, not narrowedcheck-changeset-presence·-fixed·-no-major·-claimsThe controls — each with a known direction that HITS
listDocumentswidened andcheck-doc-fence-languages.test.tsuntouched, that file went 1 failed / 30 passed on exactly the walk-equality test (expected [ …(226) ] to deeply equal [ …(230) ]) and nothing else. After extending the subtraction: 32 passed.out.push(...nestedPackageReadmePages(root))deleted; mutation proved on disk by anchoredgrep -cF1→0 plusgit hash-objectdiffering from the HEAD blob): walk falls 249 → 245 andpackages/READMEs 43 → 39; the new pins go red (2 failed); and the gate itself goes red with threestale-ungated-entryfindings — the ledger rows are a self-enforcing pin on the widening.UNSCANNED_DIRS. Removing the guard from the outer loop reds the symlink-cycle pin; removing it from the inner descent did not — so the first fixture was a pin on half the guard. That is reported rather than hidden: the second commit adds a deeper cycle atpackages/alpha/src/node_modules, after which either half reds it.trap … EXIT INT TERMrestore against an absolute repo root, and each restore verified by an emptygit diff HEADrather than by an exit code.Scope
scripts/check-doc-snippet-types.mjs, its two coupled test files, and a changeset. ⛔ No package source, ⛔ nocontent/docs/releases/, ⛔ no gate severity change, ⛔ no row removed fromUNGATED_DOCS, ⛔ no page repaired (that is #9412). The changeset has empty frontmatter:check-changeset-presencereports 0 guarded files, so nothing is released by this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code