Agent Skills: TypeScript Guidelines

TypeScript code style, type co-location, naming conventions (including acronym casing), test organization, and arktype patterns. Use when writing TypeScript code, defining types, naming variables/functions, organizing tests, or working with arktype schemas.

UncategorizedID: EpicenterHQ/epicenter/typescript

Repository

EpicenterHQLicense: AGPL-3.0
4,013273

Install this agent skill to your local

pnpm dlx add-skill https://github.com/EpicenterHQ/epicenter/tree/HEAD/.agents/skills/typescript

Skill Files

Browse the full folder contents for typescript.

Download Skill

Loading file tree…

.agents/skills/typescript/SKILL.md

Skill Metadata

Name
typescript
Description
'TypeScript project conventions: derived types, type placement, acronym casing, imports, generics, factories, and runtime schema patterns. Use when editing `.ts` files, defining exported types, reviewing type names, or organizing type tests.'

TypeScript Guidelines

Use this skill for project-wide TypeScript conventions before loading narrower skills such as arktype, typebox, testing, or method-shorthand-jsdoc.

When To Apply This Skill

Use this skill when you need to:

  • Write or refactor TypeScript with Epicenter naming and style conventions.
  • Decide whether to derive, import, or declare a type.
  • Review type ownership, copied shapes, factory return types, brands, casts, and generic names.
  • Choose clear value-mapping and control-flow patterns for unions and discriminated values.
  • Organize type tests, runtime schemas, or factory-focused refactors.

Core Rules

  • Try to derive or import a type before declaring a new named type. New named types must earn their place as a real contract, protocol vocabulary, discriminated result union, capability port, or multi-implementation shape.
  • Treat local shape copies as boundary smells. Prefer the owning runtime type, schema inference, factory return type, function signature, or a caller-owned capability function.
  • Use type, not interface.
  • Use readonly only for arrays and maps, unless matching an upstream type exactly.
  • Treat acronyms as normal words in camelCase: parseUrl, defineKv, readJson, customerId.
  • Use .js extensions in relative imports. Do not use extensionless or .ts relative imports.
  • Export symbols at their declarations. Reserve export { ... } from ... for barrel files.
  • Prefer factory functions over classes. Let closure position communicate private vs public API.
  • Use descriptive generic names with a T prefix, such as TSchema, TDefs, and TKey.
  • Destructure options in the function signature when the object is a configuration bag. Keep a named value only when it is the domain object being transformed or forwarded.
  • Let TypeScript infer private and inner return types. Annotate exported APIs only when useful for clarity or to break circular inference.
  • If an exported type is exactly the object returned by a create*, attach*, open*, or similar factory, derive it from the implementation with ReturnType<typeof createThing>. Put the exported output alias immediately after the factory. Keep input, config, data, protocol, and multi-implementation contract types above the factory.
  • Move consumer-facing JSDoc onto the returned object members. Add concrete member annotations inside the returned object when they preserve IntelliSense, narrow an implementation detail, or keep a public method/property surface stable.
  • For curried factories, derive from the inner return, such as ReturnType<ReturnType<typeof createThing>>. For generic factories, instantiate typeof when needed, such as ReturnType<typeof openThing<TActions>>.
  • Preserve intentional readonly public surfaces with getters when deriving from an object literal. Do not expose writable internal state just because the concrete implementation happens to store it that way.
  • Use a Symbol brand when identity means a specific factory output, not a coincidental shape probe.
  • Avoid as any. Use unknown, validation, brands, or narrower helpers instead.
  • Prefer optional chaining over in checks or truthiness when checking optional properties.
  • Use is, has, or can prefixes for booleans that answer a question.
  • Prefer switch over if/else for repeated equality comparisons against the same value. Use default: value satisfies never for exhaustiveness when needed.
  • Prefer Record lookup tables over nested ternaries for finite value mappings.
  • Compose typed errors bottom-up. Do not filter a broad upstream error union at the boundary.
  • Question silent fallbacks that hide invalid state. Preserve round-trip invariants when parsing and serializing.

Go-to-Definition Awareness

When organizing types and exports, always consider Go-to-Definition. A developer pressing Go-to-Def from a call site should land as close as possible to the actual source of truth. If a design choice forces an extra navigation hop, the choice has to earn it elsewhere (e.g., a real validation boundary, a published contract, or a multi-implementation port).

Concrete regressions to watch for:

  • Destructure-re-export of a module-level object: const stub = { fn, gn } satisfies T; export const { fn, gn } = stub; lands Go-to-Def on the destructuring line, not the real definition. Prefer per-export satisfies or a direct export const fn = ... satisfies T['fn'].
  • typeof Real annotation over satisfies: export const fn: typeof Real = unreachable hides the underlying value's identity from navigation. export const fn = unreachable satisfies typeof Real keeps the value as the source of truth.
  • Re-export chains in non-barrel files: export { X } from './alias' outside index.ts costs an extra hop with nothing to show for it. Reserve export { ... } from ... for barrels; export at the declaration everywhere else.
  • Adapter / proxy / wrapper with no behavior change: a fromX translator or thin passthrough makes Go-to-Def land on the wrapper. Widen the underlying factory's return shape instead (see factory-function-composition "collapsed adapter" rule).
  • Manual return type annotation duplicating zone 4: annotating a factory with a hand-written interface diverts Go-to-Def to the alias. Let the factory return its concrete object, then put the exported alias directly after it as export type Thing = ReturnType<typeof createThing>. This keeps navigation on the returned members and lets their JSDoc own the public documentation. See method-shorthand-jsdoc.
  • Noisy satisfies generic lists: if a return object should prove it extends a generic contract but satisfies Contract<A, B, C> & Extras forces callers to restate inferred table, action, or runtime types, prefer a constrained identity helper owned by the contract module. Example: return defineWorkspace({ ...workspace, ...runtime }) where the helper accepts TWorkspace extends Workspace<...> and returns TWorkspace. This keeps the call site readable, preserves the exact inferred return type, and leaves Go-to-Def on the real object members.
  • When not to add defineX: do not wrap a simple satisfies check just to give it a helper name. If the contract has no required type arguments, or its generics have defaults that make satisfies Contract readable, prefer satisfies. The helper only earns the extra name when it removes generic noise the reader would otherwise have to carry.

For broader public-shape decisions that affect navigation across packages, see cohesive-clean-breaks.

Reference Map

  • Project conventions: detailed examples for derived types, local shape copies, imports, barrels, factories, generics, destructuring, and factory return types.
  • Type safety and control flow: identity brands, casts, optional properties, boolean naming, switches, record lookups, error composition, fallback smells, and round-trip invariants.
  • Type organization: types.ts location, co-location rules, inline-vs-extract hop test, options and ID naming.
  • Factory patterns: factory-focused refactors, parameter destructuring, and coupled state extraction.
  • Runtime schema patterns: arktype, branded IDs, optional property syntax, and workspace table IDs.
  • Testing patterns: inline single-use setup and source-shadowing tests.
  • Advanced TypeScript features: iterator helpers and const generic array inference.