Principles
Read each short form first. Follow every detail-reading requirement whose trigger applies.
shape code by domain
- Name nouns and verbs precisely.
- Use one internal term for one domain concept.
- Prefer domain language over technical mechanism.
- Structure by domain:
users, machines, orders over components, controllers, repositories, routes.
- Include mechanism only to mark a real concern boundary (HTTP, persistence, external contract), e.g.
UserRepository (code), user.repository.ts (file). Do not split one concept across .types.ts / .schema.ts / .constants.ts files; keep them in user.ts.
- Avoid generic names in broad scopes:
data, entity, item, manager, helper.
- Check existing naming before introducing new terms.
- Translate external names, legacy names, UI labels, and synonyms at boundaries.
- Use abbreviations when they improve local readability.
- Avoid kitchen-sink
utils. Put extractable library-esque code in lib. Co-locate everything else with its domain.
You must read details when the work designs, changes, or reviews naming, file placement,
module structure, or feature structure.
keep boundaries sharp
- Extend the module that owns the responsibility. Add a boundary only for a responsibility that does not fit an
existing owner.
- Keep implementation mechanics inside the module whose contract they support.
- Map external concepts into internal concepts at the edge.
- Prefer explicit dependencies over ambient coupling.
- If two modules change together often, reconsider the boundary.
- Cross data and protocol boundaries with data, not behavior.
You must read details when the work designs, changes, or reviews dependencies, interfaces,
adapters, integrations, or module boundaries.
parse, don’t validate
- Parse unknown values at every boundary: IO, HTTP, DB, config, queues, env.
- Prefer type safety over casts and defensive programming.
- Use one schema library consistently.
- Put coercion, extraction, normalization, and domain transforms in the boundary schema when they belong to the type.
- Do not add one-off
parse* utilities after the boundary. Merge that logic into the schema when possible.
- Treat
parse* or validate* calls inside trusted code as a smell.
- After parsing, rely on types instead of re-checking everywhere.
- Parsing should produce a smaller, trusted type.
- Prefer discriminated unions over optional-property variants.
You must read details when the work designs, changes, or reviews a boundary that accepts
unknown input.
privilege is earned
- Treat caller-controlled input, identity, origin, and external systems as untrusted.
- Authenticate identity before using it.
- Authorize every action against the target resource.
- Deny ambiguous access by default.
- Grant the least authority needed for the operation.
- Keep secrets out of source, logs, errors, telemetry, URLs, and clients.
- Return only data the caller is allowed to know.
- Constrain filesystem, network, browser, and third-party capabilities.
- Treat attacker-triggered resource exhaustion as a security bug.
You must read details when the work designs, changes, or reviews authentication,
authorization, secrets, sensitive data, or dangerous capabilities.
handle it, or die
- Expected failures are named domain errors, carried by the Effect error channel,
Result, etc.
- Unexpected defects are bugs. Let them explode.
- Map library errors into domain errors and domain errors into protocol errors at their boundaries.
- Preserve the original cause and useful structured context when mapping an error.
- Forward safe cause messages and context up to the reader's trust boundary.
- Do not reduce a human-facing error to a type, tag, or generic summary.
- Include a corrective action only when it is known to apply.
- Format human-facing errors for their interface.
- Avoid catch-log-rethrow.
- Log unhandled errors exactly once, at the boundary.
- Do not continue after corrupted invariants.
- Do not encode programmer bugs as recoverable business errors.
You must read details when the work designs, changes, or reviews error types, error messages,
error mapping, error presentation, recovery, catching, or logging.
avoid hasty abstractions
- Write for the present, not the past or an imagined future.
- Duplicate until a clear pattern repeats.
- Abstract only proven repetition.
- Configuration is an abstraction; only promote what genuinely varies.
- Inline values that are not shared and unlikely to change.
- Keep one-use code inline unless extraction improves the caller and the body.
- Do not extract tiny object builders or pass-through wrappers just to name them.
You must read details when the work designs, changes, or reviews abstractions,
extraction, duplication, shared code, helpers, or configuration.
performance is not optional
- Treat latency and resource use as behavior.
- Model request cost before committing to a shape.
- Name bounds for item count, fan-out, bytes, rows, memory, retries, and concurrency.
- Measure tail latency, saturation, and errors, not only averages.
- Check every blocking resource: CPU, memory, disk, network, pools, locks, queues, and dependencies.
- Treat concurrency as a budget.
- Reject, shed, defer, or degrade before overload fills queues.
- Prefer avoiding work over making work faster.
- Make cache size, freshness, invalidation, and failure mode explicit.
- Avoid accidental quadratic work, chatty IO, unbounded memory, queue growth, and allocation churn.
You must read details when the work designs, changes, or reviews scale, latency, IO,
batching, concurrency, caching, or memory use.
tests are code
- Treat tests like production code.
- Add tests only for behavior, contracts, boundaries, regressions, or stable invariants whose failure matters.
- Do not test constants, type-only wiring, render-only components, pass-through wrappers, or framework plumbing.
- Prefer public behavior, boundaries, and interfaces over internals.
- Prefer plain code and data equality over framework-heavy test machinery.
- Prefer
.test.each / .it.each for repeated data cases.
- Treat tests that restate implementation, duplicate checks, or fail during harmless refactors as defects.
- Avoid complex and long blocks of assertions.
- Prefer parsing test outputs with schemas, or simple, single equality checks.
- Prefer error type or tag checks over error message checks.
- Prefer real code; mock only externals.
- Use integration/E2E tests for real boundary behavior.
- Test counterfactuals.
- Use property-based tests only for durable invariants.
You must read details when adding, changing, or reviewing tests.
integrated documentation
- Document why, context, decisions, constraints, and contracts.
- Put docs where readers look.
- Match the doc shape to the reader's job: tutorial, how-to, reference, or explanation.
- Treat public docs, commands, config snippets, and examples as contracts.
- Prioritize interface docs via
/** ... */.
- Avoid redundant docs covered by names, types, schemas, or tests.
- Prefer one source of truth and links over duplicated prose.
- Update docs in the same diff as behavior.
- Delete stale docs.
You must read details when adding, changing, or reviewing documentation, comments,
examples, or public contracts.
fight entropy
- Assume good intent, not good context.
- Avoid perpetuating bad code patterns.
- Leave code cleaner than you found it, but keep changes scoped. Bad or outdated tests in the touched area can be purged.
- Refactor opportunistically when the change already touches the code.
You must read details when the work considers cleanup or weak patterns in code already being
changed.
states are values
- Model impossible states as impossible.
- Name in-flight states (
Pending, Settling, Retrying), not only terminal ones.
- Do not store state you can cheaply derive.
You must read details when the work designs, changes, or reviews state models, variants, flags,
transitions, or derived state.
choose paradigm by fit
- Use imperative code when it is clearer or when control/performance matters.
- Use FP-style code for most domain/application logic: pure functions, immutable values, composition, and explicit effects.
- Use OO when identity, lifecycle, resource ownership, or encapsulated mutable state is central.
- When extraction is earned, use top-level functions for pure, stateless logic.
- Use methods for behavior that depends on instance state, constructor-injected dependencies, lifecycle, or encapsulation.
- Do not turn a method into a function by passing half the object as parameters.
- Avoid OO at boundaries; exchange plain data.
- Avoid inheritance; prefer composition and small explicit interfaces.
- Do not standardize on one paradigm for all problems.
You must read details when the work chooses or reviews functions, classes, mutable state,
FP, OO, or imperative code.
evolve contracts deliberately
- Treat persistent data, external models, public APIs, events, and queues as long-lived contracts.
- Know which consumers exist before changing a contract.
- Don’t assume backwards compatibility is always required.
- Prefer additive changes when compatibility is required.
- Separate schema changes from behavior changes when risk is high.
- Keep old readers/writers in mind during rolling deploys.
- Delete compatibility code once it is no longer needed.
You must read details when the work changes or reviews persistent or external
contracts, migrations, or compatibility.
design for operation
- Make long-running work cancellable where possible.
- Use timeouts, retries, and backpressure deliberately.
- Emit enough observability to debug failures without reproducing them.
- Prefer actionable logs over noisy logs.
You must read details when the work designs, changes, or reviews cancellation, timeouts,
retries, backpressure, or observability.
1---2name: ts-principles3description: Core engineering principles. Use as a guiding reference for designing, planning and reviewing.4---56## Principles78Read each short form first. Follow every detail-reading requirement whose trigger applies.910### shape code by domain1112- Name nouns and verbs precisely.13- Use one internal term for one domain concept.14- Prefer domain language over technical mechanism.15- Structure by domain: `users`, `machines`, `orders` over `components`, `controllers`, `repositories`, `routes`.16- Include mechanism only to mark a real concern boundary (HTTP, persistence, external contract), e.g. `UserRepository` (code), `user.repository.ts` (file). Do not split one concept across `.types.ts` / `.schema.ts` / `.constants.ts` files; keep them in `user.ts`.17- Avoid generic names in broad scopes: `data`, `entity`, `item`, `manager`, `helper`.18- Check existing naming before introducing new terms.19- Translate external names, legacy names, UI labels, and synonyms at boundaries.20- Use abbreviations when they improve local readability.21- Avoid kitchen-sink `utils`. Put extractable library-esque code in `lib`. Co-locate everything else with its domain.2223You must read [details](shape-code-by-domain.md) when the work designs, changes, or reviews naming, file placement,24module structure, or feature structure.2526### keep boundaries sharp2728- Extend the module that owns the responsibility. Add a boundary only for a responsibility that does not fit an29 existing owner.30- Keep implementation mechanics inside the module whose contract they support.31- Map external concepts into internal concepts at the edge.32- Prefer explicit dependencies over ambient coupling.33- If two modules change together often, reconsider the boundary.34- Cross data and protocol boundaries with data, not behavior.3536You must read [details](keep-boundaries-sharp.md) when the work designs, changes, or reviews dependencies, interfaces,37adapters, integrations, or module boundaries.3839### parse, don’t validate4041- Parse unknown values at every boundary: IO, HTTP, DB, config, queues, env.42- Prefer type safety over casts and defensive programming.43- Use one schema library consistently.44- Put coercion, extraction, normalization, and domain transforms in the boundary schema when they belong to the type.45- Do not add one-off `parse*` utilities after the boundary. Merge that logic into the schema when possible.46- Treat `parse*` or `validate*` calls inside trusted code as a smell.47- After parsing, rely on types instead of re-checking everywhere.48- Parsing should produce a smaller, trusted type.49- Prefer discriminated unions over optional-property variants.5051You must read [details](parse-dont-validate.md) when the work designs, changes, or reviews a boundary that accepts52unknown input.5354### privilege is earned5556- Treat caller-controlled input, identity, origin, and external systems as untrusted.57- Authenticate identity before using it.58- Authorize every action against the target resource.59- Deny ambiguous access by default.60- Grant the least authority needed for the operation.61- Keep secrets out of source, logs, errors, telemetry, URLs, and clients.62- Return only data the caller is allowed to know.63- Constrain filesystem, network, browser, and third-party capabilities.64- Treat attacker-triggered resource exhaustion as a security bug.6566You must read [details](privilege-is-earned.md) when the work designs, changes, or reviews authentication,67authorization, secrets, sensitive data, or dangerous capabilities.6869### handle it, or die7071- Expected failures are named domain errors, carried by the Effect error channel, `Result`, etc.72- Unexpected defects are bugs. Let them explode.73- Map library errors into domain errors and domain errors into protocol errors at their boundaries.74- Preserve the original cause and useful structured context when mapping an error.75- Forward safe cause messages and context up to the reader's trust boundary.76- Do not reduce a human-facing error to a type, tag, or generic summary.77- Include a corrective action only when it is known to apply.78- Format human-facing errors for their interface.79- Avoid catch-log-rethrow.80- Log unhandled errors exactly once, at the boundary.81- Do not continue after corrupted invariants.82- Do not encode programmer bugs as recoverable business errors.8384You must read [details](handle-it-or-die.md) when the work designs, changes, or reviews error types, error messages,85error mapping, error presentation, recovery, catching, or logging.8687### avoid hasty abstractions8889- Write for the present, not the past or an imagined future.90- Duplicate until a clear pattern repeats.91- Abstract only proven repetition.92- Configuration is an abstraction; only promote what genuinely varies.93- Inline values that are not shared and unlikely to change.94- Keep one-use code inline unless extraction improves the caller and the body.95- Do not extract tiny object builders or pass-through wrappers just to name them.9697You must read [details](avoid-hasty-abstractions.md) when the work designs, changes, or reviews abstractions,98extraction, duplication, shared code, helpers, or configuration.99100### performance is not optional101102- Treat latency and resource use as behavior.103- Model request cost before committing to a shape.104- Name bounds for item count, fan-out, bytes, rows, memory, retries, and concurrency.105- Measure tail latency, saturation, and errors, not only averages.106- Check every blocking resource: CPU, memory, disk, network, pools, locks, queues, and dependencies.107- Treat concurrency as a budget.108- Reject, shed, defer, or degrade before overload fills queues.109- Prefer avoiding work over making work faster.110- Make cache size, freshness, invalidation, and failure mode explicit.111- Avoid accidental quadratic work, chatty IO, unbounded memory, queue growth, and allocation churn.112113You must read [details](performance-is-not-optional.md) when the work designs, changes, or reviews scale, latency, IO,114batching, concurrency, caching, or memory use.115116### tests are code117118- Treat tests like production code.119- Add tests only for behavior, contracts, boundaries, regressions, or stable invariants whose failure matters.120- Do not test constants, type-only wiring, render-only components, pass-through wrappers, or framework plumbing.121- Prefer public behavior, boundaries, and interfaces over internals.122- Prefer plain code and data equality over framework-heavy test machinery.123- Prefer `.test.each` / `.it.each` for repeated data cases.124- Treat tests that restate implementation, duplicate checks, or fail during harmless refactors as defects.125- Avoid complex and long blocks of assertions.126- Prefer parsing test outputs with schemas, or simple, single equality checks.127- Prefer error type or tag checks over error message checks.128- Prefer real code; mock only externals.129- Use integration/E2E tests for real boundary behavior.130- Test counterfactuals.131- Use property-based tests only for durable invariants.132133You must read [details](tests-are-code.md) when adding, changing, or reviewing tests.134135### integrated documentation136137- Document why, context, decisions, constraints, and contracts.138- Put docs where readers look.139- Match the doc shape to the reader's job: tutorial, how-to, reference, or explanation.140- Treat public docs, commands, config snippets, and examples as contracts.141- Prioritize interface docs via `/** ... */`.142- Avoid redundant docs covered by names, types, schemas, or tests.143- Prefer one source of truth and links over duplicated prose.144- Update docs in the same diff as behavior.145- Delete stale docs.146147You must read [details](integrated-documentation.md) when adding, changing, or reviewing documentation, comments,148examples, or public contracts.149150### fight entropy151152- Assume good intent, not good context.153- Avoid perpetuating bad code patterns.154- Leave code cleaner than you found it, but keep changes scoped. Bad or outdated tests in the touched area can be purged.155- Refactor opportunistically when the change already touches the code.156157You must read [details](fight-entropy.md) when the work considers cleanup or weak patterns in code already being158changed.159160### states are values161162- Model impossible states as impossible.163- Name in-flight states (`Pending`, `Settling`, `Retrying`), not only terminal ones.164- Do not store state you can cheaply derive.165166You must read [details](states-are-values.md) when the work designs, changes, or reviews state models, variants, flags,167transitions, or derived state.168169### choose paradigm by fit170171- Use imperative code when it is clearer or when control/performance matters.172- Use FP-style code for most domain/application logic: pure functions, immutable values, composition, and explicit effects.173- Use OO when identity, lifecycle, resource ownership, or encapsulated mutable state is central.174- When extraction is earned, use top-level functions for pure, stateless logic.175- Use methods for behavior that depends on instance state, constructor-injected dependencies, lifecycle, or encapsulation.176- Do not turn a method into a function by passing half the object as parameters.177- Avoid OO at boundaries; exchange plain data.178- Avoid inheritance; prefer composition and small explicit interfaces.179- Do not standardize on one paradigm for all problems.180181You must read [details](choose-paradigm-by-fit.md) when the work chooses or reviews functions, classes, mutable state,182FP, OO, or imperative code.183184### evolve contracts deliberately185186- Treat persistent data, external models, public APIs, events, and queues as long-lived contracts.187- Know which consumers exist before changing a contract.188- Don’t assume backwards compatibility is always required.189- Prefer additive changes when compatibility is required.190- Separate schema changes from behavior changes when risk is high.191- Keep old readers/writers in mind during rolling deploys.192- Delete compatibility code once it is no longer needed.193194You must read [details](evolve-contracts-deliberately.md) when the work changes or reviews persistent or external195contracts, migrations, or compatibility.196197### design for operation198199- Make long-running work cancellable where possible.200- Use timeouts, retries, and backpressure deliberately.201- Emit enough observability to debug failures without reproducing them.202- Prefer actionable logs over noisy logs.203204You must read [details](design-for-operation.md) when the work designs, changes, or reviews cancellation, timeouts,205retries, backpressure, or observability.