Engineering Principles
How To Use This Skill
This is the installable, agent-facing copy of the repository's engineering
philosophy. Load it before any coding-related work starts, before more specific
workflow or domain skills such as architecture, implementation, debugging,
testing, code review, security, performance, CI/CD, release, or project setup.
Apply the principles with scale-appropriate judgment. Tiny edits should not gain
heavy ceremony, but they still inherit the same defaults: explicitness,
validation at boundaries, trustworthy checks, clear architecture, and simple
readable code.
Development Principles
Principles that guide architecture, coding, testing, and project setup decisions.
Agent management, workflow composition, and role rules live outside this skill;
use using-my-skills and the active session instructions for those concerns.
1. Pit of Success
Build systems where doing things correctly is the path of least resistance.
Instead of relying on conventions that developers must remember, construct boundaries that make violations impossible.
- Static analysis that rejects ambiguity at analysis time. Eg typecheck/compilation and so on
- Linters that enforce rules automatically, not through code review
- Architecture that separates concerns structurally, not by agreement
- Error handling that forces callers to address failures, not ignore them
The investment is front-loaded. We spend time setting up types, linters, libraries, and architecture to minimize time spent on bug fixes, manual testing, and debugging later.
|
Python |
TypeScript / Node |
Frontend (React) |
| Static analysis |
basedpyright strict, reportAny=error |
tsconfig strict, noUncheckedIndexedAccess |
same as TS + eslint-plugin-react-hooks exhaustive-deps |
| Linting |
ruff (replaces black, isort, flake8, bandit) |
eslint + prettier (project-specific toolchain) |
eslint-plugin-react, eslint-plugin-jsx-a11y |
| Architecture separation |
layered: UI → Domain → Utilities, UI is a plugin |
layered: transport → use-cases → entities, adapters at boundaries |
data flow unidirectional, UI as pure render of state, separate state vs presentation |
| Error handling enforcement |
Result[T, E] for expected failures, exceptions for bugs |
discriminated unions / Either types for expected failures |
error boundaries at route level, form validation at field level |
2. Explicitness Over Guesswork
Everything should be known before runtime. We always know what types and values we have. We always know whether we are on the error path or the success path.
- Static type checking where the ecosystem supports it — strict mode, no loose
any/Any, no unvalidated suppressions. Every public interface has a type signature; internal functions should follow suit.
- Errors are values, not exceptions — use Result/Either types for expected failures. Exceptions are reserved for programming errors (impossible states, invariant violations) — they mean "this is a bug."
- Data has shape — validate at every external boundary. JSON, configs, API payloads are decoded and narrowed immediately on entry. Never pass raw untyped data through business logic.
- Dynamic boundaries get wrapped — third-party libraries with weak typing get typed wrappers. Untyped data from outside (user input, network, files) gets validated immediately at the boundary.
The goal: if the type checker says it's correct, it runs correctly. If something can fail, the type signature says so.
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Type checking |
basedpyright strict, reportAny=error |
tsconfig strict, noUncheckedIndexedAccess |
same as TS |
| Errors as values |
rusty-results / Result[T, E] |
neverthrow / nevertype / discriminated unions |
neverthrow for side effects, React Query status for async |
| Data shape |
msgspec.Struct for JSON, dataclass for domain |
appropriate runtime schema validator for external boundaries (e.g., zod, valibot); infer transport DTO types from schemas |
appropriate runtime validator for form/transport boundaries; pair with the form library |
| Wrap dynamic boundaries |
typed wrappers around libraries, linter bans raw usage |
typed wrappers around untyped JS libs, adapter pattern |
wrapper hooks around untyped context, typed props on every component |
3. Fail Fast, Fail Early
Detect problems at the earliest possible moment. Compile time is better than runtime. Startup is better than mid-operation. Explicit error is better than silent corruption.
- Validate preconditions at the entry of each subsystem: required permissions, installed dependencies, valid configuration, sane inputs
- Validate postconditions where output correctness matters
- No escape hatches — don't allow loose
any/Any, casts, blanket suppressions, or bare excepts to silently bypass the safety net
- Narrow over assume — when a value could be multiple types, narrow it with type guards or pattern matching. Never assume
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Precondition checks |
raise early at boundary, use __init_subclass__ for cfg |
assert, neverthrow early return, parse with appropriate runtime validator |
form validation before submit, route guards before render |
| No escape hatches |
no # type: ignore without note, no bare except |
no as any / @ts-ignore, no eslint-disable without note |
no // @ts-ignore, no disabled hooks rules |
| Type narrowing |
isinstance, TypeIs, match statement |
type guards, discriminated unions, satisfies keyword |
same as TS |
4. Error Handling as Control Flow
Errors are a normal part of program execution, not exceptional events. The type system should track them.
- Expected failures (IO, network, user input, missing resources): return Result/Either types — the caller must handle both paths
- Programming errors (violated invariants, impossible states): raise exceptions — these are bugs, the program should crash
- Third-party boundaries: catch library errors immediately, convert to Result — don't let foreign error hierarchies leak through layers
- Error boundaries: UI/CLI layers catch all remaining errors and present user-friendly messages. Business logic never swallows errors silently
- Early returns: handle the error case first, keep the success path unindented and linear
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Expected failures |
Result[T, E] (rusty-results) |
neverthrow Result<T, E>, discriminated unions |
React Query status + error fields, neverthrow in mutations |
| Programming errors |
raise or assert for impossible states |
throw for bugs, panic-equivalent |
throw in dev, caught by error boundaries |
| Third-party boundaries |
catch library Exception → return Result |
catch → Result pattern, wrap untyped callbacks |
wrap external API calls in typed hooks with error states |
| Error boundaries |
outer CLI try/except → user message, GUI try/except → dialog |
NestJS exception filters, domain never catches |
per route, global fallback |
| Early returns |
if error: return Err(...), success path unindented |
if (error) return failure(...), success path unindented |
guard clauses before render |
5. Testing Philosophy
Tests exist to prove that features work, not to produce green checkmarks.
- Trustworthiness over coverage — a test that mocks away the thing it's testing proves nothing. Coverage numbers are a guideline, not a goal.
- Integration / e2e tests are the primary safety net — they test real behavior through real code paths. Five good integration tests give more confidence than 100 unit tests with heavy mocking.
- Unit tests for pure logic — functions that transform data without side effects. These are worth unit testing because they're honest.
- Real over mocked — prefer real HTTP servers over patched requests. Prefer real file systems (via temp directories) over mocked IO. Prefer real databases over in-memory fakes. When mocking is necessary, build test doubles, don't monkey-patch runtime.
- 20/80 rule — invest test effort where it gives the most confidence. Don't chase 100% coverage in utilities while core workflows go untested.
- Two tiers of infrastructure — lightweight (test runner + fixtures) for most projects. Heavyweight (containers, contract-powered test servers, isolated environments) when the project warrants it.
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Integration / e2e |
pytest + httpx, containerized, real process exec |
vitest + listening app server with real HTTP client, Playwright for browser e2e |
Playwright / Cypress for browser tests |
| Real over mocked |
pytest-httpserver, tmp_path for files, subprocess for CLI |
real-listening HTTP test server (OpenAPI-powered or shared-schema TS server), tmp dirs for files, execa for CLI |
contract-powered real listening test server (Mockoon for OpenAPI, or eg TS server importing shared transport schemas), testing-library (no enzyme) |
| Lightweight infra |
pytest + fixtures + markers |
vitest + describe/it/expect |
vitest + testing-library + happy-dom |
| Heavyweight infra (when needed) |
testcontainers, docker compose |
testcontainers, docker compose, isolated env |
Playwright with docker browsers, Percy for visual |
6. Architecture: Separation by Responsibility
Separate what changes for different reasons. Separate what can be tested independently.
- Layered dependency flow: Presentation (UI/CLI/API) → Domain (business logic) → Utilities. Never upward.
- Separate by expected change axis — split code where domain rules, validation, transport, infrastructure, platform integration, or workflow orchestration will evolve for different reasons.
- UI is a plugin (within its application boundary) — UI, CLI, API, workers, and automation should be thin adapters over the logic they own. In a single codebase, this often means a reusable core with multiple presentation adapters. In separate frontend/backend codebases, do not force frontend code to share backend internals; share transport contracts—transport runtime schemas or generated clients—not backend domain, persistence, entity, or use-case models. Server-side domain invariants still belong on the server. Frontend-specific state, validation UX, and interaction logic belong in the frontend application layer.
- Reusable core, thin adapters — if CLI, GUI, API, workers, or automation may share behavior (within the same application boundary), keep a composable core and treat each interface as a presentation adapter. Across separate applications, share contracts before implementation.
- State management is layered, not scattered — in UI apps, separate server state (API data), client state (UI, form, navigation), and derived state (computed from other state). Use purpose-built tools for each layer. Don't scatter low-level state management across the app, but also don't put everything in one god store.
- Data vs. logic — domain types carry data. Services operate on data. Utilities are stateless pure functions. Stateful classes exist for managing lifecycle — but their state is explicit, not hidden.
- Prefer composition over inheritance — explicit data flow, small collaborating objects, protocols over deep class hierarchies. Inheritance only when genuinely stable and semantic, not to share code.
- Scale-appropriate separation — in large projects: separate files, directories, layers. In single scripts: separate functions, clear sections within one file. The principle scales; the implementation doesn't need to.
- Wrap third-party dependencies — isolate external dependencies behind typed interfaces for type safety, testability, and swap-ability.
- Transparency over magic — important workflows should expose validation, state transitions, logs, and dry-run behavior where practical.
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Layered architecture |
UI → Domain → Utils, GUI never imports from domain |
controllers → use-cases → entities, or event handlers → services |
Zustand/Jotai for client state, TanStack Query for server state, React Hook Form for forms |
| Presentation adapters |
same core for CLI, GUI (Qt/React), API |
use-cases behind controllers, workers, CLI, serverless |
frontend app core behind pages, components, and routes; backend shared through contracts |
| State management |
n/a |
n/a |
server state: TanStack Query; client state: Zustand/Jotai; derived: useMemo; form: react-hook-form |
| Composition over inheritance |
protocols / ABCs, dataclasses composited |
interfaces, functional composition, NestJS providers |
custom hooks compose behavior, context for shared state |
| Wrap third-party |
typed wrappers + ruff banned-api |
adapter interfaces around JS libs |
custom hooks wrapping libraries, prop interfaces |
7. Tooling: One Per Job, Strictly Configured
Use tools that enforce the philosophy automatically. Prefer tools that are fast, opinionated, and cover one job well.
- One tool per job — one linter, one formatter, one type checker, one test runner. No competing tools for the same responsibility.
- Strict configuration from the start — the strictest viable config. Loosen only when there is a concrete, measured reason. Never default to lenient.
- Automate enforcement — pre-commit hooks or equivalent, CI gates. Every commit passes analysis, linting, and tests.
- Latest stable runtime — use modern language features. Don't target old versions unless explicitly required.
| Job |
Python |
TypeScript / Node |
Frontend (React) |
| Linter |
ruff |
eslint |
eslint + eslint-plugin-react |
| Formatter |
ruff format |
prettier |
prettier |
| Type checker |
basedpyright |
tsc |
tsc |
| Test runner |
pytest |
vitest |
vitest + testing-library |
| Package manager |
uv |
depending on project (e.g., pnpm) |
depending on project (e.g., pnpm) |
| Task runner |
poethepoet |
npm scripts initially; add orchestrator only when graph/caching needed |
npm scripts initially; add orchestrator only when graph/caching needed |
| Git hooks |
pre-commit |
husky + lint-staged |
husky + lint-staged |
8. Interface Standards
Each interface type gets one standard framework per ecosystem, chosen for quality and long-term viability.
| Layer |
Python |
TypeScript / Node |
Frontend (React) |
| Web framework |
FastAPI / Django |
NestJS |
Next.js / Remix |
| CLI |
typer |
commander / clack |
n/a |
| HTTP client |
httpx |
ky / ofetch |
ky / TanStack Query |
| Config |
YAML + msgspec |
config loader + appropriate runtime schema validator |
same as Node |
| Logging |
colorlog |
pino |
pino (server-side) |
| Async |
asyncio |
native async/await |
native async/await |
| GUI |
PySide6 + qasync |
tauri / electron |
React itself |
9. Frameworks: Adopt, Don't Reinvent
Choose established batteries-included frameworks over ad-hoc architecture for core concerns.
A framework codifies conventions, provides battle-tested infrastructure, and brings an ecosystem that individual developers can't replicate.
- Pick the framework for the job — NestJS over Express, FastAPI/Django over Flask. React framework selection (Next.js, Vite + React Router, etc.) is a project decision owned by
setting-up-react-projects. The framework's conventions become your conventions. Don't fight them unless you chose the wrong framework.
- Standard library from day one — don't reimplement ad-hoc state management, form handling, routing, or validation. Eg for React: TanStack Query + Zustand/Jotai + React Hook Form + appropriate runtime schema validator. Runtime validation is non-negotiable; exact state, form, and schema packages are project-specific. For Python backend: FastAPI + SQLAlchemy/psycopg3 + alembic. This should be extended/adjust per project.
- Thin application code — framework handles transport, serialization, routing, lifecycle. Your code handles business logic. When framework knowledge dominates your codebase, the separation is wrong.
- Exceptions prove the rule — a small script or experimental prototype may skip frameworks. But if the project will be maintained, introduce the framework before ad-hoc patterns harden.
10. Project Setup: Invest Early
Every project, no matter how small, starts with the safety net configured:
- Single file: inline metadata / dependencies, tool config at the top, shebang for direct execution
- Full project: src layout, AGENTS.md, principles reference, linter + type checker + test runner configured, CI from day one
- Stronger scaffolding when complexity is real — if the domain needs auth, background jobs, stateful workflows, migrations, or admin concerns, prefer stronger framework scaffolding early instead of bolting it on later
- The overhead is worth it — spending 10 minutes on setup saves hours of debugging implicit failures later. This is the pit of success in action.
| Concept |
Python |
TypeScript / Node |
Frontend (React) |
| Single file |
PEP 723 inline metadata, uv run --script |
tsx or Node native type stripping for small projects (see writing-scripts) |
npx create-next-app, vite |
| Full project bootstrap |
uv init, pyproject.toml, ruff, basedpyright, pytest |
project-specific selection: package manager, strict tsconfig, linter, formatter, runner, test runner |
project-specific: framework scaffold, linter, formatter, runner, test runner |
| CI from day one |
GitHub Actions: lint → typecheck → test |
GitHub Actions: format:check → lint → typecheck → test → build (when applicable) |
GitHub Actions: lint → typecheck → test → build |
11. Principle Over Prescription
Ceremony scales with task size. Not every project needs the full apparatus — but every project needs the principles.
- A 50-line script — inline metadata, a shebang, quick manual verification. No pre-commit, no CI.
- A growing project — add type checking, strict configs, pre-commit, linter, tests, git hooks one at a time as value becomes clear.
- A production system — full stack: CI with lint → typecheck → test, pre-merge CI e2e testing, complex release system with rollbacks.
The same principles apply at every scale. The implementation scales down. Don't force heavyweight process on trivial work. Don't skip safety net on anything that will be maintained.
12. Explicit Over Clever
Code is read far more often than it is written. Favor clarity, directness, and transparency over abstraction, metaprogramming, or conciseness tricks.
- Name identifiers for readers, not writers — variables, fields, entities and types, functions, events, jobs, and similar identifiers should use meaningful, context-rich domain names rather than generic buckets. Longer is better when it removes ambiguity or saves the reader from tracing context:
payload → solana_transaction_payload, mode → service_lifecycle_mode. Apply this scope-sensitively, not mechanically: a short name is appropriate when a small, focused local scope already makes its meaning obvious.
- Flat is better than nested — guard clauses, early returns, extracted helpers. Deep nesting is a signal the logic needs splitting
- Explicit state over implicit magic — visible data flow beats decorators, proxies, or interceptors that hide what's happening
- Simple over concise — if the concise version requires a mental pause, write the simple version. Fewer characters is not the goal
- Comment business and domain logic — comments are always required, but must explain intent, business rules, invariants, or the overall approach rather than narrating syntax
- Explain non-obvious constraints — always comment tricks, hacks, bug workarounds, and framework-imposed constraints. State the reason or constraint and, when useful, the condition for removing the workaround
- Layer comments by scale — begin large, complex logic sections with a distinct high-level overview of the whole flow, then add targeted comments at logic steps, non-obvious phases, decisions, or invariants
- Skip obvious narration — simple, self-evident boilerplate and standard framework wiring, such as routine controllers or React configuration, need no comments. Avoid line-by-line descriptions of what the code does
Applying This Skill
Architecture, coding, testing, security, performance, and other engineering-facing
skills build on these principles. Skill invocation, workflow composition,
delegation, and agent orchestration are governed by using-my-skills and the
repository's skill philosophy.
1---2name: engineering-principles3description: ALWAYS LOAD THIS SKILL BEFORE ANY CODING-RELATED WORK: writing or editing code, fixing bugs, debugging, planning architecture, designing APIs, setting up projects, testing, reviewing code, refactoring, CI/CD, release automation, security, performance, or researching implementation behavior. It provides the local engineering philosophy: strict types, validation, explicit errors, trustworthy tests, clear architecture, one strict tool per job, frameworks over ad hoc patterns, and explicit-over-clever code.4license: MIT5---67# Engineering Principles89## How To Use This Skill1011This is the installable, agent-facing copy of the repository's engineering12philosophy. Load it before any coding-related work starts, before more specific13workflow or domain skills such as architecture, implementation, debugging,14testing, code review, security, performance, CI/CD, release, or project setup.1516Apply the principles with scale-appropriate judgment. Tiny edits should not gain17heavy ceremony, but they still inherit the same defaults: explicitness,18validation at boundaries, trustworthy checks, clear architecture, and simple19readable code.2021---2223# Development Principles2425Principles that guide architecture, coding, testing, and project setup decisions.26Agent management, workflow composition, and role rules live outside this skill;27use `using-my-skills` and the active session instructions for those concerns.2829---3031## 1. Pit of Success3233Build systems where doing things correctly is the path of least resistance.34Instead of relying on conventions that developers must remember, construct boundaries that make violations impossible.3536- **Static analysis** that rejects ambiguity at analysis time. Eg typecheck/compilation and so on37- **Linters** that enforce rules automatically, not through code review38- **Architecture** that separates concerns structurally, not by agreement39- **Error handling** that forces callers to address failures, not ignore them4041**The investment is front-loaded.** We spend time setting up types, linters, libraries, and architecture to minimize time spent on bug fixes, manual testing, and debugging later.4243<details>44<summary>Ecosystem examples</summary>4546| | Python | TypeScript / Node | Frontend (React) |47| -------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ |48| Static analysis | basedpyright strict, reportAny=error | tsconfig strict, noUncheckedIndexedAccess | same as TS + eslint-plugin-react-hooks exhaustive-deps |49| Linting | ruff (replaces black, isort, flake8, bandit) | eslint + prettier (project-specific toolchain) | eslint-plugin-react, eslint-plugin-jsx-a11y |50| Architecture separation | layered: UI → Domain → Utilities, UI is a plugin | layered: transport → use-cases → entities, adapters at boundaries | data flow unidirectional, UI as pure render of state, separate state vs presentation |51| Error handling enforcement | Result[T, E] for expected failures, exceptions for bugs | discriminated unions / Either types for expected failures | error boundaries at route level, form validation at field level |5253</details>5455## 2. Explicitness Over Guesswork5657Everything should be known before runtime. We always know what types and values we have. We always know whether we are on the error path or the success path.5859- **Static type checking where the ecosystem supports it** — strict mode, no loose `any`/`Any`, no unvalidated suppressions. Every public interface has a type signature; internal functions should follow suit.60- **Errors are values, not exceptions** — use Result/Either types for expected failures. Exceptions are reserved for programming errors (impossible states, invariant violations) — they mean "this is a bug."61- **Data has shape** — validate at every external boundary. JSON, configs, API payloads are decoded and narrowed immediately on entry. Never pass raw untyped data through business logic.62- **Dynamic boundaries get wrapped** — third-party libraries with weak typing get typed wrappers. Untyped data from outside (user input, network, files) gets validated immediately at the boundary.6364The goal: if the type checker says it's correct, it runs correctly. If something can fail, the type signature says so.6566<details>67<summary>Ecosystem examples</summary>6869| Concept | Python | TypeScript / Node | Frontend (React) |70| ----------------------- | ------------------------------------------------------ | ------------------------------------------------------------ | -------------------------------------------------------------------- |71| Type checking | basedpyright strict, reportAny=error | tsconfig strict, noUncheckedIndexedAccess | same as TS |72| Errors as values | rusty-results / Result[T, E] | neverthrow / nevertype / discriminated unions | neverthrow for side effects, React Query status for async |73| Data shape | msgspec.Struct for JSON, dataclass for domain | appropriate runtime schema validator for external boundaries (e.g., zod, valibot); infer transport DTO types from schemas | appropriate runtime validator for form/transport boundaries; pair with the form library |74| Wrap dynamic boundaries | typed wrappers around libraries, linter bans raw usage | typed wrappers around untyped JS libs, adapter pattern | wrapper hooks around untyped context, typed props on every component |7576</details>7778## 3. Fail Fast, Fail Early7980Detect problems at the earliest possible moment. Compile time is better than runtime. Startup is better than mid-operation. Explicit error is better than silent corruption.8182- **Validate preconditions** at the entry of each subsystem: required permissions, installed dependencies, valid configuration, sane inputs83- **Validate postconditions** where output correctness matters84- **No escape hatches** — don't allow loose `any`/`Any`, casts, blanket suppressions, or bare excepts to silently bypass the safety net85- **Narrow over assume** — when a value could be multiple types, narrow it with type guards or pattern matching. Never assume8687<details>88<summary>Ecosystem examples</summary>8990| Concept | Python | TypeScript / Node | Frontend (React) |91| ------------------- | -------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- |92| Precondition checks | raise early at boundary, use `__init_subclass__` for cfg | assert, neverthrow early return, parse with appropriate runtime validator | form validation before submit, route guards before render |93| No escape hatches | no `# type: ignore` without note, no bare except | no `as any` / `@ts-ignore`, no `eslint-disable` without note | no `// @ts-ignore`, no disabled hooks rules |94| Type narrowing | isinstance, TypeIs, match statement | type guards, discriminated unions, satisfies keyword | same as TS |9596</details>9798## 4. Error Handling as Control Flow99100Errors are a normal part of program execution, not exceptional events. The type system should track them.101102- **Expected failures** (IO, network, user input, missing resources): return Result/Either types — the caller must handle both paths103- **Programming errors** (violated invariants, impossible states): raise exceptions — these are bugs, the program should crash104- **Third-party boundaries**: catch library errors immediately, convert to Result — don't let foreign error hierarchies leak through layers105- **Error boundaries**: UI/CLI layers catch all remaining errors and present user-friendly messages. Business logic never swallows errors silently106- **Early returns**: handle the error case first, keep the success path unindented and linear107108<details>109<summary>Ecosystem examples</summary>110111| Concept | Python | TypeScript / Node | Frontend (React) |112| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------------------------- |113| Expected failures | Result[T, E] (rusty-results) | neverthrow Result<T, E>, discriminated unions | React Query status + error fields, neverthrow in mutations |114| Programming errors | raise or assert for impossible states | throw for bugs, panic-equivalent | throw in dev, caught by error boundaries |115| Third-party boundaries | catch library Exception → return Result | catch → Result pattern, wrap untyped callbacks | wrap external API calls in typed hooks with error states |116| Error boundaries | outer CLI try/except → user message, GUI try/except → dialog | NestJS exception filters, domain never catches | <ErrorBoundary> per route, global fallback |117| Early returns | if error: return Err(...), success path unindented | if (error) return failure(...), success path unindented | guard clauses before render |118119</details>120121## 5. Testing Philosophy122123Tests exist to prove that features work, not to produce green checkmarks.124125- **Trustworthiness over coverage** — a test that mocks away the thing it's testing proves nothing. Coverage numbers are a guideline, not a goal.126- **Integration / e2e tests are the primary safety net** — they test real behavior through real code paths. Five good integration tests give more confidence than 100 unit tests with heavy mocking.127- **Unit tests for pure logic** — functions that transform data without side effects. These are worth unit testing because they're honest.128- **Real over mocked** — prefer real HTTP servers over patched requests. Prefer real file systems (via temp directories) over mocked IO. Prefer real databases over in-memory fakes. When mocking is necessary, build test doubles, don't monkey-patch runtime.129- **20/80 rule** — invest test effort where it gives the most confidence. Don't chase 100% coverage in utilities while core workflows go untested.130- **Two tiers of infrastructure** — lightweight (test runner + fixtures) for most projects. Heavyweight (containers, contract-powered test servers, isolated environments) when the project warrants it.131132<details>133<summary>Ecosystem examples</summary>134135| Concept | Python | TypeScript / Node | Frontend (React) |136| ------------------------------- | --------------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------------- |137| Integration / e2e | pytest + httpx, containerized, real process exec | vitest + listening app server with real HTTP client, Playwright for browser e2e | Playwright / Cypress for browser tests |138| Real over mocked | pytest-httpserver, tmp_path for files, subprocess for CLI | real-listening HTTP test server (OpenAPI-powered or shared-schema TS server), tmp dirs for files, execa for CLI | contract-powered real listening test server (Mockoon for OpenAPI, or eg TS server importing shared transport schemas), testing-library (no enzyme) |139| Lightweight infra | pytest + fixtures + markers | vitest + describe/it/expect | vitest + testing-library + happy-dom |140| Heavyweight infra (when needed) | testcontainers, docker compose | testcontainers, docker compose, isolated env | Playwright with docker browsers, Percy for visual |141142</details>143144## 6. Architecture: Separation by Responsibility145146Separate what changes for different reasons. Separate what can be tested independently.147148- **Layered dependency flow**: Presentation (UI/CLI/API) → Domain (business logic) → Utilities. Never upward.149- **Separate by expected change axis** — split code where domain rules, validation, transport, infrastructure, platform integration, or workflow orchestration will evolve for different reasons.150- **UI is a plugin (within its application boundary)** — UI, CLI, API, workers, and automation should be thin adapters over the logic they own. In a single codebase, this often means a reusable core with multiple presentation adapters. In separate frontend/backend codebases, do not force frontend code to share backend internals; share transport contracts—transport runtime schemas or generated clients—not backend domain, persistence, entity, or use-case models. Server-side domain invariants still belong on the server. Frontend-specific state, validation UX, and interaction logic belong in the frontend application layer.151- **Reusable core, thin adapters** — if CLI, GUI, API, workers, or automation may share behavior (within the same application boundary), keep a composable core and treat each interface as a presentation adapter. Across separate applications, share contracts before implementation.152- **State management is layered, not scattered** — in UI apps, separate server state (API data), client state (UI, form, navigation), and derived state (computed from other state). Use purpose-built tools for each layer. Don't scatter low-level state management across the app, but also don't put everything in one god store.153- **Data vs. logic** — domain types carry data. Services operate on data. Utilities are stateless pure functions. Stateful classes exist for managing lifecycle — but their state is explicit, not hidden.154- **Prefer composition over inheritance** — explicit data flow, small collaborating objects, protocols over deep class hierarchies. Inheritance only when genuinely stable and semantic, not to share code.155- **Scale-appropriate separation** — in large projects: separate files, directories, layers. In single scripts: separate functions, clear sections within one file. The principle scales; the implementation doesn't need to.156- **Wrap third-party dependencies** — isolate external dependencies behind typed interfaces for type safety, testability, and swap-ability.157- **Transparency over magic** — important workflows should expose validation, state transitions, logs, and dry-run behavior where practical.158159<details>160<summary>Ecosystem examples</summary>161162| Concept | Python | TypeScript / Node | Frontend (React) |163| ---------------------------- | -------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |164| Layered architecture | UI → Domain → Utils, GUI never imports from domain | controllers → use-cases → entities, or event handlers → services | Zustand/Jotai for client state, TanStack Query for server state, React Hook Form for forms |165| Presentation adapters | same core for CLI, GUI (Qt/React), API | use-cases behind controllers, workers, CLI, serverless | frontend app core behind pages, components, and routes; backend shared through contracts |166| State management | n/a | n/a | server state: TanStack Query; client state: Zustand/Jotai; derived: useMemo; form: react-hook-form |167| Composition over inheritance | protocols / ABCs, dataclasses composited | interfaces, functional composition, NestJS providers | custom hooks compose behavior, context for shared state |168| Wrap third-party | typed wrappers + ruff banned-api | adapter interfaces around JS libs | custom hooks wrapping libraries, prop interfaces |169170</details>171172## 7. Tooling: One Per Job, Strictly Configured173174Use tools that enforce the philosophy automatically. Prefer tools that are fast, opinionated, and cover one job well.175176- **One tool per job** — one linter, one formatter, one type checker, one test runner. No competing tools for the same responsibility.177- **Strict configuration from the start** — the strictest viable config. Loosen only when there is a concrete, measured reason. Never default to lenient.178- **Automate enforcement** — pre-commit hooks or equivalent, CI gates. Every commit passes analysis, linting, and tests.179- **Latest stable runtime** — use modern language features. Don't target old versions unless explicitly required.180181<details>182<summary>Ecosystem examples</summary>183184| Job | Python | TypeScript / Node | Frontend (React) |185| --------------- | ------------ | ------------------- | ---------------------------- |186| Linter | ruff | eslint | eslint + eslint-plugin-react |187| Formatter | ruff format | prettier | prettier |188| Type checker | basedpyright | tsc | tsc |189| Test runner | pytest | vitest | vitest + testing-library |190| Package manager | uv | depending on project (e.g., pnpm) | depending on project (e.g., pnpm) |191| Task runner | poethepoet | npm scripts initially; add orchestrator only when graph/caching needed | npm scripts initially; add orchestrator only when graph/caching needed |192| Git hooks | pre-commit | husky + lint-staged | husky + lint-staged |193194</details>195196## 8. Interface Standards197198Each interface type gets one standard framework per ecosystem, chosen for quality and long-term viability.199200| Layer | Python | TypeScript / Node | Frontend (React) |201| ------------- | ---------------- | ------------------ | ------------------- |202| Web framework | FastAPI / Django | NestJS | Next.js / Remix |203| CLI | typer | commander / clack | n/a |204| HTTP client | httpx | ky / ofetch | ky / TanStack Query |205| Config | YAML + msgspec | config loader + appropriate runtime schema validator | same as Node |206| Logging | colorlog | pino | pino (server-side) |207| Async | asyncio | native async/await | native async/await |208| GUI | PySide6 + qasync | tauri / electron | React itself |209210## 9. Frameworks: Adopt, Don't Reinvent211212Choose established batteries-included frameworks over ad-hoc architecture for core concerns.213A framework codifies conventions, provides battle-tested infrastructure, and brings an ecosystem that individual developers can't replicate.214215- **Pick the framework for the job** — NestJS over Express, FastAPI/Django over Flask. React framework selection (Next.js, Vite + React Router, etc.) is a project decision owned by `setting-up-react-projects`. The framework's conventions become your conventions. Don't fight them unless you chose the wrong framework.216- **Standard library from day one** — don't reimplement ad-hoc state management, form handling, routing, or validation. Eg for React: TanStack Query + Zustand/Jotai + React Hook Form + appropriate runtime schema validator. Runtime validation is non-negotiable; exact state, form, and schema packages are project-specific. For Python backend: FastAPI + SQLAlchemy/psycopg3 + alembic. This should be extended/adjust per project.217- **Thin application code** — framework handles transport, serialization, routing, lifecycle. Your code handles business logic. When framework knowledge dominates your codebase, the separation is wrong.218- **Exceptions prove the rule** — a small script or experimental prototype may skip frameworks. But if the project will be maintained, introduce the framework before ad-hoc patterns harden.219220## 10. Project Setup: Invest Early221222Every project, no matter how small, starts with the safety net configured:223224- **Single file**: inline metadata / dependencies, tool config at the top, shebang for direct execution225- **Full project**: src layout, AGENTS.md, principles reference, linter + type checker + test runner configured, CI from day one226- **Stronger scaffolding when complexity is real** — if the domain needs auth, background jobs, stateful workflows, migrations, or admin concerns, prefer stronger framework scaffolding early instead of bolting it on later227- **The overhead is worth it** — spending 10 minutes on setup saves hours of debugging implicit failures later. This is the pit of success in action.228229<details>230<summary>Ecosystem examples</summary>231232| Concept | Python | TypeScript / Node | Frontend (React) |233| ---------------------- | --------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------- |234| Single file | PEP 723 inline metadata, uv run --script | tsx or Node native type stripping for small projects (see writing-scripts) | npx create-next-app, vite |235| Full project bootstrap | uv init, pyproject.toml, ruff, basedpyright, pytest | project-specific selection: package manager, strict tsconfig, linter, formatter, runner, test runner | project-specific: framework scaffold, linter, formatter, runner, test runner |236| CI from day one | GitHub Actions: lint → typecheck → test | GitHub Actions: format:check → lint → typecheck → test → build (when applicable) | GitHub Actions: lint → typecheck → test → build |237238</details>239240## 11. Principle Over Prescription241242Ceremony scales with task size. Not every project needs the full apparatus — but every project needs the principles.243244- **A 50-line script** — inline metadata, a shebang, quick manual verification. No pre-commit, no CI.245- **A growing project** — add type checking, strict configs, pre-commit, linter, tests, git hooks one at a time as value becomes clear.246- **A production system** — full stack: CI with lint → typecheck → test, pre-merge CI e2e testing, complex release system with rollbacks.247248The same principles apply at every scale. The implementation scales down. Don't force heavyweight process on trivial work. Don't skip safety net on anything that will be maintained.249250## 12. Explicit Over Clever251252Code is read far more often than it is written. Favor clarity, directness, and transparency over abstraction, metaprogramming, or conciseness tricks.253254- **Name identifiers for readers, not writers** — variables, fields, entities and types, functions, events, jobs, and similar identifiers should use meaningful, context-rich domain names rather than generic buckets. Longer is better when it removes ambiguity or saves the reader from tracing context: `payload` → `solana_transaction_payload`, `mode` → `service_lifecycle_mode`. Apply this scope-sensitively, not mechanically: a short name is appropriate when a small, focused local scope already makes its meaning obvious.255- **Flat is better than nested** — guard clauses, early returns, extracted helpers. Deep nesting is a signal the logic needs splitting256- **Explicit state over implicit magic** — visible data flow beats decorators, proxies, or interceptors that hide what's happening257- **Simple over concise** — if the concise version requires a mental pause, write the simple version. Fewer characters is not the goal258- **Comment business and domain logic** — comments are always required, but must explain intent, business rules, invariants, or the overall approach rather than narrating syntax259- **Explain non-obvious constraints** — always comment tricks, hacks, bug workarounds, and framework-imposed constraints. State the reason or constraint and, when useful, the condition for removing the workaround260- **Layer comments by scale** — begin large, complex logic sections with a distinct high-level overview of the whole flow, then add targeted comments at logic steps, non-obvious phases, decisions, or invariants261- **Skip obvious narration** — simple, self-evident boilerplate and standard framework wiring, such as routine controllers or React configuration, need no comments. Avoid line-by-line descriptions of what the code does262263## Applying This Skill264265Architecture, coding, testing, security, performance, and other engineering-facing266skills build on these principles. Skill invocation, workflow composition,267delegation, and agent orchestration are governed by `using-my-skills` and the268repository's skill philosophy.