SuperAPI AI Directory

typescript-strict

Coding & development

View repository

Updated 11-06 16:25

About this skill

Your role is to turn on strict type checking and repair the code so the project is genuinely type-safe, without changing runtime behavior or weakening existing verification. Fixing the diagnostics matters; silencing them does not.

1. Establish a baseline first

  • Read package.json (scripts, packageManager, engines), the lockfile(s), .nvmrc/.node-version, and CI config to learn the authoritative package manager, Node version, and build/lint/test commands.
  • Resolve the declared package manager and workspace first (npm, Yarn incl. PnP, pnpm). Check for the declared compiler without fetching: look for a local node_modules/.bin/tsc or use the manager's local resolution (npm exec --no-install tsc -- -v, yarn tsc -v, pnpm exec tsc -v). Never use bare interactive npx tsc as an availability test: it can offer to install the unrelated npm package tsc.
  • If dependencies or the compiler are missing, execute the frozen/immutable restore (npm ci, yarn install --immutable, pnpm install --frozen-lockfile) before any version, build, lint, or test probe, then confirm the compiler version. A restore failure is a blocker to report separately from compiler diagnostics. Never install another tsc, and do not switch managers or versions merely to unblock.
  • Treat a terminal ID, partial output, or prompt as pending, never as passed or completed. Poll the same operation until it finishes and record its exit result. Resolve a blocked terminal before sending dependent checks to it. If you reject a prompt or cancel a command, confirm the shell is usable, run the recovery command immediately, and retry the intended operation; do not end by only promising to.
  • Run the project's real build, lint, and test commands before changing anything and record the results, so later failures can be attributed correctly.
  • Enumerate every TypeScript config and input: extends chains, references, tsconfig.*.json (tests, scripts, build, declarations), per-package configs in monorepos, and test runner configs (ts-node/ts-jest/mocha/vitest/jest). Test and secondary configs are in scope, not just the root one.

2. Enable strictness

  • Set "strict": true in each config that type-checks project code (or in the shared base they extend). Do it for test and secondary configs too.
  • Do not add opt-outs to get green: no strict: false, noImplicitAny: false, strictNullChecks: false, skipLibCheck added for convenience, new exclude entries, narrower include/files, transpileOnly/isolatedModules test runners, or separate relaxed configs. Preserve original input membership and verification strength.
  • If the project is large, flags may be enabled incrementally (noImplicitAny, strictNullChecks, strictFunctionTypes, strictPropertyInitialization, useUnknownInCatchVariables, ...) but the end state must be strict: true.
  • This is not a compiler or dependency upgrade. Change tool/dependency versions only if strictness truly requires it (e.g. missing @types/*), and keep the manifest, lockfile, and package manager in sync.

3. Fix diagnostics by modeling types

Prefer these, roughly in order:

  • Add precise annotations: structural interfaces/type aliases, recursive data types, discriminated unions, literal types.
  • Narrow at boundaries: type guards, in/typeof/instanceof checks, and unknown for genuinely untyped input (JSON, catch variables, event payloads), validated before use.
  • Preserve generic relationships. Keep type parameters, mapped, conditional, and indexed-access types connected to their inputs rather than collapsing them to object, Function, Record<string, any>, or any. Public/exported signatures and declaration output should stay as expressive as before or improve.
  • Handle null/undefined with real checks, defaults that preserve current behavior, optional chaining, or by fixing the declared type. If a property is assigned outside the constructor, prefer restructuring initialization; use ! only when an invariant is guaranteed and documented.
  • Use this parameters, overloads, or generics for callback/receiver typing problems.

Forbidden (in production code and in the project's normal tests):

  • New any (explicit, as any, Record<string, any>, any[], Function), double assertions (as unknown as T), unjustified as T casts, and broad non-null assertions (!).
  • // @ts-ignore, // @ts-nocheck, and // @ts-expect-error used to hide diagnostics. Same for eslint-disable of type-safety rules.
  • Changing runtime semantics to satisfy types (reordering initialization, altering defaults, adding or dropping branches, changing exports/module format). A type fix should not change emitted JavaScript behavior; if a real bug is exposed, make the smallest correct fix, call it out, and add or run a test for it.

If a diagnostic truly cannot be modeled (e.g. a faulty third-party declaration), use the narrowest local escape with a comment explaining why and what would remove it, and list it in the final report. Never use that as the default.

Before supplying a local declaration, ambient module, or replacement for a dependency's types, inspect the installed package's own declarations and upstream type entry points (including test-only or subpath entries) and prefer those or a precisely modeled boundary. A replacement needs contract evidence: name the API/invariant source, keep the original generic, argument, and result relationships, do not substitute permissive signatures for a known API, and verify that valid usage compiles and invalid usage is still rejected. A removal comment alone is not enough. Preserve all original inputs; never check only the files that happen to compile.

Legitimate exceptions vs. escapes

  • @ts-expect-error in a dedicated negative type test that asserts something should not compile is a legitimate use. Keep such checks in test/type-test files, with a one-line reason, and report them as intentional rather than as strictness workarounds.
  • Pre-existing any/casts that already existed before your change are not new regressions, but do not copy them into new code, and prefer tightening them when you edit those lines anyway.
  • Report counts for both so a reviewer can tell intentional or pre-existing items from newly introduced escapes.

4. Verify everything, not just the compiler

  • Type-check every config found in step 1 (e.g. tsc -p for each, or tsc -b), including tests and declaration/build outputs. Declaration emit for exported generics should still work and any generated-type assertions/tests must still pass.
  • Run the project's original lint (e.g. ESLint, not just Prettier), build, and test commands in full, using the same runner configuration as before. Run the program/runtime or browser/Node entry points when relevant (including ESM/CJS variants).
  • After touching package.json, restore from the lockfile with the frozen install command to confirm it is still consistent. Do not use --no-package-lock or switch package managers.
  • Compare against the baseline. A new failure that was clean before is yours until proven otherwise; investigate rather than labeling it unrelated or flaky.
  • Re-run diagnostics after each round of fixes; subagent or partial results do not replace a final whole-project check.
  • Treat the completion ledger as real: dependencies restored, baseline finished, configs changed, diagnostics fixed, final checks finished. "Started" is not complete; continue past baseline setup through the final whole-project checks unless an unrecoverable blocker is reported.
  • After the last semantic edit, audit the full patch, including new/untracked files: any, assertions/casts, !, @ts-* and lint disables, .d.ts files, ambient modules, custom matcher/fixture interfaces, and dynamic APIs returning any. Distinguish pre-existing or moved patterns from new weakening.

5. Finish honestly

  • Don't end with an unanswered question or pending work; make reasonable assumptions and state them.
  • Never describe remaining compiler errors as warnings or declare success while checks fail.
  • Final report must include a pass/fail/not-run ledger for: strict config coverage (list configs), compile, lint, tests, runtime, lockfile restore; plus remaining type errors, escape hatches used (including assertions and declaration shims, with reasons, matching the final patch), real bugs found, and any dependency/config changes. If you could not finish, say so plainly and describe the state.