agentleFS
LeahyCCLeahyCC/claude-skills/skills/typescript-safety/SKILL.md

typescript-safety

Skill3 starsChanged 8 months agoMITView on GitHub

Makes TypeScript catch more bugs at compile time on TS 6 and 7: tsconfig flags past strict, narrowing and discriminated unions, exhaustive switches, generics and inference (satisfies, const type parameters, NoInfer), utility, mapped, conditional, and template literal types, branded types, unknown at data edges, unknown in catch, hidden any, replacing any, as, ! and ts-ignore, enums and const enum, type or interface, typescript-eslint type-checked rules, Node type stripping and erasableSyntaxOnly, and the upgrade from TS 5 or 6 to 7. Use when editing tsconfig.json, fixing a type error, removing any or casts, designing the types for a module, or when a TS 6 or 7 upgrade breaks the build. Not for React rendering (react-performance), Next.js caching and routing (nextjs-app-router), or validating request bodies with Zod (api-security).

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read the project's tsconfig.json and the installed typescript version (npx tsc --version).

    line 37
  2. Distinctive:

    Read the resource for the task in the table below.

    line 39
  3. Distinctive:

    Write the change. Prefer the first tool that works: narrowing, then a predicate, then a checked cast.

    line 40
  4. Very specific:

    Run tsc --noEmit, and eslint if the project has typed linting.

    line 41

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TypeScript safety

Contents

  • Procedure
  • Where to read
  • The traps that cost the most time
  • Review checklist

Getting the compiler to catch bugs before they run, on TypeScript 6 and 7. TS 7.0 is the Go compiler, installed as plain typescript since July 2026. strict has been the default since 6.0, so the remaining gains come from the flags strict leaves off, from narrowing instead of casting, and from parsing data at the edges.

Types describe code, not data. Anything that enters the process (a response body, JSON.parse, storage, env vars) is unchecked until code checks it. An annotation on an any is a cast.

Every escape hatch turns off the check that would have caught the bug. No any in new code. Reach for unknown and narrow. A cast needs a runtime check next to it.

Source checks and dates: SOURCES.md.

Procedure

  1. Read the project's tsconfig.json and the installed typescript version (npx tsc --version). On 5.x or 6.x with deprecation errors, start with upgrade-6-to-7.md.
  2. Compare the config with the baseline in config.md. Turning on a flag in an old codebase can add hundreds of errors: count them with tsc --noEmit first, and fix by module.
  3. Read the resource for the task in the table below.
  4. Write the change. Prefer the first tool that works: narrowing, then a predicate, then a checked cast.
  5. Run tsc --noEmit, and eslint if the project has typed linting. Report both results, and say which TS version ran.

A single error fix or a direct question gets a direct answer: the fix or the answer, and the tsc --noEmit result if you ran it. No formal report.

A review or audit reports findings as Must fix / Should fix / Leave, with the file, the line, and the flag or rule name. Never report a problem you did not see in the code.

  • Must fix: a check is off and a wrong value can compile. An any leak, an unchecked as, a missing exhaustive check, an option TS 7 removed.
  • Should fix: the code is right today but the types will not stop the next mistake. A missing brand, a const enum, mixed type and interface.
  • Leave: a style the project already chose, or code the change does not touch.
data from outside ──> unknown ──> parse at the edge ──> precise types inside
                                  domain-types.md      narrowing.md, generics.md
                                                              |
  tsconfig flags catch the rest: config.md        types from types: type-level.md
                                                              |
  any / as / ! found? escape-hatches.md      lint what tsc cannot: linting.md

Where to read

TaskResource
tsconfig defaults, es2025 and dom (dom.iterable included), the baseline, flags past strict, module by runtime, the empty types defaultconfig.md
Upgrading to 6.0 and 7.0, removed options and syntax, ts5to6, stableTypeOrdering, running TS 6 next to 7upgrade-6-to-7.md
Discriminated unions, exhaustive switches, predicates, assertion functions, unknown in catch and .catch(), unsound spotsnarrowing.md
When a type parameter earns its place, constraints, satisfies, const type parameters, NoInfer, this-less method inferencegenerics.md
Utility types, the Omit trap, mapped, conditional, and template literal types, type or interface, augmentationtype-level.md
Untyped data at the edges, branded IDs and units, readonly inputsdomain-types.md
any, any from Array.isArray and computed import(), as, !, @ts-expect-error, and what to use instead (including Map.getOrInsert)escape-hatches.md
typescript-eslint configs and rules, and the TS 6 alias the linter still needslinting.md
Node type stripping, erasableSyntaxOnly, verbatimModuleSyntax, #/ subpath imports, enums or as const objects, numeric reverse mapping, const enumnode-and-emit.md

The traps that cost the most time

SymptomUsual causeRead
Cannot find name 'process' after upgradingtypes defaults to [] since 6.0config.md
TS5011, common source directoryoutDir is set, sources sit under src/, and rootDir is unsetconfig.md
TS5102 or TS5108, "has been removed"An option TS 7 dropped: baseUrl, moduleResolution: node, target: es5, outFileupgrade-6-to-7.md
typescript-eslint breaks after installing TS 7import 'typescript' on 7 is only the version. Alias TS 6 for the linter.linting.md
A typed value is wrong at runtimeany from .json() or JSON.parse assigned to a typed variabledomain-types.md
arr[0].x crashes on an empty arraynoUncheckedIndexedAccess is offconfig.md
map.has(k) then map.get(k)! used for defaultshas does not narrow get. Read once with ?? or use getOrInsert / getOrInsertComputed.escape-hatches.md
A new union member is silently ignoredThe switch has no exhaustive checknarrowing.md
Renamed field still listed in an Omit, no errorOmit does not check its keystype-level.md
A literal list types as string[]No as const or const type parametergenerics.md
A wrong value is accepted by a generic functionAnother argument widens T. Wrap it in NoInfer.generics.md
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX from NodeAn enum, parameter property, or runtime namespacenode-and-emit.md
does not provide an export named from NodeA type imported without import typenode-and-emit.md
@/* tsconfig paths fail when Node runs .tsNode ignores tsconfig.json. Map #/* in package.json "imports" instead.node-and-emit.md
A narrowed property is wrong after a callNarrowing is not reset by function callsnarrowing.md
TS2775 on an assertion function callThe function is a const with no type annotationnarrowing.md
TS18046, 'e' is of type 'unknown' in a catchstrict makes catch variables unknown. Narrow with instanceof Error.narrowing.md
A typo on e in .catch((e) => ...) compilesRejection callbacks are any. Write (e: unknown).narrowing.md
No error on any element after Array.isArray(x)x narrows to any[]escape-hatches.md
TS2748, cannot access ambient const enumsA const enum from a .d.ts under isolatedModulesnode-and-emit.md
A property is never with no error where it was declaredAn intersection hid a clash that extends would reporttype-level.md

Review checklist

  • strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, and verbatimModuleSyntax are on, and target, module, and types are explicit
  • No option or syntax removed in TS 7 remains
  • Data from outside is unknown until parsed, once, at the edge
  • No any in new code, including from Array.isArray, computed import(), and .catch() callbacks. Every as sits behind a runtime check. No @ts-ignore.
  • catch variables are narrowed, and only Error values are thrown
  • No const enum. New code uses as const objects over enums.
  • States with different fields are discriminated unions, and switches over them are exhaustive
  • A function type parameter appears at least twice. An inferred return counts. A type alias may use its parameter once.
  • IDs and units that can be mixed up are branded
  • typescript-eslint runs a type-checked config
  • The report names the TS version and shows the tsc --noEmit result

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.