Skip to content

fix(typecheck): ratchet public-reachable JSDoc files (SD-2833 follow-up)#3474

Merged
caio-pizzol merged 3 commits into
mainfrom
caio-pizzol/SD-typecheck-jsdoc-ratchet
May 24, 2026
Merged

fix(typecheck): ratchet public-reachable JSDoc files (SD-2833 follow-up)#3474
caio-pizzol merged 3 commits into
mainfrom
caio-pizzol/SD-typecheck-jsdoc-ratchet

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

@caio-pizzol caio-pizzol commented May 24, 2026

pnpm check:public now includes the JSDoc ratchet. A new public-reachable .js file with JSDoc that lands without // @ts-check fails 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_FILES gate (6 files run through tsc with --noEmit, unchanged) and adds a second gate over the public-reachable JSDoc surface. The script walks transitively from PUBLIC_ENTRY_FILES, flags JSDoc-typed .js files that aren't accounted for (no // @ts-check, not in CHECKED_FILES, not on the allowlist), and compares the result against the committed snapshot. Fails when:

  • a NEW public JSDoc file lands without // @ts-check and isn't on the allowlist - the contributor must add the directive (preferred) or an allowlist entry with a one-line reason
  • a STALE snapshot entry remains (file deleted, gained // @ts-check, or moved out of the public surface) - refresh with pnpm --filter superdoc run check:jsdoc -- --write

Allowlist contract is enforced

Every 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. Without this, the allowlist could silently widen the exclusion set.

Folded into check:public:superdoc

jsdoc-ratchet runs as stage 3 of scripts/check-public-contract.mjs between contract-tiers and build. The standalone Public-contract checkJs step in .github/workflows/ci-superdoc.yml is 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-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

Display-name only - no script files, package commands, or rerunnable commands changed. The wrapper still prints the actual cwd + cmd + args on 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.json records the 103 pre-existing public JSDoc files without // @ts-check so the gate fails for new debt, not existing debt. Existing files with // @ts-check already get type-checking from the main tsc -b run and don't need a second gate. The empty jsdoc-allowlist.cjs is 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.yml is removed.

Verified:

  • baseline 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 in CHECKED_FILES, plus 2 reachability-exempt checked files outside the public walk.
  • simulated new public JSDoc file (removed packages/superdoc/src/index.js from snapshot) β†’ fails with "1 new public JSDoc file(s) without // @ts-check"
  • simulated stale snapshot entry β†’ fails with refresh hint pointing at --write
  • simulated allowlist with empty reason β†’ fails with "missing or empty reason"
  • simulated allowlist with typo path β†’ fails with "file does not exist on disk"
  • simulated allowlist pointing at a non-public file β†’ fails with "no longer a public-reachable JSDoc file (allowlist entry is dead; remove it)"
  • pnpm check:public:superdoc --skip-build after rename β†’ PASS, 8 ran / 1 skipped, all nine stages print with the new display names

@caio-pizzol caio-pizzol requested a review from a team as a code owner May 24, 2026 09:42
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 24, 2026

SD-2833

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.
@caio-pizzol caio-pizzol force-pushed the caio-pizzol/SD-typecheck-jsdoc-ratchet branch from 4583da7 to 2823dc8 Compare May 24, 2026 09:55
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
@caio-pizzol caio-pizzol merged commit 3f10e1e into main May 24, 2026
38 checks passed
@caio-pizzol caio-pizzol deleted the caio-pizzol/SD-typecheck-jsdoc-ratchet branch May 24, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant