agentleFS
robconeryrobconery/kolea/.claude/skills/typescript-best-practices/SKILL.md

typescript-best-practices

Skill7 starsChanged 3 months agoMITView on GitHub

Idiomatic TypeScript conventions and best practices: strict compiler settings, type-level modeling (discriminated unions, branded/nominal types, `as const`/`satisfies`, exhaustiveness), `unknown` over `any`, immutability, error handling with a Result type, null/undefined hygiene, async/promise rules, module and naming conventions, and the project rule that every class exposes `toString()` and `toJSON()`. Use when writing new TypeScript, doing code review, setting up a tsconfig, modeling a domain type, or answering "what is the idiomatic TypeScript way to do this" questions. Ships ready-to- copy templates for value objects, entities, errors, Result, state machines, branded types, type guards, and a strict tsconfig.

77 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use setup-pstack, poteto-help and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • bnemaSkill

    setup-pstack

    Configure which Pi models pstack uses per role. Detects available provider/model specs and writes pstack's user configuration. Use for /setup-pstack, "configure pstack models", or changing pstack model choices.

    Also in 22 projects that use this

  • ianclearySkill

    poteto-help

    Guides users through pstack setup, /poteto-mode, and picking the skill, playbook, or principle for a task. Type /poteto-help with a question.

    Also in 12 projects that use this

  • eryzerzSkill

    make-bot-ui

    Use when building a custom UI (page, dashboard, buttons) that should wake a Grok Bot over a webhook, when the user must provide a webhook sender key, or when exposing that UI on Tailscale.

    Also in 14 projects that use this

  • ianclearySkill

    principle-explain-the-number

    Apply before you trust, report, or act on a number you measured: a speedup, a regression, a throughput, a latency, or an eval result. Find what limits it, and rule out that it measured something other than the work you think.

    Also in 20 projects that use this

  • tristanphvnSkill

    ketchup-video

    Catch the user up like /keel:ketchup, with a short silent video of slides for what happened and the waiting calls as text under it. Use for /keel:ketchup-video.

    Also in 8 projects that use this

  • instructureSkill

    ketchup

    Rebuild context after /clear by analyzing uncommitted changes across Canvas LMS and all plugin repositories

    Also in 17 projects that use this

The file

We split this skill into the rules it gives an agent

2 rules. 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 26 edited versions we read keep these.

  • 0

    Distinctive

    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
  1. Very specific:

    No floating promises — await or explicit void.

    line 119
  2. Very specific:

    Every class implements toString() and toJSON().

    line 120

“Common” is measured: kept by at least 6 in 10 of the 26 edited versions of this file we could read. “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
SKILL.md

TypeScript Best Practices & Idioms

How to write TypeScript that the compiler can actually protect: model illegal states out of existence, prefer narrow types over any, make invariants explicit, and keep runtime behavior (serialization, equality, errors) predictable.

🎯 Why: Design for Change

The goal of writing software is to be able to change it safely. Strict types, discriminated unions, branded types, and Result errors turn the compiler into a change-detector: when a shape moves, every caller lights up. any and loose types are change-hiders — they make the next edit look safe while it isn't. Idiomatic TS exists to keep the next refactor mechanical instead of archaeological.

This skill is about idioms and conventions. For OO design pressure use solid-principles and design-principles; for named patterns use gof-patterns. This skill does not re-explain those.

How to use this skill

  1. Match the symptom in the decision guide to a rule.
  2. Open references/idioms.md for that rule's full rationale, a bad example, the idiomatic refactor, and when not to apply it.
  3. Copy the closest file from templates/ and adapt it. The templates already encode the conventions below (strict types, toString/toJSON, exhaustiveness) so you start compliant instead of retrofitting.
  4. Apply the smallest change that removes real pain. Strictness is earned by demonstrated bugs, not applied as ceremony — but the defaults here (strict tsconfig, no any, no floating promises) are non-negotiable because their failure mode is silent.

Project rule: every class has toString() and toJSON()

This is a hard convention in this codebase, not a suggestion.

  • toJSON() — returns a plain, serializable object. JSON.stringify calls it automatically, so logs, API responses, and persistence get a stable, intentional shape instead of leaking private fields, class internals, or undefined. Never return the instance itself.
  • toString() — returns a short human-readable identifier for logs, error messages, and template literals (`order ${order}`). It is for humans; toJSON() is for machines. They are not interchangeable.

Why it is mandatory: without toJSON(), serialization is accidental — it exposes whatever fields happen to be public today and breaks the moment a private field is added. Without toString(), a class interpolated into a string is [object Object], which destroys logs and error context. Both methods make the class's external contract explicit and decoupled from its internal representation (information hiding). See references/tostring-tojson.md for the full rationale, edge cases (circular refs, Date, bigint, secrets redaction), and the copy-ready pattern. Every template in templates/ demonstrates it.

Decision guide

Symptom / questionRuleWhere
Reaching for any to silence the compilerUse unknown + narrowingidioms.md §1
enum Status { ... }Use a string-literal union or as const objectidioms.md §2
Object can be in contradictory statesModel with a discriminated unionidioms.md §3
Two string IDs got swapped at a call siteBrand the types (nominal)idioms.md §4, templates/branded-type.ts
switch silently misses a new caseExhaustiveness check with neveridioms.md §5
Config object loses literal typesas const + satisfiesidioms.md §6
Throwing for an expected, recoverable failureReturn a Result<T, E>idioms.md §7, templates/result.ts
catch (e) then e.messageunknown catch + error narrowingidioms.md §8, templates/app-error.ts
Mutable shared object mutated far awayreadonly / Readonly<T> / freezeidioms.md §9
null vs undefined used interchangeablyPick one convention; ?./??idioms.md §10
Floating promise / unhandled rejectionAlways await; void deliberate fire-and-forgetidioms.md §11
Hand-written { id: ..., name: ... } derived from another typeUtility types (Pick/Omit/Partial)idioms.md §12
Validating external input by castingType guard / assertion function (or a schema lib)idioms.md §13, templates/type-guards.ts
New class, modeling a domain valueValue object templatetemplates/value-object.ts
New class with identity/lifecycleEntity templatetemplates/entity.ts
interface vs type vs class indecisionDecision ruleidioms.md §14

Reference files

  • references/idioms.md — every rule above with motivation, a bad example, the idiomatic refactor, TypeScript-specific notes, and when the rule is over-engineering.
  • references/tostring-tojson.md — the mandatory toString/toJSON convention in depth: contract, edge cases, redaction, testing.

Templates (copy and adapt)

FileUse case
templates/tsconfig.jsonStrict baseline compiler config
templates/value-object.tsImmutable validated value (Money, Email) with factory, equals, toString/toJSON
templates/entity.tsDomain entity with identity, lifecycle, toString/toJSON
templates/result.tsResult<T, E> type + ok/err/map/unwrap helpers
templates/app-error.tsTyped error base class + subclasses, serializable, with toJSON/toString
templates/discriminated-union.tsState modeled as a tagged union with exhaustive handling
templates/branded-type.tsZero-cost nominal types and smart constructors
templates/type-guards.tsUser-defined type guards and assertion functions

Non-negotiable defaults

These are silent-failure rules; apply them everywhere from day one:

  1. strict: true (and noUncheckedIndexedAccess) in tsconfig.
  2. No any in committed code — unknown + narrowing instead.
  3. No floating promises — await or explicit void.
  4. Every class implements toString() and toJSON().
  5. Exported functions have explicit return types.
  6. External/untrusted data is validated at the boundary, never cast.

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.