pattern-reviewer-typescript
Skill0 starsChanged 5 months agoMITView on GitHub
TypeScript audit: `tsconfig.json` strictness (`strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`); `any` / `as any` laundering; `!` non-null without documented invariant; `interface` vs `type`; together-optional fields that should be discriminated unions; `eval` / `child_process` on user input (CRITICAL); prototype pollution; unguarded `JSON.parse`; `==` vs `===`; `forEach(async)`; return types on exports. Activate when the diff includes `.ts` / `.tsx` / `tsconfig.json`.
In this repository74 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- create-bug-issueSkill
- create-enhancement-issueSkill
- create-feature-issuesSkill
- dream-summary-memorySkill
- memory-conventionSkill
- operation-gitSkill
- pattern-architect-api-endpointSkill
- pattern-architect-data-modelSkill
- pattern-architect-deep-moduleSkill
- pattern-e2e-coding-standardSkill
- pattern-engineer-apiSkill
- pattern-engineer-backend-standardSkill
- pattern-engineer-coding-standardSkill
- pattern-engineer-containerSkill
- pattern-engineer-databaseSkill
- pattern-engineer-fastapiSkill
- pattern-engineer-frontend-standardSkill
- pattern-engineer-goSkill
- pattern-engineer-javaSkill
- pattern-engineer-kotlinSkill
- pattern-engineer-nodeSkill
- pattern-engineer-non-functionalSkill
- pattern-engineer-observabilitySkill
- pattern-engineer-pythonSkill
- pattern-engineer-rustSkill
- pattern-engineer-securitySkill
- pattern-engineer-ssrSkill
- pattern-engineer-swiftSkill
- pattern-engineer-typescriptSkill
- pattern-engineer-viteSkill
- pattern-exploratory-testingSkill
- pattern-reviewer-apiSkill
- pattern-reviewer-backend-standardSkill
- pattern-reviewer-coding-standardSkill
- pattern-reviewer-containerSkill
- pattern-reviewer-contractSkill
- pattern-reviewer-databaseSkill
- pattern-reviewer-fastapiSkill
- pattern-reviewer-frontend-standardSkill
- pattern-reviewer-goSkill
- pattern-reviewer-javaSkill
- pattern-reviewer-kotlinSkill
- pattern-reviewer-nodeSkill
- pattern-reviewer-non-functionalSkill
- pattern-reviewer-observabilitySkill
- pattern-reviewer-pythonSkill
- pattern-reviewer-rustSkill
- pattern-reviewer-securitySkill
- pattern-reviewer-ssrSkill
- pattern-reviewer-swiftSkill
- pattern-reviewer-test-coverageSkill
- pattern-reviewer-viteSkill
- pattern-test-coverageSkill
- principle-engineer-tddSkill
- scaffold-projectSkill
- workflow-architect-interviewSkill
- workflow-designer-sample-pagesSkill
- workflow-design-interviewSkill
- workflow-e2e-authorSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
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 15
After loading this skill, also check $MAIN_ROOT/.claude/memory/patterns/pattern-reviewer-typescript.md in the consuming project (resolve …
- Very specific: line 252
Use the shape in templates/review-comment.md.
≈ “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
pattern-reviewer-typescript
When to activate
- Reviewing a diff that includes
.ts/.tsx/tsconfig.jsonfiles. - A user says "review the TypeScript usage / strictness / types".
Project memory overlay
After loading this skill, also check $MAIN_ROOT/.claude/memory/patterns/pattern-reviewer-typescript.md in the consuming project (resolve MAIN_ROOT="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"). If present, load it as an additive overlay to the rules below; if absent, skip silently. See memory-convention for the full contract (additivity, severity floor, conflict surfacing).
Iron rules
- >80% confidence filter. Report only when you are >80% confident. Consolidate similar findings.
- Cite
path/to/file.ext:line. Quote the offending snippet in a BAD block; show the fix in a GOOD block. - Severity is load-bearing. CRITICAL / HIGH block the gate; MEDIUM / LOW are informational. Use the per-pattern severity assigned below.
- Never refer to a finding as
#N— GitHub auto-links those to issues. Use a non-numeric handle (quoted title,F1/F2,Finding 1).
Patterns to review
tsconfig.json strictness (HIGH)
Compare against templates/tsconfig.json. Required compilerOptions:
"strict": true"noUncheckedIndexedAccess": true"exactOptionalPropertyTypes": true"noImplicitReturns": true"noFallthroughCasesInSwitch": true"noImplicitOverride": true
Missing any of these → HIGH. Flag the missing flags by name.
compilerOptions.types for test matchers (HIGH)
compilerOptions.typesmust include@testing-library/jest-dom(or whatever matcher package the project uses) andvitest/globalsif globals are enabled.- Without it,
toBeInTheDocument()/toHaveValue()compile but failtsc --noEmitand break the frontend Docker build — surfacing as "image build broken" instead of "missing types". - Land the
typesentry in the same commit as the first test that uses jest-dom matchers, or one chore-scoped commit before.
any usage (HIGH)
// BAD — any erases the type system
function load(payload: any): User { return payload; }
// BAD — laundering through any
const user = data as any as User;
// GOOD — unknown at the boundary, narrow before use
function load(payload: unknown): User {
return UserSchema.parse(payload);
}Flag every : any annotation and every as any cast. Exception: third-party type-only declaration shims, with an inline comment.
! non-null assertion (MEDIUM)
// BAD — no invariant explained
const user = users.find(u => u.id === id)!;
// GOOD — invariant documented OR narrowed
const user = users.find(u => u.id === id);
if (!user) throw new Error(`user ${id} missing`);
// GOOD — invariant lives in an upstream guard
useQuery({
queryKey: ["user", id],
queryFn: () => getUser(id!), // enabled: !!id is the invariant
enabled: !!id,
});! without a documented invariant → MEDIUM.
interface vs type (LOW)
typefor unions, intersections, mapped types.interfacefor object shapes consumers may extend.- Empty
interface Props {}→ flag (matches everything; usually a forgotten extension).
Discriminated unions (MEDIUM)
// BAD — fields that "go together" but aren't tagged
type Fetch<T> = { data?: T; error?: Error; loading: boolean };
// GOOD — discriminated union; exhaustive switch checking
type Fetch<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };Biome organizeImports (LOW)
- Hand-ordered imports that violate biome's grouping (stdlib → third-party →
@/...alias → relative; sorted within each group) → flag and recommendnpx biome check --write .. In biome v2 this is theassist/source/organizeImportsaction (not a lint rule), but it's still surfaced bybiome check/biome ciand applied by the same command. - Mixed
import { type Foo } from "..."vsimport type { Foo } from "..."— biome rewrites consistently; flag drift.
Banned utility types (LOW)
Function(too broad) → use(...args: never[]) => unknownor a precise signature.Object(too broad) → useRecord<string, unknown>or a narrower shape.
enum usage (LOW)
enumfor fixed strings emits runtime artifacts. Preferconstobject +keyof typeofor a literal union.
eval / new Function on user input (CRITICAL)
// BAD — user-controlled code executes in the server / browser
const result = eval(req.body.expression);
const fn = new Function("payload", req.body.handler);
// GOOD — never feed untrusted strings to dynamic execution
// If you genuinely need a sandbox, use a vetted sandbox library
// and document the threat model inline.Any eval(...) or new Function(...) whose argument can be traced to user input → CRITICAL.
child_process with user input (CRITICAL)
// BAD — shell injection vector
import { exec } from "node:child_process";
exec(`tar -czf ${req.body.archiveName}.tar.gz ./uploads`);
// GOOD — array form, no shell, allowlist the inputs
import { execFile } from "node:child_process";
if (!/^[a-z0-9_-]+$/i.test(name)) throw new Error("bad name");
execFile("tar", ["-czf", `${name}.tar.gz`, "./uploads"]);exec / execSync with template-string user input → CRITICAL. execFile / spawn with a list of args and validated input is the fix.
Prototype pollution (HIGH)
// BAD — merging untrusted JSON straight into a host object
Object.assign(config, JSON.parse(req.body));
_.merge(target, untrustedSource); // older lodash versions are pollution-prone
// GOOD — schema-validate first; merge only known keys
const Body = z.object({ theme: z.enum(["light", "dark"]) });
const safe = Body.parse(JSON.parse(req.body));
Object.assign(config, safe);Untrusted object merged into a target without a schema → HIGH.
JSON.parse without try/catch (HIGH)
// BAD — throws SyntaxError on invalid input; un-handled in route handlers
const body = JSON.parse(req.body);
// GOOD — parse + validate at the boundary
let body: unknown;
try { body = JSON.parse(req.body); }
catch { return res.status(400).json({ error: "invalid JSON" }); }
const parsed = BodySchema.parse(body);JSON.parse on any value that can come from outside (req.body, localStorage, websocket frame, file read) without a try/catch → HIGH.
Throwing non-Error values (MEDIUM)
// BAD — string thrown; catchers lose `.stack` and `.message` typing
throw "user not found";
throw { code: "NOT_FOUND" };
// GOOD
throw new Error("user not found");
class NotFoundError extends Error {}
throw new NotFoundError();Any throw whose argument is not an Error (string, plain object, number) → MEDIUM.
== instead of === (MEDIUM)
// BAD — implicit coercion: `0 == ""`, `null == undefined`, `"1" == 1`
if (count == 0) { ... }
if (value != null) { ... } // the one historically-acceptable use, but `value !== null && value !== undefined` is clearer
// GOOD
if (count === 0) { ... }
if (value !== null && value !== undefined) { ... }== / != outside the deliberate value != null idiom → MEDIUM.
array.forEach(async fn) (HIGH)
// BAD — forEach throws the returned promise away; no awaiting happens
items.forEach(async (item) => { await save(item); });
console.log("done"); // logs before any save resolves
// GOOD — sequential
for (const item of items) { await save(item); }
// GOOD — parallel
await Promise.all(items.map((item) => save(item)));Array.prototype.forEach with an async callback → HIGH (the callee's promise is dropped on the floor).
Sync I/O in request handlers (HIGH)
// BAD — blocks the event loop; one slow disk read stalls every concurrent request
app.get("/config", (req, res) => {
const data = fs.readFileSync("./config.json", "utf-8");
res.json(JSON.parse(data));
});
// GOOD — async I/O
import { readFile } from "node:fs/promises";
app.get("/config", async (req, res) => {
const data = await readFile("./config.json", "utf-8");
res.json(JSON.parse(data));
});fs.readFileSync / fs.writeFileSync / execSync inside an HTTP handler or any hot async path → HIGH.
Explicit return types on public exports (MEDIUM)
- Exported functions, hooks, and class methods that cross a module boundary without an explicit return type → MEDIUM (inference drift silently changes the public contract).
- Internal helpers can rely on inference.
Templates
| Asset | Purpose |
templates/tsconfig.json | Reference strictness block; compare the project's compilerOptions against this. |
Constructing the finding
Use the shape in templates/review-comment.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
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.

