fix(typecheck): ratchet public-reachable JSDoc files (SD-2833 follow-up)#3474
Merged
Conversation
The current check-jsdoc.cjs hard-fails only when one of the
hand-curated 6 CHECKED_FILES drifts. Every other public-reachable
.js file with JSDoc β 122 of them today β is reported but ignored.
That's a silent gate: a new ungated public JSDoc file lands, the
script prints "OK 6 gated files clean" and never mentions it.
This PR adds a ratchet so new public-reachable JSDoc files cannot
land silently, without mass-promoting the 122 existing ones (which
the user explicitly flagged as the wrong move: "Do not suddenly
gate all existing public JSDoc files. That would turn cleanup into
a giant migration.").
Design (per the user's "Better rule"):
1. CHECKED_FILES stays as the hand-curated, fully-gated set. Each
must have `// @ts-check` and pass tsc. Unchanged in this PR;
today's 6 entries are preserved as the "seed/legacy coverage."
2. Discover every public-reachable .js file with JSDoc (existing
surface walk from PUBLIC_ENTRY_FILES, transitively follows
imports through `superdoc`, `superdoc/super-editor`,
`superdoc/ui`).
3. Each discovered file must be accounted for:
- In CHECKED_FILES, OR
- Carry `// @ts-check` (gated by the main `pnpm check:types`
tsc -b run; the directive is the contributor's opt-in), OR
- On the allowlist at jsdoc-allowlist.cjs (rare; each entry
carries a one-line reason β vendored code, etc.), OR
- In the debt snapshot at jsdoc-debt-snapshot.json (the
pre-existing 103 entries; tracked for later opt-in).
4. A NEW public JSDoc file that isn't in any of those four β fail
with "add // @ts-check OR allowlist with a reason."
5. A STALE entry in the snapshot (file gone, gained // @ts-check,
moved out of public surface) β fail with "rerun --write."
Files added:
- packages/superdoc/scripts/jsdoc-debt-snapshot.json
Auto-managed; 103 entries today (current public-reachable
JSDoc files without // @ts-check, minus the 6 CHECKED_FILES,
minus 19 already carrying // @ts-check for IDE feedback).
Refreshed with `pnpm --filter superdoc run check:jsdoc -- --write`.
- packages/superdoc/scripts/jsdoc-allowlist.cjs
Hand-maintained; empty today. Format: { 'path': 'reason' }.
Verified with all four scenarios:
- Baseline (no changes): OK 6 CHECKED_FILES clean; ratchet snapshot in sync.
- Simulated NEW file (removed an entry from snapshot, ran check):
FAIL "1 new public JSDoc file(s) without // @ts-check ... Either
add `// @ts-check` to the file (preferred), or add an entry to ...
jsdoc-allowlist.cjs with a one-line reason."
- Simulated STALE entry (added bogus path to snapshot):
FAIL "1 stale entry/entries in the debt snapshot ... Run
`pnpm --filter superdoc run check:jsdoc -- --write` to refresh."
- Full umbrella: pnpm check:public β PASS end-to-end.
No CI workflow changes needed: ci-superdoc.yml already calls
`pnpm --filter superdoc run check:jsdoc`, which now invokes the
extended script. README updated to describe the two gates and the
snapshot-refresh workflow.
β¦ist contract The ratchet now runs as stage 3 of scripts/check-public-contract.mjs between tier-discipline and build:superdoc. One canonical public-interface validation path: pnpm check:public:superdoc runs the ratchet automatically for PR CI, preview release, and stable release. The standalone 'Public-contract checkJs' step in ci-superdoc.yml is removed as redundant. The allowlist contract is also enforced. Each entry in jsdoc-allowlist.cjs must carry a non-empty string reason, point at a file that exists on disk, and still resolve to a public-reachable JSDoc file. Empty reasons, typo paths, and dead entries all fail.
4583da7 to
2823dc8
Compare
Display-name only. No script files, package commands, or rerunnable commands changed. The wrapper prints the actual cwd + cmd + args on failure regardless of the display name. Old display name β New tier-discipline:test β contract-tiers-test tier-discipline β contract-tiers jsdoc-ratchet β jsdoc-ratchet build:superdoc β build typecheck-matrix β consumer-typecheck-matrix deep-type-audit --strict-supported-root β deep-type-audit-supported-root package-shape-gate β package-shape snapshot --all --check β export-snapshots check-root-classification-closure β root-classification-closure Why: the old set mixed colon-namespaced, kebab, check- prefixes, and flags-baked-into-names. Now all nine names are plain kebab, scope-only, no flags, no redundant check- prefix. CI logs are easier to scan back to source. Also documents the cheap-to-expensive ordering invariant in the wrapper docstring and refreshes AGENTS.md (eight stages β nine) and packages/superdoc/scripts/README.md (six β five wrapper stages, plus the three new policy gates at the head).
Base automatically changed from
caio-pizzol/SD-typecheck-tier-enforcement
to
main
May 24, 2026 10:17
This was referenced May 24, 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.
pnpm check:publicnow includes the JSDoc ratchet. A new public-reachable.jsfile with JSDoc that lands without// @ts-checkfails the same wrapper that already gates tier discipline, the consumer typecheck matrix, and the deep-type audit. One canonical public-interface validation path.What changed
Ratchet (
packages/superdoc/scripts/check-jsdoc.cjs)Keeps the hand-curated
CHECKED_FILESgate (6 files run through tsc with--noEmit, unchanged) and adds a second gate over the public-reachable JSDoc surface. The script walks transitively fromPUBLIC_ENTRY_FILES, flags JSDoc-typed.jsfiles that aren't accounted for (no// @ts-check, not inCHECKED_FILES, not on the allowlist), and compares the result against the committed snapshot. Fails when:// @ts-checkand isn't on the allowlist - the contributor must add the directive (preferred) or an allowlist entry with a one-line reason// @ts-check, or moved out of the public surface) - refresh withpnpm --filter superdoc run check:jsdoc -- --writeAllowlist contract is enforced
Every entry in
jsdoc-allowlist.cjsmust carry a non-empty string reason, point at a file that exists on disk, and still resolve to a public-reachable JSDoc file. Empty reasons, typo paths, and dead entries all fail. Without this, the allowlist could silently widen the exclusion set.Folded into
check:public:superdocjsdoc-ratchetruns as stage 3 ofscripts/check-public-contract.mjsbetweencontract-tiersandbuild. The standalonePublic-contract checkJsstep in.github/workflows/ci-superdoc.ymlis removed as redundant. The release workflows also pick it up automatically because they already invoke the same wrapper.Stage display-name normalization
The wrapper's nine display names previously mixed colon-namespaced, kebab,
check-prefixes, and flags-baked-into-names (deep-type-audit --strict-supported-root,snapshot --all --check). All nine are now plain kebab, scope-only, no flags:Old display name β New
tier-discipline:testβcontract-tiers-testtier-disciplineβcontract-tiersjsdoc-ratchetβjsdoc-ratchetbuild:superdocβbuildtypecheck-matrixβconsumer-typecheck-matrixdeep-type-audit --strict-supported-rootβdeep-type-audit-supported-rootpackage-shape-gateβpackage-shapesnapshot --all --checkβexport-snapshotscheck-root-classification-closureβroot-classification-closureDisplay-name only - no script files, package commands, or rerunnable commands changed. The wrapper still prints the actual
cwd + cmd + argson failure (made explicit in a comment above the stages array). The cheap-to-expensive ordering invariant is also documented now.Debt snapshot starts at 103
jsdoc-debt-snapshot.jsonrecords the 103 pre-existing public JSDoc files without// @ts-checkso the gate fails for new debt, not existing debt. Existing files with// @ts-checkalready get type-checking from the maintsc -brun and don't need a second gate. The emptyjsdoc-allowlist.cjsis kept as the discoverable extension point pointed at by the failure messages.Pairs with #3473 (tier discipline): together they pin both what is public (tier discipline) and how typed the public JS surface gets to be (this ratchet). Stacked on #3473.
Review: check the snapshot diff is empty-to-103 only and the allowlist starts empty. Confirm the wrapper docstring lists nine stages with the new display names and the CI step in
ci-superdoc.ymlis removed.Verified:
pnpm --filter superdoc run check:jsdocβOK 6 CHECKED_FILES clean; ratchet snapshot in sync. 128 public JSDoc files discovered; 25 have// @ts-check; 103 are tracked as known debt; 0 allowlisted. 4 of the public files are inCHECKED_FILES, plus 2 reachability-exempt checked files outside the public walk.packages/superdoc/src/index.jsfrom snapshot) β fails with "1 new public JSDoc file(s) without // @ts-check"--writepnpm check:public:superdoc --skip-buildafter rename β PASS, 8 ran / 1 skipped, all nine stages print with the new display names