
Ts Best Practices
- 54 installs
- 217 repo stars
- Updated March 19, 2026
- poteto/noodle
ts-best-practices is a Claude Code skill for ai & agent building.
About
ts-best-practices is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- ts-best-practices
- AI & Agent Building
- AI-coding skill
Ts Best Practices by the numbers
- 54 all-time installs (skills.sh)
- Ranked #6,877 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/poteto/noodle --skill ts-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 54 |
|---|---|
| repo stars | ★ 217 |
| Last updated | March 19, 2026 |
| Repository | poteto/noodle ↗ |
How do I helps with ai & agent building tasks.?
Helps with ai & agent building tasks.
Who is it for?
Best when you're working on ai & agent building and need structured help with ts best practices.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks., or when ts-best-practices is a claude code skill for ai & agent building.
What you get
Structured output aligned to ts-best-practices: ts-best-practices, AI & Agent Building.
Files
Type Safety
This project's TypeScript policy. Apply when writing or reviewing TypeScript.
| Rule | Summary |
|---|---|
No as casts | Every as is a potential runtime crash. Validate at boundaries, then cast only if earned. Prefer Zod/Valibot over manual validation. |
unknown over any | any disables type checking for everything it touches. External data is always unknown. |
| Discriminated unions | Model variants with a shared literal discriminant. No optional-field bags. |
| Narrowing hierarchy | Prefer: discriminated union switch > in operator > typeof/instanceof > type guard > as |
| Type guards | Must actually verify the claim. Name them isX or hasX. Prefer discriminant narrowing when possible. |
| Exhaustiveness checks | Always add default: never arm to switches over discriminated unions. Use an absurd() helper to reduce boilerplate. |
satisfies over as | When verifying a value matches a type without widening, use satisfies to preserve literal types. |
| Impossible states | If a bug requires asking "can this combination happen?" the type is too loose. Tighten it. |
Read references/patterns.md for code examples of each rule.
Type Safety Patterns
Code examples for each rule in the project's TypeScript policy.
Never as cast
// BAD
const user = data as User;
// GOOD — validate at the boundary
function parseUser(data: unknown): User {
if (typeof data !== "object" || data === null) throw new Error("expected object");
if (!("id" in data) || typeof (data as Record<string, unknown>).id !== "string")
throw new Error("expected id");
// ... validate all fields
return data as User; // OK — earned cast after full validation
}Refactoring `as` out of existing code: Determine why TypeScript can't infer the type:
- Missing discriminant field — add one, use discriminated union
- Overly wide type (e.g.
Record<string, any>) — narrow the type definition - Untyped API boundary — add a type guard or schema parse at the boundary
- Genuinely impossible to express — use a branded type or
satisfiesinstead
unknown over any
// BAD
function handle(input: any) { return input.foo.bar; }
// GOOD
function handle(input: unknown) {
if (typeof input === "object" && input !== null && "foo" in input) {
// narrowed — compiler verifies access
}
}When receiving data from external sources (API responses, JSON parse, event payloads, message passing), always type as unknown and narrow.
Discriminated Unions
// BAD — optional fields create ambiguous states
type Shape = { kind?: string; radius?: number; width?: number; height?: number };
// GOOD — impossible states are unrepresentable
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };Rules:
- Discriminant field must be a literal type (string literal, number literal,
true/false) - Every variant shares the same discriminant field name
- Each variant's discriminant value is unique
Type Narrowing
// Narrowing patterns (best to worst):
// 1. Discriminated union switch/if — compiler narrows automatically
// 2. `in` operator — "key" in obj narrows to variants containing that key
// 3. typeof / instanceof — for primitives and class instances
// 4. User-defined type guard — when above aren't sufficient
// 5. `as` cast — last resort, only after validation
// `in` operator narrowing
function area(s: Shape): number {
if ("radius" in s) return Math.PI * s.radius ** 2; // narrowed to circle
return s.width * s.height; // narrowed to rect
}Type Guards
function isCircle(s: Shape): s is Shape & { kind: "circle" } {
return s.kind === "circle";
}Rules:
- The guard body must actually verify the claim — a lying guard is worse than
as - Prefer discriminated union narrowing over custom guards when possible
- Name guards
isXorhasXfor readability
Exhaustiveness Checks
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rect": return s.width * s.height;
default: {
const _exhaustive: never = s;
throw new Error(`unhandled shape: ${(_exhaustive as { kind: string }).kind}`);
}
}
}Helper to reduce boilerplate:
function absurd(x: never, msg?: string): never {
throw new Error(msg ?? `unexpected value: ${JSON.stringify(x)}`);
}
// usage in default arm:
default: return absurd(s, `unhandled shape`);satisfies Over as
// BAD — widens, loses literal types
const config = { theme: "dark", cols: 3 } as Config;
// GOOD — validates AND preserves literal types
const config = { theme: "dark", cols: 3 } satisfies Config;
// config.theme is "dark" (literal), not stringMaking Impossible States Unrepresentable
// BAD — can be { loading: true, data: User, error: Error } simultaneously
type State = { loading: boolean; data?: User; error?: Error };
// GOOD — exactly one state at a time
type State =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; error: Error };If a bug requires checking "wait, can this combination actually happen?" — the type is too loose. Tighten it so the type system answers that question at compile time.
Related skills
FAQ
What does ts-best-practices do?
ts-best-practices is a Claude Code skill for ai & agent building.
When should I use ts-best-practices?
When you need to helps with ai & agent building tasks., or when ts-best-practices is a claude code skill for ai & agent building.
What are the main capabilities?
ts-best-practices; AI & Agent Building; AI-coding skill.