From 646e552ff1c54da3d1659010e6ddaea911f952fe Mon Sep 17 00:00:00 2001 From: Julian Lechner Date: Thu, 24 Sep 2026 21:47:29 +0200 Subject: [PATCH] refactor(rules): split TypeScript guidance by topic --- shared/rules/typescript.md | 160 ------------------ shared/rules/typescript/index.md | 14 ++ .../references/async-and-resources.md | 12 ++ .../typescript/references/coding-style.md | 12 ++ .../references/modules-and-dependencies.md | 12 ++ .../typescript/references/performance.md | 13 ++ .../typescript/references/runtime-safety.md | 13 ++ .../rules/typescript/references/security.md | 13 ++ .../references/testing-and-quality.md | 11 ++ .../references/types-and-modeling.md | 12 ++ 10 files changed, 112 insertions(+), 160 deletions(-) delete mode 100644 shared/rules/typescript.md create mode 100644 shared/rules/typescript/index.md create mode 100644 shared/rules/typescript/references/async-and-resources.md create mode 100644 shared/rules/typescript/references/coding-style.md create mode 100644 shared/rules/typescript/references/modules-and-dependencies.md create mode 100644 shared/rules/typescript/references/performance.md create mode 100644 shared/rules/typescript/references/runtime-safety.md create mode 100644 shared/rules/typescript/references/security.md create mode 100644 shared/rules/typescript/references/testing-and-quality.md create mode 100644 shared/rules/typescript/references/types-and-modeling.md diff --git a/shared/rules/typescript.md b/shared/rules/typescript.md deleted file mode 100644 index 7f37ef1..0000000 --- a/shared/rules/typescript.md +++ /dev/null @@ -1,160 +0,0 @@ -# TypeScript Code Guidelines - -* Write clear, readable, maintainable code first. -* Prefer modern TypeScript and current ECMAScript syntax. -* Keep code simple and explicit. -* Avoid clever or overly abstract solutions. -* Follow the existing project style and architecture. -* Enable strict TypeScript settings where possible. -* Prefer `strict: true`. -* Avoid `any`. -* Prefer precise types over broad types. -* Prefer `unknown` over `any` for untrusted or unknown values. -* Narrow types explicitly before use. -* Prefer strongly typed models over loose objects. -* Avoid excessive type assertions. -* Avoid non-null assertions unless safety is guaranteed. -* Prefer discriminated unions for state and variant modeling. -* Prefer literal unions over arbitrary strings. -* Prefer enums only when they provide a clear benefit. -* Prefer `readonly` where mutation is not required. -* Prefer immutable data where practical. -* Avoid unnecessary mutation. -* Avoid global mutable state. -* Keep functions small and focused. -* Keep modules focused on a clear responsibility. -* Prefer composition over inheritance. -* Avoid unnecessary classes when functions and plain objects are sufficient. -* Avoid unnecessary interfaces and abstractions. -* Prefer concrete object types when no abstraction is required. -* Use interfaces primarily when extension, implementation contracts, or declaration merging is actually useful. -* Prefer `type` aliases for unions, compositions, mapped types, and local data models. -* Avoid unnecessary wrapper types. -* Avoid excessive generic complexity. -* Keep generic constraints explicit and meaningful. -* Avoid deeply nested conditional or mapped types unless they provide clear value. -* Prefer readable types over type-system cleverness. -* Avoid duplicated type definitions. -* Derive types from existing sources when practical. -* Do not duplicate backend contracts manually when generated or shared types are available. -* Prefer explicit return types for public APIs and non-trivial functions. -* Prefer inference for simple local variables. -* Avoid unnecessary annotations when inference is obvious. -* Prefer `const` by default. -* Use `let` only when reassignment is required. -* Avoid `var`. -* Prefer optional chaining and nullish coalescing where appropriate. -* Do not use `||` when `0`, `false`, or empty strings are valid values. -* Handle `null` and `undefined` intentionally. -* Avoid mixing `null` and `undefined` without a clear convention. -* Prefer early returns over excessive nesting. -* Avoid deeply nested control flow. -* Avoid overly large functions. -* Avoid hidden side effects. -* Keep pure logic pure where practical. -* Prefer deterministic behavior. -* Avoid implicit runtime coercion. -* Use strict equality. -* Avoid unnecessary object spreading in hot paths. -* Avoid unnecessary array copies. -* Avoid unnecessary intermediate arrays. -* Avoid repeated `.map()`, `.filter()`, or `.reduce()` chains in performance-critical paths. -* Use array methods when they improve readability and performance is not critical. -* Prefer appropriate data structures such as `Map` and `Set` when lookup behavior requires them. -* Avoid O(n²) patterns when a better structure is practical. -* Measure performance before micro-optimizing. -* Avoid unnecessary JSON serialization and parsing. -* Avoid unnecessary deep cloning. -* Avoid `JSON.parse(JSON.stringify(...))` as a cloning strategy. -* Prefer platform APIs such as `structuredClone` when deep cloning is actually required. -* Avoid synchronous blocking operations in server-side hot paths. -* Prefer asynchronous APIs for I/O. -* Always handle rejected promises. -* Avoid floating promises. -* Use `await` when sequencing is required. -* Use `Promise.all` for independent operations when safe. -* Limit concurrency for large workloads. -* Avoid uncontrolled `Promise.all` over very large collections. -* Support cancellation with `AbortSignal` where applicable. -* Clean up event listeners, timers, subscriptions, streams, and resources. -* Avoid memory leaks caused by retained closures or listeners. -* Do not swallow exceptions. -* Preserve useful error context. -* Use typed domain errors where they improve handling. -* Avoid exceptions for normal control flow. -* Validate external input at system boundaries. -* Never trust API payloads, query parameters, environment variables, storage data, or user input based solely on TypeScript types. -* Use runtime validation for untrusted external data. -* Do not assume compile-time types provide runtime safety. -* Prefer schema validation when external contracts are complex. -* Never expose secrets, tokens, credentials, or sensitive data. -* Do not log sensitive information. -* Avoid unsafe dynamic property access. -* Avoid `eval`, `Function`, and dynamic code execution. -* Avoid unsafe HTML injection. -* Sanitize untrusted HTML when rendering HTML is required. -* Avoid weakening TLS, authentication, authorization, CSP, validation, or other security controls. -* Treat all external data as untrusted. -* Prefer secure defaults. -* Do not disable security checks for convenience. -* Avoid prototype pollution risks when merging untrusted objects. -* Prefer allowlists over denylists for security-sensitive validation. -* Keep dependencies minimal. -* Prefer built-in platform APIs over adding small utility dependencies. -* Do not add dependencies without a clear reason. -* Avoid abandoned or unnecessary packages. -* Keep dependencies current and supported. -* Respect lockfiles. -* Do not manually modify generated lockfile content. -* Avoid broad dependency upgrades unrelated to the task. -* Use ESM or the project's existing module system consistently. -* Avoid mixing module systems without necessity. -* Prefer named exports unless the project convention favors default exports. -* Keep import paths consistent. -* Remove unused imports and exports. -* Avoid circular dependencies. -* Keep frontend, domain, application, and infrastructure concerns separated where applicable. -* Keep API and transport models separate from domain logic when necessary. -* Do not leak implementation details through public APIs. -* Avoid unnecessary public exports. -* Keep module boundaries intentional. -* Write code that is easy to test. -* Prefer behavior-based tests. -* Avoid tests coupled to implementation details. -* Mock only external boundaries or expensive dependencies where practical. -* Keep tests deterministic. -* Avoid time-, network-, and environment-dependent tests without proper isolation. -* Remove dead code instead of keeping it as commented-out code. -* Do not add comments unless the user explicitly requests them. -* Use descriptive naming. -* Avoid vague names such as `data`, `obj`, `item`, `temp`, or `value` when more precise names are available. -* Avoid abbreviations unless they are established domain terminology. -* Keep naming conventions consistent. -* Treat lint and TypeScript errors seriously. -* Do not suppress ESLint or TypeScript diagnostics without a concrete reason. -* Avoid `@ts-ignore`. -* Prefer `@ts-expect-error` only when the error is intentional and documented. -* Do not disable lint rules globally for local problems. -* Prefer correctness, security, maintainability, and predictable behavior over micro-optimizations. - -## Performance Priority - -Prioritize performance work in this order: - -* Architecture -* Network and API usage -* Database access -* Algorithms and data structures -* Rendering and state updates -* Concurrency -* Memory allocations -* CPU micro-optimizations - -Never optimize based purely on assumptions. - -## Safety Rule - -* Compile-time type safety is not runtime validation. -* Validate all external and untrusted data. -* Do not use `any`, type assertions, non-null assertions, or disabled compiler checks to bypass real type problems. -* Prefer making invalid states unrepresentable where practical. diff --git a/shared/rules/typescript/index.md b/shared/rules/typescript/index.md new file mode 100644 index 0000000..43e4958 --- /dev/null +++ b/shared/rules/typescript/index.md @@ -0,0 +1,14 @@ +# TypeScript Rules + +For TypeScript or modern ECMAScript work, open only the topic rules relevant to the task. Load applicable framework and shared security rules as well. + +| Topic | Codex rule | Claude skill | +| --- | --- | --- | +| Types and data modeling | `references/types-and-modeling.md` | `rules-typescript-types-and-modeling` | +| Coding style and control flow | `references/coding-style.md` | `rules-typescript-coding-style` | +| Runtime validation and browser safety | `references/runtime-safety.md` | `rules-typescript-runtime-safety` | +| Security-specific implementation | `references/security.md` | `rules-typescript-security` | +| Async work and resource lifetime | `references/async-and-resources.md` | `rules-typescript-async-and-resources` | +| Performance | `references/performance.md` | `rules-typescript-performance` | +| Modules, APIs, and dependencies | `references/modules-and-dependencies.md` | `rules-typescript-modules-and-dependencies` | +| Tests, diagnostics, and maintenance | `references/testing-and-quality.md` | `rules-typescript-testing-and-quality` | diff --git a/shared/rules/typescript/references/async-and-resources.md b/shared/rules/typescript/references/async-and-resources.md new file mode 100644 index 0000000..8d5315d --- /dev/null +++ b/shared/rules/typescript/references/async-and-resources.md @@ -0,0 +1,12 @@ +# TypeScript Async Work and Resources + +Apply these rules when implementing promises, I/O, streams, subscriptions, timers, or other long-lived work. + +* Handle every rejected promise. Do not leave floating promises; for intentional fire-and-forget work, attach error handling and make its owner and lifetime explicit. +* Use `await` when sequencing or error handling depends on completion. Use `Promise.all` only for independent, bounded work that can safely run together. +* Limit concurrency for large workloads with a queue or limiter. Apply backpressure to streams and producer/consumer flows instead of buffering without a bound. +* Support cancellation with `AbortSignal` when callers need to stop I/O or long-running work. Propagate it through nested calls and release resources when cancellation occurs. +* Avoid synchronous blocking and long CPU-bound work on the Node.js event loop or browser main thread. Move substantial CPU work to a worker or split it into bounded tasks when measurements justify it. +* Remove event listeners, timers, subscriptions, streams, and abort handlers when their owner is done. Avoid retaining closures or resources beyond their intended lifetime. +* Keep shared mutable state safe and deterministic when asynchronous operations can overlap. Prevent stale responses from overwriting newer state when request ordering matters. +* Retry only failures known to be transient, with bounded attempts and delay. Preserve idempotency and cancellation; avoid multiplying retries across layers. diff --git a/shared/rules/typescript/references/coding-style.md b/shared/rules/typescript/references/coding-style.md new file mode 100644 index 0000000..2c58d57 --- /dev/null +++ b/shared/rules/typescript/references/coding-style.md @@ -0,0 +1,12 @@ +# TypeScript Coding Style + +Apply these rules when changing TypeScript syntax, control flow, or everyday code organization. + +* Follow the project's established architecture, formatter, lint rules, and module conventions. +* Prefer `const`; use `let` only when reassignment is needed and avoid `var`. +* Keep functions and modules focused. Prefer early returns and shallow control flow over deeply nested branches or oversized functions. +* Prefer explicit, predictable behavior over implicit coercion, hidden side effects, or overly abstract helpers. +* Use strict equality. Handle `null` and `undefined` intentionally and do not use `||` when `0`, `false`, or an empty string is a valid value. +* Use optional chaining and nullish coalescing where they clarify behavior; avoid mixing nullish conventions without a project reason. +* Keep naming descriptive and consistent. Avoid vague names and abbreviations unless they are established domain terms. +* Remove dead code and unused imports. Do not add comments unless the user asks or a project rule requires them. diff --git a/shared/rules/typescript/references/modules-and-dependencies.md b/shared/rules/typescript/references/modules-and-dependencies.md new file mode 100644 index 0000000..7a1cb6d --- /dev/null +++ b/shared/rules/typescript/references/modules-and-dependencies.md @@ -0,0 +1,12 @@ +# TypeScript Modules and Dependencies + +Apply these rules when changing package dependencies, module structure, imports, or public exports. + +* Follow the project's existing module system consistently; avoid mixing ESM and CommonJS without a compatibility need. +* Keep modules focused and boundaries intentional. Avoid circular dependencies and unnecessary public exports. +* Separate transport models from domain logic where that boundary exists. Do not leak implementation details through public APIs. +* Prefer built-in platform APIs or installed packages before adding a dependency. Keep dependencies minimal, supported, and compatible with the lockfile. +* Review package identity, maintenance, transitive dependencies, permissions, lifecycle scripts, and update behavior before adopting a package. Pin or constrain versions according to project policy and commit the package manager's lockfile. +* Avoid broad dependency upgrades unrelated to the task. Do not hand-edit generated lockfile content; regenerate it with the intended package manager and verify the resulting diff. +* Do not treat bundled frontend configuration as secret storage. Anything shipped to a browser is observable by users. +* Remove unused imports and exports, and keep path and export conventions consistent with the surrounding code. diff --git a/shared/rules/typescript/references/performance.md b/shared/rules/typescript/references/performance.md new file mode 100644 index 0000000..8971d1c --- /dev/null +++ b/shared/rules/typescript/references/performance.md @@ -0,0 +1,13 @@ +# TypeScript Performance + +Apply these rules when optimizing TypeScript runtime, rendering, or memory use. + +* Establish a baseline using representative data, devices, concurrency, and runtime versions. Measure the user-visible path and its latency distribution, throughput, memory, and CPU cost as relevant. +* Prioritize architecture, network and API usage, database access, algorithms, rendering, concurrency, allocations, then CPU micro-optimizations. Confirm that the suspected layer dominates before changing it. +* Avoid quadratic scans and repeated full collection passes for large inputs. Choose `Map`, `Set`, or an indexed lookup when it reduces measured repeated work and its memory cost is acceptable. +* Avoid unnecessary object spreading, intermediate arrays, copies, deep clones, and serialization in hot paths. Do not replace clear code with opaque loops without measured benefit. +* In browsers, reduce unnecessary component updates and DOM work; virtualize large lists when rendering all rows is costly. Avoid memoization that adds stale-state or invalidation bugs. +* In Node.js, keep CPU-heavy synchronous work from blocking the event loop. Stream large payloads and bound queues, buffers, and concurrent requests. +* Prefer platform APIs over custom low-level code or dependencies. Use `structuredClone` only when a deep clone is required and its data constraints are met. +* Cache only repeated measurable work, with bounded size, explicit keys, expiry/invalidation, and safe treatment of user- or tenant-specific data. +* Do not add batching or parallelism without preserving ordering, cancellation, rate limits, and failure behavior. Measure end-to-end results after the change. diff --git a/shared/rules/typescript/references/runtime-safety.md b/shared/rules/typescript/references/runtime-safety.md new file mode 100644 index 0000000..dc5fa50 --- /dev/null +++ b/shared/rules/typescript/references/runtime-safety.md @@ -0,0 +1,13 @@ +# TypeScript Runtime Safety + +Apply these rules when handling API payloads, user input, browser data, or other values outside the type system. + +* Treat external values as `unknown` until validated. TypeScript annotations and assertions do not validate runtime data. +* Validate untrusted data at system boundaries before storing it, using it for authorization, or passing it to sensitive operations. Use schema validation when external contracts are complex. +* Handle `null` and `undefined` intentionally, and narrow values before use. Avoid implicit coercion and unsafe dynamic property access. +* Prefer allowlists for security-sensitive input. Prevent prototype pollution when merging untrusted objects; avoid unsafe recursive merges and dynamic writes to ordinary object prototypes. +* Treat URL, path, header, query, and redirect destinations as untrusted. Server-side fetches must restrict schemes and destinations, validate every redirect, and enforce response size and timeout limits. +* Use framework-safe text binding and automatic output encoding. Avoid `innerHTML`, `dangerouslySetInnerHTML`, and equivalent escape hatches unless sanitized content is required and a maintained sanitizer is applied. +* For cookie-based authentication, preserve secure cookie attributes and the application's CSRF protections. Do not store authentication secrets in browser-accessible storage without an explicit threat model and security design. +* Never expose secrets or sensitive data in output, logs, or errors. Client-side validation and route guards do not replace server-side authentication, authorization, or validation. +* Bound input size, nesting, parsing work, and response size before processing untrusted JSON or other structured data. diff --git a/shared/rules/typescript/references/security.md b/shared/rules/typescript/references/security.md new file mode 100644 index 0000000..e613ab3 --- /dev/null +++ b/shared/rules/typescript/references/security.md @@ -0,0 +1,13 @@ +# TypeScript Security + +Apply these rules when implementing or reviewing security-sensitive TypeScript code. Load the matching shared security rule for the affected API, identity, data, browser, file, network, cryptographic, or dependency boundary as well. + +* Validate external data at runtime before using it in authorization decisions, database operations, filesystem access, or privileged actions. A type assertion is not validation. +* Enforce authentication, authorization, tenant scoping, and input validation on the server. Browser checks, hidden controls, route guards, and TypeScript types do not create a security boundary. +* Keep credentials and private keys on trusted servers. Never embed secrets in frontend bundles, source maps, browser storage, or public runtime configuration. +* Use cryptographically secure platform randomness for tokens and security-sensitive identifiers. Do not use `Math.random()` for secrets, session values, or authorization codes. +* Use parameterized database APIs and allowlists for dynamic identifiers. Avoid constructing SQL, shell commands, HTML, or other interpreted input by string concatenation. +* For server-side URL fetching, restrict protocols and destinations, validate redirects, and bound time, response size, and concurrency to reduce SSRF and resource-exhaustion risk. +* Use the framework's safe output encoding and preserve CSP, TLS, cookie, and CSRF protections. Do not disable these controls to make a request or test pass. +* Review untrusted object merges for prototype pollution, unsafe deserialization, and mass assignment. Copy only fields the caller is allowed to control. +* Review dependency additions and upgrades for source, package identity, install scripts, transitive risk, and lockfile changes. diff --git a/shared/rules/typescript/references/testing-and-quality.md b/shared/rules/typescript/references/testing-and-quality.md new file mode 100644 index 0000000..596a7ce --- /dev/null +++ b/shared/rules/typescript/references/testing-and-quality.md @@ -0,0 +1,11 @@ +# TypeScript Testing and Quality + +Apply these rules when changing tests, lint configuration, compiler diagnostics, or maintainability checks. + +* Prefer behavior-based tests that assert externally observable results. Avoid coupling tests to private implementation details. +* Keep tests deterministic. Control time, randomness, filesystem state, and external services when they affect the result. +* Mock external boundaries or expensive dependencies only when needed; avoid mocks that merely restate implementation details. +* Treat TypeScript, compiler, and lint diagnostics seriously. Do not suppress errors globally or use `@ts-ignore` to hide a real type problem. +* Use `@ts-expect-error` only for an intentional, narrow case whose expected error is part of the test or contract. +* Keep code easy to test through clear module boundaries without adding test-only production exports or hooks without a concrete reason. +* Run focused tests, type checks, and lint for changed paths. Expand checks when shared contracts or generated types are affected. diff --git a/shared/rules/typescript/references/types-and-modeling.md b/shared/rules/typescript/references/types-and-modeling.md new file mode 100644 index 0000000..893ddbe --- /dev/null +++ b/shared/rules/typescript/references/types-and-modeling.md @@ -0,0 +1,12 @@ +# TypeScript Types and Modeling + +Apply these rules when defining TypeScript types, public function contracts, or domain data models. + +* Prefer `strict: true`, precise types, and `unknown` for values whose shape has not been validated. Avoid `any`, broad assertions, and non-null assertions that hide uncertainty. +* Model variants with discriminated unions and use literal unions for constrained values. Make invalid states difficult to represent. +* Prefer `readonly` and immutable data when mutation is unnecessary. Use interfaces for implementation contracts or declaration merging; use type aliases for unions and compositions. +* Give exported APIs, callbacks, and non-trivial functions explicit contracts; rely on inference for simple local values where it keeps code clearer. +* Derive types from trusted existing contracts or generated sources instead of duplicating backend schemas manually. Regenerate generated contracts from their source rather than hand-editing them. +* Keep generic constraints meaningful and types readable. Avoid deep conditional or mapped types when a simpler model expresses the contract. +* Keep compile-time types aligned with runtime schemas. If a schema changes, update its inferred types and tests so the two do not silently drift. +* Avoid representing authorization state, validation success, or trusted input using a type assertion alone. Construct trusted values only after the required checks succeed.