# Pathrule Pattern: TypeScript Strict (1.0.0)
# ::pathrule:package:typescript-strict

### [RULE] Turn on full strict mode and keep it on  (path: /)
<!-- scope: project | priority: high | strict -->

Strict mode is where almost all of TypeScript's bug-catching lives. A codebase with strict off is using a fraction of the tool.

- Set `"strict": true` in tsconfig, and add the flags strict does not cover: `noUncheckedIndexedAccess` (array/object index access may be undefined), `exactOptionalPropertyTypes`, and `noImplicitOverride`.
- Do not turn strict flags off to make legacy code compile. Fix the code, or isolate the legacy area, but keep the default for new code strict. Loosening the global config quietly disables safety everywhere.
- Enable `noImplicitAny` (part of strict) so a value with no inferable type is an error, not a silent `any`. An unfixable inference gap should be an explicit, searchable annotation, not an implicit hole.
- Treat type errors as build failures in CI (`tsc --noEmit` as a gate); a green build should mean the types actually check.

---

### [RULE] Ban any and the escape hatches  (path: /src)
<!-- scope: folder | priority: high | strict -->

`any`, `as`, and `!` all tell the compiler to stop checking. Each one is a place a runtime bug can walk straight through a green build.

- Do not use `any`. For a value whose type you do not know, use `unknown` and narrow it with checks before use. `unknown` forces you to prove the type; `any` lets you skip it.
- Avoid `as` type assertions, especially `as SomeType` and the `as unknown as T` double-cast. An assertion overrides the compiler's judgment with yours, and yours can be wrong. Prefer a type guard or a validated parse.
- Avoid the non-null assertion `!`. If a value can be null/undefined, handle that case; asserting it away is exactly how `cannot read property of undefined` reaches production.
- When you genuinely must use an escape hatch (interop, a library type gap), isolate it behind a small, well-named, commented function so the unsafe surface is one reviewable spot, not scattered through the code.

---

### [RULE] Model variant state with discriminated unions  (path: /src)
<!-- scope: folder | priority: medium | advisory -->

When several fields are 'sometimes present together', optional fields let the compiler accept combinations that can never actually happen. A discriminated union makes the illegal states unrepresentable.

- Model mutually exclusive states as a union with a common literal discriminant: `type State = { status: 'loading' } | { status: 'error'; error: Error } | { status: 'success'; data: Data }`. The presence of `data` or `error` is tied to the status, so you cannot have an error with data.
- Switch on the discriminant; TypeScript narrows each branch to exactly the fields that exist there. No optional-field guessing, no `data!`.
- Add an exhaustiveness check (a `never` default case) so adding a new state forces every switch to handle it, turning a missed case into a compile error.
- Prefer making bad data unrepresentable over validating it at runtime everywhere: if the type cannot express the illegal combination, you do not need a check for it.

---

### [RULE] Validate untrusted input at the boundary  (path: /src)
<!-- scope: folder | priority: high | advisory -->

Types are erased at runtime. Declaring that a fetch returns `User` does not make the data a `User`; it just tells the compiler to assume so. At any boundary with the outside world, that assumption is a lie waiting to break.

- Validate data crossing a trust boundary (HTTP responses, request bodies, `JSON.parse` results, `localStorage`, environment variables, message payloads) with a runtime schema validator (Zod, Valibot, or similar). Parse, then use the parsed result.
- Infer the static type from the schema (`type User = z.infer<typeof UserSchema>`) so there is one source of truth and the runtime check and the compile-time type can never drift apart.
- Do not cast an `await res.json()` to a type and move on; that is an `as` in disguise across the most dangerous boundary. Parse it.
- Inside the program, after the boundary, you can trust the types; the validation at the edge is what earns that trust.

---

### [MEMORY] Type conventions: inference, readonly, and narrow types  (path: /src)

Beyond the hard rules, a few conventions keep the types honest and readable.

- Let inference do its job: annotate function parameters, return types of exported/public functions, and boundaries; do not annotate every local variable the compiler can infer correctly.
- Prefer the narrowest type that fits: literal unions (`'sm' | 'md' | 'lg'`) over `string`, `readonly` arrays and properties for data that should not be mutated, and `as const` for fixed literal data.
- Use `type` aliases for unions, intersections, and function types; `interface` for object shapes that may be extended or implemented. Pick one convention and stay consistent.
- Avoid TypeScript `enum`; prefer a union of string literals (optionally with `as const` objects), which is simpler, tree-shakeable, and does not emit runtime code.
- Use utility types (`Pick`, `Omit`, `Partial`, `Record`) to derive related types from a single source rather than redeclaring shapes that can drift.

See /src for the no-any, discriminated-union, and boundary-validation rules and / for the strict-config rule. For React-specific typing see the react-typescript pattern.

---

### [SKILL] typescript-strict-review  (path: /)

---
name: typescript-strict-review
description: Type-safety review checklist for TypeScript changes. Run before merging code that adds types, handles external data, or models state.
---

# TypeScript strict review

- [ ] `tsc --noEmit` passes; `strict` is on plus `noUncheckedIndexedAccess` (and `exactOptionalPropertyTypes` where feasible); no strict flag was relaxed to compile.
- [ ] No `any` (or implicit any): unknown values use `unknown` + narrowing.
- [ ] No `as` casts forcing a type and no `!` non-null assertions; any unavoidable escape hatch is isolated behind one commented helper.
- [ ] Mutually exclusive state is a discriminated union with a literal tag; switches narrow on it and have a `never` exhaustiveness default.
- [ ] External data (API/JSON/form/env/storage) is parsed with a runtime validator; static types are inferred from the schema, not asserted.
- [ ] No `await res.json()` cast straight to a type without parsing.
- [ ] Types are as narrow as practical (literal unions over string, `readonly`/`as const` where appropriate); enums avoided in favor of literal unions.
- [ ] Inference is used for locals; annotations focus on parameters, public return types, and boundaries.
