agentleFS
HermeticOrmusHermeticOrmus/typescript-conventions-skills/CLAUDE.md

typescript-conventions-skills

CLAUDE.md0 starsChanged 6 months agoMITView on GitHub

TypeScript conventions. Apply to every .ts, .tsx, and tsconfig*.json. Project-specific overrides win — these are defaults. Tradeoff: bias toward strict-but-modern. For one-off scripts or legacy projects with much existing JS, use judgment. Standard tsconfig.json: The extras beyond strict: true: License: MIT.

The file

We split this CLAUDE.md into the rules it gives an agent

6 rules. About 0 are common, 0 are distinctive, and 6 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.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    strict: true always: If a project's tsconfig doesn't have it, enabling it is the right opening move.

    line 9
  2. Very specific:

    Never any: If you genuinely don't know the shape, use unknown and narrow.

    line 10
  3. Very specific:

    Prefer as const over enum for string-literal sets — better tree-shaking, no runtime cost

    line 17
  4. Very specific:

    Prefer satisfies over type assertion (as) for object literals — preserves narrowing while validating shape

    line 18
  5. Very specific:

    Always handle the rejection path — never await fn() without a try/catch or a result-type wrapper at API boundaries

    line 25
  6. Very specific:

    Never Promise.race without a timeout — accidental dropped work

    line 27

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md

TypeScript conventions. Apply to every .ts, .tsx, and tsconfig*.json. Project-specific overrides win — these are defaults.

Tradeoff: bias toward strict-but-modern. For one-off scripts or legacy projects with much existing JS, use judgment.

Type discipline

  • strict: true always. If a project's tsconfig doesn't have it, enabling it is the right opening move.
  • Never any. If you genuinely don't know the shape, use unknown and narrow.
  • Discriminated unions over optional fields for state machines. { status: 'idle' } | { status: 'loading' } | { status: 'error', message: string } beats { status: string, message?: string }.
  • Type-only imports (import type { Foo } from '...') for types — keeps bundler/runtime trees clean.
  • Result types over throw for failure modes the caller is expected to handle. Reserve throw for programmer errors and unrecoverables.

Patterns

  • Prefer as const over enum for string-literal sets — better tree-shaking, no runtime cost
  • Prefer satisfies over type assertion (as) for object literals — preserves narrowing while validating shape
  • readonly on arrays and props that shouldn't mutate — catches a class of bugs at zero cost
  • Brand types for IDs (type UserId = string & { readonly __brand: unique symbol }) when mixing IDs is a real risk

Async

  • async/await over raw .then() chains
  • Always handle the rejection path — never await fn() without a try/catch or a result-type wrapper at API boundaries
  • Promise.all for parallel; Promise.allSettled when partial success is meaningful
  • Never Promise.race without a timeout — accidental dropped work

Anti-patterns

  • any (use unknown + narrow)
  • // @ts-ignore (use // @ts-expect-error so it errors when the issue is fixed)
  • Unsafe type assertions (x as Foo) where satisfies or a guard would work
  • Mutating function parameters
  • enum (use union literals + as const)
  • Catching errors and swallowing them silently (} catch {})

Project setup

Standard tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}

The extras beyond strict: true:

  • noUncheckedIndexedAccess — arr[0] is T | undefined, not T. Catches off-by-one + missing-key bugs.
  • noImplicitOverride — must write override explicitly. Catches accidental override of unrelated methods.
  • exactOptionalPropertyTypes — { x?: string } does NOT accept { x: undefined }. Distinguishes "not provided" from "provided as undefined".

Tooling

  • tsc --noEmit as the type-check pass in CI
  • eslint with @typescript-eslint for linting
  • prettier for formatting (or eslint's formatting rules if you prefer one tool)
  • vitest or jest for tests; vitest is faster + ESM-native
  • tsx for running TypeScript directly without a build step

License: MIT.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.