Skip to content
cccskills
FreePublished on GitHub

typescript-best-practices

TypeScript best practices. Use when reading or editing any .ts or .tsx file, designing a module's public types, fixing a type error, choosing between type and interface, or setting up tsconfig, and whenever a diff reaches for any, as, ! or @ts-ignore. Triggers - "typescript best practices", "is this type safe", "how should I type this", "review this TS", "any vs unknown".

The English summary is being prepared. Showing the original description.

See how to install

Files included (2)

  • SKILL.md3.7 KB
  • references/patterns.md11.9 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

TypeScript best practices

One idea underneath every rule: a wrong program should not compile. Every any, as, ! and @ts-ignore is a place where you took that back.

RuleSummary
Discriminated unionsModel variants with a kind literal discriminant so impossible states can't be represented. No optional-field bags.
Branded typesBrand primitives with & { readonly __brand: "X" } so they can't be mixed up. Validate once at creation.
Constructive modelingBuild the shape so the illegal value can't be constructed. [T, ...T[]] for non-empty, [T, T][] for even length, start plus duration for a range. Not a runtime guard, not a wish for refinement types.
Simplest total typeKeep T[] while every operation on it stays total. Strengthen to NonEmpty<T> only where the loose type forces !, a cast, or a "should never happen" throw.
unknown over anyExternal data is unknown. any disables type checking everywhere it touches.
No as castsEvery as is a runtime crash waiting. Cast only after validation.
Narrowing hierarchyDiscriminant switch > in operator > typeof/instanceof > user-defined type guard > as.
Type guardsMust verify the claim. A lying guard is worse than as because the bug hides behind a name that says it's safe. Name them isX or hasX.
ExhaustivenessInline const _exhaustive: never = x; in default arms so the compiler errors when a new variant is added.
satisfies over asValidates the value without widening literal types.
Boundary validationValidate where data crosses in, trust the types inside. A schema at every trust boundary, never a typeof chain behind an as cast.
Schema-derived typesReach for Pick/Omit/Parameters/ReturnType/Awaited/typeof before declaring a new interface.
Object argsPass objects, not positional, so argument order is self-documenting. Skip on hot paths (per-frame render, tokenizers, parsers).
Real testsDon't mock what you can run. Prefer the framework's real test primitives with leak/disposable checks, and verify UI in a running build. Mock only what you can't run locally.
Structured telemetryPrefer structured logger diagnostics with enough context to debug from an id. No console.log in shipped code.
Strict by defaultstrict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, verbatimModuleSyntax, isolatedModules. Below that floor the rest of this table is decoration. A project missing one raises it rather than working around it.
Errors as valuesExpected failure returns a Result with a union of literal codes. Throw only for a violated invariant. catch (error: unknown), never a silent catch {}.
No floating promisesEvery promise is awaited, returned, or handed to an owner. Independent work runs under one Promise.all. Long work takes an AbortSignal.
Named exportsA default export renames itself at every call site. import type for types, no barrel over a package's internals.

Examples: references/patterns.md, which covers the rules that need one.

Read the project's tsconfig and lint config before deciding a rule here does not apply. Where a project also writes Rust, the same table lives one language over in rust-best-practices.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

One file, one job, and short enough to hold in your head. Covers the size policy (hard-split over 300 lines, split 200 to 300 at a seam, aim for 150), what a natural seam is and where to cut, the exemptions, the dependency rules that decide where the split lands, and the signals that a file is already two files. Use before adding to a file that is already long, when naming a new file, when a function has nowhere obvious to live, or when a review says a file is doing too much. Triggers - "this file is too long", "where should this go", "split this file", "is this file too big", "one responsibility".

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

naming

Free

What things get called. A file is named after its export in the project's case convention, a suffix is a resolution instruction rather than decoration, an index file only re-exports, and some symbol names are always wrong (utils, helpers, V2, Enhanced, New). Covers learning a tree's local vocabulary before adding to it, test and platform-variant naming, and the per-language conventions. Use when creating a file or directory, naming a test file, adding a platform variant, or when a name has to change. Triggers - "what should I call this file", "where does this file go", "naming convention", "kebab or camel", "name this test".

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

Spawn the comment hunter, fix accepted findings, and offer encodings for claimed constraints.

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

Write a short release note people finish reading. Picks the few changes a user will notice from the pull requests since the last release, names each by what the reader can now do, keeps to a length budget, writes every language from the intent instead of translating, and traces every line to a PR. Use when cutting a release, or filling a GitHub Release body, a store "what's new", a package changelog or an in-app what's new screen. Triggers - "write the release notes", "release notes for 0.4.0", "what's new", "changelog for this release", "notes de version".

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

Rust best practices. Use when reading or editing any .rs file, designing a crate's public API, choosing an error type, splitting a workspace, or writing async code on tokio. Triggers - "rust best practices", "is this idiomatic", "should this be a newtype", "anyhow or thiserror", "why does the borrow checker", "review this Rust".

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

Write cargo tests. A test name is a sentence, the body is setup, mock, test and verify blocks separated by blank lines with no comments, and shared setup lives in a named test-support module rather than in each test. Covers naming, the block shape, where a test file goes, async and time, workspaces, and running one crate or one filter. Use when adding or changing any #[test] or #[tokio::test], or when Rust coverage has to come up. Triggers - "write a rust test", "cargo test", "test this crate", "cover this handler".

The English summary is being prepared. Showing the original description.

kromatv/kroma62Updated Oct 20, 2026

See all skills by kromatv

Report a problem with this skill