Typescript
Agent skills that give AI agents the operational competence of expert practitioners (Salesforce, AWS, GitHub Actions, web). SKILL.md format; Claude Code plugins. Not test-prep — certification is the scaffold and benchmark, not the product.
npx -y skills add toddkasper/expert-skills --skill typescriptAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Writing and reviewing TypeScript — the type system and structural typing, generics, narrowing and inference, conditional/mapped/utility types, strictness configuration (tsconfig, the strict family), module resolution and project references, typing third-party/Node APIs, and declaration files (.d.ts). Use when adding or reviewing types in any TS codebase (Node, React, Lambdas). Framework-specific concerns live in the react/nodejs/nextjs/react-native skills. Competence skill anchored on the official TypeScript Handbook — no first-party certification.
SKILL.md
31.6 KB, as published. Nobody here has run it
TypeScript — Skills Reference
Overview
TypeScript is a statically typed superset of JavaScript maintained by Microsoft. The goal of this
skill is the working competence a strong TypeScript engineer applies: using the type system to make
illegal states unrepresentable, without fighting the compiler or hiding behind any. There is no
official TypeScript certification; competence is anchored to the TypeScript Handbook and official
release notes.
Scope of this skill: the TypeScript type system and compiler toolchain — type syntax,
inference, narrowing, generics, configuration, and declaration files. Framework usage (React JSX,
Next.js server components, Node.js APIs) is deferred to the sibling skills (react, nextjs,
nodejs).
Load this skill when… designing or reviewing type models (discriminated unions, generics, mapped types); configuring
tsconfig.json(strictness, module resolution, project references); authoring or fixing.d.tsdeclaration files; migrating a JavaScript codebase to TypeScript. Not this skill: React component and hook typing → seereact; Node.js API typing in the context of building a service → seenodejs; Next.js server-component typing → seenextjs; React Native/Expo typing → seereact-native.
Study resources and version history live in references/study-resources.md — load that file when planning a learning path or verifying a version-specific fact.
Verify steps assume nothing about your tooling — use your project's own scripts and the language toolchain (
tsc,node, the test runner, the package manager), in that order of preference.
Uncertainty & Escalation
- Always re-verify live: TypeScript compiler defaults and strictness options changed materially at 6.0 and will change again at 7.0 — always check the installed TypeScript version (
npm ls typescript) and compare against the release notes.[volatile — verify live]marks apply to:strictdefault (wasfalsebefore TS 6.0);module/targetdefaults (changed in TS 6.0);typesdefault (changed from auto-load all to[]in TS 6.0);--moduleResolution nodedeprecation timeline (deprecated 6.0, removal in 7.0 —[volatile — verify live]);consttype parameters (TS 5.0+ only); TypeScript 7.0 Go-native compiler timeline and option removal (Beta shipped 2026-04-21; stable expected ~June 2026 —[volatile — verify live]). - Live wins: the installed TypeScript version's actual compiler behavior and typescriptlang.org are authoritative over this file → log discrepancies via Feedback protocol below.
- Escalate to a human: major TypeScript version upgrades in production monorepos (breaking defaults can silently change inferred types); removing
strict: falseoverrides across a large legacy codebase (compile errors may cascade); dependency major-version bumps; production deploys after tsconfig changes. - Confidence taxonomy: facts in this file are stable unless tagged
[volatile — verify live]or[opinion — house style].
TypeScript 6.0 — Breaking-change release (March 2026) [volatile — verify live]
TypeScript 6.0 shipped March 2026 as the last release compiled by TypeScript itself. It changed nine defaults at once. Know these before touching a tsconfig in 2026.
| Option | Old default | New default (6.0) |
|---|---|---|
strict | false | true |
module | commonjs | esnext |
target | es5 | es2025 |
rootDir | inferred | . (tsconfig directory) |
types | ["*"] (all @types) | [] (none) |
noUncheckedSideEffectImports | false | true |
libReplacement | true | false |
Removed entirely in 6.0: --outFile, --module amd/umd/systemjs/none,
--moduleResolution classic, legacy module keyword (use namespace),
import assertions with assert (use with).
Deprecated in 6.0 (will be removed in 7.0): target: es5, --downlevelIteration,
--baseUrl, --moduleResolution node (node10), --esModuleInterop false,
--allowSyntheticDefaultImports false. Still usable in 6.0 via "ignoreDeprecations": "6.0";
must be cleaned up before adopting 7.0.
types: [] default is a stealth breaking change: any project that relied on auto-loaded
@types/node, @types/jest, etc., must now list them explicitly:
{ "compilerOptions": { "types": ["node", "jest"] } }
TypeScript 7.0 (Go-native compiler) [volatile — verify live] Beta shipped 2026-04-21; stable
expected ~June 2026 (RC a few weeks prior). Will remove everything deprecated in 6.0 and is
expected to deliver ~10× build speedups. All 6.0-deprecated options must be cleaned up before
adopting 7.0.
1. Type-system fundamentals & narrowing
Structural typing
TypeScript uses structural (duck) typing — a type is compatible if its shape matches, regardless of nominal origin. Consequence: two interfaces with identical properties are interchangeable even if declared in different files or by different authors. This is intentional and correct; do not work around it with phantom properties unless you genuinely need nominal distinctions.
Literal types and widening: const x = "blue" infers type "blue"; let x = "blue" widens
to string. Add as const to freeze an object's values to their literal types:
const DIRECTIONS = ["north", "south", "east", "west"] as const;
type Direction = typeof DIRECTIONS[number]; // "north" | "south" | "east" | "west"
Template literal types compose string unions at the type level:
type EventName = `on${Capitalize<"click" | "focus">}`; // "onClick" | "onFocus"
Narrowing
TypeScript's control-flow analysis narrows union types as code branches. The narrowing tools, in order of preference:
| Technique | When to use |
|---|---|
typeof x === "string" | Primitives |
x instanceof Date | Class instances |
"swim" in x | Structural property presence |
Discriminated union (kind field) | Modeling mutually exclusive states |
Type predicate (x is Fish) | Custom guard, isolates runtime check |
Assertion function (asserts x is T) | Throws on failure, narrows after call |
never exhaustiveness check | Compile-time guarantee all union arms are handled |
Discriminated unions are the primary modeling tool for state machines and variant data. Use a
shared literal-typed kind (or type, tag) field; the compiler then narrows each branch
automatically in switch/if.
Exhaustiveness pattern — assign to never in the default branch so adding a new union member
causes a compile error in every switch that forgets to handle it:
function assertNever(x: never): never {
throw new Error("Unhandled case: " + x);
}
satisfies vs as: use satisfies to validate an object against a type while preserving the
most-specific inferred type (e.g., config objects, discriminated union literals). Use as only
when you genuinely know more than the compiler (e.g., DOM refs, casting from an untyped API
response after manual validation). as bypasses type checking entirely; satisfies does not.
2. Generics & advanced types
Generics
Generics are parameterized types. Keep them simple: one parameter with a clear constraint is almost always better than three unconstrained parameters.
Constraints with extends: <T extends { length: number }> means "T must have at least
length." Constraints make generics useful without over-broadening.
const type parameters (TS 5.0+): <const T> infers literal types without requiring callers
to write as const:
function first<const T extends readonly unknown[]>(arr: T): T[0] { return arr[0]; }
When generics hurt: a generic function where every call site has T = the same concrete type is not gaining anything. Generics earn their complexity only when the relationship between input and output types (or between two parameters) genuinely varies per callsite.
Conditional types
T extends U ? X : Y — branch at the type level. Distributive by default over union T:
type Unwrap<T> = T extends Promise<infer U> ? U : T.
infer extracts a type from a pattern inside extends: use it to pull out return types,
element types, or parameter types. The built-in utility types ReturnType<F>,
Parameters<F>, Awaited<T> are implemented this way.
Avoid deeply nested conditional chains. More than two levels of nesting is a maintenance problem; consider a lookup table (mapped type over a union) instead.
Mapped types
Transform every key in a type:
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
Key modifiers: readonly, ? (optional), -readonly (remove readonly), -? (remove optional).
Remap keys with as: { [K in keyof T as Capitalize<string & K>]: T[K] }.
Utility types — the ones engineers reach for most
| Utility | What it does |
|---|---|
Partial<T> | All props optional |
Required<T> | All props required |
Readonly<T> | All props readonly |
Pick<T, K> | Keep only keys K |
Omit<T, K> | Drop keys K |
Record<K, V> | Object with keys K and values V |
Exclude<T, U> | Union T minus U |
Extract<T, U> | Union T intersected with U |
NonNullable<T> | Remove null/undefined |
ReturnType<F> | Return type of function F |
Parameters<F> | Parameter tuple of function F |
Awaited<T> | Unwrap Promise (recursive) |
3. Configuration & strictness
The strict flag hierarchy
"strict": true (now the 6.0 default) enables nine sub-flags:
| Sub-flag | What it catches |
|---|---|
noImplicitAny | Variables/params inferred as any |
strictNullChecks | null/undefined not assignable to non-nullable types |
strictFunctionTypes | Contravariant param checking for function types |
strictBindCallApply | Correct argument types for .call/.bind/.apply |
strictPropertyInitialization | Class props must be assigned in constructor |
noImplicitThis | this typed as any in functions |
alwaysStrict | Emits "use strict" |
useUnknownInCatchVariables | Catch variable typed unknown not any |
strictBuiltinIteratorReturn | Built-in iterator TReturn is undefined not any (TS 5.6+) |
Opt-in beyond strict (not yet defaults, but strongly recommended):
| Flag | Why |
|---|---|
noUncheckedIndexedAccess | arr[i] returns T | undefined, forces index bounds awareness |
exactOptionalPropertyTypes | { x?: T } disallows explicit undefined assignment; cleaner option semantics |
noImplicitOverride | Must write override keyword when overriding a base method |
noUnusedLocals + noUnusedParameters | Dead code detection at compile time |
noFallthroughCasesInSwitch | Catches missing break |
noImplicitReturns | All code paths must return a value |
Gradual adoption: on a legacy JS→TS migration, set strict: false and enable flags one at a
time, starting with strictNullChecks (highest value/effort ratio). Never leave strict: false
on new code.
Module resolution
| Setting | Use when |
|---|---|
"module": "nodenext" + "moduleResolution": "nodenext" | Node.js packages with ESM or dual CJS/ESM |
"module": "esnext" + "moduleResolution": "bundler" | Bundler-based apps (Vite, webpack, esbuild) |
"module": "esnext" + "moduleResolution": "nodenext" | Not valid — do not mix |
--moduleResolution node (node10) is deprecated in 6.0; migrate to nodenext or bundler.
"moduleResolution": "bundler" is the right choice for any project that goes through a bundler;
it does not enforce the .js extension requirement that nodenext requires.
--module node20 (introduced TS 5.9) [volatile — verify live]: stable, non-floating alias for Node 20 behavior with
--target es2023 implied. Prefer over nodenext when pinning to Node 20 specifically.
Project references
Large monorepos should use project references ("composite": true, "references": [...]) to
enable incremental type checking per package. Requirements: "composite": true implies
"declaration": true. Add "declarationMap": true to enable jump-to-source across package
boundaries. Use tsc --build (or tsc -b) to build the reference graph.
4. Typing the outside world
Third-party @types packages
As of TypeScript 6.0, "types": [] is the default — @types packages are not auto-loaded.
List every needed @types package explicitly in tsconfig.json under "types".
Resolution: TypeScript looks for @types/<package> automatically when you import "package". If
no @types package exists and the library ships no .d.ts, you must author one or use
declare module "package-name" as a stub (document this as technical debt).
DefinitelyTyped (github.com/DefinitelyTyped/DefinitelyTyped) hosts community-maintained
declarations for packages that don't ship their own types. Library authors writing in TypeScript
should ship .d.ts files directly (via "declaration": true) rather than relying on
DefinitelyTyped.
Declaration files (.d.ts)
Write .d.ts files when:
- Typing an existing plain-JS library with no types.
- Publishing a TS library and exposing its public API to consumers.
- Augmenting a third-party module's types (module augmentation).
Rules for .d.ts files:
- Declare only what the JS actually exports. Phantom types that don't exist at runtime cause runtime errors.
- Use
export declare(notexport) in module declaration files. - For global augmentation use
declare global { ... }inside a module file. - For module augmentation use
declare module "library" { ... }in a.d.tsfile. - Prefer
interfacefor extensible shapes (consumers can merge),typefor unions/tuples/aliases.
When any and type assertions are justified
any legitimate uses (very short list):
- Interop with an API that genuinely returns unstructured data (JSON from an unknown schema).
- Incremental JS→TS migration: annotate as
anyexplicitly, track with// TODO: type this. - Type-level utilities that must accept anything (e.g. the argument to
JSON.stringify).
any red flags:
(x as any).someMethod()to silence a compile error that would catch a real bug.as unknown as TargetTypedouble-cast — almost always a type model problem.- Returning
anyfrom a public function's signature.
For data crossing a runtime boundary (API responses, JSON.parse, environment variables), use a
runtime validator (Zod, Valibot, etc.) and let the validator produce the typed output — do not
cast raw parsed data with as.
Executable Workflows
Workflow 1 — Type an external/Node API safely (runtime validator at boundary → narrow → honest .d.ts)
- Identify the trust boundary: any value arriving via
JSON.parse,fetch().then(r => r.json()),req.body,process.env, or a third-party callback isunknownat the boundary. - Define a Zod (or Valibot) schema that matches what the API actually returns — not what you hope it returns. Use
.parse()to validate; on failure it throws aZodErrorwith field-level diagnostics. → gate: call.parse()with a deliberately malformed payload; confirm it throwsZodError, not a silentundefined. - Let the validator infer the TypeScript type:
type MyPayload = z.infer<typeof MyPayloadSchema>. Do not write thetypeseparately and then cast — the schema is the single source of truth. - If you must author a
.d.tsfor a plain-JS library, load the library in a Node.js REPL and inspectObject.keys(require('the-lib'))and the prototype chain before declaring anything. → gate:node -e "const lib = require('the-lib'); console.log(typeof lib.method)"— only declare methods that return'function'. - Publish the
.d.tsalongside the JS output by setting"declaration": trueand"declarationMap": trueintsconfig.json. → gate:tsc --buildproduces.d.tsand.d.ts.mapfiles indist/; consumers can jump-to-source across the package boundary.
Workflow 2 — Tighten strictness incrementally on a JS→TS codebase
- Check the installed TypeScript version (
npm ls typescript). If on TS 6.0+,strict: trueis already the default — confirm the project'stsconfig.jsonis not explicitly setting"strict": false. → gate:tsc --showConfig | grep strictreflects the actual effective value. - If
strict: falseis set and the codebase is large, enable sub-flags one at a time in order of value/effort ratio:strictNullChecksfirst (highest yield — catches most real bugs), thennoImplicitAny, then the rest of the strict family. - After enabling each flag, run
tsc --noEmit 2>&1 | wc -lto count new errors. Fix or explicitly type-assert with a// TODO: type thiscomment — never add// @ts-ignorewithout a ticket. → gate: error count decreases monotonically across commits; no// @ts-ignorewithout a linked tracking note. - Add
noUncheckedIndexedAccessandexactOptionalPropertyTypesafter the strict family is clean — these are the highest-value non-strict additions. → gate:tsc --noEmitexits 0 with both flags enabled. - Add
"noUnusedLocals": trueand"noUnusedParameters": truein a CI-onlytsconfig.ci.jsonthat extends the base. Run it in CI to catch dead code before review without blocking local development. → gate: CItsc -p tsconfig.ci.json --noEmitexits 0.
Workflow 3 — Model a discriminated union with exhaustiveness checking
- Define a shared literal-typed discriminant field (
kind,type, ortag) on every variant:{ kind: 'circle'; radius: number },{ kind: 'rect'; w: number; h: number }. The discriminant must be a string literal, not a generalstring. → gate:tscnarrows correctly in aswitch (shape.kind)block without type assertions. - Combine variants into a union type:
type Shape = Circle | Rect | Triangle. - Write a switch over the discriminant. In the
defaultbranch, assign tonever:const _exhaustive: never = shape. → gate: add a new union member (Square) without adding a case —tscreports "Type 'Square' is not assignable to type 'never'" immediately. - If the switch is in a function that must return a value, throw in the default:
throw new Error('Unhandled shape: ' + (shape as any).kind). This satisfiesnoImplicitReturnsand provides a runtime safety net for values that enter the union after a bad cast. - Use
satisfies(notas) when constructing union members to validate the literal shape while preserving the inferred specific type. → gate: attemptingconst s = { kind: 'circle' } satisfies Shapewith a missingradiusfield produces a compile error, not a runtime one.
Decision Scenarios
Scenario 1 — A .d.ts declaration file promises a method that the underlying JS library does not export
Situation: A developer hand-writes a
.d.tsfile for an untyped legacy charting library. They add achart.destroy()method to the declaration because they saw it mentioned in an old README. After shipping, users callchart.destroy()and get a runtimeTypeError: chart.destroy is not a functionon the current version of the library.
Competent move: Audit the actual JS runtime export before declaring it. Load the library in a Node.js REPL and inspect its prototype, or read its source, before adding anything to the
.d.ts. The declaration file must faithfully describe what the JS actually does — phantom declarations give TypeScript false confidence and shift the error from compile time to runtime.
Tempting-but-wrong: Adding the declaration and leaving a
// TODO: verify this method existscomment, deferring the audit to later. Until the comment is resolved, every caller that useschart.destroy()compiles cleanly but crashes at runtime. "Works in TypeScript" gives no runtime guarantee when the declaration is a lie.
Verify: In a test file, import the library and call the declared method in isolation. Run
node test.js(nottsc) — the runtime will immediately throw if the method doesn't exist. Only after the runtime test passes should the declaration be considered correct.
Scenario 2 — Structural typing allows an OrderId to be passed where a UserId is expected
Situation: A codebase uses
type UserId = stringandtype OrderId = string. Both are aliases forstring. A functiongetOrder(userId: UserId, orderId: OrderId)is called in one place with the arguments reversed —getOrder(orderId, userId). TypeScript emits no error and the bug reaches production.
Competent move: Use branded (nominal) types for distinct ID domains. A minimal brand:
type UserId = string & { readonly __brand: 'UserId' }. Assign them via constructor functions:function toUserId(s: string): UserId { return s as UserId }. NowUserIdandOrderIdare structurally distinct and the compiler catches transposed arguments.
Tempting-but-wrong: Adding a code comment like
// first arg must be userIdand relying on review. Comments do not survive refactoring. A structural type alias (type UserId = string) provides no compiler protection whatsoever — both types are assignment-compatible with each other and withstring.
Verify: After branding, swap the arguments intentionally and run
tsc. The compiler should report "Argument of type 'OrderId' is not assignable to parameter of type 'UserId'." This is the compile-time guarantee the plain type alias could not provide.
Scenario 3 — Overly broad generic constraint lets any object through instead of enforcing a minimum shape
Situation: A generic utility
function pluck<T, K extends keyof T>(obj: T, key: K): T[K]is being used correctly. A developer then writes a new utilityfunction merge<T>(a: T, b: T): Tfor merging config objects. Code review flags theTconstraint as too broad — the function is called with non-plain-object values like arrays and class instances.
Competent move: Add a constraint that matches the intended usage:
function merge<T extends Record<string, unknown>>(a: T, b: T): T. This narrowsTto plain object types and prevents accidental calls with arrays, primitives, or class instances, all of which would compile without the constraint. The constraint communicates intent and catches misuse at the callsite.
Tempting-but-wrong: Removing generics entirely and typing the parameters as
object.objectexcludes primitives but is too broad — it includes arrays and class instances — and the return type loses the specific shape ofT, forcing callers to cast.
Verify: Attempt to call
merge([1, 2], [3, 4])with and without theRecord<string, unknown>constraint. Without it, the call compiles silently. With it, TypeScript reports "Argument of type 'number[]' is not assignable to parameter of type 'Record<string, unknown>'."
Scenario 4 — Runtime boundary: unknown API response cast with as instead of a runtime validator
Situation: A service receives a webhook payload, parses it with
JSON.parse, and immediately casts:const event = JSON.parse(body) as WebhookPayload. The type checks pass. In production, a vendor changes the payload schema and omits a required field — the code silently proceeds withundefinedwhere a string is expected, causing corrupt database writes.
Competent move: Use a runtime validator (Zod, Valibot, or similar) to parse and validate the payload at the boundary:
const event = WebhookPayloadSchema.parse(JSON.parse(body)). The validator throws on schema mismatch, turning a silent corruption into an explicit 400/500 error. The TypeScript type ofeventis then inferred from the schema — noascast needed.
Tempting-but-wrong: Adding defensive
if (event.field !== undefined)checks throughout the business logic downstream. This scatters validation across the codebase and is easy to miss for new fields. Validating at the single entry point (the boundary) catches all failures in one place regardless of how many fields the payload has.
Verify: Define a Zod schema that matches the expected payload. Call
.parse()with a deliberately malformed payload (missing a required field). Confirm the validator throws aZodErrorwith a clear field-level message. Then confirm the correctly shaped payload parses cleanly and the inferred type matches.
Scenario 5 — unknown vs any in an error-handling utility at an API edge
Situation: A shared
handleError(err: any)utility function is used throughout the codebase to log and format errors. A junior engineer proposes changingerr: anytoerr: unknownbut a teammate objects that doing so will break every call site whereerr.messageis accessed directly.
Competent move: Change the parameter to
unknownand add a type guard at the top of the function:const message = err instanceof Error ? err.message : String(err). This is exactly the right design for a shared error handler — errors from any source (rejected Promises, thrown strings, legacy code) can have any shape.unknownforces the narrowing to happen inside the utility, once, rather than spreading unchecked.messageaccess across every call site.
Tempting-but-wrong: Keeping
err: anyto avoid touching existing code.anypropagates upward — every downstream expression derived fromerralso becomesany, silently disabling type checking on anything the error touches. A singleunknown+ guard in the utility is a smaller and safer change thananyleft in place indefinitely.
Verify: With
err: unknownin place, attempt to accesserr.messagedirectly (without a guard) inside the utility.tscshould report "Property 'message' does not exist on type 'unknown'." After adding theinstanceof Errorguard, the access compiles and the check is explicit.
Operational Rules Quick Reference
- DO model with discriminated unions (shared
kindliteral field) for mutually exclusive states — the compiler narrows automatically. - DO use
satisfiesto validate config/literal objects against a type while keeping the inferred literal shape; prefer it overasfor object initialization. - DON'T use
asto silence a type error — fix the type model. Reserveasfor genuine type-narrowing the compiler can't see (post-validation casts, DOM refs). - DON'T use
anywithout a comment explaining why; preferunknownand narrow it. - DO enable
strict: trueon all new code (it is now the 6.0 default). LayernoUncheckedIndexedAccessandexactOptionalPropertyTypeson top for stricter semantics. - DON'T fight
noUncheckedIndexedAccessby casting — add bounds checks or use.at()with a default. - DO assert exhaustiveness in
switchover a discriminated union by assigning the default tonever; this turns a missing case into a compile error. - DON'T use TypeScript
enumin new code — preferas constobjects + a derived union type (smaller bundle, works witherasableSyntaxOnly, no runtime artifact). - DO use
consttype parameters (<const T>) to infer literal types from call-site values without requiringas constat the callsite (TS 5.0+). - DO use
--module nodenext(ornode20) +"moduleResolution": "nodenext"for Node packages; use--module esnext+"moduleResolution": "bundler"for bundled apps. - DO explicitly list
"types": ["node"](or whichever @types you need) in tsconfig — the 6.0 default is"types": [], so nothing is auto-loaded. - DON'T use 6.0-deprecated options (
--baseUrl,--moduleResolution node,target: es5,--downlevelIteration) in new projects; they will be removed in TypeScript 7.0. (--outFileand--moduleResolution classicwere removed outright in 6.0 — do not use at all.) - DO add
"declarationMap": truein library packages so consumers can jump-to-source. - DON'T cast
JSON.parse(...)withas MyType— use a runtime validator (Zod/Valibot) and let the validator produce the typed output. - DO keep
.d.tsdeclarations honest: declare only what actually exists at runtime. - DON'T add generics for the sake of flexibility if every callsite resolves T to the same concrete type — they add noise without benefit.
- DO put
noUnusedLocals: trueandnoUnusedParameters: truein CI configs to catch dead code before review.
Red flags in code review
- A function returning
anyin a public API signature. as unknown as Tdouble-cast (structural lie — find the real type mismatch).- Enum declarations (prefer
as const+ derived union). - Array index access
arr[i]used without a bounds check, especially whennoUncheckedIndexedAccessis not enabled. - An optional property
foo?: stringused everywhere asfoo?: string | undefined— withexactOptionalPropertyTypesthese differ. - A
catch (e)block castinge as Errorwithout checking — TS 6.0 makese: unknownthe default; narrow it withinstanceof Error. .d.tsfile declaring methods or properties the underlying JS library does not actually export.- A
tsconfig.jsonthat still has"moduleResolution": "node"(node10) or--baseUrl— both deprecated in 6.0, removed in 7.0. JSON.parse(rawInput) as SomeTypewithout a runtime validation step.
Feedback protocol
Using this skill and hit a wall? If you find a claim contradicted by the live system or official docs, a missing rule that cost you a wrong attempt, or a decision this skill gave no criteria for — append an entry in the moment to .skill-feedback/typescript.md at the project root (create it if absent):
date | skill last-reviewed | claim or gap | what you observed instead | evidence (error text / doc URL / query output) | suggested fix
These are harvested back into the skill via the learning loop. When the live system and this file disagree, trust the live system.
Changelog
- 2026-06-10 — Cycle-4 curation (inbox): (1) TS 7.0 timeline corrected — Beta 2026-04-21, stable ~June 2026 (not "late 2026"); (2)
target: es5and--downlevelIterationmoved from "Removed in 6.0" to "Deprecated in 6.0" (still usable viaignoreDeprecations; removed in 7.0); (3) strict sub-flag count corrected 8→9, addedstrictBuiltinIteratorReturn(TS 5.6); (4)libReplacementdefaulttrue→falseadded to 6.0 defaults table; Operational Rules updated to match. Four held-out eval probes added (situations 13–16).last-reviewedset to 2026-06-10. - 2026-06-09 — Conformed to the 12-dimension skill standard: task-vocab description + Scope block, Uncertainty & Escalation guidance with inline
[volatile — verify live]marks, executable workflows, tool-agnostic verify steps, and the feedback protocol above.last-reviewedset to 2026-06-09.
Independent educational content to upskill AI agents. TypeScript is a trademark of Microsoft. Not affiliated with or endorsed by Microsoft. Guidance only — verify against official documentation at typescriptlang.org.