CommunityWriting & Editinggithub.com

spnngl/agent-skills

TypeScript coding workflow and idioms, including React, Next.js, Recharts, and Zod. Use when writing, reviewing, or refactoring TypeScript, TSX, or JSDoc-typed JavaScript, or editing tsconfig.json, package.json, workspace, or Biome, oxlint, or oxfmt config.

What is agent-skills?

agent-skills is a Claude Code agent skill that typeScript coding workflow and idioms, including React, Next.js, Recharts, and Zod. Use when writing, reviewing, or refactoring TypeScript, TSX, or JSDoc-typed JavaScript, or editing tsconfig.json, package.json, workspace, or Biome, oxlint, or oxfmt config.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/spnngl/agent-skills/tree/HEAD/skills/typescript/ts-coding

Installed? Explore more Writing & Editing skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

TypeScript coding

Workflow

  1. Orient. Read the project's sources of truth before writing: package.json (type, engines, packageManager, exports, scripts, installed majors), the lockfile, every tsconfig*.json along its extends chain, the root's workspace, framework, lint, and format config (pnpm-workspace.yaml, turbo.json, next.config.ts, biome.json, .oxlintrc.json), and CI for the canonical check commands. Run tsc --showConfig to resolve the extends chain; it omits options left at their default, so read those from references/modules.md (TypeScript 6 and 7 changed them). Read the module you are changing and its neighbours; reuse their helpers, error types, schemas, and patterns. Done when: you can name the package manager, the TypeScript major, module and moduleResolution, the strictness flags beyond strict (noUncheckedIndexedAccess, exactOptionalPropertyTypes, erasableSyntaxOnly), the lint and format toolchain, and the project's check commands.

  2. Model the types. Design data before control flow, following Types. Input from outside the process: read references/zod.md. Changing a package's public API: read references/api.md. Done when: every invariant the change relies on is encoded in a type, and every value crossing a trust boundary is parsed by a schema before use.

  3. Implement in a tight loop. Work in small steps and run tsc --noEmit (tsc -b with project references) after each. Read the whole diagnostic; its code (TS2742) is the search key into the tables in references/modules.md and references/types.md. For a dependency's API, read its .d.ts under node_modules/<pkg>/ at the installed version. Bug fix: write the regression test first and watch it go red. Done when: the change type-checks with no new diagnostics, and any regression test went red before the fix and green after.

  4. Verify. Run the project's commands from step 1 through its package manager (pnpm exec, npx, bunx); otherwise, for the toolchain the project uses:

    tsc --noEmit                                           # tsc -b when tsconfig.json only lists references
    biome ci                                               # Biome
    oxfmt --check && oxlint --type-aware --deny-warnings   # oxlint + oxfmt
    vitest run --typecheck                                 # or the project's test runner
    
    • A root tsconfig.json with "files": [] and references makes tsc --noEmit check nothing; use tsc -b. In a workspace without a root tsconfig, run the checks per package through the task runner (turbo run check-types test).
    • Keep tsc in the loop with every linter: Biome's type inference and oxlint's type-aware rules complement it, never replace it.
    • Formatting failures: apply the formatter (biome check --write, oxfmt) instead of editing by hand.
    • Fix diagnostics at the cause. When suppression is right, scope it to one line and give the reason: // biome-ignore lint/<group>/<rule>: reason, // oxlint-disable-next-line <rule> -- reason, // @ts-expect-error reason.
    • Lint or format config touched: check it against references/lints.md, then run the linter and formatter over the whole repository, not only changed files.
    • Public API of a published package touched: run references/api.md. Done when: every command exits 0, lines you changed raise no warnings, and every new suppression carries a reason.
  5. Self-review the diff. Re-read the full git diff (git add -A && git diff --cached when files are new), not your memory of it, against Rules and the rules of each reference file whose branch the diff touches. Done when: every hunk is required by the task, console.log, debugger, .only, and commented-out code are gone, doc comments match behaviour, and every applicable rule holds for every hunk.

Reviewing someone else's code: run step 1, run step 4's commands when you can, then apply step 5 to their diff. Report each finding as path:line, the rule, and the fix.

Rules

Types

  • Narrow with evidence: typeof, instanceof, in, a discriminant check, a type predicate, or a schema parse. any, the non-null !, and as T are banned, in tests too; as const and satisfies stay. When a library's types are wrong, fix them at the source with module augmentation (references/types.md).
  • Make illegal states unrepresentable: a discriminated union with a literal kind field, over boolean flags plus optional fields. Brand IDs, units, and validated values through a schema (z.string().brand<"UserId">()) or a type predicate, never as.
  • Parse, don't validate: JSON.parse, Response.json(), env vars, storage, URL params, and postMessage data are unknown until a Zod schema parses them at the boundary; pass the parsed type inward.
  • Switch exhaustively on unions you own: end with default: x satisfies never; throw new Error(...), or map with a Record<Union, V> lookup that fails to compile when a member is added.
  • Indexed access is T | undefined under noUncheckedIndexedAccess: handle the undefined (guard, ?? fallback, for...of) instead of asserting it away.
  • Exported functions declare their return type; locals stay inferred.
  • interface for object shapes, extends over & intersections; type for unions, mapped, and conditional types.
  • Accept readonly T[] and Readonly<T> for inputs the function does not mutate.
  • A string-literal union or an as const object, over enum and namespaces with runtime code (both banned under erasableSyntaxOnly).

Errors and async

  • Expected failures the caller must handle return a discriminated result; bugs and unrecoverable states throw an Error subclass with { cause }. Details: references/errors.md.
  • Every promise is awaited, returned, or passed to a handler that observes its rejection.

Current idioms

Training data predates TypeScript 6 and 7: TypeScript 7 is the Go-native compiler, strict is on by default, types defaults to [], and baseUrl, outFile, target: "ES5", and moduleResolution: "node10" are removed (references/modules.md). Prefer the current form, including standard-library APIs by lib level, when the installed versions allow it. Where a reference disagrees with the installed package's .d.ts or CHANGELOG, the installed version wins.

PreferOverSince
const config = { ... } satisfies Configconst config: Config (widens), as Config4.9
function f<const T>(x: T)as const at every call site5.0
import type { T } with verbatimModuleSyntaximportsNotUsedAsValues (removed)5.0
using / await using with [Symbol.dispose]try / finally cleanup5.2, lib esnext
import data from "./d.json" with { type: "json" }assert { type: "json" } (error since 6.0)5.3
NoInfer<T>T & {} inference blockers5.4
inferred predicates: xs.filter((x) => x !== undefined)hand-written (x): x is T => guards5.5

Docs and comments

  • Exported items get /** */ docs stating the contract: @throws for thrown errors, @deprecated naming the replacement, @example when usage is not obvious from the signature.

Dependencies

  • Reuse what package.json already lists, and prefer the platform when it covers the need: fetch, structuredClone, crypto.randomUUID, AbortSignal.timeout, node:-prefixed builtins.
  • Add with the project's package manager, pinning @types/* to the runtime package's major (@types/node: the lowest Node in engines).

References

Load the one matching the branch you are on:

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Related Skills