About typescript-conventions
A skill that guides TypeScript coding and review, for developers working in TypeScript repositories. It covers runtime validation, expression structure, wrappers, spreads, type shapes, and documenting non-obvious decisions.
Key features
- 01Use Valibot for runtime validation
- 02Extract non-trivial computations
- 03Avoid pass-through wrappers
- 04Require a purpose for each spread
- 05Keep named object types flat
- 06Explain non-obvious decisions
Use cases
- 01Apply conventions when writing TypeScript
- 02Review TypeScript changes for maintainability
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ perdolique/ workflow --skill typescript-conventionsAfter installing, type/typescript-conventionsin Claude Code to confirm it loaded.
Bundled filesevals/evals.json
Details
From the project READMETypeScript coding conventions
- Apply the active file's repository documentation, AGENTS.md, lint rules, and configuration first. Use this guidance for TypeScript, including embedded TypeScript, and combine available framework, testing, or library skills when needed.
- Use Valibot for runtime validation schemas, following the project's schema organization and reusing existing contracts where applicable.
Avoid return undefined
Do not use return undefined. When exiting a function without a value, use a bare return.
Extract non-trivial intermediate computations
- Before embedding a non-trivial computation in an argument, property value, array element, condition, loop input, spread source, return value, or larger expression, assign it to a named
constorlet. - Give ordinary nested calls, awaited values, constructors, chained transforms, template-built values, ternaries, and multi-operator expressions meaningful intermediate names before another computation consumes them.
- Exception: keep immediately clear elementary expressions inline, including property or element access, optional chaining,
??, unary checks, simple comparisons, and one-step arithmetic. Do not split them solely to satisfy this rule. An initializer that already gives its result a meaningful name may contain a clear calculation. - Arrange dependent statements in execution order: resolve input, transform it, then consume the result.
- Keep nested or chained calls in declarative library APIs (embedded DSLs) when their structure expresses a declaration, such as a schema, query, or rule set. Follow the API's established composition style. Extract ordinary data preparation used as input; for example, name the result of
parseId(rawId)before using it insideeq(users.id, id). A chain of data transformations follows the ordinary extraction rule. - Preserve behavior and algorithmic complexity; do not add intermediate collections or passes solely for formatting.
Avoid meaningless pass-through wrapper functions
- Do not create a function whose only job is to call another function and immediately return its result or await it without adding any behavior. If the wrapper does not name a real concept and does not own logic such as validation, branching, retries, mapping, instrumentation, or error handling, inline the call at the usage site or rename the underlying function instead.
- Use a wrapper only when it creates a clearer domain boundary or adds behavior that the caller should not repeat.
Keep wrapper contracts source-owned when possible
- When wrapping a typed API, reuse source types and inference first. Add local named types only when the wrapper owns a narrower contract or typecheck proves inference is insufficient.
- If the wrapper changes return/error shape, lifecycle, validation, mapping, or side effects, name that owned contract and test it.
Require a concrete reason for every spread
- Every object or array spread must serve a concrete purpose at that construction site, such as changing selected fields immutably, merging collections, or creating a required ownership boundary. If the original value will not be mutated and sharing it is safe, reuse it directly instead of making a shallow copy. Do not spread defensively or merely to make code look immutable.
- When creating a view model, API payload, or other deliberately shaped object, list its properties explicitly. Do not use a broad source spread that hides the resulting contract or copies unrelated fields. Spread is shallow; do not use it when the required isolation is deeper.
- During authoring and review, be able to state the purpose of each spread. Add a short code comment only when that purpose is not evident from the surrounding operation. Apply the same standard to production code, tests, and fixtures; a focused fixture override is a valid purpose, while an unnecessary copy is not.
Prefer flat interface structures
- Keep
interfacedeclarations and named objecttypealiases to one level of named properties. When a property needs an object shape, extract that shape into a separate named interface and reference it from the parent type. - Apply this to direct object properties, arrays of objects, and nullable or union forms such as
brand: { ... },items: { ... }[], andbrand: { ... } | null. - This rule covers type declarations only. It does not apply to runtime object literals, validator schemas, framework configuration objects, or library DSL values where the object shape is executable configuration rather than a reusable type contract.
Explain non-obvious decisions
- Add a short comment when a purpose, external constraint or workaround is not clear from the code. For a shared utility, explain the contract it owns when nearby layers make its role easy to confuse.
- Keep the reason when simplifying the code. Use clear names and structure for obvious mechanics instead of line-by-line narration or unrelated implementation detail.
Comments
Markdown supported