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).
In this repository17 other files it gives its agents.
- api-securitySkill
- ascii-artSkill
- code-readabilitySkill
- gotchasSkill
- llm-agent-securitySkill
- nextjs-app-routerSkill
- plain-docsSkill
- product-marketingSkill
- prompt-rewriteSkill
- react-performanceSkill
- react-testingSkill
- seoSkill
- spa-performanceSkill
- tailwind-design-systemSkill
- ux-copySkill
- wcag-accessibilitySkill
- webgpuSkill
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
- Very specific: line 37
Read the project's tsconfig.json and the installed typescript version (npx tsc --version).
- Distinctive: line 39
Read the resource for the task in the table below.
- Distinctive: line 40
Write the change. Prefer the first tool that works: narrowing, then a predicate, then a checked cast.
- Very specific: line 41
Run tsc --noEmit, and eslint if the project has typed linting.
≈ “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.
Files
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
- Read the project's
tsconfig.jsonand the installedtypescriptversion (npx tsc --version). On 5.x or 6.x with deprecation errors, start with upgrade-6-to-7.md. - 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 --noEmitfirst, and fix by module. - Read the resource for the task in the table below.
- Write the change. Prefer the first tool that works: narrowing, then a predicate, then a checked cast.
- Run
tsc --noEmit, andeslintif 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
anyleak, an uncheckedas, 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, mixedtypeandinterface. - 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.mdWhere to read
| Task | Resource |
tsconfig defaults, es2025 and dom (dom.iterable included), the baseline, flags past strict, module by runtime, the empty types default | config.md |
Upgrading to 6.0 and 7.0, removed options and syntax, ts5to6, stableTypeOrdering, running TS 6 next to 7 | upgrade-6-to-7.md |
Discriminated unions, exhaustive switches, predicates, assertion functions, unknown in catch and .catch(), unsound spots | narrowing.md |
When a type parameter earns its place, constraints, satisfies, const type parameters, NoInfer, this-less method inference | generics.md |
Utility types, the Omit trap, mapped, conditional, and template literal types, type or interface, augmentation | type-level.md |
Untyped data at the edges, branded IDs and units, readonly inputs | domain-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 needs | linting.md |
Node type stripping, erasableSyntaxOnly, verbatimModuleSyntax, #/ subpath imports, enums or as const objects, numeric reverse mapping, const enum | node-and-emit.md |
The traps that cost the most time
| Symptom | Usual cause | Read |
Cannot find name 'process' after upgrading | types defaults to [] since 6.0 | config.md |
| TS5011, common source directory | outDir is set, sources sit under src/, and rootDir is unset | config.md |
| TS5102 or TS5108, "has been removed" | An option TS 7 dropped: baseUrl, moduleResolution: node, target: es5, outFile | upgrade-6-to-7.md |
| typescript-eslint breaks after installing TS 7 | import 'typescript' on 7 is only the version. Alias TS 6 for the linter. | linting.md |
| A typed value is wrong at runtime | any from .json() or JSON.parse assigned to a typed variable | domain-types.md |
arr[0].x crashes on an empty array | noUncheckedIndexedAccess is off | config.md |
map.has(k) then map.get(k)! used for defaults | has does not narrow get. Read once with ?? or use getOrInsert / getOrInsertComputed. | escape-hatches.md |
| A new union member is silently ignored | The switch has no exhaustive check | narrowing.md |
Renamed field still listed in an Omit, no error | Omit does not check its keys | type-level.md |
A literal list types as string[] | No as const or const type parameter | generics.md |
| A wrong value is accepted by a generic function | Another argument widens T. Wrap it in NoInfer. | generics.md |
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX from Node | An enum, parameter property, or runtime namespace | node-and-emit.md |
does not provide an export named from Node | A type imported without import type | node-and-emit.md |
@/* tsconfig paths fail when Node runs .ts | Node ignores tsconfig.json. Map #/* in package.json "imports" instead. | node-and-emit.md |
| A narrowed property is wrong after a call | Narrowing is not reset by function calls | narrowing.md |
| TS2775 on an assertion function call | The function is a const with no type annotation | narrowing.md |
TS18046, 'e' is of type 'unknown' in a catch | strict makes catch variables unknown. Narrow with instanceof Error. | narrowing.md |
A typo on e in .catch((e) => ...) compiles | Rejection 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 enums | A const enum from a .d.ts under isolatedModules | node-and-emit.md |
A property is never with no error where it was declared | An intersection hid a clash that extends would report | type-level.md |
Review checklist
strict,noUncheckedIndexedAccess,exactOptionalPropertyTypes, andverbatimModuleSyntaxare on, andtarget,module, andtypesare explicit- No option or syntax removed in TS 7 remains
- Data from outside is
unknownuntil parsed, once, at the edge - No
anyin new code, including fromArray.isArray, computedimport(), and.catch()callbacks. Everyassits behind a runtime check. No@ts-ignore. catchvariables are narrowed, and onlyErrorvalues are thrown- No
const enum. New code usesas constobjects 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 --noEmitresult
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
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.

