--- name: coding-standards description: Use whenever code is created, modified, reviewed, or refactored. Enforces architecture, file-size limits, Atomic Design, minimal-diff change strategy, and code-quality rules. --- # CODING STANDARDS Governs HOW code is written. Composes with `feature` (what to build) and `documentation` (what to document). # ON ACTIVATION — CLASSIFY THE CHANGE Sets read scope + approval needs: - **SMALL_CHANGE** — bug fix / localized edit. Read only relevant files. Don't scan the repo. - **FEATURE** — new behavior. Read only affected modules. Expand scope only when needed. - **REFACTOR** — structural. Full impact analysis allowed; significant structural changes REQUIRE approval first. Overriding rule: **match the surrounding code** — REPO PATTERNS in `skills/_shared/blocks.md` governs the DEFAULTS below. It never overrides the HARD RULES at the end; those hold against any repo pattern. # CORE PRINCIPLES Prefer: separation of concerns · small focused modules · reusable components · explicit dependencies · consistent patterns. Avoid: monolithic files · duplicated logic · hidden side effects · premature abstraction. Prefer a primitive that makes rules unnecessary over rules patching a primitive's gaps — if one invariant needs three or more restatements to hold, the mechanic is wrong, not the wording. # LANGUAGE LANGUAGE in `skills/_shared/blocks.md` owns it — including identifiers and the i18n rule. Code comments & docstrings: governed entirely by `documentation` — invoke it, don't restate its rules here. # FILE SIZE - Target **300–500 lines**. Soft cap **700**. - Guidelines, not hard failures — a cohesive file slightly over beats an arbitrary split. - Too large → extract along seams (components, hooks, services, helpers). Never split mid-responsibility. ## Frontend / TS-JS specifics Building or editing frontend/TS-JS code → also read `reference/frontend.md` (Atomic Design layout, arrow-const function style). Not applicable to non-frontend work (Python, scripts, backend-only, ML) — skip it there. ## Python / ML specifics Python / ML work (training scripts, notebooks, model configs) → also read `reference/python-ml.md`. Not applicable to frontend/TS-JS work. # BACKEND ARCHITECTURE Layer: Controllers → Services → Repositories → Domain Models. No business logic in controllers or UI — it belongs in services/domain models. API conventions (if building anything consumed externally): consistent error response shape · pick one versioning strategy (URL vs. header) and one pagination pattern (cursor vs. offset), stay consistent · idempotent mutating endpoints. # CHANGE STRATEGY Prefer: minimal diffs · localized edits · existing patterns · incremental improvements. Avoid: full-file rewrites · broad restructuring · unnecessary renaming · large refactors without approval. Introducing a whole-repo formatter/linter alongside other edits → run the format pass FIRST, or commit functional changes before formatting, so mechanical churn lands in its own commit, never intermingled with functional diffs. # REFACTOR RULES Before: identify affected files, assess risk, minimize scope, preserve behavior. Significant structural changes need approval. **"Significant" means ANY of:** > 3 files touched · a public API/exported-signature changes · a module boundary moves · a folder/module is renamed or split. Below this bar → proceed without an extra approval gate (still subject to normal task classification). Significant (per the threshold above) → capture as a `feature` draft before touching code, same as any new behavior. Non-significant → inline approval question is enough, no feature file needed. Moving/renaming/splitting files: TRACKED → VCS move (`git mv`) to keep history. UNTRACKED (never committed) → plain `mv`; `git mv` fails "not under version control" and there is no history to keep. Rewrite every affected import/reference in one pass, then run the project's typecheck/build to confirm no stale references before done. A missed reference fails silently until built. Deleting/merging a SECTION (doc, config, prose) needs the same rigour with none of the safety net — prose has no compiler. Before deleting, grep for BOTH (a) pointers to it and (b) the content itself. A stale pointer or a rule that quietly vanished fails silently forever; nothing builds to catch it. # TESTS Match the project's testing setup — don't invent one it lacks. - Project has tests → new behavior and bug fixes get tests. Bug fix: write the reproduction test first, then make it pass. - Match the existing framework, layout, naming. Don't add a new test stack unasked. - Never delete/weaken a test to make it green — fix the cause. - No test setup at all → don't force one; note the gap if the change is risky. # RESEARCH ORDER Local first: 1) relevant files 2) AGENTS.md (nearest up) 3) `/features` specs 4) `/docs`, ADRs 5) existing code. Before offering the user a keep-vs-change choice about existing behavior, read the code that implements it first — its current state may make the choice moot. External only for: third-party APIs · framework/SDK updates · version-specific behavior (WHEN UNCERTAIN → blocks.md). Prefer official docs; for any framework/SDK check the INSTALLED version's docs — APIs may differ from training data. Consuming/pinning an external artifact at a ref → read its API AT that ref (fetch/checkout it), not a local copy that may be dirty or ahead of the pin; the shipped API can differ. Adding/upgrading a dependency → also read `reference/dependencies.md`. # INDEPENDENT REVIEW — main loop only **You are a subagent → skip this section entirely; you cannot dispatch another agent.** Main loop, diff worth a second pair of eyes (broad, structural, or pre-commit) → delegate the `standards-reviewer` agent: read-only, fetches the diff itself, reviews against THESE rules rather than generic best practice. Returns violations + FRICTION. Optional — skip for a one-line fix. Stack addendum applies (frontend/TS-JS · Python/ML · dependency change) → join the matching `reference/.md` onto this skill's announced base directory and hand the agent that ABSOLUTE path. It has no base directory and its CWD is the reviewed repo — unbriefed, it cannot load the addendum at all. # COMPLETION CHECKLIST - [ ] No obvious duplication introduced - [ ] File sizes within guidelines - [ ] Architecture consistent with surrounding code - [ ] Existing patterns respected - [ ] Diff minimal, matches task scope - [ ] No unnecessary complexity / premature abstraction - [ ] Verification (compile/tests/smoke) ran against the project's OWN env/toolchain (its virtualenv/lockfile/interpreter), not a global install — so a failure means a real defect - [ ] Long-running verification launched in the background prints UNBUFFERED (`PYTHONUNBUFFERED`, `stdbuf`, equivalent) — a buffered pipe hides all progress until exit, so a stalled or mis-sized run looks identical to a working one - [ ] Orphaned imports/vars/functions YOUR change made unused are removed (see CLAUDE.md Surgical Changes — don't touch pre-existing dead code) # HARD RULES Non-obvious, high-severity only — the sections above are not repeated here. - **Never hardcode secrets, API keys, tokens, or credentials** in source — env vars / secret manager per the project's existing pattern. Notice one already committed → flag it immediately, don't silently fix unrelated instances. Reading a config/state file that may hold secrets → read only the keys you need; never dump the whole file into output or logs. - **No business logic in controllers or UI** — BACKEND ARCHITECTURE above owns it. - **Threading a new param through a call chain:** confirm every function declares it AND every caller passes it (grep the name). A value used in a helper but only added to the outer function is a runtime error that typechecks. - **A check guarded on a field's presence never fires when the field is ABSENT.** `if (cfg.x) validate(cfg.x)` passes everything when `x` is omitted. Default-permissive setting → absence IS the dangerous case; test for it explicitly. - **Changing a DEFAULT another layer can also send** (client form, config file, CLI flag): grep every layer that supplies that value. The caller's own default silently overrides the new one and typechecks — the source of truth looks correct while every real request still carries the old value. - **A field's value domain is set by its PRODUCER, not its type.** `string` hides how many values it emits — read the producing code before branching on it. A two-way test on an N-value field mislabels every other value (a sentinel read as success). - **A performance claim about a primitive is a claim about a BACKEND, not a fact.** "cheap", "one extra layer's cost", "negligible" hold for ONE implementation; another library/driver may lack the fast path and fall back orders of magnitude slower. Measure on the TARGET before the claim goes into a comment, doc, or design rationale — a figure reasoned from the literature is not a measurement. See `skills/_shared/blocks.md` for WHEN UNCERTAIN / AFTER THE TASK / LANGUAGE.