From 30442006e74d19d22988e9f29e632ae99aea031d Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 00:13:53 +0800 Subject: [PATCH 01/12] docs(specs): audit feature artifacts and phase-1 corpus baseline Co-Authored-By: Claude Fable 5 --- .../checklists/requirements.md | 37 ++++ .../contracts/findings-report.md | 19 ++ .../contracts/guidance-style.md | 27 +++ .../contracts/stack-pack-structure.md | 25 +++ .../corpus-baseline.md | 53 +++++ specs/002-audit-repo-guidance/data-model.md | 90 ++++++++ specs/002-audit-repo-guidance/plan.md | 99 +++++++++ specs/002-audit-repo-guidance/quickstart.md | 59 ++++++ specs/002-audit-repo-guidance/research.md | 84 ++++++++ specs/002-audit-repo-guidance/spec.md | 176 ++++++++++++++++ specs/002-audit-repo-guidance/tasks.md | 194 ++++++++++++++++++ 11 files changed, 863 insertions(+) create mode 100644 specs/002-audit-repo-guidance/checklists/requirements.md create mode 100644 specs/002-audit-repo-guidance/contracts/findings-report.md create mode 100644 specs/002-audit-repo-guidance/contracts/guidance-style.md create mode 100644 specs/002-audit-repo-guidance/contracts/stack-pack-structure.md create mode 100644 specs/002-audit-repo-guidance/corpus-baseline.md create mode 100644 specs/002-audit-repo-guidance/data-model.md create mode 100644 specs/002-audit-repo-guidance/plan.md create mode 100644 specs/002-audit-repo-guidance/quickstart.md create mode 100644 specs/002-audit-repo-guidance/research.md create mode 100644 specs/002-audit-repo-guidance/spec.md create mode 100644 specs/002-audit-repo-guidance/tasks.md diff --git a/specs/002-audit-repo-guidance/checklists/requirements.md b/specs/002-audit-repo-guidance/checklists/requirements.md new file mode 100644 index 0000000..593acd2 --- /dev/null +++ b/specs/002-audit-repo-guidance/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: Template Guidance Audit & Streamlining + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-07-02 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan` +- Validation performed 2026-07-02: all items pass. Ambiguities in the original description (reduction target, scope of vendored tooling, disposition of historical specs) were resolved with reasonable defaults and recorded in the spec's Assumptions section rather than left as clarification markers. +- Re-validated 2026-07-02 after two scope updates: (1) public-showcase goal — added US3 (public front door), the release gate (history scan, license, positioning), and voice-consistency requirements; "award-winning" is operationalized via measurable proxies per the Assumptions. (2) Stack uniformity — US4 extended with canonical pack structure (FR-015, SC-011). All items still pass. +- Re-validated 2026-07-03 after update 3: Cavalry branding on front door and design guide (FR-016, SC-012, self-containment edge case) and the license decision resolved to permissive/MIT per maintainer direction (FR-013, SC-009). Corpus baseline re-measured after the design-guide revision landed: 36,978 words. All items still pass. diff --git a/specs/002-audit-repo-guidance/contracts/findings-report.md b/specs/002-audit-repo-guidance/contracts/findings-report.md new file mode 100644 index 0000000..4ff2bee --- /dev/null +++ b/specs/002-audit-repo-guidance/contracts/findings-report.md @@ -0,0 +1,19 @@ +# Contract: Findings Report Format + +The P1 audit produces `specs/002-audit-repo-guidance/audit-report.md`. It is the gate artifact for every later slice, so its format is a contract: the maintainer must be able to approve, reject, and track every change from this one document. + +## Required sections, in order + +1. **Header** — audit date, commit audited, corpus word-count baseline, scanner tool + version used for the history scan. +2. **Executive summary** — one paragraph: overall health, count of findings by severity, whether the repo is currently release-ready. +3. **Release gate** — the four gate items (secrets, internal references, license, front door) each marked pass/fail with evidence. +4. **Findings** — every finding, ordered `release-blocking → high → medium → low`, using the Finding schema from [data-model.md](../data-model.md): id, location, issue, violated convention/principle, severity, remedy, destructive flag, disposition. Destructive findings carry an explicit **approval requested** marker. +5. **Instruction-discovery map** — every corpus file with its `audience` and `loading` classification (the written answer to the maintainer's README question, SC-002); files at risk of being missed are called out. +6. **File-by-file verdicts** — appendix listing 100% of in-scope files with `clean` or their finding ids (SC-001). +7. **Decisions requested** — the consolidated list the maintainer must rule on: disposition of historical spec directories and every destructive finding. (The license is already decided — MIT — and appears in the release gate, not here.) + +## Rules + +- A finding that names no violated convention or principle is invalid (FR-002). +- No file in scope may be absent from section 6 — coverage is checkable by diffing the file list against the corpus inventory. +- Dispositions are edited in place as the maintainer decides; the report stays the single source of truth through P2–P5. diff --git a/specs/002-audit-repo-guidance/contracts/guidance-style.md b/specs/002-audit-repo-guidance/contracts/guidance-style.md new file mode 100644 index 0000000..20af164 --- /dev/null +++ b/specs/002-audit-repo-guidance/contracts/guidance-style.md @@ -0,0 +1,27 @@ +# Contract: Guidance Style & Voice + +Every streamlined guidance file must satisfy this contract (FR-014, SC-010). It is deliberately one page: a style guide longer than the files it governs fails its own standard. + +## Voice + +- **Imperative, present tense.** "Keep business logic out of controllers." Not "we decided that business logic should be kept…". +- **Opinionated default + explicit escape hatch.** State the rule as the way things are done; when an exception is legitimate, name its trigger: "Do X. If Y, do Z instead." +- **One rule, one statement.** A rule appears once, in the file that owns it; elsewhere, point to it rather than restate it. +- **Confident, not hedged.** No "consider possibly", "it might be good to", "generally try to". If the team believes it, say it plainly; if the team doesn't believe it, cut it. + +## Prohibited content + +- **Historical narrative** — how a rule came to be, what it replaced, references to past refactors or renamed concepts. If a past decision is load-bearing, keep the decision as a one-line rule; cut the story. +- **Meta-commentary** — text about the document itself ("this section was added to clarify…", "as mentioned above"). +- **Tool-generated filler** — restating the obvious, summarizing what the reader just read, enumerating what a section is about to say. +- **Duplication across tiers** — generic guidance states the contract; stack packs state the concrete binding; neither repeats the other. + +## Shape + +- Open each file with one or two sentences: who reads this and when it applies. +- Prefer short lists of rules over paragraphs of prose; prefer a table only for genuinely enumerable facts. +- Intentional placeholders are framed as deliberate instantiation points ("filled at instantiation — see the Day-1 checklist"), never left looking abandoned. + +## Check + +Sample any three guidance files: same voice, no prohibited content, rules stated once. That sampling is the SC-010 acceptance test. diff --git a/specs/002-audit-repo-guidance/contracts/stack-pack-structure.md b/specs/002-audit-repo-guidance/contracts/stack-pack-structure.md new file mode 100644 index 0000000..b889305 --- /dev/null +++ b/specs/002-audit-repo-guidance/contracts/stack-pack-structure.md @@ -0,0 +1,25 @@ +# Contract: Canonical Stack-Pack Structure + +Every directory under `stacks/` is a stack pack and must satisfy this contract (FR-015, SC-011). The contract itself gets documented for future authors in `stacks/README.md` — this file is the checkable form. + +## Required files + +| File | Purpose | +|---|---| +| `README.md` | What the stack is, when to choose it, adoption steps at instantiation | +| `backend.md` | Binds `apps/backend/CLAUDE.md` contracts to the concrete stack | +| `frontend.md` | Binds `apps/frontend/CLAUDE.md` contracts to the concrete stack | +| `db.md` | Binds `db/CLAUDE.md` contracts to the concrete stack | +| `infra.md` | Binds `infra/CLAUDE.md` contracts to the concrete stack | + +## Rules + +- **Absence is a statement.** An area that genuinely doesn't apply still ships its file, reduced to a stub: what doesn't apply and **why** (e.g. "no self-managed infrastructure: this stack deploys exclusively to "). A missing file is a conformance failure. +- **Same shape, parallel reading.** Each area file addresses the same concerns its generic counterpart defines, in the same order, so any two packs can be compared side by side. +- **No silent contradictions.** A pack instruction may not contradict the generic tier. A sanctioned deviation is marked in place: `**Exception** (sanctioned): `. Unlabelled contradictions are findings (FR-006). +- **Detail lives here.** Implementation-level specifics (framework idioms, service names, tool commands) belong in the pack, never in the generic tier (FR-005). +- **Genuine differences only.** Structural divergence between packs is allowed only where the stacks genuinely differ, and the difference is stated, not implied. + +## Conformance check + +For each pack: all five files `present` or `n/a-declared`; zero unlabelled contradictions against the generic counterpart; side-by-side comparison with any other pack shows the same section shape. diff --git a/specs/002-audit-repo-guidance/corpus-baseline.md b/specs/002-audit-repo-guidance/corpus-baseline.md new file mode 100644 index 0000000..c6d18f5 --- /dev/null +++ b/specs/002-audit-repo-guidance/corpus-baseline.md @@ -0,0 +1,53 @@ +# Corpus Baseline — captured 2026-07-03 at bdd463d + +Coverage base for SC-001 (every file gets a verdict) and SC-003 (word-count target). Corpus per quickstart.md: every `*.md` outside `.git/`, `.claude/`, `.specify/`, `specs/`. Counts via `wc -w`, gitleaks 8.30.1 available for the history scan. + +**Total: 36,978 words → SC-003 target ≤ 27,733** + +## Guidance corpus (29 files) + +| Words | File | +|---|---| +| 5,051 | apps/frontend/CLAUDE.md | +| 2,817 | apps/backend/CLAUDE.md | +| 2,804 | stacks/nextjs-nestjs-postgres/backend.md | +| 2,644 | stacks/nextjs-nestjs-postgres/db.md | +| 2,488 | CLAUDE.md | +| 2,035 | stacks/vercel/frontend.md | +| 1,837 | stacks/nextjs-nestjs-postgres/frontend.md | +| 1,778 | infra/CLAUDE.md | +| 1,580 | README.md | +| 1,411 | stacks/taro-fastify-mysql-tencent/frontend.md | +| 1,397 | stacks/taro-fastify-mysql-tencent/backend.md | +| 1,206 | stacks/taro-fastify-mysql-tencent/infra.md | +| 1,196 | stacks/vercel/backend.md | +| 1,183 | stacks/vercel/db.md | +| 1,182 | stacks/vercel/infra.md | +| 846 | stacks/taro-fastify-mysql-tencent/db.md | +| 724 | stacks/vercel/README.md | +| 722 | stacks/README.md | +| 685 | stacks/taro-fastify-mysql-tencent/README.md | +| 560 | db/CLAUDE.md | +| 525 | add-ons/otp-auth/README.md | +| 492 | design/README.md | +| 492 | add-ons/README.md | +| 479 | add-ons/test-mode/README.md | +| 463 | stacks/nextjs-nestjs-postgres/README.md | +| 304 | .github/PULL_REQUEST_TEMPLATE.md | +| 45 | .github/ISSUE_TEMPLATE/bug.md | +| 23 | .github/ISSUE_TEMPLATE/feature.md | +| 9 | db/migrations/README.md | + +## Structural artifacts (audited, outside word target) + +- `.github/workflows/ci.yml`, `.github/workflows/deploy.yml` +- `.gitignore` +- Directory layout (`apps/`, `db/`, `infra/`, `design/`, `stacks/`, `add-ons/`, `specs/`) +- Vendored Spec Kit tooling: `.claude/skills/`, `.specify/` — **untracked in git** (not committed); flag-only scope +- `specs/001-enhance-design-guide/` — committed; disposition to be recommended (T012) + +## Observations for the audit + +- Five files carry ~43% of the corpus: frontend CLAUDE.md, backend CLAUDE.md, nextjs backend.md, nextjs db.md, root CLAUDE.md. +- The nextjs pack's `backend.md` (2,804) and `db.md` (2,644) are word-denser than most generic-tier files — possible tier imbalance (implementation detail volume vs. the pack median ~1,200). +- `db/migrations/README.md` is 9 words — likely a placeholder needing disposition. diff --git a/specs/002-audit-repo-guidance/data-model.md b/specs/002-audit-repo-guidance/data-model.md new file mode 100644 index 0000000..a44cbc2 --- /dev/null +++ b/specs/002-audit-repo-guidance/data-model.md @@ -0,0 +1,90 @@ +# Data Model: Template Guidance Audit & Public Showcase + +**Date**: 2026-07-02 | **Spec**: [spec.md](./spec.md) + +This feature manipulates documents, not database rows. The "data" is the audit's working records, kept as markdown tables inside the findings report and rule inventory. The entities below define those records' fields, allowed values, and state transitions. + +## GuidanceFile + +One record per file in the audit scope (SC-001 requires 100% coverage). + +| Field | Values | Notes | +|---|---|---| +| `path` | repo-relative path | identity | +| `audience` | `agent` \| `human` \| `both` | drives FR-007 (agent-binding) and the dual-audience edge case | +| `tier` | `root` \| `generic-area` \| `stack-pack` \| `add-on` \| `meta` \| `vendored` | `vendored` = Spec Kit tooling, flag-only (never streamlined) | +| `loading` | `auto` \| `lazy-subtree` \| `one-hop` \| `orphaned` | per the R1 loading model; the discovery map (SC-002) is this column for all files | +| `verdict` | `clean` \| list of Finding ids | every file gets exactly one (SC-001) | +| `words_before` / `words_after` | integer | feeds SC-003; blank for `vendored` | + +**Validation rules** +- Every file with `audience` ∈ {`agent`, `both`} must end with `loading` ∈ {`auto`, `lazy-subtree`, `one-hop`} — `orphaned` is a Finding (FR-007, SC-005). +- A `human`-audience file must not be the sole carrier of any Rule (US5 scenario 2). + +## Finding + +One record per issue discovered by the audit. + +| Field | Values | Notes | +|---|---|---| +| `id` | `F-###` | stable across report revisions | +| `location` | `path[:section]` | where the issue lives | +| `issue` | prose | what is wrong | +| `violates` | principle/convention reference | which stated rule or convention (FR-002) | +| `severity` | `release-blocking` \| `high` \| `medium` \| `low` | `release-blocking` = public-readiness (FR-011, FR-013) | +| `remedy` | prose | recommended fix | +| `destructive` | yes/no | delete/rename/move/history rewrite ⇒ yes | +| `disposition` | see states below | maintainer-owned | + +**States**: `proposed → approved | rejected`; `approved → applied → verified`. + +**Transition rules** +- `destructive: yes` findings may not leave `proposed` without explicit maintainer approval (FR-010). +- The release gate (below) counts only `release-blocking` findings; all must reach `verified` or `rejected` before release (SC-009). + +## Rule + +One record per actionable rule in the pre-streamlining corpus — the zero-loss ledger (FR-004, SC-003). + +| Field | Values | Notes | +|---|---|---| +| `id` | `R-###` | assigned during inventory, before any edit | +| `source` | file path | where the rule lived at baseline | +| `statement` | condensed rule text | what it obliges | +| `disposition` | `kept` \| `merged(R-###)` \| `moved(path)` \| `removed` | `removed` requires a linked approved Finding | + +**Validation rule**: after streamlining, zero rules with `disposition: removed` lack an approving Finding reference. + +## StackPack + +One record per pack (FR-015, SC-011). + +| Field | Values | Notes | +|---|---|---| +| `name` | pack directory name | | +| `docs` | presence map for `README, backend, frontend, db, infra` | `present` \| `n/a-declared` \| `missing` | +| `exceptions` | list of labelled deviations | each must carry the exception label (FR-006) | +| `conforms` | yes/no | yes ⇔ no `missing` docs and all deviations labelled | + +**Validation rule**: `missing` is never acceptable at completion — an area is `present` or `n/a-declared` with a stated reason. + +## ReleaseGate + +Singleton checklist derived from `release-blocking` findings (SC-009). + +| Item | Check | +|---|---| +| Secrets | scanner clean over working tree **and** full history | +| Internal references | grep pass clean: no internal URLs/hosts, personal data, internal project names | +| License | `LICENSE` (MIT) present at root (FR-013) | +| Front door | positioning statement present and passes the fresh-reader test (FR-012, SC-008) | +| Branding | Cavalry mark + attribution on front door and design guide, assets local to this repo (FR-016, SC-012) | + +**Rule**: all items pass ⇒ repository is release-ready; the actual flip to public is the maintainer's action, outside this feature. + +## Relationships + +- `Finding.location` → `GuidanceFile.path`; a file's `verdict` lists its findings. +- `Rule.source` → `GuidanceFile.path`; `Rule.disposition: removed` → an approved `Finding.id`. +- `StackPack.docs` → five `GuidanceFile` records; conformance failures surface as Findings. +- `ReleaseGate` items ← all `release-blocking` Findings. diff --git a/specs/002-audit-repo-guidance/plan.md b/specs/002-audit-repo-guidance/plan.md new file mode 100644 index 0000000..17bb987 --- /dev/null +++ b/specs/002-audit-repo-guidance/plan.md @@ -0,0 +1,99 @@ +# Implementation Plan: Template Guidance Audit & Public Showcase + +**Branch**: `002-audit-repo-guidance` | **Date**: 2026-07-02 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `/specs/002-audit-repo-guidance/spec.md` + +## Summary + +Audit the entire template repository — every guidance document, structural artifact, and the git history — against established agent-guidance conventions, the template's own stated principles, and the bar of a public flagship repo, producing a severity-ranked findings report (P1). The report gates four cleanup slices: streamline all guidance into one lean, opinionated voice with zero rules lost (P2); build a public front door with license and release gate (P3); align the generic/stack tiers and impose one canonical stack-pack structure (P4); wire every agent-binding file to within one reference hop of auto-loaded guidance (P5). + +Technical approach: this is prose-and-structure engineering, not application code. Verification is measurement-driven — scripted checks (word-count baseline vs. target, cross-reference resolution, secret scan over working tree and history) plus structured manual review against the contracts in `contracts/`. All destructive changes (delete/rename/move/history rewrite) are listed in the findings report and applied only after maintainer approval. + +## Technical Context + +**Language/Version**: English prose in GitHub-flavored Markdown; no application code is touched + +**Primary Dependencies**: git; `gitleaks` (single-binary secret scanner, run locally for the history scan); standard Unix text tools (`wc`, `grep`, `find`) for measurement — no runtime or build dependencies added to the template + +**Storage**: N/A — files in the repository; audit deliverables live in `specs/002-audit-repo-guidance/` + +**Testing**: scripted verification (corpus word count, relative-link resolution, secret scan) + structured manual review against the contracts (`contracts/findings-report.md`, `contracts/stack-pack-structure.md`, `contracts/guidance-style.md`); acceptance mapped SC-by-SC in `quickstart.md` + +**Target Platform**: public GitHub repository (repo is currently private; going public is the maintainer's action after the release gate passes) + +**Project Type**: documentation / project-template repository (guidance corpus + scaffolding) + +**Performance Goals**: N/A in the machine sense; the human metric is SC-008 — an outside senior engineer orients from the front door in ≤ 10 minutes + +**Constraints**: zero actionable rules lost (enforced by a before/after rule inventory); template remains instantiation-ready after every slice; intentional placeholders preserved; vendored Spec Kit tooling (`.claude/skills/`, `.specify/`) is flag-only, never rewritten; destructive changes gated on maintainer approval + +**Scale/Scope**: ~30 guidance/doc files at a measured baseline of 36,978 words (re-captured 2026-07-03; 25% target ⇒ ≤ 27,733 words); 3 stack packs × up to 5 docs; 2 add-ons; full git history scanned for release blockers + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +`.specify/memory/constitution.md` is an unratified template (placeholders only), so no formal constitution gates exist. The de facto constitution is the **Principles (must follow)** section of the root `CLAUDE.md`; this plan is gated against those. (The unratified constitution is itself expected to surface as an audit finding.) + +| Principle (root CLAUDE.md) | Evaluation | Status | +|---|---|---| +| Think before coding | Ambiguities resolved in spec Assumptions; the one factual unknown (instruction-file discovery) is a Phase 0 research item, not an assumption | PASS | +| Simplicity first / YAGNI | No new frameworks or services; scripted checks use existing Unix tools; the only tool adopted is `gitleaks`, run locally and not added to the template | PASS | +| Change the right place, surgically | Edits land in the guidance files that own each rule; vendored tooling untouched; app skeletons untouched | PASS | +| Goal-driven execution | Every SC has a named verification in `quickstart.md`; the P1 report is evidence, not memory | PASS | +| Don't reinvent existing solutions | Secret scanning via an established tool rather than hand-rolled patterns; grep/wc for the rest | PASS | +| Don't overfit to the immediate request | Canonical stack-pack structure and style contract are written for future packs, not just the three existing ones | PASS | + +**Post-design re-check (after Phase 1)**: PASS — the design added only three small contracts, one data model, and one validation guide; no new tooling, no new abstraction beyond what the spec requires. + +## Project Structure + +### Documentation (this feature) + +```text +specs/002-audit-repo-guidance/ +├── plan.md # This file (/speckit-plan command output) +├── research.md # Phase 0 output (/speckit-plan command) +├── data-model.md # Phase 1 output (/speckit-plan command) +├── quickstart.md # Phase 1 output (/speckit-plan command) +├── contracts/ # Phase 1 output (/speckit-plan command) +│ ├── findings-report.md # format the P1 audit report must follow +│ ├── stack-pack-structure.md # canonical structure every stack pack must follow +│ └── guidance-style.md # voice/style contract streamlined guidance must meet +└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan) +``` + +### Source Code (repository root) + +No application source is touched. The change surface is the guidance corpus and structural artifacts: + +```text +CLAUDE.md # root guidance (auto-loaded) — streamline, wire references +README.md # public front door — positioning, philosophy, Cavalry lockup, day-1 checklist +LICENSE # MIT (maintainer directed "not restrictive") — release gate item +design/brand/ # Cavalry brand SVGs copied from the company brand pack (FR-016) +design/design-guide.html # design guide — carries Cavalry mark + attribution (FR-016) +apps/backend/CLAUDE.md # generic tier — streamline, keep stack-agnostic +apps/frontend/CLAUDE.md # generic tier — streamline, keep stack-agnostic +db/CLAUDE.md # generic tier +db/migrations/README.md # near-empty; audit decides disposition +infra/CLAUDE.md # generic tier +design/README.md # human-facing reference notes +specs/README.md # spec-workflow conventions +specs/001-enhance-design-guide/ # historical spec — report recommends disposition +add-ons/README.md # add-on adoption rules +add-ons/{test-mode,otp-auth}/README.md +stacks/README.md # pack adoption rules + canonical structure documented here +stacks/{nextjs-nestjs-postgres,taro-fastify-mysql-tencent,vercel}/ + # each: README.md, backend.md, frontend.md, db.md, infra.md + # (nextjs-nestjs-postgres currently lacks infra.md — FR-015 target) +.github/workflows/, .github/ISSUE_TEMPLATE/, .github/PULL_REQUEST_TEMPLATE.md +.claude/skills/, .specify/ # vendored Spec Kit tooling — audited, flag-only +``` + +**Structure Decision**: single documentation surface at the repository root; audit deliverables (findings report, rule inventory) live inside `specs/002-audit-repo-guidance/` so the template's own areas stay clean. The findings report is the sole gate artifact — every P2–P5 edit traces back to an approved finding. + +## Complexity Tracking + +No constitution-gate violations; table intentionally empty. diff --git a/specs/002-audit-repo-guidance/quickstart.md b/specs/002-audit-repo-guidance/quickstart.md new file mode 100644 index 0000000..0413cbb --- /dev/null +++ b/specs/002-audit-repo-guidance/quickstart.md @@ -0,0 +1,59 @@ +# Quickstart: Validating the Guidance Audit & Public Showcase + +How to prove each success criterion, mapped SC-by-SC. Run scripted checks from the repo root; manual protocols reference the [contracts](./contracts/). + +## Prerequisites + +- `gitleaks` installed locally (`brew install gitleaks`) — used for the scan only, not added to the template. +- A scratch directory outside the repo for the dry-run instantiation (SC-006). + +## Corpus definition (used by several checks) + +```bash +CORPUS=$(find . -name "*.md" -not -path "./.git/*" -not -path "./.claude/*" \ + -not -path "./.specify/*" -not -path "./specs/*") +``` + +Baseline re-captured 2026-07-03: **36,978 words** ⇒ SC-003 target ≤ **27,733**. + +## Checks + +| SC | What proves it | How | +|---|---|---| +| SC-001 | Every in-scope file has a verdict | Diff `$CORPUS` (plus structural artifacts) against the report's file-by-file appendix; zero missing entries | +| SC-002 | Discovery question answered | The report's instruction-discovery map classifies every file's `loading`; the answer cites the loading model in [research.md R1](./research.md) | +| SC-003 | ≥25% smaller, zero rules lost | `wc -w $CORPUS` ≤ 27,597; rule inventory shows every `removed` rule linked to an approved finding | +| SC-004 | No unlabelled tier contradictions | Read each pack file beside its generic counterpart; every deviation carries the exception label per [stack-pack-structure.md](./contracts/stack-pack-structure.md) | +| SC-005 | No orphaned agent-binding file | For each `audience: agent/both` file, trace: auto-loaded, lazy subtree CLAUDE.md, or referenced with a "read when" pointer from one — one hop max | +| SC-006 | Instantiation still works | In the scratch clone, follow the front-door Day-1 checklist end to end; no broken references, missing files, or stale placeholders | +| SC-007 | Two-doc orientation | Pick any area; read only root `CLAUDE.md` + that area's `CLAUDE.md`; state the area's binding rules; nothing load-bearing missing | +| SC-008 | 10-minute front door | Fresh-reader protocol ([research.md R8](./research.md)): README only, 10 minutes, then answer *what is it / what philosophy / how to start*; all three correct | +| SC-009 | Release gate clean | `gitleaks git .` clean over full history; grep pass for internal URLs/hosts, personal emails, internal project names clean; `LICENSE` (MIT) exists at root | +| SC-010 | One consistent voice | Sample any three guidance files; hold each against [guidance-style.md](./contracts/guidance-style.md); zero prohibited content | +| SC-011 | Uniform stack packs | Per pack: five files `present` or `n/a-declared`; side-by-side shape matches; canonical structure documented in `stacks/README.md` | +| SC-012 | Cavalry attribution | README renders the Cavalry lockup (light/dark) and names Cavalry; design guide carries the mark + attribution; all brand assets resolve to files under `design/brand/` in this repo — no external or internal URLs | + +## Useful commands + +```bash +# SC-003: corpus size vs target +wc -w $CORPUS | tail -1 + +# SC-009: secrets across working tree and full history +gitleaks git . --verbose + +# SC-009: internal references / personal data (extend patterns as found) +grep -rInE "(internal\.|corp\.|@(?!users\.noreply)[a-z-]+\.(sg|com))" \ + --include="*.md" . | grep -v ".git/" + +# SC-005 / FR-008: relative links that don't resolve +grep -rhoE "\]\((\./|\.\./)?[A-Za-z0-9_./-]+\.md" --include="*.md" . \ + | sed 's/](//' | sort -u | while read -r f; do [ -f "$f" ] || echo "broken: $f"; done +``` + +## Order of operations + +1. Run the P1 audit → `audit-report.md` per [findings-report.md](./contracts/findings-report.md); SC-001, SC-002 checkable immediately. +2. Maintainer rules on the **Decisions requested** section (historical specs, destructive changes — the license is already decided: MIT). +3. Execute P2–P5 slices, each closing its findings; re-run the affected checks after each slice. +4. Before declaring release-ready: the full table above passes, and every release-blocking finding is `verified` or `rejected`. diff --git a/specs/002-audit-repo-guidance/research.md b/specs/002-audit-repo-guidance/research.md new file mode 100644 index 0000000..6a726d1 --- /dev/null +++ b/specs/002-audit-repo-guidance/research.md @@ -0,0 +1,84 @@ +# Phase 0 Research: Template Guidance Audit & Public Showcase + +**Date**: 2026-07-02 | **Spec**: [spec.md](./spec.md) | **Plan**: [plan.md](./plan.md) + +All Technical Context unknowns are resolved below. No NEEDS CLARIFICATION markers remain. + +## R1. How the coding agent discovers instruction files + +**Decision**: Classify every file by the documented Claude Code loading model and wire discoverability with lazy, one-hop references — not imports, not renames. + +- **Auto-loaded at launch**: managed-policy CLAUDE.md → `~/.claude/CLAUDE.md` → project `./CLAUDE.md` (or `./.claude/CLAUDE.md`) → `./CLAUDE.local.md`, plus `.claude/rules/*.md` without `paths` frontmatter. +- **Lazy-loaded on subtree entry**: nested `CLAUDE.md` files (e.g. `apps/backend/CLAUDE.md`, `db/CLAUDE.md`) load when the agent reads files in that subdirectory — not at session start. +- **Never auto-loaded**: `README.md` and every other markdown file. They are read only when explicitly `@`-imported from a CLAUDE.md or when an instruction tells the agent to read them. +- **`@`-imports are eager**: imported files expand into context at launch (max depth 4). Importing the stack packs or add-on docs would load them into *every* session. + +**Rationale**: This directly answers the maintainer's question — yes, content that exists *only* in README-named files is invisible to the agent unless something auto-loaded points at it. But the fix is not renaming (README.md serves GitHub browsing) and not `@`-imports (eager loading contradicts the "keep context light" goal). The repo's existing pattern — an auto-loaded or lazily-loaded CLAUDE.md saying "read `` before working on ``" — is the correct mechanism; the audit verifies every agent-binding file has exactly such a pointer (FR-007), and flags rules that live *only* in never-loaded files. + +**Alternatives considered**: mass-rename README→CLAUDE.md (rejected: destroys human-facing repo browsing; GitHub renders README.md); `@`-import everything from root (rejected: eager expansion bloats every session, the opposite of the feature's goal); `.claude/rules/` with `paths` scoping (viable mechanism, rejected for now: the nested-CLAUDE.md + pointer pattern already covers the need — adopting a second mechanism adds concept count for no new capability). + +**Source**: Claude Code memory documentation, https://code.claude.com/docs/en/memory.md (current as of 2026-06-30). + +## R2. Secret and unsuitable-content scanning for public release + +**Decision**: `gitleaks` over the working tree **and full git history** (`gitleaks git .`), plus a targeted grep pass for what secret scanners don't model: internal hostnames/URLs, personal email addresses, company-internal project references. + +**Rationale**: Established, single-binary, widely-used scanner beats hand-rolled regexes (root-CLAUDE.md principle: never hand-roll security). History matters because making the repo public publishes every commit ever made, not just HEAD. The grep pass covers the "internal-only reference" class that is release-blocking per FR-011 but not a "secret." + +**Alternatives considered**: trufflehog (heavier, credential-verification features unneeded here); manual review only (rejected: unreliable for history); GitHub secret scanning after publishing (rejected: too late — the point is to find blockers *before* going public). + +## R3. Corpus measurement and the zero-rules-lost proof + +**Decision**: The measured corpus is every `*.md` outside `.git/`, `.claude/`, `.specify/`, and `specs/`. Baseline re-captured 2026-07-03 after the design-guide revision landed (it touched `apps/frontend/CLAUDE.md`): **36,978 words** (`wc -w`); SC-003 target ≤ **27,733**. The zero-loss proof is a **rule inventory**: before editing, enumerate every actionable rule per file with an ID; after streamlining, every rule maps to kept / merged / moved / removed-with-approval (FR-004). + +**Rationale**: Word count is the honest size metric for prose (line counts reward cramming). A rule inventory is the only way to *prove* nothing was lost rather than assert it; it also becomes the maintainer's approval surface for any deliberate removals. + +**Alternatives considered**: diff review alone (rejected: demonstrates change, not preservation); token counts (rejected: tooling-dependent, word count is stable and reproducible). + +## R4. Voice and style standard + +**Decision**: A one-page style contract at `contracts/guidance-style.md`: imperative present tense, one rule = one statement, opinionated default + explicit escape hatch, no historical narrative, no meta-commentary, no hedging. Derived from agent-guidance best practice (concise, direct, structured) and the template's own Principles. + +**Rationale**: "Award-winning voice" must be checkable, so it becomes a short contract that any reviewer can hold a sampled page against (SC-010). One page, because a style guide longer than the files it governs would fail its own standard. + +**Alternatives considered**: adopting a full external prose style guide (Microsoft/Google) — rejected: built for end-user product docs, would add far more rules than this corpus needs (YAGNI). + +## R5. License + +**Decision**: **MIT**. The maintainer resolved the open decision on 2026-07-03 with the direction "choose something not restrictive"; MIT is the most permissive mainstream license and the norm for public templates. + +**Rationale**: MIT imposes essentially one obligation (keep the copyright notice), maximizing adoption — exactly what "not restrictive" asks for. The release-gate item becomes concrete: `LICENSE` at root containing the MIT text with Cavalry's copyright line. + +**Alternatives considered**: Apache-2.0 (rejected: its patent grant and NOTICE obligations add restriction the maintainer didn't ask for); no license (rejected: an unlicensed public repo is all-rights-reserved — unusable by others and the opposite of a showcase). + +## R6. Canonical stack-pack structure + +**Decision**: Every pack ships the same five files — `README.md` (identity, when to choose it, adoption steps) plus `backend.md`, `frontend.md`, `db.md`, `infra.md` binding the generic contracts to the concrete stack. An area that genuinely doesn't apply ships a short stub stating **why** it doesn't, in place — absence is always a statement. Sanctioned deviations from generic guidance are labelled exceptions. The canonical structure is documented once in `stacks/README.md` (the file a future pack author reads first) and contract-tested via `contracts/stack-pack-structure.md`. + +**Rationale**: Derived from the packs' existing common shape — the majority already follow it; `nextjs-nestjs-postgres` lacking `infra.md` is the live conformance failure FR-015 exists to fix. Per-area files (not one monolith) preserve the one-hop pointer pattern from each area's CLAUDE.md. + +**Alternatives considered**: single monolithic doc per pack (rejected: breaks per-area lazy references, forces agents to load irrelevant areas); free-form packs (rejected: structural drift is the defect being repaired). + +## R7. Cross-reference verification + +**Decision**: A small grep-based check: extract relative markdown links and backticked file paths from the corpus, verify each target exists; list files nothing references (orphan candidates for FR-008). + +**Rationale**: The corpus is ~30 files with few external links; existing Unix tools cover it. Adding a link-checker dependency fails YAGNI. + +**Alternatives considered**: lychee / markdown-link-check (rejected: new tooling for a job grep does at this scale). + +## R8. Front-door comprehension test (SC-008) + +**Decision**: A fresh-reader protocol: a reader (person or agent session) with no prior context gets the front-door README only, 10 minutes, then must answer three questions — *what is this repo, what philosophy does it encode, what are the first steps to start a project from it*. Pass = all three answered correctly. + +**Rationale**: Cheap, repeatable, and measures exactly what SC-008 promises an outside engineer. + +**Alternatives considered**: readability formulas (rejected: measure sentence mechanics, not orientation); no test (rejected: SC-008 must be observable, not asserted). + +## R9. Cavalry branding assets + +**Decision**: Copy the needed SVGs from the official brand pack in the company website repository (`cavalry-website/design/brand/` — lockup and mark variants in light/dark/mono) into this repository under `design/brand/`. The front-door README renders the lockup via a `` element with light/dark variants (GitHub supports `prefers-color-scheme` source switching); the design guide embeds the mark inline with an attribution line. Attribution text names Cavalry alongside the mark. + +**Rationale**: FR-016 requires self-containment — the public repo may not reference the private website repository or any internal URL (that would itself be a release blocker). SVG lockups scale cleanly in READMEs; the light/dark pair keeps the mark legible in both GitHub themes. Copying only the variants actually used keeps the template lean. + +**Alternatives considered**: hotlinking assets from the company website (rejected: external dependency, and the source repo is private — broken image for the public); embedding as base64 (rejected: unreadable diffs, no reuse); copying the whole brand pack (rejected: YAGNI — take the lockup light/dark pair and one mark, add more only when something uses them). diff --git a/specs/002-audit-repo-guidance/spec.md b/specs/002-audit-repo-guidance/spec.md new file mode 100644 index 0000000..4d51541 --- /dev/null +++ b/specs/002-audit-repo-guidance/spec.md @@ -0,0 +1,176 @@ +# Feature Specification: Template Guidance Audit & Public Showcase + +**Feature Branch**: `002-audit-repo-guidance` + +**Created**: 2026-07-02 + +**Status**: Draft + +**Input**: User description: "somewhat gone out of control. Can you do a deep review of this entire repo? At the end of the day we want to ensure that everything we do is according to established conventions and best practices. please review to see if anything is sketchy. Also, we want these instructions to allow us to quickly set up new projects, and a lot of the AI generated additions will include historical context etc which adds unnecessary bloat to this project. the biggest value this repo brings is an opinionated approach to development. hence we wanna keep the context light and just give simple straight forward guidance on how to build software. the backend and frontend/CLAUDE.md in general contains the high-level guidance, and the stacks instructions can provide deeper implementation level detail. however, stacks should not conflict with the generic instructions. lastly, this repo has a lot of README.md and also other files not using CLAUDE.md naming. im wondering if this causes claude to miss out instructions?" + +**Input (update, 2026-07-02)**: "please also make it award-winning. we will make this a public repo on our company's github. it should showcase our deep experience, and make us look like award-winning world-class software engineering team. opinionated, concise and quality-driven!" + +**Input (update 2, 2026-07-02)**: "also we will have different stacks in future but we want to keep all the stacks structure more or less similar while respecting their individual differences." + +**Input (update 3, 2026-07-03)**: "please make sure to pepper Cavalry logo into the readme, and maybe also design guide. anyway make sure people know this is cavalry's work. … for licensing, choose something not restrictive." + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Deep audit with a findings report (Priority: P1) + +As the template maintainer, I receive a complete review of the repository — every guidance document and every structural artifact — measured against established conventions, the template's own stated principles, and the bar of a public flagship repository, so I know exactly what is sketchy, contradictory, bloated, unsuitable for public release, or at risk of being missed before anything is changed. + +**Why this priority**: The template has grown through many AI-assisted iterations and the maintainer no longer trusts that every addition is sound. The report is the deliverable the maintainer explicitly asked for ("review to see if anything is sketchy"), and every later slice depends on knowing what is wrong. On its own it is a viable MVP: a trustworthy picture of the repo's health and its readiness to go public. + +**Independent Test**: Can be fully tested by picking any guidance or structural file in the repository and confirming the report covers it with either a "no issues" verdict or concrete findings. Delivers standalone value as a review document even if no fixes are ever applied. + +**Acceptance Scenarios**: + +1. **Given** the current repository, **When** the audit completes, **Then** every guidance document (root guidance, per-area guidance, readme-style documents, stack packs, add-ons) and every structural artifact (workflow definitions, issue/PR templates, ignore rules, directory layout) appears in the report with an explicit verdict. +2. **Given** any finding in the report, **When** the maintainer reads it, **Then** it states the location, what is wrong, which convention or stated principle it violates, a severity, and a recommended remedy. +3. **Given** the findings list, **When** the maintainer scans it, **Then** findings are ordered by severity so the sketchiest items surface first, with release-blocking items (anything unsuitable for a public repository) at the top. +4. **Given** the maintainer's question about instruction-file naming, **When** the report is read, **Then** it definitively explains how the coding agent discovers instructions (which files are loaded automatically, which are only read when explicitly referenced) and lists every instruction file currently at risk of being missed. +5. **Given** the intent to publish the repository, **When** the audit completes, **Then** it has checked the entire repository — including its version history — for content unsuitable for public release (credentials, personal data, internal-only references) and reported any occurrence as release-blocking. + +--- + +### User Story 2 - Lean, opinionated guidance (Priority: P2) + +As a maintainer spinning up new projects from this template, I want every guidance file stripped of historical context, change narrative, and AI-generated filler, and rewritten in one confident, opinionated voice, so that agents and developers get the team's approach in the fewest words that still carry every rule — guidance that reads as authored by a world-class team, not accreted by a tool. + +**Why this priority**: The maintainer states the repo's biggest value is its opinionated approach and that context must stay light. The guidance body is also the substance the public showcase will be judged on — a polished front door over bloated guidance would not be credible. It ranks below the audit only because the audit tells us precisely what to cut. + +**Independent Test**: Can be fully tested by comparing any streamlined file against its previous version — every actionable rule still present, narrative gone, voice consistent — and by comparing the total corpus size before and after. + +**Acceptance Scenarios**: + +1. **Given** a guidance file containing historical narrative (how a rule came to be, references to past refactors or renamed concepts), **When** it is streamlined, **Then** the narrative is gone and each rule it carried remains, stated once in imperative present tense. +2. **Given** a before/after inventory of actionable rules across the corpus, **When** compared, **Then** zero rules are lost or weakened except by an explicit, maintainer-visible decision recorded in the findings report. +3. **Given** any three streamlined guidance documents sampled together, **When** read in sequence, **Then** they read in a single consistent voice — opinionated, imperative, and concise — with no hedging filler or tool-generated commentary. +4. **Given** the streamlined corpus, **When** measured, **Then** it meets the size-reduction target defined in Success Criteria. + +--- + +### User Story 3 - A public front door worthy of the work (Priority: P3) + +As a company, when an outside senior engineer lands on our public repository, they should understand within minutes what the template is, the engineering philosophy behind it, and how to start a project from it — and come away with the impression of a quality-driven, world-class team. + +**Why this priority**: The public showcase is the company's stated goal for publishing the repository. It follows the streamlining pass because the front door's promise ("opinionated, concise, quality-driven") must be true of the guidance behind it before it is made. + +**Independent Test**: Can be fully tested by having someone unfamiliar with the repository read only the front-door document and then correctly state what the template is, its philosophy, and the first steps to use it. + +**Acceptance Scenarios**: + +1. **Given** an outside engineer landing on the repository, **When** they read the front-door document, **Then** it states what the template is, the opinionated philosophy it encodes, and how to start a new project from it — without requiring any internal context. +2. **Given** the repository at release time, **When** inspected, **Then** it carries an explicit permissive license and contains no internal-only working artifacts whose removal the maintainer approved in the findings report. +3. **Given** the repository's presentation as a whole (front door, directory naming, document titles), **When** browsed, **Then** it is coherent and self-explanatory — nothing looks half-finished except placeholders that are clearly framed as deliberate instantiation points. +4. **Given** any visitor to the public repository, **When** they view the front door or the design guide, **Then** the Cavalry brand mark and attribution make unmistakable whose work this is, using brand assets stored inside this repository. + +--- + +### User Story 4 - Two clean tiers, uniform stack packs (Priority: P4) + +As an agent or developer working in an instantiated project, I want the generic guidance files to carry only high-level, stack-agnostic direction and the stack packs to carry the implementation-level detail, with no contradiction between the two — and as a maintainer authoring future stacks, I want every stack pack to follow the same canonical structure, differing only where the stacks genuinely differ, so adding a new stack is predictable and comparing stacks is trivial. + +**Why this priority**: A contradiction between tiers silently corrupts every project built from the template — and would be embarrassing in a public showcase. Structural drift between packs compounds with every future stack added. Fixing tier alignment presupposes the audit (P1) has located the conflicts and overlaps with the streamlining pass (P2), so it lands after them. + +**Independent Test**: Can be fully tested by reading each stack pack side by side with its generic counterpart (backend, frontend, db, infra) and confirming no instruction in the pack contradicts the generic tier, and by comparing the packs with each other and confirming they share one structure. + +**Acceptance Scenarios**: + +1. **Given** any generic guidance file, **When** reviewed after the change, **Then** it contains no stack-specific implementation detail — that detail lives only in stack packs. +2. **Given** each stack pack document and its generic counterpart, **When** read together, **Then** no pack instruction contradicts the generic tier, and any sanctioned deviation is explicitly labelled as an exception in the pack. +3. **Given** a conflict discovered during the work, **When** it is resolved, **Then** the generic tier's rule prevails unless the maintainer explicitly sanctions the exception. +4. **Given** any two stack packs, **When** compared side by side, **Then** they share the same document set and cover the same areas in the same shape, and any divergence reflects a genuine difference between the stacks, stated explicitly rather than silently omitted. +5. **Given** a maintainer authoring a future stack pack, **When** they start, **Then** a documented canonical pack structure exists for them to follow. + +--- + +### User Story 5 - No instruction can be silently missed (Priority: P5) + +As the maintainer, I want every instruction file that agents must obey to be either automatically loaded or explicitly pointed to (with a "read this before working on X" reference) from a file that is automatically loaded, so no binding guidance can be skipped just because of its file name. + +**Why this priority**: A missed instruction defeats the purpose of the template, but the audit (P1) already answers whether the risk is real and how large it is; this slice is the wiring fix and can follow once the map exists. + +**Independent Test**: Can be fully tested by tracing each agent-binding instruction file back to an automatically loaded file and confirming the chain is at most one explicit reference deep. + +**Acceptance Scenarios**: + +1. **Given** any instruction file agents must obey, **When** its loading path is traced, **Then** it is either loaded automatically or referenced from an automatically loaded file in at most one hop, with the reference stating when it must be read. +2. **Given** a document intended for humans browsing the repository, **When** reviewed, **Then** it is identifiable as human-facing and contains no agent-binding rule that exists nowhere else. + +--- + +### Edge Cases + +- Narrative that turns out to be load-bearing (e.g., a recorded decision that justifies a rule): the decision survives as a one-line rule or learnings entry; only the story around it is cut. +- A stack pack that genuinely must deviate from generic guidance: the deviation is kept only as an explicitly labelled exception — never as a silent contradiction. +- An area that genuinely does not apply to a particular stack (today, one pack ships without an infrastructure document while the other two have one): the pack says so explicitly in its canonical place rather than silently omitting the document, so absence is always a statement, never an oversight. +- Intentional placeholders (unfilled toolchain commands, day-1 checklist items) are part of the template's design and must not be flagged or removed as bloat — but in a public repository they must be clearly framed as deliberate instantiation points, not unfinished work. +- Vendored third-party tooling (the spec-workflow skills and their templates) is audited for problems but not rewritten by the streamlining pass. +- Historical feature specs produced while building the template itself: the audit must recommend whether they belong in a public template that ships to fresh projects, and the maintainer decides. +- Documents that serve two audiences (humans browsing the repo and agents working in it): the remedy must preserve both — mass-renaming human-facing documents to the auto-loaded name is not an acceptable fix. +- Unsuitable content found in version history (not just the working tree): remediation rewrites published history, which is destructive — it is reported as release-blocking and applied only with explicit maintainer approval. +- Brand assets originate in another company repository: the asset files are copied into this repository — the public repo must never reference an internal or private location, which would itself be a release-blocking finding. +- "Award-winning" has no objective judge: the spec holds it to measurable proxies (consistency, concision, zero contradictions, fast outside-reader comprehension, zero embarrassing artifacts) rather than an unfalsifiable impression. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The audit MUST cover 100% of the repository's guidance and documentation files and its structural artifacts (workflow definitions, issue and PR templates, ignore rules, directory layout, spec-workflow configuration). +- **FR-002**: Every finding MUST record its location, a description of the issue, the convention or stated principle it violates, a severity (including a release-blocking tier for public-readiness issues), and a recommended remedy. +- **FR-003**: The report MUST definitively explain how the coding agent discovers instruction files — which are loaded automatically and which are read only on explicit reference — and list every instruction file currently at risk of being missed. +- **FR-004**: Streamlining MUST remove historical context, change narrative, and self-referential commentary from guidance files while preserving every actionable rule; any removal or weakening of a rule MUST be an explicit, maintainer-visible decision. +- **FR-005**: After the change, generic guidance files MUST contain only stack-agnostic guidance; implementation-level detail MUST live in stack packs. +- **FR-006**: After the change, no stack pack instruction may contradict the generic tier; where the two disagreed, the generic tier prevails unless a deviation is explicitly labelled as a sanctioned exception. +- **FR-007**: Every agent-binding instruction file MUST be reachable from automatically loaded guidance in at most one explicit reference hop, with the reference stating when the file must be read. +- **FR-008**: The audit MUST verify that cross-references between guidance files resolve — no pointers to files that do not exist and no instruction file that nothing points to. +- **FR-009**: The template MUST remain instantiation-ready throughout: intentional placeholders preserved, the day-1 checklist accurate, and no broken references introduced by renames, moves, or deletions. +- **FR-010**: All structural or destructive changes (deleting, renaming, or moving files, or rewriting version history) MUST appear in the findings report and be approved by the maintainer before they are applied. +- **FR-011**: The audit MUST check the entire repository — working tree and version history — for content unsuitable for public release (credentials, tokens, personal data, internal-only URLs or references) and report every occurrence as a release-blocking finding. +- **FR-012**: The repository's front-door document MUST state what the template is, the opinionated philosophy it encodes, and how to start a new project from it, in terms an outside senior engineer can absorb without internal context. +- **FR-013**: The public repository MUST carry an explicit permissive (non-restrictive) license, per the maintainer's direction; its absence is a release-blocking finding. +- **FR-014**: All guidance MUST read in a single consistent voice — opinionated, imperative, and concise — across every file in the corpus. +- **FR-015**: All stack packs MUST follow one canonical structure — the same document set, coverage areas, and organization — with stack-specific differences expressed inside that structure and areas that do not apply declared explicitly; the canonical structure MUST be documented so future packs follow it. +- **FR-016**: The front door MUST display the Cavalry brand mark and attribute the template to Cavalry, and the design guide MUST carry the same attribution; the brand assets MUST live inside this repository so the public repo is self-contained. + +### Key Entities + +- **Guidance corpus**: the set of instruction and documentation files that direct how software is built from this template — root guidance, per-area guidance, readme-style documents, stack packs, and add-on documents. +- **Finding**: a single identified issue — location, description, violated convention or principle, severity (release-blocking, high, medium, low), recommended remedy, and (once decided) the maintainer's disposition. +- **Guidance tier**: the level a rule belongs to — generic (always true for any project from this template) or stack pack (true only for the concrete stack adopted at instantiation). +- **Instruction-loading chain**: the path by which an agent encounters a file — loaded automatically by naming convention, or read because an automatically loaded file explicitly points to it. +- **Release gate**: the set of findings that must be resolved before the repository can be made public — unsuitable content anywhere in working tree or history, missing license, missing front-door positioning. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 100% of guidance and structural files appear in the audit report with an explicit verdict (no issues, or one or more findings). +- **SC-002**: The maintainer's instruction-discovery question is answered in writing, with every current instruction file classified by how (or whether) an agent will encounter it. +- **SC-003**: The guidance corpus shrinks by at least 25% in total word count while a before/after rule inventory shows zero actionable rules lost without an explicit decision. +- **SC-004**: A side-by-side read of every stack pack against its generic counterpart finds zero unlabelled contradictions. +- **SC-005**: Every agent-binding instruction file is reachable from automatically loaded guidance within one reference hop; none is orphaned. +- **SC-006**: A dry-run instantiation following the day-1 checklist encounters no broken references, missing files, or stale placeholders introduced by the cleanup. +- **SC-007**: A newcomer can state the binding rules for any one area after reading at most two documents: the root guidance plus that area's guidance file. +- **SC-008**: A reader unfamiliar with the company can correctly state what the template is, its philosophy, and how to start a project from it within 10 minutes of landing on the repository, using only the front-door document. +- **SC-009**: Zero release-blocking findings remain open at release time: no credentials, personal data, or internal-only references anywhere in the working tree or version history, and a permissive license and positioning statement are present. +- **SC-010**: A reviewer sampling any three guidance documents finds one consistent voice — opinionated, imperative, concise — with no tool-generated filler or historical narrative. +- **SC-011**: A side-by-side comparison of all stack packs shows one shared structure — same document set and coverage areas — with every divergence explicitly declared as a genuine stack difference, and a documented canonical structure exists for authoring future packs. +- **SC-012**: A visitor viewing the front door or the design guide sees the Cavalry brand mark and attribution, rendered from assets stored in this repository — no reference to any internal or private location. + +## Assumptions + +- "Established conventions and best practices" means current best practice for agent-facing guidance (concise, imperative, stated once, hierarchically organized) plus the template's own stated principles; where the two disagree, the template's stated principles win and the disagreement is flagged. +- "Award-winning / world-class" is operationalized through the measurable proxies in Success Criteria (consistency, concision, zero contradictions, fast outside-reader comprehension, zero release-blocking artifacts); no literal award or external judge adjudicates the outcome. +- The vendored spec-workflow tooling and its templates are third-party: the audit flags problems, but the streamlining pass does not rewrite them. +- The applications are placeholder skeletons, so the audit targets guidance, structure, and configuration rather than application source code. +- The 25% size-reduction target is a default the maintainer can tune; the hard constraint is zero rules lost, not the percentage. +- The P1 report acts as the gate: the maintainer reviews findings before the P2–P5 slices apply structural or destructive changes. +- Historical spec directories from building the template are audit subjects like any other file; the report recommends a disposition and the maintainer decides. +- Human-facing documents keep their conventional names for repository browsing; discoverability is fixed by wiring explicit references from auto-loaded files, not by mass-renaming. +- The maintainer directed a non-restrictive license (2026-07-03); MIT is selected as the most permissive mainstream choice — Apache-2.0 was considered and set aside because its patent and notice obligations add restriction the maintainer didn't ask for. +- Cavalry's official brand asset pack (mark and lockup variants) exists in the company's website repository; the needed files are copied into this repository at implementation time so the public repo stands alone. +- Actually making the repository public is a maintainer action outside this feature's scope; the feature's job is to make the repository release-ready as defined by the release gate. +- The canonical stack-pack structure is derived from the existing packs (their common shape wins by default); which divergences count as "genuine stack differences" versus drift is recommended by the audit and decided by the maintainer. diff --git a/specs/002-audit-repo-guidance/tasks.md b/specs/002-audit-repo-guidance/tasks.md new file mode 100644 index 0000000..9f675dd --- /dev/null +++ b/specs/002-audit-repo-guidance/tasks.md @@ -0,0 +1,194 @@ +# Tasks: Template Guidance Audit & Public Showcase + +**Input**: Design documents from `/specs/002-audit-repo-guidance/` + +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md + +**Tests**: Not applicable in the TDD sense — this feature edits prose and structure, not runtime code. Each story phase ends with the verification tasks that prove its success criteria (see quickstart.md); the repo's lint/typecheck/test/build verbs are still toolchain TODOs, which the merge gate must state explicitly per the Definition of Done. + +**Organization**: Tasks are grouped by user story. Note one deliberate, spec-mandated deviation from full story independence: US2–US5 execute against findings **approved in the US1 report** (FR-010 gates all destructive change on maintainer disposition). Each story remains independently *testable*; execution order is gated by design. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (US1–US5) + +## Phase 1: Setup + +**Purpose**: Working environment and measurement base + +- [X] T001 Create a git worktree + branch `002-audit-repo-guidance` per the root `CLAUDE.md` worktree workflow (gitignored env files: none exist yet in the template — note the skip) +- [X] T002 [P] Verify `gitleaks` is installed locally (`gitleaks version`); install via `brew install gitleaks` if missing — used for the scan only, never added to the template +- [X] T003 [P] Write `specs/002-audit-repo-guidance/corpus-baseline.md`: the corpus file inventory (per quickstart.md corpus definition) with per-file and total `wc -w` (baseline 36,978), plus the structural-artifact list (`.github/`, ignore files, `.specify/` config) — the coverage base for SC-001 and SC-003 + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: The zero-loss ledger — must exist before any file is edited + +**⚠️ CRITICAL**: No editing story (US2–US5) may start before this phase; US1 uses it as review input + +- [ ] T004 Build `specs/002-audit-repo-guidance/rule-inventory.md`: enumerate every actionable rule as `R-###` (source file, condensed statement, disposition blank) across all non-vendored corpus files, per the Rule entity in data-model.md + +**Checkpoint**: Baseline and ledger frozen — the audit can begin + +--- + +## Phase 3: User Story 1 — Deep audit with a findings report (Priority: P1) 🎯 MVP + +**Goal**: `audit-report.md` covering 100% of guidance and structural files, findings ranked by severity with release-blocking items first, the instruction-discovery map, and the decisions the maintainer must rule on. + +**Independent Test**: Pick any in-scope file — the report has a verdict for it (SC-001); the discovery map classifies every file's loading behaviour (SC-002); every finding names its violated convention/principle (FR-002). + +### Implementation for User Story 1 + +- [ ] T005 [P] [US1] Run `gitleaks git . --verbose` over working tree + full history from the repo root; record tool version, command, and results in `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) +- [ ] T006 [P] [US1] Run the internal-reference/personal-data grep pass (patterns per quickstart.md: internal URLs/hosts, personal emails, internal project names) over the working tree; append results to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) +- [ ] T007 [P] [US1] Build the instruction-discovery map: classify every corpus file's `audience`, `tier`, and `loading` (auto / lazy-subtree / one-hop / orphaned) per research.md R1 and the GuidanceFile entity; draft as `specs/002-audit-repo-guidance/discovery-map.md` (FR-003, FR-007 input) +- [ ] T008 [P] [US1] Run the cross-reference check (quickstart.md command): extract relative links and backticked path references from the corpus, list broken targets and orphaned files; append to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-008) +- [ ] T009 [P] [US1] Review root tier against conventions, Principles, and `contracts/guidance-style.md`: `CLAUDE.md`, `README.md`, `specs/README.md`, `design/README.md` — draft findings (bloat, contradictions, sketchy guidance, missing front-door elements) with severity + remedy +- [ ] T010 [P] [US1] Review generic area tier: `apps/backend/CLAUDE.md`, `apps/frontend/CLAUDE.md`, `db/CLAUDE.md`, `db/migrations/README.md`, `infra/CLAUDE.md` — draft findings incl. stack-specific detail that belongs in packs (FR-005) +- [ ] T011 [P] [US1] Review all three stack packs under `stacks/` against `contracts/stack-pack-structure.md` and their generic counterparts — draft findings: contradictions, unlabelled deviations, structure non-conformance (known: `stacks/nextjs-nestjs-postgres/` lacks `infra.md`), pack-vs-pack drift (FR-006, FR-015) +- [ ] T012 [P] [US1] Review add-ons (`add-ons/README.md`, `add-ons/test-mode/README.md`, `add-ons/otp-auth/README.md`) and structural artifacts (`.github/workflows/`, issue/PR templates, ignore files, directory layout); flag-only pass over vendored tooling (`.claude/skills/`, `.specify/` — incl. the unratified constitution); recommend a disposition for `specs/001-enhance-design-guide/` +- [ ] T013 [US1] Assemble `specs/002-audit-repo-guidance/audit-report.md` per `contracts/findings-report.md`: header w/ scan metadata, executive summary, release gate w/ evidence, findings `F-###` ordered release-blocking → low with destructive flags, discovery map, file-by-file verdicts, decisions requested (historical specs + every destructive finding; license already decided: MIT) +- [ ] T014 [US1] Validate the report: diff file-by-file verdicts against `corpus-baseline.md` (zero missing = SC-001); confirm every finding names a violated convention/principle (FR-002); confirm discovery map covers every file (SC-002); fix gaps +- [ ] T015 [US1] GATE — present the report to the maintainer; record their disposition (`approved`/`rejected`) on every destructive finding and the historical-specs decision in `audit-report.md` (FR-010). **Hard stop: no US2–US5 task runs before this completes** + +**Checkpoint**: The review the maintainer asked for is delivered and dispositioned — MVP complete + +--- + +## Phase 4: User Story 2 — Lean, opinionated guidance (Priority: P2) + +**Goal**: Every non-stack guidance file stripped of narrative and filler, rewritten in the contract voice, zero rules lost. (Stack pack files are streamlined in US4 during restructuring, to avoid double-editing.) + +**Independent Test**: Any streamlined file vs its previous version — rules preserved per the inventory, prohibited content gone (contracts/guidance-style.md); corpus measurably smaller. + +### Implementation for User Story 2 + +- [ ] T016 [US2] Streamline root `CLAUDE.md` per `contracts/guidance-style.md`, applying approved findings; update each touched rule's disposition in `rule-inventory.md` — this file sets the voice exemplar for all others +- [ ] T017 [P] [US2] Streamline `apps/backend/CLAUDE.md` (same procedure, match the T016 voice) +- [ ] T018 [P] [US2] Streamline `apps/frontend/CLAUDE.md` +- [ ] T019 [P] [US2] Streamline `db/CLAUDE.md` and apply the approved disposition for `db/migrations/README.md` +- [ ] T020 [P] [US2] Streamline `infra/CLAUDE.md` +- [ ] T021 [P] [US2] Streamline `design/README.md`, `specs/README.md`, `add-ons/README.md`, `add-ons/test-mode/README.md`, `add-ons/otp-auth/README.md` +- [ ] T022 [US2] Sweep `rule-inventory.md`: every rule has a disposition; every `removed` links to an approved finding — zero unapproved losses (FR-004) +- [ ] T023 [US2] Measure progress: corpus `wc -w` (interim, final target ≤ 27,733 lands after US4); sample 3 streamlined files against `contracts/guidance-style.md` (SC-010); record evidence in `audit-report.md` dispositions + +**Checkpoint**: Non-stack guidance is lean and on-voice; ledger proves zero loss + +--- + +## Phase 5: User Story 3 — A public front door worthy of the work (Priority: P3) + +**Goal**: README that positions the template and unmistakably brands it as Cavalry's, MIT license, approved internal-artifact removals applied — the repo looks release-ready. + +**Independent Test**: Fresh-reader protocol (research.md R8): README only, 10 minutes, answers what/philosophy/how-to-start (SC-008); lockup renders from local assets (SC-012); `LICENSE` present. + +### Implementation for User Story 3 + +- [ ] T024 [P] [US3] Copy the Cavalry brand SVGs — lockup light + dark, one mark — from `/Users/adam/GitHub/cavalry-website/design/brand/` into `design/brand/` in this repo (only the variants used; FR-016, research.md R9) +- [ ] T025 [P] [US3] Add `LICENSE` at repo root: MIT text with Cavalry's copyright line (FR-013, research.md R5) +- [ ] T026 [US3] Rewrite `README.md` as the public front door: Cavalry lockup via light/dark `` element referencing `design/brand/`, what-this-is positioning, the opinionated philosophy, how to start (Day-1 checklist kept accurate), attribution naming Cavalry (FR-012, FR-016; depends on T024) +- [ ] T027 [P] [US3] Add the Cavalry mark + attribution line to `design/design-guide.html`, referencing `design/brand/` assets (FR-016) +- [ ] T028 [US3] Apply the maintainer-approved destructive dispositions from `audit-report.md`: remove approved internal-only artifacts (e.g. `specs/001-enhance-design-guide/` if approved); if history remediation was approved for release-blocking content, execute it against the repo's git history — only what T015 approved (FR-010, FR-011) +- [ ] T029 [US3] Validate: run the fresh-reader 10-minute test on `README.md` (SC-008); verify every brand-asset reference resolves to files under `design/brand/` with no external/internal URLs (SC-012); confirm `LICENSE` present; update release-gate section in `audit-report.md` + +**Checkpoint**: The repo presents as Cavalry's public flagship + +--- + +## Phase 6: User Story 4 — Two clean tiers, uniform stack packs (Priority: P4) + +**Goal**: Generic tier fully stack-agnostic; all three packs conform to the canonical structure, streamlined in the contract voice, with labelled exceptions only. + +**Independent Test**: Side-by-side read of each pack vs its generic counterpart — zero unlabelled contradictions (SC-004); pack-vs-pack comparison shows one shape, `stacks/README.md` documents it (SC-011). + +### Implementation for User Story 4 + +- [ ] T030 [US4] Document the canonical pack structure in `stacks/README.md` per `contracts/stack-pack-structure.md`: the five files, absence-is-a-statement rule, exception label format, authoring guidance for future packs (FR-015) +- [ ] T031 [P] [US4] Conform `stacks/nextjs-nestjs-postgres/`: add `infra.md` (content or n/a-declared stub with reason), align section shape with the contract, label sanctioned deviations, streamline prose to the contract voice, move any misplaced generic content up / pull implementation detail down from generic files per approved findings +- [ ] T032 [P] [US4] Conform `stacks/taro-fastify-mysql-tencent/` (same procedure) +- [ ] T033 [P] [US4] Conform `stacks/vercel/` (same procedure) +- [ ] T034 [US4] Resolve remaining tier conflicts in the generic files per approved findings: strip stack-specific detail from `CLAUDE.md`, `apps/*/CLAUDE.md`, `db/CLAUDE.md`, `infra/CLAUDE.md` — generic prevails unless the pack carries a labelled exception (FR-005, FR-006; touches US2's files, so runs after Phase 4) +- [ ] T035 [US4] Validate: build the StackPack conformance table (data-model.md) — five files present/n-a-declared per pack, zero unlabelled contradictions in side-by-side reads (SC-004, SC-011); update `rule-inventory.md` for moved rules + +**Checkpoint**: Tiers are clean; packs are parallel; future-pack authoring is documented + +--- + +## Phase 7: User Story 5 — No instruction can be silently missed (Priority: P5) + +**Goal**: Every agent-binding file reachable within one explicit "read this when X" hop from auto-loaded or lazy-loaded guidance. + +**Independent Test**: Trace each agent-binding file from the discovery map to its loader — max one hop, zero orphans (SC-005). + +### Implementation for User Story 5 + +- [ ] T036 [US5] Wire missing references per the discovery map's `orphaned` entries: add "read `` before working on ``" pointers in root `CLAUDE.md` and the area `CLAUDE.md` files so every agent-binding file is one hop from a loaded file (FR-007) +- [ ] T037 [US5] For every `audience: human` file, verify no agent-binding rule exists only there; move any such rule into the owning CLAUDE.md-tier file and update `rule-inventory.md` (US5 scenario 2) +- [ ] T038 [US5] Validate: update `discovery-map.md` — zero `orphaned` agent-binding files remain (SC-005); re-run the cross-reference check for broken links introduced by wiring (FR-008) + +**Checkpoint**: Nothing binding can be missed for naming reasons + +--- + +## Phase 8: Polish & Cross-Cutting Validation + +**Purpose**: Final measurements, end-to-end proof, and the merge-back gate + +- [ ] T039 [P] Final SC-003 measurement: corpus `wc -w` ≤ 27,733 and `rule-inventory.md` shows zero unapproved losses; if over target, iterate on the wordiest files (evidence into `audit-report.md`) +- [ ] T040 [P] Dry-run instantiation (SC-006): fresh clone in a scratch directory, follow the README Day-1 checklist end to end; zero broken references, missing files, or stale placeholders +- [ ] T041 [P] Run SC-007 (two-doc orientation: root + one area file states the area's binding rules) and re-run SC-008 if `README.md` changed after T029 +- [ ] T042 Full quickstart.md sweep: every row SC-001…SC-012 passes; release-gate section of `audit-report.md` all-pass with evidence +- [ ] T043 Close the ledger: every finding in `audit-report.md` reaches `applied`/`verified` or `rejected`; no new TODO/FIXME in touched files (Definition of Done); note explicitly that lint/typecheck/test/build are still toolchain TODOs +- [ ] T044 Merge-back gate per root `CLAUDE.md`: self-review the full diff end to end, rebase onto `main`, fast-forward merge, remove the worktree — and **confirm with the maintainer before any push** (deploy workflow triggers on `main`) + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)** → **Foundational (Phase 2)** → **US1 (Phase 3)**: strictly sequential. +- **T015 (maintainer gate)** blocks Phases 4–8 — this is the FR-010 approval gate, a deliberate deviation from free story parallelism. +- **US2 (Phase 4)** before **US4 (Phase 6)**: T034 edits the same generic files US2 streamlines. +- **US3 (Phase 5)** is independent of US2/US4 (different files) and may run in parallel with Phase 4 after T015. +- **US5 (Phase 7)** last of the stories: wiring targets the post-restructure file set. +- **Polish (Phase 8)** after all stories. + +### Parallel Opportunities + +- Phase 3: T005–T012 (eight review/scan tasks) all parallel — different scopes, separate draft outputs. +- Phase 4: T017–T021 parallel after T016 sets the voice. +- Phase 5: T024, T025, T027 parallel; T026 after T024. +- Phase 6: T031–T033 (one per pack) parallel after T030. +- Phase 8: T039–T041 parallel. +- Cross-phase: Phase 5 (US3) can run alongside Phase 4 (US2) — disjoint files. + +## Parallel Example: User Story 1 + +```text +# After T004, launch the audit fan-out together: +Task: T005 gitleaks scan → scan-evidence.md +Task: T006 internal-reference grep → scan-evidence.md (own section) +Task: T007 discovery map → discovery-map.md +Task: T008 cross-reference check → scan-evidence.md (own section) +Task: T009 root-tier review Task: T010 generic-tier review +Task: T011 stack-pack review Task: T012 add-ons/structural/vendored review +# Then T013 assembles, T014 validates, T015 gates. +``` + +## Implementation Strategy + +**MVP first (US1 only)**: Phases 1–3 deliver the maintainer's actual ask — the deep "is anything sketchy" review — with zero risk, since nothing is edited yet. Stop at T015, get dispositions, demo the report. + +**Incremental delivery**: after the gate, each story phase leaves the repo consistent and shippable: lean guidance (US2) → public face (US3) → uniform packs (US4) → wiring (US5) → final proof (Phase 8). Any story can be deferred without breaking the previous ones; SC-003's final number simply waits for US4. + +## Notes + +- All edits trace to findings in `audit-report.md`; destructive ones only with recorded approval (FR-010). +- Vendored tooling (`.claude/skills/`, `.specify/`) is never edited — findings only. +- Intentional template placeholders are preserved and framed, not "fixed". +- Commit per task or logical group, Conventional Commits style, on the worktree branch. From 9aaee6c50f8508f1aac334fa8ff0dc635d4e45d5 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 00:17:11 +0800 Subject: [PATCH 02/12] docs(specs): audit scan evidence and instruction-discovery map Co-Authored-By: Claude Fable 5 --- .../002-audit-repo-guidance/discovery-map.md | 38 +++++++++++++++++++ .../002-audit-repo-guidance/scan-evidence.md | 37 ++++++++++++++++++ specs/002-audit-repo-guidance/tasks.md | 8 ++-- 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 specs/002-audit-repo-guidance/discovery-map.md create mode 100644 specs/002-audit-repo-guidance/scan-evidence.md diff --git a/specs/002-audit-repo-guidance/discovery-map.md b/specs/002-audit-repo-guidance/discovery-map.md new file mode 100644 index 0000000..020793d --- /dev/null +++ b/specs/002-audit-repo-guidance/discovery-map.md @@ -0,0 +1,38 @@ +# Instruction-Discovery Map — 2026-07-03 (T007, FR-003) + +How the coding agent encounters each file, per the documented loading model (research.md R1): only `CLAUDE.md`-named files load automatically — root at session start, nested ones lazily on subtree entry. Everything else is read **only** when a loaded file points at it. `@`-imports are eager (would defeat "context light") and are correctly not used. + +| File | Audience | Tier | Loading | Via | +|---|---|---|---|---| +| CLAUDE.md | agent+human | root | **auto** (session start) | naming convention | +| apps/backend/CLAUDE.md | agent | generic-area | **lazy-subtree** | naming convention | +| apps/frontend/CLAUDE.md | agent | generic-area | **lazy-subtree** | naming convention | +| db/CLAUDE.md | agent | generic-area | **lazy-subtree** | naming convention | +| infra/CLAUDE.md | agent | generic-area | **lazy-subtree** | naming convention | +| README.md | both | root | one-hop | root CLAUDE.md (Day-1 checklist pointer) | +| specs/README.md | both | meta | one-hop | root CLAUDE.md | +| stacks/README.md | both | meta | one-hop | root CLAUDE.md | +| add-ons/README.md | both | add-on | one-hop | root CLAUDE.md | +| add-ons/test-mode/README.md | agent+human | add-on | one-hop | root categorical rule ("every kept add-on: read its README") | +| add-ons/otp-auth/README.md | agent+human | add-on | one-hop | root categorical rule | +| design/README.md | both | meta | one-hop | apps/frontend/CLAUDE.md | +| stacks/*/backend.md (×3) | agent | stack-pack | one-hop | apps/backend/CLAUDE.md categorical ("read the adopted pack's backend.md") | +| stacks/*/frontend.md (×3) | agent | stack-pack | one-hop | apps/frontend/CLAUDE.md categorical | +| stacks/*/db.md (×3) | agent | stack-pack | one-hop | db/CLAUDE.md categorical | +| stacks/*/infra.md (×2) | agent | stack-pack | one-hop | infra/CLAUDE.md categorical (conditional "if it ships one") | +| stacks/nextjs-nestjs-postgres/README.md | both | stack-pack | **two-hop** ⚠️ | root → stacks/README.md → pack README | +| stacks/taro-fastify-mysql-tencent/README.md | both | stack-pack | **two-hop** ⚠️ | same chain | +| stacks/vercel/README.md | both | stack-pack | **two-hop** ⚠️ | same chain | +| db/migrations/README.md | unclear | generic-area | **orphaned** ⚠️ | nothing references it | +| .github/PULL_REQUEST_TEMPLATE.md | both | meta | **orphaned** ⚠️ | GitHub UI renders it for humans; no guidance pointer for agents (root mentions "the PR template" without a path) | +| .github/ISSUE_TEMPLATE/bug.md | human | meta | n/a (GitHub UI) | human-facing only — acceptable if no unique agent-binding rules | +| .github/ISSUE_TEMPLATE/feature.md | human | meta | n/a (GitHub UI) | same | +| .claude/skills/** | agent | vendored | on skill invocation (harness) | untracked in git | +| .specify/** | agent | vendored | read by Spec Kit skills | untracked in git | + +## At-risk summary (feeds FR-007 wiring in US5) + +1. `db/migrations/README.md` — orphaned; 9 words; disposition needed (delete, or fold its content into `db/CLAUDE.md`). +2. `stacks//README.md` (×3) — two hops from auto-loaded guidance. Mitigation: they are instantiation-time documents (a human-led moment via the Day-1 checklist), and post-instantiation work flows through the one-hop area appendices — wire an explicit pointer or accept with rationale. +3. `.github/PULL_REQUEST_TEMPLATE.md` — carries the Test-plan-evidence expectation agents must satisfy; root CLAUDE.md names the obligation but not the file. One explicit pointer fixes it. +4. Issue templates — human-only, no unique binding rules (verify in T037). diff --git a/specs/002-audit-repo-guidance/scan-evidence.md b/specs/002-audit-repo-guidance/scan-evidence.md new file mode 100644 index 0000000..1a548fc --- /dev/null +++ b/specs/002-audit-repo-guidance/scan-evidence.md @@ -0,0 +1,37 @@ +# Scan Evidence — 2026-07-03, commit bdd463d (branch worktree-002-audit-repo-guidance) + +## T005 — Secret scan (FR-011) + +- Tool: gitleaks **8.30.1** — `gitleaks git . --redact --no-banner` +- Scope: full git history, **54 commits**, ~817 KB scanned +- Result: **no leaks found** ✅ + +## T006 — Internal references & personal data (FR-011) + +**Working tree** (all files, excluding `.git/`, `.claude/`, `.specify/`): + +- Email-shaped strings: only `name@company.com` (an intentional placeholder) ✅ +- URLs: `code.claude.com` docs citation (in specs/002), `fonts.googleapis.com` / `fonts.gstatic.com` (design guide webfonts), `zeroheight.com/showcase/` (public reference) — no internal hosts ✅ +- IP-shaped strings: only `0.0.0.0` ✅ + +**Git history author/committer identities** (published if the repo goes public): + +| Email | Assessment | +|---|---| +| `adam@cavalry.sg` | company address — normal for a company repo | +| `adam@cavalry.online` | company address — normal | +| `adam@raspberri.es` | **personal-looking address** — release-gate decision needed: accept exposure, or rewrite history (destructive) | + +→ Finding F-001 (release-blocking, pending maintainer disposition). + +## T008 — Cross-reference check (FR-008) + +- Every relative markdown link in the corpus resolves: **0 broken links** ✅ +- Reference-edge scan (which loaded files point at each README-named file): + - `specs/README.md`, `stacks/README.md`, `add-ons/README.md`, `README.md` ← root `CLAUDE.md` ✅ + - `design/README.md` ← `apps/frontend/CLAUDE.md` ✅ + - add-on READMEs ← root's categorical rule ("every directory kept under `add-ons/` is adopted — read its `README.md`") ✅ + - stack pack area docs (`backend/frontend/db/infra.md`) ← categorical pointers in each matching area `CLAUDE.md` ✅ + - `db/migrations/README.md` ← **nothing** (orphaned) + - `stacks//README.md` ← only via `stacks/README.md` (two hops from auto-loaded guidance) + - `.github/PULL_REQUEST_TEMPLATE.md` ← no guidance pointer (GitHub UI only) diff --git a/specs/002-audit-repo-guidance/tasks.md b/specs/002-audit-repo-guidance/tasks.md index 9f675dd..f79a7c6 100644 --- a/specs/002-audit-repo-guidance/tasks.md +++ b/specs/002-audit-repo-guidance/tasks.md @@ -43,10 +43,10 @@ ### Implementation for User Story 1 -- [ ] T005 [P] [US1] Run `gitleaks git . --verbose` over working tree + full history from the repo root; record tool version, command, and results in `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) -- [ ] T006 [P] [US1] Run the internal-reference/personal-data grep pass (patterns per quickstart.md: internal URLs/hosts, personal emails, internal project names) over the working tree; append results to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) -- [ ] T007 [P] [US1] Build the instruction-discovery map: classify every corpus file's `audience`, `tier`, and `loading` (auto / lazy-subtree / one-hop / orphaned) per research.md R1 and the GuidanceFile entity; draft as `specs/002-audit-repo-guidance/discovery-map.md` (FR-003, FR-007 input) -- [ ] T008 [P] [US1] Run the cross-reference check (quickstart.md command): extract relative links and backticked path references from the corpus, list broken targets and orphaned files; append to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-008) +- [X] T005 [P] [US1] Run `gitleaks git . --verbose` over working tree + full history from the repo root; record tool version, command, and results in `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) +- [X] T006 [P] [US1] Run the internal-reference/personal-data grep pass (patterns per quickstart.md: internal URLs/hosts, personal emails, internal project names) over the working tree; append results to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) +- [X] T007 [P] [US1] Build the instruction-discovery map: classify every corpus file's `audience`, `tier`, and `loading` (auto / lazy-subtree / one-hop / orphaned) per research.md R1 and the GuidanceFile entity; draft as `specs/002-audit-repo-guidance/discovery-map.md` (FR-003, FR-007 input) +- [X] T008 [P] [US1] Run the cross-reference check (quickstart.md command): extract relative links and backticked path references from the corpus, list broken targets and orphaned files; append to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-008) - [ ] T009 [P] [US1] Review root tier against conventions, Principles, and `contracts/guidance-style.md`: `CLAUDE.md`, `README.md`, `specs/README.md`, `design/README.md` — draft findings (bloat, contradictions, sketchy guidance, missing front-door elements) with severity + remedy - [ ] T010 [P] [US1] Review generic area tier: `apps/backend/CLAUDE.md`, `apps/frontend/CLAUDE.md`, `db/CLAUDE.md`, `db/migrations/README.md`, `infra/CLAUDE.md` — draft findings incl. stack-specific detail that belongs in packs (FR-005) - [ ] T011 [P] [US1] Review all three stack packs under `stacks/` against `contracts/stack-pack-structure.md` and their generic counterparts — draft findings: contradictions, unlabelled deviations, structure non-conformance (known: `stacks/nextjs-nestjs-postgres/` lacks `infra.md`), pack-vs-pack drift (FR-006, FR-015) From c07d8dd5c9401ba888060cd715ea15c1b016b4cd Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 01:27:19 +0800 Subject: [PATCH 03/12] docs(specs): rule inventory and 38-finding audit report Co-Authored-By: Claude Fable 5 --- specs/002-audit-repo-guidance/audit-report.md | 224 ++++++ .../002-audit-repo-guidance/rule-inventory.md | 718 ++++++++++++++++++ specs/002-audit-repo-guidance/tasks.md | 14 +- 3 files changed, 949 insertions(+), 7 deletions(-) create mode 100644 specs/002-audit-repo-guidance/audit-report.md create mode 100644 specs/002-audit-repo-guidance/rule-inventory.md diff --git a/specs/002-audit-repo-guidance/audit-report.md b/specs/002-audit-repo-guidance/audit-report.md new file mode 100644 index 0000000..32f5b4c --- /dev/null +++ b/specs/002-audit-repo-guidance/audit-report.md @@ -0,0 +1,224 @@ +# Audit Report — Cavalry Template SPA + +**Audited**: 2026-07-03, commit `bdd463d`, branch `worktree-002-audit-repo-guidance` +**Corpus baseline**: 36,978 words / 29 guidance files + structural artifacts ([corpus-baseline.md](./corpus-baseline.md)) +**Scanners**: gitleaks 8.30.1 (full history), grep passes, link resolution ([scan-evidence.md](./scan-evidence.md)) +**Format**: per [contracts/findings-report.md](./contracts/findings-report.md); finding fields per [data-model.md](./data-model.md) + +## Executive summary + +The repository's engineering substance is strong — the architecture contracts are coherent, the add-on docs are exemplary, every internal link resolves, and the full git history is secret-free. But it is **not release-ready**: 3 release-blocking findings (publishing strategy for history/personal data, internal references inside committed spec artifacts, missing license), 9 high findings (a real fork-PR hole in `deploy.yml`, security-by-omission in the OTP guidance, two unregistered pack-vs-base contradictions, dangling security delegations, and three self-contradictions in the repo's own conventions), 21 medium (the bloat and duplication the maintainer suspected — though the design-guide revision was *not* the cause), and 5 low. **38 findings total.** Dispositions recorded below after maintainer review (T015). + +## Release gate + +| Item | Status | Evidence | +|---|---|---| +| Secrets (tree + full history) | ✅ PASS | gitleaks 8.30.1, 54 commits, no leaks | +| Internal references / personal data | ❌ FAIL | F-001 (history emails), F-002 (spec artifacts) | +| License | ❌ FAIL | F-003 — no LICENSE; remedy decided (MIT) | +| Front door | ❌ FAIL | README is a checklist, not a positioning front door; no branding (US3 scope, FR-012/FR-016) | + +## Findings + +### Release-blocking + +**F-001 — Personal email in git history** +- Location: git history (author/committer identities, all 54 commits) +- Issue: history publishes `adam@raspberri.es` (personal-looking) alongside `adam@cavalry.sg` / `adam@cavalry.online` (company) +- Violates: FR-011 (personal data in a public release) +- Remedy: choose a publishing strategy — see Decision 1. A fresh-history public release solves this without touching the private repo; an in-place rewrite is destructive; acceptance is legitimate (it is the maintainer's own address) +- Destructive: yes (if history rewrite) | Disposition: **pending — Decision 1** + +**F-002 — Committed spec artifacts carry internal-only references** +- Location: `specs/002-audit-repo-guidance/` (tasks.md T024: absolute path `/Users/adam/GitHub/cavalry-website/...` with machine username; scan-evidence.md: personal-email deliberation), `specs/001-enhance-design-guide/` (template-development history) +- Issue: if the historical spec dirs ship, internal paths and personal-data discussion ship with them — and deleting them later still leaves them in history unless the publishing strategy addresses it +- Violates: FR-011; SC-012 (no internal references) +- Remedy: exclude template-development specs from the public artifact — see Decisions 1 & 3 +- Destructive: yes (directory removal) | Disposition: **pending — Decisions 1 & 3** + +**F-003 — No LICENSE** +- Location: repo root; README has no license section +- Issue: an unlicensed public repo is all-rights-reserved — legally unusable, fatal for a template whose first instruction is "Use this template" +- Violates: FR-013; front-door completeness +- Remedy: add MIT LICENSE with Cavalry copyright line + README license section +- Destructive: no | Disposition: **approved — maintainer chose "not restrictive" → MIT (2026-07-03); lands in US3 (T025)** + +### High + +**F-004 — `deploy.yml` gate is bypassable by a fork PR** +- Location: `.github/workflows/deploy.yml` `jobs.deploy.if` +- Issue: gate is `conclusion == 'success' && head_branch == 'main'`; a fork PR from a branch named `main` with green CI satisfies both, then checks out the fork's SHA with base-repo secrets +- Violates: the workflow's own stated contract; *Don't overfit* (handle the adversarial case) +- Remedy: add `github.event.workflow_run.event == 'push'` to the condition, with a comment so it survives the TODO fill-in +- Destructive: no | Disposition: proposed + +**F-005 — OTP guidance is unsound by omission** +- Location: `add-ons/otp-auth/README.md` §Choose a model / §Make it robust +- Issue: never mandates CSPRNG generation or minimum length, single-use consumption on successful verify, a bounded TTL (minutes), or max-failed-attempts-then-invalidate — the four classic OTP failure modes +- Violates: add-on invariant (SOP for a capability that's easy to get wrong); never hand-roll auth +- Remedy: add the four rules to "Make it robust" +- Destructive: no | Disposition: proposed + +**F-006 — Stale claim: "CI and the PR template are still stubs"** +- Location: `CLAUDE.md` §Definition of Done preamble +- Issue: the PR template is fully authored (evidence-based test plan, DB and UI gates); the stale clause invites agents to disregard it +- Violates: accuracy of the always-loaded file; guidance-style (no historical narrative) +- Remedy: drop the clause (or "CI is still a stub") +- Destructive: no | Disposition: proposed + +**F-007 — Spec convention contradicts the repo's own specs** +- Location: `specs/README.md` §Convention; echoed in `design/README.md` example and `.github/PULL_REQUEST_TEMPLATE.md` +- Issue: declares `YYYY-MM-DD-.md` single files while the repo's tracked specs are `NNN-/` directories; root CLAUDE.md promises spec-tool agnosticism but the convention has no escape hatch +- Violates: stated-rule vs repo reality; guidance-style (default + escape hatch) +- Remedy: state the file convention as default with the directory-per-feature escape hatch; point the two echoes at `specs/README.md` instead of restating the shape +- Destructive: no | Disposition: proposed + +**F-008 — `stacks/README.md` contradicts the five-file canon** +- Location: `stacks/README.md` §Required file set + closing note +- Issue: mandates four files with "infra.md the only optional fifth" — the canonical structure (FR-015) requires five with absence-is-a-statement stubs +- Violates: FR-015; SC-011 +- Remedy: rewrite the required-file section to the five-file canon + n/a-stub rule + per-area section skeleton (also fixes F-029) +- Destructive: no | Disposition: proposed + +**F-009 — nextjs pack missing `infra.md`** +- Location: `stacks/nextjs-nestjs-postgres/` (absence declared only as a README aside) +- Issue: conformance failure under the canon; absence must be an in-pack stub +- Violates: FR-015 (absence is a statement) +- Remedy: add the n/a-stub `infra.md` (precedence line; platform-agnostic declaration; deploy-seam pointers; no-conflicts line); trim the README aside to a pointer +- Destructive: no | Disposition: proposed + +**F-010 — vercel staging prescribes an unregistered long-lived `develop` branch** +- Location: `stacks/vercel/infra.md` §Staging + `stacks/vercel/db.md` §Production & staging migrations +- Issue: contradicts root's trunk-based/single-long-lived-branch rule; neither conflict register mentions it +- Violates: FR-006 (no silent contradictions) +- Remedy: add the register entry (develop receives only fast-forwards from main, never feature work — Vercel branch-scoped previews need a stable branch) +- Destructive: no | Disposition: proposed + +**F-011 — vercel pins analytics SDKs against the vendor-agnostic base rule** +- Location: `stacks/vercel/frontend.md` §Analytics & Speed Insights +- Issue: base says "stay vendor-agnostic; don't pin an analytics SDK"; the pack mandates `@vercel/analytics` + `@vercel/speed-insights` unregistered +- Violates: FR-006 +- Remedy: add the register entry (platform identity; product events still flow through the shared analytics service) +- Destructive: no | Disposition: proposed + +**F-012 — Security bindings delegated to packs that don't deliver them** +- Location: `stacks/nextjs-nestjs-postgres/{backend,frontend}.md`, `stacks/taro-fastify-mysql-tencent/{backend,frontend}.md` +- Issue: the base delegates security headers/CSP mechanism, SSRF concrete check, and secret write-only masking to "the active stack pack"; only the vercel pack supplies them — two packs leave adopters with dangling security-critical delegations +- Violates: FR-006 (base promises the pack binds it); SC-011 (same coverage) +- Remedy: add a security-bindings section to each of the four files +- Destructive: no | Disposition: proposed + +### Medium + +**F-013 — Root CLAUDE.md historical/meta narrative** — §Coding standards "now live next to the code" (references the past move); §Principles "adapted from Andrej Karpathy's guidelines, folded into this file so no external reference is needed". Violates: guidance-style (no historical narrative). Remedy: state where standards live; reduce credit to a bare parenthetical or cut. Disposition: proposed + +**F-014 — Mockup lifecycle rule stated in five places** — root §UI mockup, `design/README.md` (which points at root then restates all of it), `specs/README.md` item 2 (90-word parenthetical), `apps/frontend/CLAUDE.md` §Verify, PR template. Violates: one rule, one statement. Remedy: `design/README.md` owns the full loop; everyone else keeps one line + pointer. Disposition: proposed + +**F-015 — Spec-slice rules triplicated** — root §Development workflow, `specs/README.md` §Convention, README §ideology, near-identical wording. Remedy: `specs/README.md` owns it; root keeps one line; README paraphrases philosophy. Disposition: proposed + +**F-016 — README restates contract text verbatim** — What's-included rows copy root Repo-shape lines; the Keystone description appears twice (row + step 9); ideology quotes the Principles line. Remedy: one-clause rows with links; detail only in step 9. Disposition: proposed + +**F-017 — README's false "repo name is immutable" note** — GitHub repos can be renamed; template-derived repos are user-named; self-deprecating admission on a flagship front door. Remedy: delete the sentence. Disposition: proposed + +**F-018 — Day-1 checklist never clears template-development specs** — instantiated repos inherit the template's own specs; step 13's grep even excludes `specs/`. Remedy: add a Day-1 step ("delete the template's development specs, keep `specs/README.md`") — moot for the public artifact if Decision 3 removes them, still right for future template development. Disposition: proposed + +**F-019 — Root Repo-shape map is stale** — omits `specs/` entirely (load-bearing for the workflow); `design/` bullet omits the design guide that Day-1 gates on; `infra/` bullet doesn't frame emptiness as deliberate. Remedy: add/extend the three bullets. Disposition: proposed + +**F-020 — Vendored tooling untracked *and* unignored** — `.claude/` and `.specify/` sit as `??` in git status; one `git add -A` ships ten Spec Kit skills + an unratified bracket-placeholder constitution. Remedy: gitignore both (recommended) — see Decision 2. Disposition: **pending — Decision 2** + +**F-021 — `apps/frontend/CLAUDE.md` intra-file duplication** — largest file (5,051 words); atomic-tier definitions stated twice; grouping rule three times; `lib/` helpers rule twice; 320px/200% floor three times; never-violate gates restate body rules; connective aphorisms and companion-note meta. History shows the bloat predates the design-guide work (3,360 → 5,312 → trimmed 4,850 → 5,051): accreted via "fold learnings" commits, re-grew past its own trim. Remedy: single-owner per rule; ~3,500 words holds every rule (ledger-verified). Disposition: proposed + +**F-022 — `apps/frontend/CLAUDE.md` toolchain leaks & fossils** — `package.json`/semver named in a toolchain-TBD file; `BidTable` example leaks a prior project's auction domain; restates root's mockup + dates rules. Remedy: "package manifest" wording; neutral example names; pointers. Disposition: proposed + +**F-023 — `apps/backend/CLAUDE.md` stack leaks & meta** — unhedged "In Node this is Express middleware / Fastify plugins…" sentence; "(e.g. Awilix)"; security-baseline preamble summarizes the document; "Standards reference" grab-bag with a one-bullet subsection; ring-naming heritage clause. Remedy: packs own the bindings; cut meta; promote sections. Disposition: proposed + +**F-024 — `db/CLAUDE.md` duplication & meta** — "rules below are checkable and client-agnostic" meta sentence; shared-DB-across-worktrees rule stated in root *and* here *and* inside the round-trip rule. Remedy: db file owns the rule once; root keeps a pointer. Disposition: proposed + +**F-025 — GCP specifics in the generic infra tier** — "blessed cloud is GCP", `gcloud` commands, GCP networking section — while two of three packs bind other platforms and register "replaces the GCP blessing". Remedy: keep agnostic intents (verify context; never default networks; provider export tooling); mark GCP material explicitly as the *default binding that an adopted pack's infra.md replaces* — no new pack invented (YAGNI). Disposition: proposed + +**F-026 — `infra/CLAUDE.md` restates root principles & self-triplicates** — smallest-change/scope-control bullets mirror root Principles; environment-explicit rule ×3; apply-needs-approval ×3; "Purpose" section narrates the doc; "no separate AGENTS.md" meta; hedged rules without triggers ("where appropriate", "unless absolutely necessary"). Remedy: Guardrails owns approval + environment rules once; cut restatements/meta; default + named exception wording. Disposition: proposed + +**F-027 — nextjs pack meta & register narrative** — "How to read this file" section about the document; register Because-clauses citing "the draft" and "the overhaul"; "three picks recorded in the pack README" is false (README records one). Remedy: cut meta; present-tense Because clauses; record the three picks. Disposition: proposed + +**F-028 — nextjs pack density is duplication, not topics** — db.md register entries restate §Migrations at ~150 words/field; backend.md re-registers the forward-only conflict db.md owns; rejected-alternative rationales stated twice. Detail itself is legitimate (Babel order, enum ordering, `migrate diff` flags are load-bearing). Remedy: registers point at owning sections; one statement per rationale. Disposition: proposed + +**F-029 — Pack shape drift** — backend.md: 16 sections (nextjs) vs 6 (taro) vs 8 (vercel); register formats differ; equivalent decisions registered in one pack, unregistered in siblings; nextjs README omits the Learnings step both siblings carry. Remedy: define the per-area skeleton in `stacks/README.md` (with F-008) and normalize all three. Disposition: proposed + +**F-030 — Stack-agnostic rules trapped in packs** — money-never-float, timestamps-on-every-table, unique-constraints-encode-invariants→409, per-worktree DB naming scheme (duplicated verbatim in two packs), serverless instance-memory rule (×2), pagination windowing, modal-sizing-once. Violates: the packs' own "if a line is true without naming the stack, it does not belong". Remedy: hoist each to its base file; packs keep only the engine binding. Disposition: proposed + +**F-031 — Cross-pack references dangle after instantiation** — "contrast `vercel`" ×5 in the taro pack; "the sibling nextjs pack" in vercel — siblings are deleted on Day-1. Remedy: self-contained statements. Disposition: proposed + +**F-032 — Project-specific values in the taro pack** — `users/*`/`posts/*` COS prefixes from a prior product; deployed-product voice ("Cross-region DR replication is configured"); a leftover `purpose` example. Remedy: generalize to imperative template guidance. Disposition: proposed + +**F-033 — Discovery wiring gaps** — `db/migrations/README.md` orphaned (nothing references it); pack READMEs two hops from auto-loaded guidance; PR template carries agent obligations but no guidance pointer names it. Violates: FR-007. Remedy: US5 wiring (pointer from `db/CLAUDE.md`; explicit pack-README pointer in `stacks/README.md` chain acknowledged at root; name the PR template where root cites the Test-plan obligation). Disposition: proposed + +### Low + +**F-034 — Root minor filler** — "These two rules are load-bearing…" announcement; self-review restates the rules it points to; "In short" digest re-owns the dates rule; Readability section is two orphan sentences + a lone sub-heading; local-deploy hedge legislates a nonexistent script. Remedy: cut/merge. Disposition: proposed + +**F-035 — Placeholder grep doesn't match the placeholders** — Day-1 step 13 greps `TODO: replace`; the seven commented CI gates say `# TODO: install command` etc. Remedy: unify the marker or widen the grep. Disposition: proposed + +**F-036 — Ignore/template polish** — `.gitignore` lacks `coverage/` and `pnpm-debug.log*` (pnpm is the CI worked example); `ISSUE_TEMPLATE/` has no `config.yml`. Remedy: cheap additions. Disposition: proposed + +**F-037 — Provenance narrative in vercel pack** — "Validated end to end by a shipped production project" (README) + "validated in production use" (register). Remedy: cut both. Disposition: proposed + +**F-038 — Minor cross-file dedups** — frontend re-quotes the backend's correlation-id transport shape; the 45-word stack-pack pointer paragraph is near-verbatim ×4 (trim to one sentence + pointer to `stacks/README.md` precedence). Remedy: pointers. Disposition: proposed + +## Instruction-discovery map (SC-002) + +**The maintainer's question answered**: yes — only `CLAUDE.md`-named files load automatically (root at session start; nested ones lazily when the agent enters that subtree). `README.md` and every other markdown file are **never** auto-loaded; they are read only when a loaded file explicitly points at them. The template's "read X before working on Y" pointer pattern is the correct mechanism (`@`-imports would load eagerly and defeat "context light"; mass-renaming would break human browsing). Verified against current Claude Code docs (research.md R1). + +Classification of all 29 files: [discovery-map.md](./discovery-map.md). Summary: 5 auto/lazy-loaded (the CLAUDE.md tier) · 18 correctly one-hop referenced · 3 at risk (orphaned `db/migrations/README.md`; two-hop pack READMEs; unreferenced PR template → F-033) · 3 human-facing with no unique agent-binding rules (issue templates, verified in T037). + +## File-by-file verdicts (SC-001) + +| File | Verdict | +|---|---| +| CLAUDE.md | F-006, F-013, F-015, F-019, F-034, F-038 | +| README.md | F-003, F-016, F-017, F-018, F-035 | +| specs/README.md | F-007, F-014, F-015 | +| design/README.md | F-007 (echo), F-014 | +| apps/backend/CLAUDE.md | F-023, F-038 | +| apps/frontend/CLAUDE.md | F-014, F-021, F-022, F-038 | +| db/CLAUDE.md | F-024, F-030 (receives hoists) | +| db/migrations/README.md | F-033 | +| infra/CLAUDE.md | F-025, F-026 | +| stacks/README.md | F-008, F-029 | +| stacks/nextjs-nestjs-postgres/README.md | F-027, F-029 | +| stacks/nextjs-nestjs-postgres/backend.md | F-012, F-027, F-028 | +| stacks/nextjs-nestjs-postgres/frontend.md | F-012 | +| stacks/nextjs-nestjs-postgres/db.md | F-027, F-028, F-030 | +| stacks/nextjs-nestjs-postgres/infra.md | F-009 (missing) | +| stacks/taro-fastify-mysql-tencent/README.md | F-031 | +| stacks/taro-fastify-mysql-tencent/backend.md | F-012, F-030 | +| stacks/taro-fastify-mysql-tencent/frontend.md | F-030, F-031 | +| stacks/taro-fastify-mysql-tencent/db.md | F-030, F-031, F-032 | +| stacks/taro-fastify-mysql-tencent/infra.md | F-031, F-032 | +| stacks/vercel/README.md | F-037 | +| stacks/vercel/backend.md | F-030 | +| stacks/vercel/frontend.md | F-011, F-030, F-031, F-037 | +| stacks/vercel/db.md | F-010, F-030 | +| stacks/vercel/infra.md | F-010 | +| add-ons/README.md | ✅ clean | +| add-ons/test-mode/README.md | ✅ clean | +| add-ons/otp-auth/README.md | F-005 | +| .github/PULL_REQUEST_TEMPLATE.md | F-007 (echo), F-033 | +| .github/ISSUE_TEMPLATE/bug.md | ✅ clean (F-036 optional) | +| .github/ISSUE_TEMPLATE/feature.md | ✅ clean (F-036 optional) | +| .github/workflows/ci.yml | F-035 | +| .github/workflows/deploy.yml | F-004 | +| .gitignore | F-020, F-036 | +| git history | F-001 | +| specs/001-* + specs/002-* (committed) | F-002 | +| .claude/skills/, .specify/ (vendored, untracked) | F-020 — flag-only; constitution.md is an unratified placeholder | + +## Decisions requested + +1. **Publishing strategy (F-001, F-002)** — how does this repo go public? + - **(a) Fresh-history release (recommended)**: publish a clean snapshot (orphan branch or new repo, single initial commit authored with a company email). Solves the personal email AND everything ever committed (spec artifacts, machine paths) in one move; the private repo keeps full history untouched. + - (b) Rewrite history in place (destructive: all SHAs change) then publish this repo. + - (c) Accept exposure: publish as-is history; only delete unwanted files going forward (they remain visible in old commits). +2. **Vendored Spec Kit tooling (F-020)** — `.claude/` + `.specify/`: gitignore (recommended), commit, or delete? +3. **Template-development specs (F-002, F-018)** — do `specs/001-*` and `specs/002-*` ship in the public template? Recommended: no (keep `specs/README.md`; the work stays in the private history). + +All other findings are non-destructive edits already authorized by the spec's P2–P5 stories; they proceed after this gate. diff --git a/specs/002-audit-repo-guidance/rule-inventory.md b/specs/002-audit-repo-guidance/rule-inventory.md new file mode 100644 index 0000000..387b4ac --- /dev/null +++ b/specs/002-audit-repo-guidance/rule-inventory.md @@ -0,0 +1,718 @@ +# Rule Inventory — the zero-loss ledger (T004, FR-004) + +**Captured**: 2026-07-03 at commit bdd463d, before any streamlining edit. + +Every actionable rule in the non-vendored guidance corpus, one line per rule, grouped by source file. **Rule ID = ` #`** (file heading + ordinal within it) — a practical adaptation of the data model's `R-###`. **Disposition convention**: a rule with no annotation is `kept` (still present in the streamlined file, possibly reworded). Only exceptions are annotated inline during US2/US4/US5: `[merged → #]`, `[moved → ]`, or `[REMOVED — F-### approval]`. The T022/T035 sweeps verify no rule is missing an owner and no removal lacks an approved finding. + +## CLAUDE.md (root) + +1. Never use persistent file-based memory; record cross-session notes in the **Learnings** section instead. +2. Read the matching area file before working there: `apps/backend/CLAUDE.md`, `apps/frontend/CLAUDE.md`, `db/CLAUDE.md`, `infra/CLAUDE.md`. +3. Migrations live under `db/migrations/` and are reversible. +4. Stack packs: one chosen at instantiation, rest deleted; read the adopted pack's matching appendix before working in an area. +5. Add-ons: every directory kept under `add-ons/` is adopted — read its README and follow it whenever touching that capability. +6. If Common commands are still ``/TODO: detect the real command from lockfile/manifest/Makefile/CI; never run literal ``; never guess a package manager; if undeterminable, stop and ask. +7. After learning real commands, offer to fill in the Common commands block and `.github/workflows/ci.yml`. +8. When instantiating the template, work through the README Day-1 checklist before feature work. +9. Deployment goes through CI/CD with workflows under `.github/workflows/`; never invoke a local deploy path as part of normal work. +10. Backend: onion architecture (Domain → Service → Repo/Controller), dependencies inward via ports; cross-cutting concerns as decorators/aspects, not per-handler middleware. +11. Frontend: store/services/pages/components layering; consistent loading/error/empty/success states; reuse base UI primitives. +12. `design/` mockups are reference only (not buildable); source for visual design/screen inventory/copy/flows on a screen's *initial build* only; never copy mockup code; after first build the running app is the reference — don't re-check later changes against the mockup; point mockup files at the spec when planning a new screen. +13. Keep cross-cutting concerns in shared decorators/plugins (backend) or hooks/services (frontend); don't duplicate them. +14. Keep `utils/`/`lib/` pure and un-peppered. +15. Use real libraries instead of hand-rolling — especially dates. +16. Config: read from environment in one place per app, validated at startup against a declared schema, fail fast with a named error; `.env.example` is the canonical documented variable list, updated in the same change that adds a key; inner layers never read config directly — it is passed inward as values. +17. Readable code is a review priority; assess whether names make intent clear without reconstructing implementation. +18. Naming: no non-standard abbreviations; precise over short; no misleading names; no single-letter variables except trivial loop counters/math convention; names reflect business meaning, not mechanics. +19. Think before coding: state assumptions, ask when uncertain, present multiple interpretations, suggest simpler alternatives and push back, name confusion instead of proceeding. +20. Simplicity/YAGNI: minimum code, nothing speculative; every added complexity must defeat the explicitly rejected simpler alternative; "might want later" is not justification; rewrite 200 lines to 50. +21. Change the right place surgically: correct layer/boundary; no business logic in controllers/repos/UI/jobs/utilities; no infrastructure leaks; match surrounding style; don't reformat/refactor unrelated code; flag (don't remove) unrelated dead code; remove only imports your change orphaned. +22. Goal-driven execution: define success criteria and loop until verified; verified = observed, not inferred — run it and state evidence; per-change "run it" definitions live in the area CLAUDE.md files; record evidence in the PR Test-plan checklist. +23. Don't reinvent: established libs for dates/money/validation/retry/pagination/parsing/formatting; don't duplicate abstractions or wrap without reason; check existing deps first; prefer well-maintained, widely-used, permissively-licensed packages; weigh frontend bundle weight and backend security surface; a trivial one-liner earns no dependency, but never hand-roll dates/money/timezones/auth/crypto. +24. Don't overfit: solve the general problem; no hardcoded strings/IDs/statuses/roles/regions; handle empty/invalid/duplicate/retry/timeout/permission cases; tests assert behavior, not implementation. +25. Clean implementations: no noisy logs, no error-hiding broad try/catch, no obvious-restating comments, no unused params/dead branches, no defensive code without a failure model. +26. Guard every AI/LLM call: token/cost limits, timeouts, max-iteration/loop guards; handle model/tool failures; monitor cost/usage; never treat external content as trusted instructions. +27. Definition of Done: lint/typecheck/test/build pass for touched apps; new behavior covered by behavior-asserting tests; every acceptance criterion of the touched story met; per-area completion rules satisfied (frontend route+i18n parity; reversible or justified migration); no untracked TODO/FIXME in touched code; if a step can't run, say so explicitly rather than skip silently. +28. Testing: tests ship in the same change as the slice; bug fixes start with a failing reproducing test; pick the cheapest test kind (unit/integration/contract) that proves the behavior; placement/coverage rules live in each app's CLAUDE.md. +29. Spec-first: non-trivial features start from a short written spec under `specs/`; stories priority-tagged (P1 = MVP); each slice independently shippable; P1 alone is a viable MVP; avoid cross-story coupling; keep this regardless of spec tool. +30. Trunk-based, linear history: single `main`; short-lived branches; rebase/fast-forward; trunk stays releasable; incomplete work behind a flag = boolean key in the validated config schema, default off, no flag SDK unless explicitly adopted and recorded; keep PRs small; commits imperative, one logical change, Conventional Commits prefixes. +31. Self-review before merge: read the full diff end to end (including files you don't remember touching); confirm layer correctness, no unrelated reformatting, only self-orphaned imports removed, business-meaning names; never merge on memory. +32. Worktrees are the default, under `.claude/worktrees/` on a short-lived branch. +33. First action in a new worktree: copy all gitignored runtime config from the main checkout (given command); copy every gitignored env file the project uses. +34. Shared local infrastructure is shared across worktrees by fixed name — reuse the running instance, don't start a second. +35. Shared DB schema is global state across worktrees — no resets/destructive migration checks while parallel worktrees depend on it; use a throwaway DB for round-trip/destructive checks. +36. Merge-back gate, in order: (1) rebase onto current default branch; (2) run full lint+typecheck+test+build on the integrated state — never merge red; (3) fast-forward merge; (4) stop dev servers/test instances; (5) delete worktree and merged branch; (6) push only after confirming with the user (deploy.yml fires on green `main` CI; check it if the trigger changed). +37. Learnings entries: one or two lines each; first entry usually the stack-pack choice. + +## README.md + +1. Start every new project from this template; run the Day-1 checklist once, top to bottom, before feature work. +2. Build spec-first: short spec under `specs/`, then implement; area contracts are picked up automatically. +3. Day-1: create repo via "Use this template"; clone it. +4. Open `project.code-workspace` in VS Code (keeps agent worktrees out of search/watchers). +5. Read all five CLAUDE.md contract files. +6. Pack path: pick one `stacks/` pack; `rm -rf` every other pack directory; copy the pack's **dev** command block into root CLAUDE.md Common commands (delete the banner) and its **CI** block into `ci.yml` — never paste a dev-only migration command into CI; record the choice in root CLAUDE.md Learnings (`Stack: ; appendices under stacks//`). +7. Agnostic path: keep or delete `stacks/`; fill the toolchain yourself. +8. Add-ons: keep the capability directories you want, delete the rest — every kept directory is adopted. +9. Agnostic toolchain fill-in: replace the seven ``/TODO commands and delete the banner; fill `ci.yml` TODOs including i18n key-parity and migration up/down round-trip; fill the `deploy.yml` TODO; add a real `.env.example` (whitelisted in `.gitignore`). +10. Declare the primary form factor in `apps/frontend/CLAUDE.md` (`mobile-first | desktop-first | responsive-equal`). +11. Rebrand and confirm the design guide before building any screen: edit the primitive tier in `tokens.css` (or regenerate from your brand), open the guide in a browser, confirm coherence; don't build screens against an unconfirmed system. +12. Copy gitignored runtime config (`.env`, secrets) into the local checkout. +13. Protect `main`: branch protection requiring CI to pass before merge; on ship-on-main packs, green-CI-before-merge *is* the deploy gate. +14. Stand up staging if the pack defines one (vercel: `develop` branch + dedicated Neon branch, migrated with the pack's manual runbook). +15. Confirm green: watch the first CI run pass; run the two greps to confirm no placeholder survives; optionally delete the README checklist after instantiation. +16. If the server-first `nextjs-nestjs-postgres` pack is chosen, soften the SPA framing in root CLAUDE.md ("the single-page app" → "the web frontend") and the What's-included "Frontend SPA" row. + +## specs/README.md + +1. Non-trivial features start from a short written spec here before implementation. +2. One file per feature: `YYYY-MM-DD-.md`. +3. Tag stories P1 (MVP, must ship) / P2 / P3; each story independently shippable; avoid cross-story coupling; P1 alone forms a viable MVP. +4. Spec contents: Goal (one sentence); priority-tagged user stories with acceptance criteria *plus how each is verified* (exact command/endpoint/screen+states); Out of scope list; Open questions with deadline or owner; UX & non-functional notes (form-factor impact, loading/error/empty states, perf/security) as one short list. +5. A UI story building a new screen names the `design/` mockup file(s) it implements; "matches the referenced mockup" is part of done for the initial build; later iterations verify against the running app. +6. If no mockup exists for a new screen: record under Open questions and resolve before implementation — don't invent the design. +7. Workflow: spec approved → short-lived branch → PR that links the spec. +8. A story is done only when every acceptance criterion is demonstrated by its stated verification; capture evidence in the PR Test-plan checklist. +9. Merged specs stay as a record of the decision. + +## design/README.md + +1. `design/` is reference only, not part of the buildable workspace; drop mockups here as the source for visual design, screen inventory, copy, and flows. +2. Do not copy mockup code into the apps. +3. The folder also holds the design guide (`design-guide.html` + `tokens.css`) — the visual keystone confirmed before any UI work. +4. Keep the inventory table current as screens are added; columns: screen (semantic name) · mockup file/folder · owning spec. +5. The screen name must match the central route registry in `apps/frontend/CLAUDE.md`; the table carries no route column (the registry stays the only route→URL surface). +6. One file or folder per screen, named by the screen's semantic name — never by tool export names. +7. Show flows via mockup ordering/links or an optional flow file; don't mandate separate flow diagrams. +8. A screen's initial build is verified against its design reference — never declared done from reading code; later changes verify against the running app, not the mockup. +9. With a mockup: after the first build, run the app, view at the declared primary form factor plus the other end of the responsive baseline, compare to the mockup, iterate until layout/spacing/hierarchy/copy match; capture and actually look at rendered output (screenshot or equivalent); capture tool is per-project, the view-and-compare step is mandatory. +10. Without a mockup, non-trivial new screen: sketch screens/copy/flow in the feature spec and get approval there (reuse the spec gate, no second approval process); never improvise UI for a non-trivial new screen. +11. Minor changes to an existing screen: build to `apps/frontend/CLAUDE.md` conventions and note "built to convention, no mockup" in the PR. + +## apps/backend/CLAUDE.md + +1. Read this file before touching anything under `apps/backend/`; repo-wide rules live in the root `CLAUDE.md`. +2. If a stack pack is adopted, read its `backend.md` appendix before working here; its conflict register overrides this file for that stack only. +3. Treat file extensions and framework specifics in examples (`container.js`, Express/Fastify-style) as illustrative, not mandates. +4. Dependencies point inward; nothing in an inner ring knows anything about an outer ring; the domain depends on nothing. +5. Inner rings needing outer capabilities define a port (interface in typed languages; agreed duck-typed shape, optionally a JSDoc `@typedef`, in untyped); the implementation is supplied from outside. +6. Data crosses boundaries translated: DTOs at the edge, domain objects inside; HTTP requests, DB rows, and SDK objects never travel inward and are never imported or named by the domain. +7. Boundary test: what's outside must be swappable without touching what's inside; if a change wants an outward dependency, reshape the change, not the rule. +8. Four rings from centre out: Domain → Service → Repo/Controller; Repo and Controller are peer outer adapters that never depend on each other. +9. Domain holds entities, value objects, domain services, invariants, and defines the ports; pure and stateless — no I/O, DB handle, clock, network, or framework types; never names a technology. +10. Express domain rules so they are testable in isolation (a rule testable only via a database is mis-shaped); keep the domain ring small, dense, protected. +11. Service orchestrates one use case end to end and owns the transaction boundary — one transaction per use case. +12. Service depends only on the domain and its ports; never touches HTTP concepts, builds queries, or reaches for framework globals. +13. Repo ring implements the ports (DB repositories + external-service clients); each adapter carries a mapper so storage/external shapes stop at this boundary. +14. Repo never holds business rules; branching beyond what a query/call needs means a rule leaked out of the domain. +15. Controller handlers validate input, invoke exactly one use case, map the result out; auth guards live here. +16. Controller never holds business logic, transactions, or queries, and never reaches past the service into the repo ring. +17. Organise by feature first, layers within: `modules//{domain,service,repo,controller,dtos}` plus `shared/{aspects,utils}` and a composition root; add a ring folder only when it earns one. +18. A module never imports another module's inner rings — cross-module use goes through the other module's service or a shared port. +19. `shared/utils/` is pure/stateless with no I/O or framework; `shared/aspects/` wrap a ring and depend inward only. +20. Wire ports to implementations only in the composition root; inner rings receive dependencies via constructor/factory and never import a concrete adapter. +21. The composition root is the only place knowing both a port and its implementation; adapter swaps happen there and nowhere else. +22. Manual constructor wiring by default; adopt a DI container only when the graph grows unwieldy. +23. Test per ring: domain = pure unit tests (no mocks/I/O); service = use-case tests with in-memory port fakes; repo = integration tests against a real DB/sandbox; controller = contract tests (status codes, validation, guards, schemas); most coverage in the inner rings, thinning outward. +24. Verify a backend change: run the touched module's tests; exercise the endpoint over HTTP (happy path + at least one error path); confirm status code, error shape, and correlation id; state what you observed, not just that you ran it. +25. REST naming: plural lowercase nouns, hyphens for multi-word, parent–child in path segments. +26. Version/visibility prefixes `/internal/v1` and `/external/v1`; default to internal when unclear; never expose externally until consumers, permissions, and contract are clear. +27. Methods: GET retrieve, POST create, PUT full replace, DELETE remove; no PATCH by default — only as a project-wide decision with documented merge semantics; flag method/intent mismatches. +28. Pagination: `page`/`recordsPerPage`/`sortBy`/`sortOrder` params after resource filters; one envelope `{data, page, recordsPerPage, totalRecords}`; empty result is `200` + `[]`, never `404`; flag unbounded list endpoints. +29. Use the standard status-code set (200/201/204/400/401/403/404/409/429/500); flag unclear or misleading codes. +30. One error envelope `{error:{code,message,correlationId}}` produced only at the single error-mapping site; `code` is stable SCREAMING_SNAKE_CASE in domain terms (clients branch on it, never parse `message`); `message` is human-safe (no stack traces/SQL/internal ids); `400` may add `error.details` `[{field,message}]`. +31. Correlation id also travels as the `x-correlation-id` header on every response, success or failure. +32. Success shapes: single resource is the bare object; lists use the pagination envelope. +33. Every endpoint defines permissions, request/response schema, validation rules, and error behaviour; every field defines name, type, required/optional, description, constraints. +34. Cross-cutting concerns are decorators/aspects declared once and applied declaratively; scope each to the subtree that needs it, not globally; each obeys the dependency rule and passes data inward as plain values. +35. Edge guards reject unauthenticated requests; authorisation depending on domain state lives in the domain/use case. +36. Request context/identity is established at the edge and passed inward as an argument, never read from a global. +37. Logging: one shared path carrying the correlation id; structured key/value records at levels (error/warn/info/debug-behind-flag); never log secrets, tokens, credentials, auth headers, or PII — redact at the boundary, log identifiers not payloads; log a failure once, where handled. +38. Audit trail is distinct from logging: one shared `record()` call per meaningful state change (actor, action, target, before/after) in the service ring, to durable queryable storage, carrying the correlation id. +39. The domain raises failures in domain terms; the controller ring is the single place mapping them to transport responses. +40. Validate state transitions against the rules before applying a status/lifecycle change — never merely because an external request/callback/event asked. +41. Treat every external API, callback, webhook, queue, and event as untrusted and unreliable; the integration is a repo-ring adapter, its decisions live in the domain. +42. Idempotency: handle retries/replays without duplicating actions; key on the primary business record id unless a clearer business key exists. +43. Concurrency: assume parallel workers; use conditional updates, locking, transactions, or version checks. +44. Validate structure, required fields, types, business rules, authenticity, and ownership before sending or applying anything. +45. Where order matters, process by event time / sequence / business rule, not arrival order. +46. Classify failures (transient/permanent/invalid/unsupported/duplicate/unknown); retry only transient with bounded retries + backoff and a defined final-failure path. +47. Never treat a timeout, transport error, malformed/ambiguous response as success; preserve valid data and route to reconciliation or manual recovery. +48. Gate risky integrations (SMS/email/payment/push) behind a default-off validated-config boolean read in one place, routing to a no-op sink when off; flipping it off is the rollback. +49. Parameterised data access only — never interpolate request data into query/filter strings. +50. Secrets come from the environment, never hardcoded/committed/echoed; inner rings receive config as injected values, not globals. +51. Verify ownership on every client-supplied id before acting on the record. +52. Send standard security headers + CSP from one shared place; roll out new/tightened CSP report-only first, then enforce (exact set: stack pack). +53. SSRF guard on user-supplied URLs: allowed schemes and public hosts only; reject loopback/private/link-local/metadata targets, validated at save *and* call time. +54. Secrets stored through the API are write-only: never returned on read (only a "configured" indicator); blank on update means keep existing. +55. Dates/timezones, phone canonicalisation, identifiers, CSV, and schema validation use an established library via a single shared helper — never hand-rolled (root rule). +56. Schema changes only via reversible migrations under `db/`; never issue DDL from application code — repo adapters read the schema, never mutate it. + +## apps/frontend/CLAUDE.md + +1. Read this file before touching anything under `apps/frontend/`; repo-wide rules live in the root `CLAUDE.md`. +2. If a stack pack is adopted, read its `frontend.md` appendix first; its conflict register overrides this file for that stack only. +3. Two axes never blur: horizontal layers (store/service/page/component) and vertical feature slices; components follow atomic design. +4. Mirror the prescribed `src/` layout (store, services, pages, components/{atoms,molecules,organisms/,templates}, i18n, lib, single route registry, single token source); file extensions/framework specifics are illustrative. +5. Atoms/molecules are grouped by type, shared globally, and carry no business vocabulary; organisms are grouped by feature; a feature slice spans `store/` + `services/` + `organisms/` and is removable as a unit. +6. Promote code into `atoms/`/`molecules/`/`lib/` only once genuinely shared — not in anticipation of reuse. +7. Each layer depends only on layers beneath it, never upward. +8. Store owns application state, one slice per domain; may depend on services; never imports a page or renders. +9. Services own all data fetching/mutation; each domain mirrors a backend route group; all network access lives here; never hold view state. +10. The backend endpoint contract is the single source of truth for shapes and status codes; the service mirrors it, never invents its own shape. +11. Prefer a generated/shared contract artifact; otherwise every contract change is one PR touching backend endpoint + mirroring frontend service together. +12. Validate responses against the declared shape so a contract break surfaces as a typed error feeding the `error` state. +13. Pages compose organisms, hold no business logic, never fetch directly or embed reusable UI inline. +14. Templates arrange organisms with no real data and hold no business logic. +15. Organisms may use atoms/molecules/lib; never imported by a primitive. +16. Shared primitives depend only on the UI library and design tokens; never know a feature or page. +17. Loading/error/empty/success states handled consistently on every data-backed screen. +18. Empty states are designed, not blank: state why + primary next action; distinguish first-run, no-results/filtered, and access-restricted; a load failure is an error state with retry, never an empty state. +19. Don't accumulate one-off helpers in `src/lib/` — co-locate with the only caller until reuse appears. +20. URLs stay clean and human-meaningful; never expose internal build/source paths. +21. One central route registry; register the route the moment the page is created; audit routing by reading the registry; maintain no second route list anywhere. +22. Build URLs via named routes from the registry, never by concatenating path strings. +23. Lock the visual system in the design guide (`design/design-guide.html`, rendered live from `design/tokens.css`) before building any screen; the guide is foundations-only by design. +24. Confirm-the-guide gate: for a new project or rebrand, no screen/component work until the guide reflects the brand, is browser-reviewed, and signed off; established systems don't re-gate small additions, but a new foundational token lands in the guide first. +25. Customise by editing the primitive token tier, not screens; semantic tier and guide re-derive. +26. Every component consumes semantic tokens, follows the guide's state ladder and focus spec, and meets its accessibility floor; a component violating a foundation is the defect; recurring cross-project patterns earn a guide specimen. +27. Gate 1: every colour, size, space, and duration resolves to a semantic token — a hex or px literal in a screen is a defect. +28. Gate 2: pick the screen archetype before building; its zones, rhythm, and width are fixed, never re-derived per page. +29. Gate 3: surface ladder — no card-like container inside another; separate in order whitespace → background shift → border → divider (tables/dense rows only). +30. Gate 4: reuse first — archetype → documented pattern → existing screens/primitives → extend a primitive → only then new, with the PR recording why nothing fit. +31. Gate 5: one density app-wide, set at the token layer, never mixed within a page hierarchy. +32. Gate 6: forms and view states follow the guide's composition patterns; the pattern outranks the component library's defaults. +33. Declare the primary form factor + supported viewport range at setup (deliberate placeholder). +34. One shared layout supplies all standing furniture; pages provide content only; the layout owns every clearance/inset via one clearance token; top-spacing variants are a layout prop the page picks. +35. Layouts are responsive by default across the declared range; no fixed pixel widths that break reflow. +36. One token source, three tiers (primitive/semantic/component); pages and components consume semantic tokens and never reach past them to raw primitives. +37. Guard committed token values against their documented scale with a check. +38. Author from the smallest supported width up; floor is WCAG reflow at 320 CSS px and 200% text zoom. +39. Prefer intrinsic sizing; reusable components adapt to container width; add viewport breakpoints last, only for genuine page-level layout changes. +40. No horizontal overflow at minimum width: atomic values never wrap mid-token (shared no-wrap primitive); free text wraps/truncates; flex/grid children get `min-width: 0`; wide tables/code scroll in their own box, never the page. +41. Reserve fixed/sticky chrome space with one semantic clearance token applied by the shared layout, never re-measured per page. +42. Size full-bleed sections to content, not viewport; prefer `svh` over `vh`; `dvh` only deliberately. +43. Treat configurable copy as variable-length (survives one-word and three-line values); balance headings by default. +44. Multi-field rows collapse to full width below the breakpoint, each field keeping a legible min-width. +45. Adapt by disclosure, never by hiding meaning — collapse nav into a menu; never drop destinations/actions on small screens. +46. Render overlays and fixed chrome in a top-level portal. +47. Reset/restore scroll in an effect keyed on the actual route/view change; a keep-alive surface has one explicit scroll owner. +48. Global-nav visibility is a denylist of chrome-less routes, not an allowlist. +49. Under the soft keyboard the flex column scrolls (`overflow-y: auto`; non-shrinkable panels `flex-shrink: 0`), it does not squeeze. +50. Type: one modular scale; ≤2 font families; ~4 sizes and ~2 weights per screen; 60–75ch body measure; a new size is a new scale step in tokens. +51. Spacing: every margin/padding/gap resolves to an existing scale step; fix the scale, not the instance. +52. Hierarchy: exactly one primary (filled) action per view; one H1 per page; heading levels never skip. +53. Colour: semantic intent tokens; never encode meaning in colour alone (pair with text/icon); limit accent surfaces so the primary CTA dominates. +54. Content aligns to the shared layout's grid/gutters; density follows the declared form factor and stays consistent within a view. +55. Control states (pressed/active, focus-visible, disabled) are defined on shared primitives from semantic tokens; touch-primary paths always show press feedback; surface the headless foundation's focus-visible, don't suppress it. +56. In-flight feedback stays on the triggering control (inline busy + disable); full-screen/section loading only for a screen's initial fetch. +57. Prefer optimistic updates for low-risk mutations with rollback + error message; blocking spinners only for genuinely blocking waits. +58. Initial load uses skeletons matching final layout; short waits use a spinner; no spinner-to-content layout shift. +59. Delay busy indicators (~150 ms) with a minimum visible time; debounce live search/filter (~250 ms); tunable defaults, not magic numbers. +60. Move focus deliberately after navigational/destructive actions (next logical element, confirmation, or back to trigger). +61. Validation: validate a field on blur after first interaction, whole form on submit; re-validate erroring fields on change; never error on first keystroke. +62. Field errors inline, `aria-describedby`-associated, conveyed by more than colour; failed submit moves focus to first invalid field. +63. Destructive actions require an explicit confirm naming the consequence; irreversible/high-risk actions require deliberate (typed) confirmation. +64. Warn before discarding meaningful unsaved edits on route change and browser unload; not for trivial inputs. +65. Disable the submit control in flight and prevent re-submission; surface progress via the shared loading/error/success convention. +66. Uniform capitalization project-wide; default sentence case except proper nouns. +67. Action labels are verb-first and specific ("Save changes", not "OK"/"Submit"). +68. Error copy is actionable and blame-free; never exposes stack traces, status codes, internal ids, or raw exception text. +69. Empty/loading/success copy is concise and human. +70. Centralize user-facing copy (i18n dictionaries or a single strings module); no hardcoded display literals in components. +71. Five atomic tiers over a headless foundation you never skip; the foundation is a dependency, not a folder, and solves focus/keyboard/widget-ARIA for components routed through it. +72. Tier crossover test: speaks the business's language → organism (feature-owned); generic → atom/molecule (shared). +73. Reuse-first: search `atoms/`/`molecules/` before building; a second variant of an existing component is the canonical failure. +74. Never build a one-off header, button, input, modal, table, or icon button; wrap shared components from the start. +75. No feature-specific atoms or molecules: genuinely generic → global `molecules/`; business-meaning → organism. +76. Audit periodically for duplicated components; two components rendering the same thing are a defect to merge. +77. Colour tokens meet WCAG 2.1 AA against intended backgrounds (4.5:1 body, 3:1 large text/UI boundaries) — a constraint on the token set. +78. Every interactive element keyboard-reachable/operable; visible focus indicator; never remove the outline without a token-based replacement. +79. Logical focus order; modals trap focus and restore to trigger on close. +80. All inputs labeled; icon-only controls have accessible names; images have alt text or are marked decorative. +81. One H1, no skipped heading levels, correct landmark regions. +82. Honour `prefers-reduced-motion`; never convey essential feedback by motion alone. +83. Never convey state by colour alone; announce dynamic updates via live region or managed focus. +84. Touch targets ~44×44px minimum on touch-primary form factors only. +85. Content reflows at 320 px width and 200% zoom; genuinely 2-D content scrolls in its own box. +86. An automated a11y check runs in CI alongside lint/test/build; automation is the floor, not the bar. +87. i18n (if multilingual): one reference language; one dictionary per language under `src/i18n/`; every new key added to every language in the same change; CI key-parity check failing on drift in both directions; keys named by meaning, not location. +88. Never reimplement what the UI library gives you; typography/buttons/inputs are wrapped through the shared atoms/molecules tier. +89. Cross-cutting concerns live in shared hooks/services, never duplicated per screen. +90. Use libraries instead of hand-rolling — especially dates (root rule). +91. No secrets or API keys in the SPA bundle; secrets stay server-side behind a backend endpoint. +92. Treat all rendered server/user data as untrusted; rely on default escaping; never inject raw HTML with unsanitised input. +93. Auth tokens live in the one agreed store/service, never scattered or hand-read in views. +94. App sends security headers + CSP at the framework's header layer; report-only first, then enforce; allow-list only real origins; never `unsafe-inline`/`*` to silence reports. +95. Render an unobtrusive version tag sourced from the package manifest at build time; bump semver every release. +96. Detect stale bundles and show a dismissible "Refresh to update" prompt; never force a reload. +97. Correlation id is never discarded: shared error path reads it from failed responses, shows it as an error reference in the UI, and attaches it to telemetry. +98. One shared services-layer auth interceptor: 401 → login preserving the requested URL and returning after sign-in; 403 → shared forbidden state, no login bounce; guard redirect loops; sign-out clears client/store state; token/session refresh is one shared concern. +99. Analytics (if shipped) go through one shared service/hook with a by-meaning event taxonomy; UI stories name their key events in the spec; stay vendor-agnostic. +100. Testing: store slices and lib helpers as plain units; services with network mocked at the edge (assert request shape + response/error mapping); organisms for behaviour + the four states; no broad DOM snapshots; at least one automated check runs at the narrow viewport. +101. Verify: a new screen's initial build is verified against its `design/` mockup by rendering and looking; no mockup → sketch in spec and get approval; later iterations verify against the running app. +102. Verify: start the dev server, load the touched screen, force all four states, do a keyboard-only pass, exercise the primary form factor plus minimum width and 200% zoom, and state what you observed. + +## db/CLAUDE.md + +1. Read this file before touching anything under `db/`; if a stack pack is adopted, read its `db.md` appendix first; its conflict register wins for that stack only. +2. Migration files are timestamp-prefixed with a short description, monotonic, never reused; never a hand-incremented sequence; before merging, rebase and confirm your migration sorts after everything on trunk. +3. Every migration is reversible (up + down) OR carries an explicit irreversible-change justification comment — never neither. +4. Never edit a merged/applied migration; fix forward with a new one. +5. Keep schema migrations separate from data backfills; backfills are batched, idempotent, resumable. +6. Destructive/non-additive changes follow expand → migrate → contract across separate migrations/releases. +7. Prove the round-trip (up, down, up) on a throwaway scratch DB before merging; state the evidence observed. +8. Run each migration in a transaction where the engine supports it. +9. Seed/reset scripts are idempotent and non-production only; use realistic, named seed data (not `user1`/`user2`); seeds back the test-mode add-on picker if adopted. +10. The local DB is global state shared across worktrees; run round-trip/destructive checks against a throwaway DB, never the shared one, while parallel worktrees depend on the schema. + +## db/migrations/README.md + +1. Migrations live in this folder; rules are in `../CLAUDE.md` (pointer only — the file contains no rules of its own). + +## infra/CLAUDE.md + +1. Read this file before touching anything under `infra/`; if the adopted stack pack ships an `infra.md`, read it first; its conflict register wins for that stack only. +2. GCP is the template's blessed cloud; (GCP)-marked items apply there; on AWS/Azure use the equivalent context/auth/discovery tooling. +3. Prefer the smallest change that solves the request; scope to the requested environment and outcome. +4. Optimize for readability and straightforward rollback; prefer stable patterns over clever abstractions; preserve existing repo conventions absent strong reason. +5. Treat all infrastructure changes as potentially high impact until proven otherwise. +6. Run Terraform commands from inside `infra//`, never from `infra/` or the git root. +7. Never assume a change applies to all environments; make the target environment explicit in summaries and approvals. +8. Set up observability (metrics, logs, product analytics for user-facing apps; retained and queryable) from day 1; give it its own concern file where IaC owns it; platform wiring lives in the stack pack. +9. At the start of each chat, verify active cloud auth/context, run the provider's context-listing command outside the sandbox, show contexts, and ask which to use; never assume the previous context. +10. Read-only tasks (inspection, search, tracing, `fmt -check`, `validate`, `plan`, diffs) proceed automatically without confirmation between steps. +11. Mutations follow the sequence: understand env/outcome → inspect config → smallest change → format+validate → plan → present the risk-review format → ask approval before `apply`. +12. Prefer explicit individually named `resource` blocks; no `for_each`/`count`/`dynamic`/`locals` collections to generate resources unless the user requests it or a clear repo convention requires it. +13. Favor repetition over abstraction when it improves readability, reviewability, importability, and rollback clarity. +14. No `lifecycle.ignore_changes` unless the user confirms the diff is noisy and intentionally acceptable. +15. Prefer importing existing resources over replacing them; replace only when explicitly requested or clearly safer; after import, Terraform is that resource's source of truth. +16. Import the smallest scope; never bulk-import a project/folder/org unless explicitly requested; large imports may bootstrap drafts with a provider export tool. +17. Generated import output is scaffolding: reshape to the folder's conventions (named blocks, no generated loops, no ignore_changes, provider-default churn removed) before merging. +18. Before import approval: identify exact environment/resources, confirm import IDs and addresses, run plan, review all drift, call out additions/changes/replacements/deletions/IAM/networking/stateful risk. +19. Present every apply request in the prescribed format: plan summary (environment), planned actions (add/change/replace/destroy, skipping empty sections), and a four-dimension risk checklist (security, availability, data durability, cost). +20. `infra/` is organized per workload; each subdirectory is a self-contained Terraform root module with its own `backend.tf` and `terraform.tfvars`, mapping to a single project. +21. No cross-workload references or shared local modules without explicit approval; new workloads follow the same self-contained pattern. +22. Introduce `infra/modules/` only after concrete duplication across at least two workloads; keep modules focused on the genuinely shared concern; modules still follow the explicit authoring style. +23. Group `.tf` files by concern with predictable names; avoid excessive fragmentation; split oversized categories with still-specific names. +24. Commit `.terraform.lock.hcl` (adding teammate/CI platforms via `terraform providers lock`); keep `.terraform/`, plan artifacts, and generated local execution artifacts out of version control; plan files are local and ephemeral. +25. Do not use default VPCs/subnets; use a custom-mode network with explicit subnets, ranges, firewall rules, and NAT; call out existing default-VPC usage and propose migration; same intent on any provider. +26. Never run destructive actions without explicit confirmation; never `terraform apply` without explicit user approval. +27. Treat deletion, replacement, and security-boundary changes as high risk; production requires extra caution even for small changes. +28. Always state the target environment before asking approval; never hide or collapse destructive/replacement actions in the plan summary. +29. Never edit Terraform state directly unless the user explicitly requests a state operation with risks explained. +30. Do only what the request asks; avoid unrelated refactors/renames/moves; call out assumptions; keep environment boundaries strict; state explicitly when a shared-module change affects multiple environments. +31. Never apply without a reviewed plan; if a plan cannot run, never guess the impact — explain why; always state the environment being planned/applied. +32. Never commit secrets, credentials, keys, tokens, certificates, or private data; no hardcoded secrets in `.tf` or checked-in `.tfvars`; inject via CI/CD env vars, secret managers, runtime injection, or gitignored local overrides. +33. Mark sensitive input variables `sensitive = true`; avoid sensitive outputs. +34. Out of scope unless explicitly requested: broad architecture migrations, provider/platform switches, state/backend migrations, CI policy changes, cross-environment restructuring, module redesign beyond the task. + +## stacks/README.md + +1. A stack pack rides on top of the base: adds bindings and resolves conflicts, never restates the base. +2. Exactly one pack is chosen at instantiation; delete the rest so "the adopted pack" is unambiguous. +3. Packs are guidance-as-text: config/command snippets to copy — no installed deps, lockfiles, or scaffolding in the buildable tree. +4. Name packs `--`, lowercase-hyphenated; append the client/ORM when it is the distinguishing choice. +5. Platform exception: a platform-identity pack may be named for the platform; its README records the would-be triple; rename if a second pack on that platform appears. +6. Every pack carries at least README.md, backend.md, frontend.md, db.md; `infra.md` is the only optional fifth. *(superseded by the five-file canon if F-008 is approved)* +7. Additions-only: if a line is true without naming the stack, it does not belong in a pack. +8. Register or obey: any base override (even structural) exists only via a conflict-register entry; a silent contradiction invalidates the pack; layer separation and one-way dependencies may never be dropped. +9. Verbatim precedence line atop every appendix. +10. Conflict register ends every appendix; entry shape: Base says / In this stack / Because / Concretely (one checkable DO/DON'T); a zero-conflict appendix states the no-conflicts line. +11. No project-specific values: no DB URLs, secrets, env, per-project form-factor declarations, or route tables. +12. Size discipline: each appendix well under 200 lines, terse and checkable. +13. Activation by instruction: each area's CLAUDE.md points at the adopted pack's matching appendix; appendices are read in place from `stacks/` (no generated copies). +14. To add a pack: create the four files, precedence line + register, write the manifest README (identity, mapping, dev+CI `` blocks, deploy-seam pointer); nothing else to wire. +15. `infra.md` may be added later under the same invariants. *(superseded by the five-file canon if F-008 is approved)* + +## stacks/nextjs-nestjs-postgres/ (README.md, backend.md, frontend.md, db.md) + +*README.md* + +1. Identity: Next.js App Router (server-first) / NestJS / Postgres via Prisma; TypeScript or plain JS with JS-path notes per appendix. +2. Conflicts live in the appendices, never in the manifest. +3. Day-1: delete every other `stacks/*` directory; copy the dev block over root Common commands (delete the banner); copy the CI block into `ci.yml`; never the same block in both. +4. Never `prisma migrate dev` in CI (it can reset the DB or prompt); CI uses `prisma migrate deploy`. +5. Suggested toolchain pnpm workspaces; the seven root verbs bound (bootstrap/dev/lint/typecheck/test/build/migrate = `prisma migrate dev` locally). +6. CI: Postgres service container; `pnpm install --frozen-lockfile`; `prisma generate`; `migrate deploy`; typecheck; both builds; non-watch tests; keep one verb per base placeholder if swapping tools. +7. Zod is the schema library at the NestJS edge and for Next form/response schemas; a shared shape is defined once and reused; no class-validator. +8. Deploy seam: `prisma migrate deploy` + Next build ship through the cloud pipeline per `infra/CLAUDE.md`; pack ships no infra.md. *(superseded by the n/a-stub if F-009 is approved)* + +*backend.md* + +9. Rules are language-neutral; TS is the default spine; JS deltas flagged `JS:` or collected in `## JavaScript path`. +10. HTTP: NestJS on the Fastify adapter (rejected: Express); Express acceptable with a concrete reason recorded in the pack README. +11. Prisma is owned by `db/`; this file covers only how it is consumed in the repo ring. +12. One base feature module = one Nest `@Module()`; keep the base ring folders inside each module — never flatten to Nest's `*.controller.ts`/`*.service.ts` naming. +13. `.module.ts` is the module's composition root; `shared/aspects/` → an exported `SharedModule`; `shared/utils/` stays framework-free. +14. Cross-module use goes through the exported service provider or a shared port token — never another module's `domain/`/`repo/`. +15. `domain/` is plain classes/functions with zero Nest and zero Prisma imports; entities are constructed by hand, never container-resolved. +16. No third-party `nestjs-prisma`; the pack ships its own `PrismaService`. +17. A port stays in `domain/` plus a DI token: TS decorator-free abstract class as type+token; JS a `Symbol` token. +18. Composition root = the module `providers` array; no `container.js`, no hand-rolled wiring graph. +19. Inner code never imports a concrete adapter; services depend on port tokens by constructor injection only. +20. Validation: Zod via a custom `ZodValidationPipe` (rejected: class-validator); DTOs are Zod schemas in `dtos/`. +21. Prisma types never appear in DTOs; controllers map domain → response DTOs explicitly; never spread a domain entity or return a Prisma row. +22. The single global exception filter is the only domain-error → HTTP mapping site; `domain/`/`service/` throw plain domain errors, never `HttpException`. +23. Aspect mapping: auth = Guard, validation = Pipe, request context = ALS seeder, logging = global interceptor, errors = one filter, audit = injectable `AuditService`, transactions = service ring. +24. Prefer `@UseGuards`/`@UseInterceptors`/module providers over `app.useGlobalX()`; only the error filter and the single correlation-id+logging path register globally. +25. All audit/analytics events flow through one injectable `AuditService`; never scattered across handlers. +26. A use case = one `@Injectable()` in `service/` depending only on domain + port tokens. +27. Transaction boundary: `TransactionRunner` UoW port in `domain/`, implemented in `repo/` over Prisma's interactive transaction; the service never names `Prisma.TransactionClient`; no tx via ALS. +28. `TransactionRunner` only for multi-repository-write use cases; single writes rely on the repo's own atomic call. +29. No nested use-case transactions; a use-case service never calls another use-case service — compose in domain services. +30. Repos implement port tokens over `PrismaClient`, bound in `providers`; every repo carries a mapper; Prisma rows never cross inward. +31. Explicit `select`s — no implicit full-row returns where a subset suffices. +32. One `PrismaService` (extends `PrismaClient`, connect/disconnect on module init/destroy) in a shared `PrismaModule`; never `new PrismaClient()` in a repository. +33. External gateways (storage, mail/SMS, payments, translation) are repo-ring adapters with their own mappers. +34. HTTP integrations bind to `@nestjs/axios`/fetch wrapper with per-call timeout, bounded retry+backoff, idempotency keys; queues via BullMQ/`@nestjs/schedule`; base Integrations rules apply unchanged. +35. Request context: `AsyncLocalStorage` (recommend `nestjs-cls`) seeded by a DI-capable guard/interceptor — not request-scoped providers, not global `app.use()` middleware. +36. The seeder honours inbound `x-request-id`/`x-correlation-id`. +37. Inner rings never read ALS; the controller pulls context at the edge and passes plain values inward. +38. Cookies: register `@fastify/cookie` at bootstrap; read/write at the edge, passed inward as values. +39. Config: `@nestjs/config` + one Zod schema validated at boot, fail fast; no `process.env` reads outside the config module. +40. Lint boundaries: domain bans `@nestjs/*`+`@prisma/client`; service bans `repo/`+`PrismaClient`; `@prisma/client` only under `repo/`+`PrismaService`; `HttpException` only controller ring + filter; `process.env` only config module. +41. Testing: domain by plain instantiation; service via `Test.createTestingModule` + in-memory port fakes; controller/e2e via Fastify `.inject()`; repo integration against real Postgres — never mock Prisma. +42. Bootstrap registers the global filter, global `ZodValidationPipe`, `@fastify/cookie`, and the correlation-id/logging path. +43. `internal`/`external` is a path segment, not a Nest version; the version segment is the `v1` token only; default internal. +44. The Next.js server tier is an internal trusted consumer on `/internal/v1`; only genuinely third-party consumers use `/external/v1`. +45. JS path: Babel required (specified plugin set, order matters) or the SWC builder; `Symbol` port tokens via `@Inject`; Zod schemas duck-typed. +46. Register entries (12): Nest fills the unchosen slot; `providers` array replaces `container.js`; port tokens; global-registration limits; `TransactionRunner`; ALS context; ring folders kept; internal/external route shape; exception filter only; one `AuditService`; repo ring not Prisma-only; Prisma forward-only flag (mechanics in db.md). + +*frontend.md* + +47. A file is a Server Component unless it opens `'use client'`; reach for client only at an interaction leaf, directive placed leaf-ward — never on a page/layout "to be safe". +48. Next `layout` files are the base shared layout; the `app/` tree is the route registry. +49. JS or TS, project's choice; one language per app; JS uses `jsconfig.json` for aliases. +50. App location pinned to `src/app/`; colocated non-route code lives elsewhere under `src/`. +51. Folder mapping: `pages/`→app segments (four-state files); `store/` client state only; `services/{server,client}` split by execution context with Server Actions in dedicated files; atomic tiers unchanged; `routes.` replaced by the `app/` tree + a link-helper. +52. Network access lives in `services/`; Server Components call `services/server/`; Client Components mutate via a Server Action (default) or `services/client/`; ad-hoc `fetch`/`axios`/SDK in a component is forbidden (grep smell). +53. State home URL → Server → Client; Next 15+ is uncached-by-default — opt into caching explicitly per call; never copy server data into a `store/` slice. +54. Mutations: Server Action → `revalidatePath`/`revalidateTag`; a write without revalidation is a bug; `'use server'` files export only async functions; `services/client/` direct calls only when an action can't, justified in the PR. +55. `services/server/` wraps the NestJS `/internal/v1` API: one base URL, forwards auth, propagates the correlation id; a Server Component never reaches the DB; validate responses with the shared Zod schema. +56. Build parameterized hrefs through a `routes` link-helper — never a raw URL string literal; static routes may be literal; `` default; `typedRoutes` optional TS-only. +57. Four states: `loading.*`+Suspense+shared skeleton; `error.*` (Client Component) wiring `reset()` into shared `` and surfacing the correlation id; `global-error.*` for root-layout failures; shared ``; `not-found.*`+`notFound()`. +58. Radix UI is the blessed headless foundation, wrapped as atoms; swap only by recording it in `apps/frontend/CLAUDE.md`; never mix two. +59. Interactive atoms/molecules are correctly client leaves; do not hand-roll a server-only control to dodge `'use client'`. +60. Tokens must be consumable by Server Components without a client runtime; runtime CSS-in-JS forcing `'use client'` at the token boundary is disallowed; global tokens declared once on `:root` in the root layout. +61. Only RSC-serializable props cross into client leaves; format dates on one side with timezone/locale pinned; move focus to the main landmark on navigation and announce via a live region. +62. Zod schemas for forms and API responses, shared with the NestJS edge, defined once. +63. i18n: load the active locale's dictionary server-side per request; pin locale to a route segment (`app/[locale]/`); keep the parity check. +64. Versioning: render the `v` banner; refresh prompt only for long-lived/PWA sessions; no default `version.json` poll; don't disable Next's chunk recovery. +65. Testing additions: Server Actions/data-access with the API mocked at the network edge (assert request shape, auth+correlation forwarding, revalidation); test the four-state + `not-found` + `global-error` files; assert hrefs resolve through the helper. +66. Register entries (5): SPA→App Router; services fetch server-side; `app/` tree replaces `routes.`; store holds client state only; platform-handled deployment skew. + +*db.md* + +67. One `schema.prisma` owned by the backend; never hand-write DDL outside a generated migration's SQL. +68. Models PascalCase singular `@@map`ped to snake_case plural tables; fields camelCase `@map`ped to snake_case columns. +69. Name both sides of every relation with a named FK; declare `onDelete` explicitly; `Cascade` only with a stated reason. +70. Prisma `enum` for fixed sets; Postgres enums are append-only — one value per migration, never use a freshly-added value as a default in the same migration. +71. IDs: `uuid()` for externally-exposed ids; `autoincrement()` only for internal-only tables with a stated reason. +72. Every model carries `createdAt`/`updatedAt` stored `timestamptz`. +73. Money/quantity is `Decimal(p,s)`, never `Float`; Prisma `Decimal` stops at the mapper; `Json` only for genuinely schemaless payloads. +74. Soft delete is opt-in (`deletedAt`, filtered in the repo ring); no global soft-delete middleware; uniqueness on soft-deletable columns is a partial unique index. +75. `migrations.path = "db/migrations"` in the root `prisma.config.*`; config-file-only, no env override. +76. Reversibility: additive changes forward-only; destructive changes are expand-and-contract or a `-- IRREVERSIBLE:` header stating why + recovery path. +77. The base "prove the down path" is met by the §CI gates (apply-from-zero + drift gate); state observed evidence. +78. Review generated SQL (`--create-only`): no unintended DROP, no unguarded NOT NULL on populated tables, no lock-heavy DDL on hot tables, enum rule respected. +79. Never edit an applied migration — fix forward. +80. Naming: `--name ` snake_case, never `update`/`fix`; one logical change per migration. +81. `migrate deploy` in CI/release; `migrate dev` local-only. +82. Structural DDL in migration SQL; backfills are idempotent re-runnable scripts under `db/backfills/`, never inside migrate SQL. +83. Expand → backfill (asserting row counts, non-zero exit on mismatch) → switch → contract. +84. Zero-downtime: every migration safe against previously-deployed code. +85. `prisma generate` outputs a backend-only client; only the repo ring imports it. +86. HARD RULE: the Next.js app never imports Prisma and never touches the database. +87. Mapper discipline: Prisma result objects (incl. `Decimal`/`Json`/relations) stop at the repo boundary, both directions. +88. `TransactionRunner` is defined in backend.md; this file binds it to `prisma.$transaction`; the service never imports `$transaction`. +89. Keep transactions short — no network/LLM/external calls inside; set explicit `timeout`/`maxWait`; array form only for independent writes. +90. One shared Postgres server, one database per worktree (`app_`, 63-byte cap); re-point `DATABASE_URL` after copying `.env`; drop on teardown; never migrate/reset another worktree's DB. +91. Explicit `connection_limit`; `pgbouncer=true` behind a pooler, avoiding session-level prepared statements. +92. Seed via `prisma db seed` configured at `migrations.seed`; idempotent, realistic minimal; reset = `prisma migrate reset`. +93. Index every FK and frequent filter/sort column (`@@index`) — Postgres doesn't auto-index FKs. +94. Unique constraints encode business invariants; map unique-violation → domain conflict → 409. +95. Avoid N+1 with `include`/`select` in one query; select only needed columns. +96. Pagination binds the base REST params; whitelist sortable columns before dynamic `orderBy`; prefer keyset/cursor over deep `skip`. +97. CI: apply-from-zero on scratch DB; drift gate (`migrate status` + `migrate diff --from-migrations … --exit-code`, `--to-schema` not the legacy flag, shadow-DB flag per version); seed run twice. +98. Register entries (6): forward-only reversibility; replacing the three base round-trip surfaces; migrations-home redirect; Nest DI as composition root; `TransactionRunner` binding direction; per-worktree databases. + +## stacks/taro-fastify-mysql-tencent/ (README.md, backend.md, frontend.md, db.md, infra.md) + +*README.md* + +1. Identity: Taro 4 H5 (React 18, plain JS) / Fastify 4 CJS / MySQL 8 CynosDB via Knex; Tencent Cloud (SCF web+migrate functions, COS, VOD, EdgeOne); `tencentcloud` Terraform; GitHub Actions deploy. +2. `-tencent` suffix marks the load-bearing platform; lift to another cloud → triple stays, suffix changes. +3. Ships the optional infra.md (platform is load-bearing). +4. Day-1: delete other packs; copy the dev block; apply CI notes to `ci.yml`; never the same block in both; record the stack choice in root `CLAUDE.md` Learnings. +5. Toolchain: pnpm workspaces, Node 20/pnpm 9, backend CommonJS (no `"type": "module"`), pin `packageManager`. +6. Root verbs bound; typecheck is an explicit no-op; the backend test suite is destructive against a `*_test` schema. +7. CI: `mysql:8` service; vitest against `*_test` (one-time `test:db:setup`); migration up→down→up round-trip; i18n key-parity; the two OpenAPI drift guards; Playwright e2e runs separately with `x-tenant: test`. +8. Validation: Fastify JSON Schema on every route, request and response; schemas under `schemas/.js`; OpenAPI is the source of truth. +9. Deploy seam: this pack fills `deploy.yml`; push to default branch → build, one SCF zip, resume CynosDB, `terraform apply`, out-of-band code push, invoke migrate function, smoke test; protect the default branch so push = merge + deploy. + +*backend.md* + +10. Fastify 4 used directly; plugins/hooks/decorators are the aspect mechanism. +11. Plain JavaScript CommonJS; no typecheck (explicit no-op); esbuild bundle is packaging, not typechecking. +12. Layout is layer-first: `routes/ services/ repos/ schemas/ lib/ utils/ plugins/ db/` (registered override). +13. Every route declares request and response JSON Schemas; OpenAPI source of truth with `lint:openapi`+`lint:schemas` drift guards. +14. `routes/` are thin: attach schema, run guards, call one `services/` function, shape the reply; never touch Knex/repos; no business rules. +15. `services/` orchestrate use cases, own `knex.transaction`, enforce rules, call `lib/`, emit audit; never see `request`/`reply`; raise `HttpError`. +16. `repos/` are Knex query building only; first arg is `db` (knex or trx) then named args; no rules, no `HttpError`. +17. All schemas in `schemas/` (no inline); `lib/` side-effecting integrations; `utils/` pure no-I/O; `plugins/` are the aspects. +18. Config validated at boot in the entry; fail fast on missing env; values flow inward; no `process.env` in rings. +19. `plugins/db.js` provides the single Knex instance; repos never construct their own. +20. `plugins/ctx.js` packs `{requestId, sourceIp, userAgent, logger}` into `request.ctx`, passed inward as a value; the id rides `x-request-id` end to end. +21. One global error handler in `app.js` maps `HttpError` → the base error envelope; rings never shape an HTTP response; rate limits set `retryAfterSeconds`. +22. `plugins/auth.js` guards as `preHandler`s on the scopes that need them. +23. `plugins/tenant.js` resolves `x-tenant` (missing/unknown ⇒ `production`, fail-closed), cached in memory. +24. Scope-to-subtree = plugin encapsulation; only db, cookie, ctx, tenant, and the error handler register app-wide. +25. Audit: services call `services/audit.record(ctx, …)` — one durable append per state change; never `lib/audit` directly. +26. `DEV_OTP_SINK` and `EMAIL_SENDING_ENABLED` are the default-off booleans routing SMS/email to a stdout sink. +27. test-mode binding: `x-tenant: test` signal; `skipOtpChecks()` swaps delivery only (challenge still issued/verified); test-only reads return empty for `production`. +28. otp-auth binding: model A self-managed store — HMAC-SHA256 hash, short TTL, timing-safe verify; one `otp_challenge` table with a `purpose` column driving the rate limit; Tencent SMS/SES delivery; E.164 via `libphonenumber-js`; phone and email as two identity records on one account. +29. Dual entry sharing one `buildApp()`: `handler.js` listens on the SCF port (no per-request wrapper; `scf_bootstrap` is the container entry); `server.js` local with swagger-ui + multipart in non-prod. +30. One function serves `/api/*` and the built H5 bundle via `@fastify/static`. +31. Deploy bundle: esbuild with all SQL drivers external, only `mysql2` installed into the zip; `migrate.js` bundled as a separate function. +32. Serverless: never rely on instance memory for correctness; finish all work inside the request; tolerate a resuming CynosDB on cold paths. +33. Vitest; service tests drive the use case on real Knex against `*_test` (most coverage sits here); route via `app.inject()`; repo integration on `*_test`; unset `DEV_OTP_SINK` so OTP paths exercise the real verify. +34. Register entries (3): layer-first with no domain ring/DI container (discipline kept); CJS no-typecheck with an esbuild bundle; services raise `HttpError` with one global handler (transport leak accepted deliberately; repos never throw it). + +*frontend.md* + +35. Taro 4 H5 target only, React 18 function components, plain JS; typecheck no-op. +36. Built static and served by the backend; `TARO_APP_API_BASE=/api` same-origin so cookies stay first-party and CORS never enters. +37. State: Zustand, one slice per domain; slices may call services, never import a page or render. +38. REST-only through `src/services/` over a shared `api.js` wrapper injecting `x-tenant` + credentials and mapping the error envelope to a typed error. +39. Routing = two files kept in sync: `app.config.js` registers every page; `config/index.js` `customRoutes` maps internal path → clean URL; add both entries when a page is created; keep no third route→URL list. +40. Compare routes against the customRoute alias, never the internal `/pages/...` path. +41. Portal anything that must survive navigation to `document.body`; the base portal rule is mandatory here. +42. `Taro.redirectTo` collapses the stack and skips the enter transition; custom switch animations require portalled fixed chrome. +43. Patch `history.pushState`/`replaceState` once to emit an event; drive all chrome off a single `usePathname`. +44. Capital `Px` opts a value out of `postcss-pxtransform`; lowercase `px` is rem-rescaled. +45. Re-implement a Taro built-in (pull-to-refresh) when the real scroll container is body-portalled. +46. `src/styles/tokens.css` mirrors the confirmed design guide; one token source, three tiers; semantic tokens only. +47. Author colours as hex, not oklch (in-app WebView support) — a real H5 constraint. +48. Mobile-first base styles + `min-width` queries; viewport meta and safe-area insets belong to the shared layout. +49. Full-height surfaces use `100dvh` with `100vh` fallback (registered); scrollable input pages use `min-height`. +50. Wide content scrolls in its own `overflow-x:auto` box; atomic values `white-space: nowrap`. +51. Test at a phone viewport: Playwright mobile-first with `x-tenant: test`. +52. Base atomic tiers unchanged; DRY gate applies (shared `` for every nav bar). +53. Video: `vod-js-sdk-v6` upload with a short-lived backend signature; `hls.js` playback; client uploads direct to VOD, transcode is VOD's job. +54. Keeps the base `version.json` poll + dismissible refresh banner + `v` tag; PWA; `version.json` served `no-store`. +55. i18n en/zh under `src/i18n/`; CI `i18n:check` enforces parity both directions; keys named by meaning. +56. Test-user picker: one-tap picker on the login screen, fed by a `test`-tenant-gated endpoint returning empty in `production`. +57. Register entries (2): two-file route registry replaces `routes.`; `100dvh`+`100vh` fallback overrides the base svh preference. + +*db.md* + +58. Knex migrations with real paired up/down; base reversibility and round-trip rules apply verbatim; keep Knex's timestamp prefix — never hand-numbered sequences. +59. Knex query builder, no ORM; values bound by Knex; `db.raw()` only for `ON DUPLICATE KEY UPDATE`/`INSERT IGNORE`. +60. Migrations under `db/migrations/`, run via the root `migrate` verb; each exports `up(knex)`/`down(knex)`. +61. Every up ships its real down, or the base justification comment; one logical change per migration (MySQL DDL auto-commits). +62. Separate schema from data; backfills batched, idempotent, resumable (nullable → backfill → enforce later). +63. snake_case; every table carries `created_at`/`updated_at`; index every FK and frequent filter/sort column. +64. MySQL 8 `CHECK` validates existing rows — enforce such invariants in the service when legacy data may violate them. +65. Native MySQL `ENUM` acceptable for fixed sets; widening is a plain reversible migration. +66. One Knex instance per process from `plugins/db.js`; repos take `db` first, then named args. +67. Mappers at the boundary: snake_case rows ↔ camelCase DTOs; no raw row inward; explicit column lists over `SELECT *`. +68. Multi-write use cases open `knex.transaction` in the service and pass `trx` as each repo's `db`; single writes rely on statement atomicity. +69. Local MySQL is one fixed-name shared container; no per-worktree databases in this stack. +70. Seed realistic named accounts + content, idempotent, upsert by business key. +71. Test suite is destructive; the runner refuses unless `DB_NAME` ends `_test`; `pnpm test` auto-suffixes; `test:db:setup` is the idempotent one-time setup. +72. Production migrations run in a dedicated SCF event function invoked after `terraform apply`; keep every migration backward-compatible (expand→migrate→contract). +73. Register entry (1): controlled data seeds do run against production inside the migrate function (idempotent, non-fatal); `resetSchema` never reaches prod except as an explicit opt-in flag. + +*infra.md* + +74. `tencentcloud` Terraform provider; base workflow, risk-review format, and approval guardrails apply unchanged. +75. `infra/terraform/` per-environment root modules (`envs//`) calling shared `modules/` (permitted: 2+ envs share the shape); keep explicit-resource/no-`for_each` style inside modules. +76. Auth via `TENCENTCLOUD_SECRET_ID/KEY`; verify account+region with `tccli` before any plan/apply; confirm the target environment; never assume prior context. +77. One SCF Web Function (`type = "HTTP"`) runs the app, `listen()`ing on the platform port, serving API + H5. +78. A separate migrate event function (~900s timeout, no HTTP trigger, CAM-authenticated), pipeline-invoked, never public. +79. Terraform owns function config/env/CAM role/triggers — not the code zip; code pushed out-of-band via `UpdateFunctionCode`; one explicit resource per object. +80. Author a VPC + one private subnet for the VPC-locked CynosDB; outbound uses public-net egress — no NAT gateway; EdgeOne fronts the function URL; the direct SCF URL stays unadvertised. +81. CynosDB `SERVERLESS` (min/max CCU); the pipeline resumes a paused cluster before `apply`; tolerate first-hit latency on cold paths. +82. Serverless-tier physical backups are hard-capped at 7 days; longer retention via automatic logical dump → COS. +83. DB credentials injected via pipeline secrets, never committed (rejected: SSM/KMS on cost). +84. Media COS bucket private (AES256 + versioning); signed GET / presigned PUT minted server-side; role prefix-scoped; lifecycle rule expires orphaned uploads; cross-region DR replication. +85. VOD upload signing uses a permanent scoped CAM sub-user key (STS unsupported); a VOD procedure transcodes to adaptive HLS + cover; the unsigned callback is validated by FileId ownership + an authoritative VOD read. +86. EdgeOne zone fronts app + api domains; WAF + rate limiting live there; mainland deployments need ICP filing; a response-header rule strips the injected `Content-Disposition` until filing lands; budget ICP lead time. +87. `deploy.yml` filled: ordered 7-step pipeline; branch protection makes push=deploy safe. +88. SCF logs drain to CLS; set topic retention explicitly (platform default is short). +89. Register entries (2): Tencent replaces the GCP blessing; deploy triggers on push + `workflow_dispatch` instead of `workflow_run`, gated by branch protection; no second deploy path. + +## stacks/vercel/ (README.md, backend.md, frontend.md, db.md, infra.md) + +*README.md* + +1. Identity: Next.js App Router TS / Fastify plain JS ESM / Postgres Neon via node-pg-migrate + `pg`; two Vercel projects + Blob; Vercel Terraform provider. +2. Platform-exception naming; would-be triple `nextjs-fastify-postgres`; rename if a second Vercel pack appears. +3. Ships the optional infra.md (platform is the pack's identity). +4. Day-1: delete other packs; copy the dev block (delete the banner); apply CI notes; never the same block in both; record the Learnings entry. +5. Toolchain: pnpm workspaces, root ESM, Node 22; pin `packageManager`; keep the Node major synced between `engines` and Terraform `node_version`. +6. Root verbs bound; backend build/typecheck are explicit no-ops. +7. CI: `postgres:16` service; lint; frontend `tsc --noEmit`; backend `node --test`; `next build`; migration round-trip up→down→up; seed twice; Playwright runs against a preview via `E2E_BASE_URL`, not in this job. +8. Zod on both sides; shared shapes defined once. +9. Deploys = Vercel's GitHub integration; `ci.yml` is the merge gate; protect `main`; `deploy.yml` is never filled — delete the stub; local `vercel deploy` is emergency-only. + +*backend.md* + +10. Fastify 5 used directly; plugins/hooks/decorators are the aspect mechanism. +11. Plain JavaScript ESM; build/typecheck are explicit no-op scripts (rejected: TypeScript). +12. Folder layout: the base shape verbatim (`modules//…`, `shared/`, `container.js`). +13. Zod DTOs at the controller edge; one Zod schema parses env at boot. +14. `container.js` is an Awilix container: factories registered `asFunction`/`asValue`, resolved once at boot; rings never import awilix; `buildContainer({env, overrides})` swaps test fakes. +15. One Fastify plugin per module (`controller/routes.js`) registered under one `/internal/v1` prefix + `/health`; add `/external/v1` only when a genuine third-party consumer exists. +16. A handler validates with the module's Zod DTO, invokes one use case, maps to a response DTO. +17. Aspects: `env.js` fail-fast config; `db.js` single `pg.Pool` + `withTransaction`; `request-context.js` correlation id passed inward as a value; `errors.js` single `setErrorHandler` → base envelope + `x-correlation-id`; `auth.js` cookie-session `preHandler` guards. +18. Only the error handler, request context, cookie plugin, and DB pool register app-wide. +19. Sessions are signed HTTP-only cookies (stateless; no session store). +20. `TRUST_PROXY=true` in deployed environments. +21. `@fastify/rate-limit` in-memory per-instance store is an acceptable soft limit; shared store only when a limit must be globally exact. +22. `@fastify/helmet` once at bootstrap; the API sets its own headers; default CSP fine for a JSON API. +23. SSRF guard: public `https` only; resolve the host and reject loopback/private/link-local/metadata IPs, checked at config-save and immediately before the outbound fetch. +24. Admin secrets are write-only: read DTO masks + `…Set` flag; blank update preserves the stored value. +25. `src/server.js` dual entry: Vercel handler lazily builds the app (no top-level await), dispatches via `app.server.emit("request")`; never `listen()` on Vercel; local listener when `!process.env.VERCEL`. +26. `vercel.json` catch-all rewrite → one function; Fastify's router stays in charge. +27. Serverless: never rely on instance memory for correctness; finish all work inside the request — no fire-and-forget after the response. +28. `node:test` runner (rejected: Jest/Vitest); domain plain units; service via container `overrides`; controller via `app.inject()`; repo integration against real Postgres with `--test-concurrency=1` where shared. +29. Register entries (2): stack bound for real with no build/typecheck; Awilix from day one replaces manual wiring. + +*frontend.md* + +30. App Router under `src/app/`, TypeScript; Server Component default; `'use client'` at the smallest interaction leaf only. +31. REST-only data flow: no Server Actions, no direct DB; every read/mutation goes through `services/` to `/internal/v1` (rejected: Server Actions/route-handler access). +32. Plain `fetch` through services — no react-query/SWR by default; add only when client cache invalidation genuinely appears. +33. State: React Context providers under `src/store/`, one per domain, seeded with server-fetched data (rejected: Redux/Zustand; registered divergence). +34. `/api` proxy: `rewrites()` maps `/api/:path*` → `${BACKEND_URL}/internal/v1/:path*`; browser talks only to the frontend origin; cookies first-party, no CORS. +35. `services/http.ts` (browser) maps the error envelope to a typed `ApiError` carrying the correlation id; `services/server-api.ts` (RSC) calls `BACKEND_URL` directly and forwards incoming cookies. +36. A `fetch` inlined in a component is the greppable smell. +37. `BACKEND_URL` must be present at build time and runtime. +38. Folder mapping: pages→app segments; store→context providers; services split browser/RSC; atomic tiers unchanged; tokens → Tailwind 4 `@theme` CSS variables. +39. Parameterized hrefs through `src/lib/routes.ts` — never hand-concatenated; `` default. +40. Four states: `loading.tsx`/Suspense skeletons; `error.tsx` with `reset()` → shared `` + correlation id; `global-error.tsx`; shared ``; `not-found.tsx`+`notFound()`. +41. Tailwind 4 CSS-first; the `@theme` declaration is the single token source; semantic tokens via utilities. +42. Radix primitives wrapped as atoms; `class-variance-authority` variants; `clsx`+`tailwind-merge` in one `cn()`; `lucide-react` icons; `next/font`; no prebuilt styled kit on top; swap the headless lib only by recording it. +43. Mobile-first stepped utilities; no custom breakpoints without a real reason. +44. Prefer intrinsic sizing (clamp, auto-fit grids, flex-wrap) before breakpoints. +45. Component responsiveness uses `@container` queries; viewport breakpoints for page-level layout. +46. No `tailwind.config`; header clearance and screen gutter are semantic tokens, not magic numbers. +47. One ``/`
` atom owns the gutter idiom; hand-composed gutter strings are the greppable smell. +48. Full-bleed hero: content-driven `min-h` + `py-*`; never `h-screen`/`100vh`; never `aspect-[…]` on a flex child; `min-h-[100svh]` for true fill; clearance from the layout token; top-anchor copy. +49. Atomic values `whitespace-nowrap`; long unbreakables `overflow-wrap`; `truncate max-w-*` inside `min-w-0` parents; wide tables/code in `overflow-x-auto`. +50. `text-balance` is the heading default, set in the shared heading atom. +51. `` primitive: `overflow-x-auto` wrapper, nowrap columns, windowed pagination ≤ ~7 slots. +52. Modal sizing fixed once: `w-full max-w-[calc(100%-2rem)] sm:max-w-lg`; don't re-solve per feature. +53. Images: `next/image` `fill` + explicit `sizes` + aspect wrapper; art-direct via `object-position`; `unoptimized` only for `data:` URLs and the logo. +54. Version inlined via `env.NEXT_PUBLIC_APP_VERSION`; render the `v` tag; no `version.json` poll (registered). +55. Wire `@vercel/analytics` and `@vercel/speed-insights` in the root layout; enable both on the frontend Vercel project. +56. Security headers via `next.config` `headers()`: HSTS, nosniff, `X-Frame-Options: DENY`, referrer policy, CSP report-only → promote once reports are clean; allow-list actual origins. +57. Testing: `tsc --noEmit` + `next build` + Playwright e2e covering every screen's four states; a second mobile device project beside desktop; add a unit runner only when a slice/service accrues real branching logic (registered). +58. Register entries (5): SPA→App Router; `app/` tree replaces the route registry; seeded context providers; platform-handled skew; e2e-first testing default. + +*db.md* + +59. node-pg-migrate with real paired up/down — base reversibility and round-trip rules apply verbatim. +60. `pg` directly, no ORM (rejected: Prisma/Drizzle); repos are thin mappers over explicit SQL. +61. Migrations under `db/migrations/` via the root `migrate` verb; create with `node-pg-migrate create `; epoch-ms prefix satisfies the base. +62. Migrations are CommonJS `.cjs` (the workspace is ESM; a `.js` migration fails to load); each exports `up(pgm)`/`down(pgm)`. +63. Every up ships its real down; a genuinely irreversible change sets `exports.down = false` and carries the justification comment — never silently. +64. Each migration runs in a transaction by default; disable per migration only for DDL that demands it, with a comment. +65. Prefer `pgm` builders; drop to `pgm.sql` otherwise. +66. Run migrations against the direct (non-pooled) connection string — DDL through a transaction pooler misbehaves. +67. snake_case; uuid PKs via `gen_random_uuid()`; `created_at`/`updated_at` `timestamptz` on every table. +68. Index every FK and frequent filter/sort column explicitly. +69. Fixed value sets are `text` + `CHECK`, not native enums (rejected: enum types are effectively one-way). +70. Money is integer minor units or `numeric(p,s)`, never float; unique constraints encode invariants; unique-violation → 409. +71. One `pg.Pool` per process (`max: DB_POOL_MAX`); repos receive it via the container. +72. Keep `DB_POOL_MAX` single-digit in production; runtime `DATABASE_URL` points at Neon's pooled endpoint. +73. Parameterized `$1` queries only; string-built SQL is the greppable violation. +74. Mappers at the boundary; raw rows never cross inward; explicit column lists. +75. `withTransaction(work)` from the db aspect; repos accept an optional client; services never import `pg`; no transaction for a single write. +76. Local Postgres is one fixed-name `postgres:16` container with start-or-run semantics, shared across worktrees. +77. Seed `db/seed-dev.` idempotent, non-production only. +78. One database per worktree (`app_`); bootstrap creates it if missing; drop on teardown; round-trip/destructive checks run against your own worktree DB only. +79. CI: apply from zero; round-trip up→down→up; seed twice. +80. Deploys do not run migrations — run them manually before the push that needs them, against the target Neon branch's direct connection string; verify `pgmigrations` and affected tables. +81. A shell-set `DATABASE_URL` wins over `--env-file`. +82. Migrate then push; keep every migration backward-compatible (expand→migrate→contract). +83. Staging `develop`: same runbook on its own Neon branch; `vercel env pull --git-branch=develop` does not export the branch-scoped `DATABASE_URL` — read it from Terraform state or the console; never migrate through the Neon-injected `POSTGRES_URL` (points at production). +84. Register entry (1): per-worktree databases on the shared fixed-name server. + +*infra.md* + +85. Vercel Terraform provider; base workflow, risk-review format, and approval guardrails apply unchanged. +86. One self-contained workload directory: `versions.tf`, `providers.tf` (`api_token` from `TF_VAR_vercel_api_token`, never committed), `variables.tf`, plus `web.tf`/`api.tf`/`storage.tf`. +87. Two `vercel_project` resources with `root_directory` per app; pin `node_version` (synced with `engines`) and function region via `resource_config`. +88. One explicit resource block per environment variable, `sensitive = true` for secrets; never duplicate integration-injected vars (`DATABASE_URL`, `BLOB_READ_WRITE_TOKEN`). +89. The state file holds secret values — remote access-controlled backend from day 1; never commit state. +90. Context check: `vercel whoami` via `VERCEL_TOKEN`; confirm team/project with the user before plan/apply. +91. Both projects declare `git_repository` with `production_branch = "main"`; grant the Vercel GitHub App repo access on day 1. +92. `ci.yml` is the merge gate, not the deploy pipeline; protect `main` so PRs merge only on green CI. +93. `deploy.yml` is never filled in — delete the stub (or reduce to a pointer) on day 1 so no second deploy path exists (registered). +94. A push deploys API and frontend together — releases must be backward-compatible; expand-first; migrate the Neon branch before the push. +95. Point Playwright at a preview URL via `E2E_BASE_URL`. +96. `.vercelignore` keeps `.env*`, `infra/`, `design/`, `docs/`, `.claude/` out of uploads. +97. Local token `vercel deploy` is the emergency path only. +98. Staging: `develop`-branch preview environments on both projects; web preview's `BACKEND_URL` points at the API preview alias; a dedicated long-lived Neon branch backs it, Terraform-authored; develop migrations are manual too. +99. Enable Vercel Observability on both projects; ship runtime logs off-platform via a log drain. +100. The log drain is integration-owned, NOT Terraform — authoring a `vercel_log_drain` resource creates a duplicate drain; widen coverage in the integration's settings. +101. Recover log-only outcomes from the dashboard request logs, not `vercel logs` (~2-minute live tail). +102. Register entries (2): Vercel replaces the GCP blessing; Vercel's git integration replaces `deploy.yml` (delete the stub; confirm-before-push stands). + +## add-ons/README.md + +1. An add-on is `add-ons//` containing a single `README.md` of agnostic guidance — docs only, no dependencies, lockfiles, or scaffolding. +2. `` is lowercase, hyphenated, capability-named. +3. Adoption is keeping the directory; opting out is deleting it — every directory present under `add-ons/` is adopted; the Day-1 checklist is where a fresh project chooses. +4. Activation is by instruction from the always-loaded root `CLAUDE.md`; the add-on's README is the single source of truth, edited in place — no generated copy. +5. Invariants: agnostic (never names a framework, table, SDK, or cloud); states the approach concisely (SOP, not history); names its stack seam in a "Binds to a stack" section; names its interactions with base rules and other add-ons; stays well under ~150 lines. +6. Stack packs vs add-ons: exactly one pack; zero or more add-ons; the active pack supplies each adopted add-on's concrete bindings. +7. Adding an add-on: write the README per the invariants; add a bindings section to each active stack pack's appendices; wire it into the root `README.md` Day-1 "choose your add-ons" step. + +## add-ons/test-mode/README.md + +1. Test mode is a first-class runtime mode that stubs external side effects; distinct from a feature flag and from seed data. +2. Select the mode per request from an inbound client signal (header, signed cookie, tenant); no or unknown signal = production — fail closed. Never infer test mode from hostname or build flag; never store it where a live request can pick it up. +3. Stub the side effect, don't skip the flow: the code path still runs, only the external step is replaced by a sink, with any user-needed value made knowable. Don't branch business logic on the mode past that boundary. +4. Select credentials by the record being acted on, not the caller's session. +5. Every test-only affordance is gated on the mode and fails closed — unreachable and empty/denied in production; test mode is never a way to skip verification or payment. +6. Test-user picker: fed by a test-mode-gated, unauthenticated read that returns empty in production; rendered only on the login screen and only under the mode signal; backed by realistic, named, run-stable seed accounts. +7. "Fails closed" is asserted in the test suite, not hoped. +8. The stack pack supplies: the mode signal and where it's resolved, each integration's sink, and the picker endpoint plus its gate. +9. Interactions: adopt together with otp-auth; the mode signal and test credentials are validated config. + +## add-ons/otp-auth/README.md + +1. Choose model A (self-managed: store hashed code + short TTL + purpose) or B (provider-owned); under either you still own rate-limiting, idempotency, the test bypass, and delivery-failure handling; under A also hashing, TTL, and timing-safe verify. +2. One purpose-scoped flow: login/signup/contact-change share a single challenge mechanism with a `purpose` discriminator; a code minted for one purpose never satisfies another; add a purpose rather than fork a second flow. +3. Phone and email are interchangeable proofs of one account — model both from the start. +4. Canonicalise the target before storing or sending (phone → E.164 via a library, never hand-rolled); reject unsupported regions with a clear error; store the canonical form. +5. Issue a session only after a successful verify; from there the session is the base auth concern. +6. Idempotent verify: unique constraint on the natural key (target + purpose) so a retry/double-submit race resolves to `409`; the client treats `409` as "already done, proceed". +7. A knowable test code exists only behind test mode; the verify path still runs — only delivery is stubbed. +8. Log every send and verify with `{purpose, masked target, test-mode, provider status, correlation id}` — never the code or the full contact. +9. Rate-limit send and verify (per target, per challenge); answer `429` with a retry hint. +10. Surface delivery failures — "provider accepted" is not "user received"; classify transient vs permanent; never swallow a failed send. +11. Admin-issued fallback code: per-account, hashed, short-lived, revocable, behind its own flag. +12. Select credentials by the record's mode, not the caller's session; live credentials never fall back to a test default. +13. The stack pack supplies: model choice and concrete store/provider, hashing + TTL utilities, the phone-canonicalisation library, the rate-limit store, and how the test-mode code is produced. + +## .github/PULL_REQUEST_TEMPLATE.md + +1. Every PR names the spec it implements or "N/A" with a one-line reason. +2. Test plan records the commands run and output observed — actual evidence, not "tests pass". +3. Checklist: lint/test/build pass locally (or name the unwired check and why); every acceptance criterion demonstrated with evidence; tests cover the changed behaviour where a harness exists; happy path plus reachable error/empty paths exercised. +4. Schema changes: migration verified per `db/CLAUDE.md`'s merge gate — or the irreversible change is justified in the spec; the section is deleted when there's no schema change. +5. UI changes: all four data states verified; keyboard-only pass; new screens compared to their `design/` mockup, existing-screen changes built to convention; all user-facing strings via i18n or the single strings module; verified at minimum supported width; before/after screenshots at the primary form factor and the narrow width. +6. Notes disclose deployment steps, feature flags, and follow-ups; incomplete work must be behind a flag. + +## .github/ISSUE_TEMPLATE/bug.md + +1. Bug reports are auto-labelled `bug` and require: a clear description, numbered reproduction steps, expected vs actual behaviour, and environment. + +## .github/ISSUE_TEMPLATE/feature.md + +1. Feature requests are auto-labelled `enhancement` and require: the problem, the proposed solution, alternatives considered, and acceptance criteria as checkboxes. diff --git a/specs/002-audit-repo-guidance/tasks.md b/specs/002-audit-repo-guidance/tasks.md index f79a7c6..9908c93 100644 --- a/specs/002-audit-repo-guidance/tasks.md +++ b/specs/002-audit-repo-guidance/tasks.md @@ -29,7 +29,7 @@ **⚠️ CRITICAL**: No editing story (US2–US5) may start before this phase; US1 uses it as review input -- [ ] T004 Build `specs/002-audit-repo-guidance/rule-inventory.md`: enumerate every actionable rule as `R-###` (source file, condensed statement, disposition blank) across all non-vendored corpus files, per the Rule entity in data-model.md +- [X] T004 Build `specs/002-audit-repo-guidance/rule-inventory.md`: enumerate every actionable rule as `R-###` (source file, condensed statement, disposition blank) across all non-vendored corpus files, per the Rule entity in data-model.md **Checkpoint**: Baseline and ledger frozen — the audit can begin @@ -47,12 +47,12 @@ - [X] T006 [P] [US1] Run the internal-reference/personal-data grep pass (patterns per quickstart.md: internal URLs/hosts, personal emails, internal project names) over the working tree; append results to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-011) - [X] T007 [P] [US1] Build the instruction-discovery map: classify every corpus file's `audience`, `tier`, and `loading` (auto / lazy-subtree / one-hop / orphaned) per research.md R1 and the GuidanceFile entity; draft as `specs/002-audit-repo-guidance/discovery-map.md` (FR-003, FR-007 input) - [X] T008 [P] [US1] Run the cross-reference check (quickstart.md command): extract relative links and backticked path references from the corpus, list broken targets and orphaned files; append to `specs/002-audit-repo-guidance/scan-evidence.md` (FR-008) -- [ ] T009 [P] [US1] Review root tier against conventions, Principles, and `contracts/guidance-style.md`: `CLAUDE.md`, `README.md`, `specs/README.md`, `design/README.md` — draft findings (bloat, contradictions, sketchy guidance, missing front-door elements) with severity + remedy -- [ ] T010 [P] [US1] Review generic area tier: `apps/backend/CLAUDE.md`, `apps/frontend/CLAUDE.md`, `db/CLAUDE.md`, `db/migrations/README.md`, `infra/CLAUDE.md` — draft findings incl. stack-specific detail that belongs in packs (FR-005) -- [ ] T011 [P] [US1] Review all three stack packs under `stacks/` against `contracts/stack-pack-structure.md` and their generic counterparts — draft findings: contradictions, unlabelled deviations, structure non-conformance (known: `stacks/nextjs-nestjs-postgres/` lacks `infra.md`), pack-vs-pack drift (FR-006, FR-015) -- [ ] T012 [P] [US1] Review add-ons (`add-ons/README.md`, `add-ons/test-mode/README.md`, `add-ons/otp-auth/README.md`) and structural artifacts (`.github/workflows/`, issue/PR templates, ignore files, directory layout); flag-only pass over vendored tooling (`.claude/skills/`, `.specify/` — incl. the unratified constitution); recommend a disposition for `specs/001-enhance-design-guide/` -- [ ] T013 [US1] Assemble `specs/002-audit-repo-guidance/audit-report.md` per `contracts/findings-report.md`: header w/ scan metadata, executive summary, release gate w/ evidence, findings `F-###` ordered release-blocking → low with destructive flags, discovery map, file-by-file verdicts, decisions requested (historical specs + every destructive finding; license already decided: MIT) -- [ ] T014 [US1] Validate the report: diff file-by-file verdicts against `corpus-baseline.md` (zero missing = SC-001); confirm every finding names a violated convention/principle (FR-002); confirm discovery map covers every file (SC-002); fix gaps +- [X] T009 [P] [US1] Review root tier against conventions, Principles, and `contracts/guidance-style.md`: `CLAUDE.md`, `README.md`, `specs/README.md`, `design/README.md` — draft findings (bloat, contradictions, sketchy guidance, missing front-door elements) with severity + remedy +- [X] T010 [P] [US1] Review generic area tier: `apps/backend/CLAUDE.md`, `apps/frontend/CLAUDE.md`, `db/CLAUDE.md`, `db/migrations/README.md`, `infra/CLAUDE.md` — draft findings incl. stack-specific detail that belongs in packs (FR-005) +- [X] T011 [P] [US1] Review all three stack packs under `stacks/` against `contracts/stack-pack-structure.md` and their generic counterparts — draft findings: contradictions, unlabelled deviations, structure non-conformance (known: `stacks/nextjs-nestjs-postgres/` lacks `infra.md`), pack-vs-pack drift (FR-006, FR-015) +- [X] T012 [P] [US1] Review add-ons (`add-ons/README.md`, `add-ons/test-mode/README.md`, `add-ons/otp-auth/README.md`) and structural artifacts (`.github/workflows/`, issue/PR templates, ignore files, directory layout); flag-only pass over vendored tooling (`.claude/skills/`, `.specify/` — incl. the unratified constitution); recommend a disposition for `specs/001-enhance-design-guide/` +- [X] T013 [US1] Assemble `specs/002-audit-repo-guidance/audit-report.md` per `contracts/findings-report.md`: header w/ scan metadata, executive summary, release gate w/ evidence, findings `F-###` ordered release-blocking → low with destructive flags, discovery map, file-by-file verdicts, decisions requested (historical specs + every destructive finding; license already decided: MIT) +- [X] T014 [US1] Validate the report: diff file-by-file verdicts against `corpus-baseline.md` (zero missing = SC-001); confirm every finding names a violated convention/principle (FR-002); confirm discovery map covers every file (SC-002); fix gaps - [ ] T015 [US1] GATE — present the report to the maintainer; record their disposition (`approved`/`rejected`) on every destructive finding and the historical-specs decision in `audit-report.md` (FR-010). **Hard stop: no US2–US5 task runs before this completes** **Checkpoint**: The review the maintainer asked for is delivered and dispositioned — MVP complete From bf48ed3dfd597305e7e7138be3d4d82224d23cc4 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:38:53 +0800 Subject: [PATCH 04/12] fix(ci): require push event on deploy gate; ignore coverage and pnpm logs A fork PR from a branch named 'main' with green CI previously satisfied the workflow_run condition and would deploy the fork's commit with repo secrets. Co-Authored-By: Claude Fable 5 --- .github/workflows/deploy.yml | 5 ++++- .gitignore | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a825c11..8109439 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,9 +13,12 @@ on: jobs: deploy: runs-on: ubuntu-latest - # Only on a successful CI run, and only for main. + # Only on a successful CI run of a push to main. The `event == 'push'` guard is + # load-bearing: without it, a fork PR opened from a branch named "main" satisfies + # `head_branch == 'main'` and would deploy the fork's commit with this repo's secrets. if: >- github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' steps: # When filling in: check out the exact commit CI tested — a `workflow_run` diff --git a/.gitignore b/.gitignore index 66522fe..e51ef38 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,12 @@ dist/ build/ .output/ .cache/ +coverage/ *.tgz npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* # Environment .env From 9234e694a45759eda5b2a956af216af7f546a090 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:38:53 +0800 Subject: [PATCH 05/12] =?UTF-8?q?docs:=20streamline=20root,=20backend,=20a?= =?UTF-8?q?nd=20db=20contracts=20=E2=80=94=20zero=20rules=20lost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root 2488->1953w, backend 2817->2228w, db 560->438w per the audit ledger; single-owner rules with pointers replace cross-file restatements; db gains the hoisted money/timestamps/unique-constraint rules. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 102 +++++++++++++-------------------- apps/backend/CLAUDE.md | 124 +++++++++++++++++++--------------------- db/CLAUDE.md | 28 ++++----- db/migrations/README.md | 2 +- 4 files changed, 116 insertions(+), 140 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 83b5bb6..334edde 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,13 +8,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co A monorepo with two apps under `apps/*`, infrastructure, and shared DB scripts: -- `apps/backend` — the API server. See `apps/backend/CLAUDE.md`. -- `apps/frontend` — the single-page app. See `apps/frontend/CLAUDE.md`. -- `db/` — top-level **database scripts**: reversible migrations under `db/migrations/` (plus seed/reset scripts). See `db/CLAUDE.md`. -- `infra/` — Terraform for the project's cloud resources. See `infra/CLAUDE.md`. -- `design/` — design mockups / UI reference, **reference only** (not part of the buildable workspace). See *UI mockup / design reference* below. -- `stacks/` — optional stack packs: appendix docs binding the agnostic contracts to one concrete stack; one chosen at instantiation, the rest deleted. Each area's `CLAUDE.md` tells you to read the adopted pack's matching appendix before working there. See `stacks/README.md`. -- `add-ons/` — optional capability add-ons: agnostic patterns for features the base leaves out (test mode, OTP login); zero or more chosen at instantiation, the rest deleted, the active stack pack supplying their concrete bindings. **Every directory kept under `add-ons/` is adopted — read its `README.md` and follow it whenever you touch the capability it covers.** See `add-ons/README.md`. +- `apps/backend` — the API server. Read `apps/backend/CLAUDE.md` before working here. +- `apps/frontend` — the single-page app. Read `apps/frontend/CLAUDE.md` before working here. +- `db/` — database scripts: reversible migrations under `db/migrations/`, plus seed/reset scripts. Read `db/CLAUDE.md` before working here. +- `infra/` — home of the project's Terraform, empty until the first workload. Read `infra/CLAUDE.md` before working here. +- `design/` — UI mockups plus the design guide (`design-guide.html` + `tokens.css`), reference only — not part of the buildable workspace. See `design/README.md`. +- `specs/` — feature specs written before implementation. Convention in `specs/README.md`. +- `stacks/` — optional stack packs binding the agnostic contracts to one concrete stack; one chosen at instantiation, the rest deleted. Each area's `CLAUDE.md` points at the adopted pack's matching appendix; a new adoption starts at the pack's own `README.md`. See `stacks/README.md`. +- `add-ons/` — optional capability add-ons (test mode, OTP login); zero or more kept at instantiation, the rest deleted, the active stack pack supplying their concrete bindings. **Every directory kept under `add-ons/` is adopted — read its `README.md` and follow it whenever you touch that capability.** See `add-ons/README.md`. ## Common commands @@ -34,99 +35,78 @@ A monorepo with two apps under `apps/*`, infrastructure, and shared DB scripts: migrate # TODO: run db/ migrations ``` -**Deployment should go through CI/CD, not a local `deploy` script.** Keep workflows under `.github/workflows/`. A local deploy path may exist for emergencies; do not invoke it as part of normal work. +Deployment goes through CI/CD — workflows under `.github/workflows/` — never a local script. ## Architecture at a glance -### Backend - -**Backend** — an onion with a pure domain at the centre (Domain → Service → Repo/Controller), dependencies pointing inward via ports. Cross-cutting concerns are decorators/aspects, not middleware sprinkled in handlers. **Read `apps/backend/CLAUDE.md` before touching `apps/backend/`.** - -### Frontend - -**Frontend** — store / services / pages / components layering with consistent loading/error/empty/success states and reuse of base UI primitives. **Read `apps/frontend/CLAUDE.md` before touching `apps/frontend/`.** - -### UI mockup / design reference - -Design mockups live in the **`design/`** folder, kept as **reference only** — not part of the buildable workspace. **They are the reference for a screen's *initial build* only.** Use them as the source for visual design, screen inventory, copy, and flows when planning and first building a screen; **don't copy their code** (the mockup's framework is usually not the app's). After the first build, expect the screen to drift as it's iterated and improved — from then on the **running app is the reference, not the mockup**, so don't re-check later changes against it. When planning a *new* screen, point the relevant mockup files at the spec so it starts aligned. +- **Backend** — an onion with a pure domain at the centre (Domain → Service → Repo/Controller), dependencies pointing inward via ports; cross-cutting concerns are decorators/aspects, not middleware sprinkled in handlers. Contract: `apps/backend/CLAUDE.md`. +- **Frontend** — store / services / pages / components layering with consistent loading/error/empty/success states and reuse of base UI primitives. Contract: `apps/frontend/CLAUDE.md`. +- **UI mockups** — `design/` mockups are the reference for a screen's *initial build only*; never copy their code. After the first build, the running app is the reference. Full lifecycle: `design/README.md`. ## Coding standards -These apply to **both** apps and now live next to the code they govern — see the coding-standards material in `apps/backend/CLAUDE.md` (its **Cross-cutting concerns** and **Coding standards** sections) and the **Coding standards** section in `apps/frontend/CLAUDE.md`. In short: keep cross-cutting concerns in shared decorators/plugins (backend) or hooks/services (frontend) rather than duplicating them; keep `utils/`/`lib/` pure and un-peppered; and use real libraries instead of hand-rolling — especially for dates. - -- **Configuration.** All runtime config is read from the environment in one place per app and validated at startup against a declared schema, so a missing or malformed value fails fast with a clear, named error rather than misbehaving mid-request. `.env.example` is the canonical, comment-documented list of every variable, updated in the same change that adds a config key. No inner layer reads config directly — it is passed inward as values. +Per-app standards live next to the code they govern — see `apps/backend/CLAUDE.md` and `apps/frontend/CLAUDE.md`. Cross-app: -### Readability and Naming - -Readable code is a review priority. - -Assess whether names make intent clear without requiring the reviewer to reconstruct meaning from implementation details. - -#### Naming - -- Avoid abbreviations unless they are standard in the domain or codebase. -- Prefer precise names over short names. -- Avoid misleading names. -- Avoid single-letter variables except for trivial loop counters or conventional mathematical usage. -- Use names that reflect business meaning, not only technical mechanics. +- Cross-cutting concerns live in shared decorators/plugins (backend) or hooks/services (frontend) — never duplicated per handler or screen. +- `utils/` / `lib/` stay pure and un-peppered. +- **Configuration.** All runtime config is read from the environment in one place per app and validated at startup against a declared schema, so a missing or malformed value fails fast with a clear, named error. `.env.example` is the canonical, comment-documented list of every variable, updated in the same change that adds a config key. No inner layer reads config directly — it is passed inward as values. +- **Naming.** Readable code is a review priority; names must make intent clear without reconstructing the implementation: no non-standard abbreviations, precise over short, never misleading, no single-letter names outside trivial loop counters or mathematical convention, business meaning over technical mechanics. ## Principles (must follow) -Load-bearing engineering rules; honor them on every change. They are stack- and tooling-agnostic. The first four are adapted from Andrej Karpathy's coding guidelines, folded into this file so no external reference is needed. +Load-bearing engineering rules, stack- and tooling-agnostic (the first four adapted from Andrej Karpathy's coding guidelines). -- **Think before coding.** Don't assume, don't hide confusion, surface tradeoffs. State your assumptions and ask when uncertain; present multiple interpretations rather than silently picking one; suggest simpler alternatives and respectfully push back when warranted; stop and name what's confusing rather than proceeding on unclear requirements. -- **Simplicity first / YAGNI.** The minimum code that solves the problem, nothing speculative — no unrequested features, no abstractions for single-use code, no configurability or error handling for cases that can't occur. Any added complexity (extra project, framework, abstraction layer, build target, third-party SDK, distributed component) must be justified with the simpler alternative explicitly rejected; "we might want X later" is not a justification. If 200 lines could be 50, rewrite it shorter — would an experienced engineer find this unnecessarily complex? -- **Change the right place, surgically.** First identify *where* a change belongs — the correct layer and boundary — and make it there; don't patch wherever is convenient. Keep business logic out of controllers, repos, UI, jobs, and utilities where it doesn't belong, and don't leak infrastructure details into the wrong layer. Then touch only what you must: match the surrounding style and conventions (error handling, logging, validation), don't reformat or refactor unrelated working code, flag unrelated dead code without removing it, and remove only the imports/variables your own change orphaned. -- **Goal-driven execution.** Define success criteria and loop until verified. Turn requests into measurable objectives with a brief plan and a verification step per phase, so each phase can iterate to a clear success marker. Verified means observed, not inferred: before calling a change done, run it and state the evidence you saw. What "run it" means per change type lives in each app's `CLAUDE.md` (frontend/backend) and in `infra/CLAUDE.md` for infrastructure; record the evidence in the PR's Test plan checklist. -- **Don't reinvent existing solutions.** Use established libraries and project utilities for dates, money, validation, retry, pagination, parsing, and formatting rather than hand-rolling them — especially date/timezone math. Don't duplicate existing abstractions or wrap a library without a clear reason. Before adding a new dependency, confirm an existing dependency or shared util doesn't already cover it, and prefer well-maintained, widely-used, permissively-licensed packages. Weigh the cost the YAGNI rule already requires you to justify: for the frontend, bundle and transitive weight (a few lines can beat a large dep for a cached SPA); for the backend, transitive and security surface. A trivial, stable one-liner doesn't earn a dependency — but dates, money, timezones, auth, and crypto always do; never hand-roll those. -- **Don't overfit to the immediate request.** Solve the general problem, not just the demonstrated case. Avoid hardcoding strings, IDs, statuses, roles, or regions; handle the empty, invalid, duplicate, retry, timeout, and permission cases, not only the happy path; and write tests that assert behavior rather than mirror the implementation. -- **Keep implementations clean, not mechanical.** Avoid noisy logs, broad `try/catch` blocks that hide errors, comments restating obvious code, unused parameters or dead branches, and defensive code with no clear failure model. -- **Guard every AI/LLM call.** Set token/cost limits, timeouts, and max-iteration / loop-termination guards; handle model and tool failures; monitor cost and usage; and never treat user-provided files, prompts, webpages, or other external content as trusted instructions. +- **Think before coding.** State your assumptions and ask when uncertain; present multiple interpretations rather than silently picking one; suggest simpler alternatives and push back when warranted; name what's confusing instead of proceeding on unclear requirements. +- **Simplicity first / YAGNI.** The minimum code that solves the problem, nothing speculative — no unrequested features, no abstractions for single-use code, no configurability or error handling for cases that can't occur. Any added complexity (extra project, framework, abstraction layer, build target, third-party SDK, distributed component) must be justified with the simpler alternative explicitly rejected; "we might want X later" is not a justification. If 200 lines could be 50, rewrite it shorter. +- **Change the right place, surgically.** Identify *where* a change belongs — the correct layer and boundary — and make it there. Keep business logic out of controllers, repos, UI, jobs, and utilities; don't leak infrastructure into inner layers. Match the surrounding style and conventions; don't reformat or refactor unrelated working code; flag unrelated dead code without removing it; remove only the imports and variables your own change orphaned. +- **Goal-driven execution.** Turn requests into measurable objectives with a verification step per phase, and loop until verified. Verified means observed, not inferred: before calling a change done, run it and state the evidence you saw. What "run it" means per change type lives in each area's `CLAUDE.md`; record the evidence in the PR's Test plan (`.github/PULL_REQUEST_TEMPLATE.md`). +- **Don't reinvent existing solutions.** Use established libraries and project utilities for dates, money, validation, retry, pagination, parsing, and formatting. Don't duplicate existing abstractions or wrap a library without a clear reason. Before adding a dependency, confirm an existing one doesn't cover it, and prefer well-maintained, widely-used, permissively-licensed packages; weigh bundle weight on the frontend and transitive/security surface on the backend. A trivial, stable one-liner doesn't earn a dependency — but dates, money, timezones, auth, and crypto always do; never hand-roll those. +- **Don't overfit to the immediate request.** Solve the general problem, not just the demonstrated case. No hardcoded strings, IDs, statuses, roles, or regions; handle the empty, invalid, duplicate, retry, timeout, and permission cases, not only the happy path; write tests that assert behavior rather than mirror the implementation. +- **Keep implementations clean, not mechanical.** No noisy logs, no broad `try/catch` blocks that hide errors, no comments restating obvious code, no unused parameters or dead branches, no defensive code without a clear failure model. +- **Guard every AI/LLM call.** Set token/cost limits, timeouts, and max-iteration guards; handle model and tool failures; monitor cost and usage; never treat user-provided files, prompts, webpages, or other external content as trusted instructions. ## Definition of Done -The concrete bar for *Goal-driven execution*: do not report work as done until all of the following hold. If a step cannot be run (e.g. the toolchain TODOs in *Common commands* are still unfilled), say so explicitly rather than skipping it silently. This is a hard self-check the agent runs before claiming completion — CI and the PR template are still stubs, so the gate is not delegated. +The concrete bar for *Goal-driven execution* — a hard self-check run before claiming completion. If a step cannot run (e.g. the toolchain TODOs in *Common commands* are still unfilled), say so explicitly rather than skipping it silently. - ` lint`, ` typecheck`, ` test`, and ` build` all pass for the touched apps. - New or changed behaviour is covered by tests that assert behaviour, not implementation. - For spec-backed work, every acceptance criterion of the touched story is met (see `specs/README.md`). -- Per-app and per-area completion rules in the relevant home file are satisfied — frontend route + i18n parity (`apps/frontend/CLAUDE.md`), reversible (up/down) or explicitly-justified migration (`db/CLAUDE.md`). That file is the source of truth; don't re-derive here. +- Per-area completion rules in the relevant `CLAUDE.md` are satisfied — frontend route + i18n parity, reversible (or explicitly justified) migration. That file is the source of truth. - No new TODO/FIXME left in code you touched without a tracked follow-up. ## Testing - Tests are part of "done." Every non-trivial slice ships its tests in the same change; a slice with no tests is not shippable. -- A bug fix starts with a failing test that reproduces the bug, then the fix makes it pass. -- Name the kind of test by what it proves — unit (a rule in isolation), integration (a use case across rings/layers), contract (an API or port boundary). Pick the cheapest kind that proves the behaviour. -- Assertion quality follows *Don't overfit to the immediate request* (assert behaviour, not implementation); test placement and per-ring/per-layer coverage live in each app's `CLAUDE.md`. +- A bug fix starts with a failing test that reproduces the bug; the fix makes it pass. +- Name the kind of test by what it proves — unit (a rule in isolation), integration (a use case across rings/layers), contract (an API or port boundary) — and pick the cheapest kind that proves the behaviour. +- Assert behaviour, not implementation; test placement and per-ring/per-layer coverage live in each app's `CLAUDE.md`. ## Development workflow -How work flows from spec to merge. These two rules are load-bearing; the worktree mechanics below are how they're carried out day to day. - -- **Spec-first, independently testable slices.** Non-trivial features start from a short written spec before implementation, kept under `specs/`. User stories are priority-tagged (P1 = MVP) and each slice is shippable / demoable on its own; P1 alone is a viable MVP. Avoid cross-story coupling that breaks that independence. Keep this discipline regardless of which spec tool (if any) you use. -- **Trunk-based, linear history.** A single long-lived integration branch, `main`. Feature work happens on short-lived branches (see *Working in a git worktree* below); rebase / fast-forward onto trunk to keep history linear. Trunk stays releasable — hide incomplete work behind a flag. A flag here is a boolean key in the app's validated config schema (see *Configuration*), default off — no flag service or SDK unless a project explicitly adopts one and records the choice. Keep PRs small where practical. Commits: imperative subject, one logical change per commit; follow the repo's existing Conventional Commits prefix style (feat/fix/docs/refactor/test/chore, optional scope) so history stays scannable. +- **Spec-first, independently shippable slices.** Non-trivial features start from a short written spec under `specs/` before implementation; stories are priority-tagged and P1 alone is a viable MVP. Convention and slice rules: `specs/README.md`. +- **Trunk-based, linear history.** A single long-lived integration branch, `main`. Feature work happens on short-lived branches in worktrees (below); rebase / fast-forward onto trunk to keep history linear. Trunk stays releasable — hide incomplete work behind a flag: a boolean key in the app's validated config schema (see *Configuration*), default off; no flag service or SDK unless a project explicitly adopts one and records the choice. Keep PRs small. Commits: imperative subject, one logical change per commit, Conventional Commits prefixes (feat/fix/docs/refactor/test/chore, optional scope). ### Self-review before merge -Before opening a PR or merging, read your **full diff** end to end — as a reviewer would, including files you don't remember touching — and confirm it satisfies the rules already stated above and in the relevant `apps/*/CLAUDE.md`: the change lives in the correct layer/ring with no business logic leaked outward, no unrelated code was reformatted, and only imports your own change orphaned were removed (see *Change the right place, surgically*), and names reflect business meaning (see *Readability and Naming*). Don't merge on memory of what you edited; re-read what actually changed. +Before opening a PR or merging, read your **full diff** end to end — as a reviewer would, including files you don't remember touching — and confirm it satisfies the rules above and in the relevant area `CLAUDE.md`. Never merge on memory of what you edited. ### Working in a git worktree -Worktrees are the **default** here — most work runs in parallel with Claude across several worktrees at once. Feature work happens in a git worktree under `.claude/worktrees/` (or your preferred location) on its own short-lived branch. +Worktrees are the **default** — work runs in parallel across several worktrees at once, under `.claude/worktrees/` on short-lived branches. - **Before anything else in a new worktree, copy over all gitignored runtime config** — a fresh worktree is created without it (root `.env`, any `apps/*/.env*`, local secrets) and anything depending on it will silently misbehave. From the worktree root: `main="$(git worktree list --porcelain | sed -n 's/^worktree //p' | head -1)"; for f in .env apps/backend/.env apps/frontend/.env; do if [ -f "$main/$f" ]; then cp "$main/$f" "./$f" && echo "copied $f"; else echo "not in main checkout (skipped): $f"; fi; done` — it reports each file so a missing one is visible, not silent. Copy every gitignored env file your project uses, not only the three listed. -- Shared local infrastructure (a containerized DB, etc.) is typically **shared** across worktrees by a fixed name — starting a second copy will conflict; reuse the running one. -- The shared DB's schema is **global state** across worktrees — a migration, reset, or seed run in one worktree changes every worktree's app. Don't run a reset or destructive migration check while a parallel worktree depends on the current schema; use a throwaway DB for round-trip/destructive checks. +- Shared local infrastructure (a containerized DB, etc.) is **shared** across worktrees by a fixed name — reuse the running instance; never start a second copy. +- The shared DB's schema is global state across worktrees — rules in `db/CLAUDE.md`. **When the work is done** — an ordered merge-back gate; the moment trunk is mutated is the moment quality is enforced: -1. **Rebase** the branch onto the current default branch and resolve any conflicts — pulling in changes that landed on trunk while you worked. -2. On the rebased branch, **run the full lint + typecheck + test + build suite and confirm it passes** — never merge red. The suite must run on the integrated state (after the rebase, not before). If no suite exists yet (toolchain still TODO), say so explicitly per the Definition of Done. -3. **Fast-forward merge** into the default branch (the rebase makes this a clean ff, preserving linear history). +1. **Rebase** the branch onto the current default branch and resolve any conflicts. +2. On the rebased branch, **run the full lint + typecheck + test + build suite and confirm it passes** — never merge red. The suite runs on the integrated state (after the rebase, not before). If no suite exists yet (toolchain still TODO), say so explicitly per the Definition of Done. +3. **Fast-forward merge** into the default branch. 4. **Stop** any dev servers / test instances started for the work. 5. **Delete** the worktree (`git worktree remove`) and its merged branch. -6. **Push** the default branch only after confirming. By default this template's `.github/workflows/deploy.yml` runs after a green CI run on `main` (a `workflow_run` trigger), so once its deploy step is filled in a push to the default branch ships to the configured target — confirm with the user before pushing, and check `deploy.yml` if the trigger has been changed. +6. **Push** the default branch only after confirming with the user — `.github/workflows/deploy.yml` runs after a green CI run on `main`, so once its deploy step is filled in, a push ships to the configured target. Check `deploy.yml` if the trigger has been changed. ## Learnings diff --git a/apps/backend/CLAUDE.md b/apps/backend/CLAUDE.md index 3606f50..62c3f12 100644 --- a/apps/backend/CLAUDE.md +++ b/apps/backend/CLAUDE.md @@ -1,58 +1,57 @@ # Backend -The backend contract — read before touching anything under `apps/backend/`. Repo-wide rules (principles, workflow, cross-app standards) live in the root `CLAUDE.md`. Stack: not yet chosen (see the root `CLAUDE.md`). **If a stack pack is adopted (a single directory kept under `stacks/`), also read its `backend.md` appendix before working here** — it adds the concrete bindings, and its conflict register resolves any disagreement with this file, for that stack only. The examples below use JS-style filenames (`container.js`) and an Express/Fastify-style HTTP layer **illustratively**; treat file extensions and framework specifics as examples, not mandates — the same way `apps/frontend/CLAUDE.md` does. +The backend contract — read before touching anything under `apps/backend/`. Repo-wide rules (principles, workflow, cross-app standards) live in the root `CLAUDE.md`. Stack pack adopted? Read its `backend.md` appendix first — precedence rules in `stacks/README.md`. Examples below (JS-style filenames such as `container.js`, an Express/Fastify-style HTTP layer) are illustrative, not mandates. -The backend is an **onion**: a pure domain at the centre, wrapped by rings that depend inward toward it. Everything below follows from that. +The backend is an **onion**: a pure domain at the centre, wrapped by rings that depend inward toward it. ## The dependency rule **Dependencies point inward. Nothing in an inner ring knows anything about an outer ring.** - The domain depends on nothing; each outer ring depends only on the rings inside it. -- When an inner ring needs an outer capability (load data, send mail), it defines the **contract** — a "port" — and an outer ring provides the implementation. A port is a documented set of method signatures: in a language with first-class interfaces (e.g. TypeScript) express it as an interface; in one without (e.g. plain JS) express it as an agreed shape honoured by duck typing and optionally pinned with a JSDoc `@typedef`. Either way the implementation is supplied from outside (see *Wiring*), so the dependency is inverted and the arrow still points inward. -- Data crosses in translated form: DTOs at the edge, domain objects inside. Outer-ring types — HTTP requests, database rows, SDK objects — never travel inward; the domain neither imports nor names them. - -Test any boundary: can you swap what's outside (the database, the delivery mechanism) without touching what's inside? If not, something has leaked. If a change wants to point a dependency outward, reshape the change, not the rule. +- When an inner ring needs an outer capability (load data, send mail), it defines a **port** — an interface in a typed language, or an agreed duck-typed shape (optionally pinned with a JSDoc `@typedef`) in an untyped one. The implementation is supplied from outside (see *Wiring*), so the arrow still points inward. +- Data crosses boundaries translated: DTOs at the edge, domain objects inside. HTTP requests, database rows, and SDK objects never travel inward; the domain neither imports nor names them. +- Boundary test: what's outside (the database, the delivery mechanism) must be swappable without touching what's inside. If a change wants to point a dependency outward, reshape the change, not the rule. ## The four rings -From the centre out: **Domain → Service → Repo / Controller**. Repo and Controller are both outer adapters; neither depends on the other. The outer three keep the project's familiar controller / service / repo names; the domain at the centre is the DDD core they build on. - -For each ring: what it holds, what it may depend on, what it must never do. +From the centre out: **Domain → Service → Repo / Controller**. Repo and Controller are peer outer adapters; neither depends on the other. ### Domain — the core -The business expressed in code: entities, value objects, domain services, and the invariants and rules true regardless of how the system is delivered or stored. It also defines the **ports** — the repository and gateway contracts the outer rings implement. +The business expressed in code: entities, value objects, domain services, and the invariants true regardless of how the system is delivered or stored. Defines the **ports** the outer rings implement. - **Depends on:** nothing. Pure and stateless — no I/O, no database handle, no clock, no network, no framework types. -- **Never:** performs I/O or names a specific technology. A rule that can only be tested by standing up a database is in the wrong shape — express it so it can be tested in isolation. +- **Never:** performs I/O or names a specific technology. +- Express rules so they test in isolation — a rule testable only by standing up a database is mis-shaped. +- Validate state transitions against the rules **before** applying a status or lifecycle change — never merely because an external request, callback, message, or event asked for it. -Keep this ring small, dense, and protected. It's the part worth defending. +Keep this ring small, dense, and protected. ### Service — use cases -Orchestrates one use case end to end: load through the domain's repository ports, invoke the domain, persist the result. Owns the transaction boundary — **one transaction per use case**. +Orchestrates one use case end to end: load through the domain's ports, invoke the domain, persist the result. Owns the transaction boundary — **one transaction per use case**. - **Depends on:** the domain and the ports it defines — nothing concrete. -- **Never:** touches HTTP/web concepts, builds queries, or reaches for framework globals. Orchestration lives here; the *rules* live in the domain. +- **Never:** touches HTTP concepts, builds queries, or reaches for framework globals. Orchestration lives here; the rules live in the domain. ### Repo — adapters outward -Concrete implementations of the ports the inner rings define: repositories backed by the database, plus clients for external services (storage, mail/SMS, payments, translation). Each adapter carries a **mapper** that translates between the outside shape (a DB row, an external payload) and domain objects — so storage shapes stop at this boundary. +Implements the ports: repositories backed by the database, plus clients for external services (storage, mail/SMS, payments). Each adapter carries a **mapper**, so storage and external shapes stop at this boundary. -- **Depends on:** the inner rings, to *implement* their ports. -- **Never:** holds business rules or decisions. Adapters move and translate data across the boundary; branching beyond what a query or call needs means a rule has leaked out of the domain. +- **Depends on:** the inner rings, to implement their ports. +- **Never:** holds business rules or decisions. Branching beyond what a query or call needs means a rule has leaked out of the domain. ### Controller — delivery inward -The edge where the outside world meets the app: REST handlers, request/response DTOs, and the auth guards protecting them. A handler validates input, invokes **one** use case, and maps the result back out. +The edge: REST handlers, request/response DTOs, and the auth guards protecting them. A handler validates input, invokes **one** use case, and maps the result back out. - **Depends on:** the service ring it invokes. - **Never:** holds business logic, transactions, or queries, or reaches past the service into the repo ring. ## Folder layout -Organise **by feature first, layers within**: each domain area is a self-contained module owning its four rings, with shared building blocks and the wiring beside the modules. (A small module needn't use every folder — add a ring's folder when it earns one.) +Organise **by feature first, layers within**: each domain area is a self-contained module owning its rings; add a ring's folder only when it earns one. ``` apps/backend/ @@ -71,38 +70,39 @@ apps/backend/ └─ tests/ # mirrors src/ (or co-locate per module) ``` -A module never imports another module's inner rings — cross-module use goes through the other module's service or a shared port. Within a module, dependencies point inward: `controller/ → service/ → domain/`, and `repo/ → domain/` (implementing its ports). `shared/utils/` depends on nothing; `shared/aspects/` wrap a ring and depend inward only. +- A module never imports another module's inner rings — cross-module use goes through the other module's service or a shared port. +- Within a module, dependencies point inward: `controller/ → service/ → domain/`, and `repo/ → domain/` (implementing its ports). +- `shared/utils/` is pure and stateless — no I/O, no framework. `shared/aspects/` wrap a ring and depend inward only. ## Wiring -Ports are defined inside, implemented outside, and connected in one place — the **composition root** (`container.js`). This is how the backend does dependency inversion (see *The dependency rule* for the typed/untyped port forms): the contract is the agreed method shape, and the concrete implementation is supplied at boot. +Ports are defined inside, implemented outside, and connected in one place — the **composition root** (`container.js`). -- An inner ring receives its dependencies (a constructor argument or factory parameter); it never `import`s a concrete adapter directly. -- The composition root is the only place that knows both a port and its implementation. Swapping an adapter (real database → in-memory for a test) is a change there and nowhere else. -- Keep wiring out of the rings — it is glue, not logic. Manual constructor wiring is enough; reach for a DI container (e.g. Awilix) only once the graph grows unwieldy. +- An inner ring receives its dependencies as constructor or factory arguments; it never imports a concrete adapter. +- The composition root is the only place that knows both a port and its implementation; adapter swaps (real database → in-memory for a test) happen there and nowhere else. +- Manual constructor wiring by default; adopt a DI container only once the graph grows unwieldy. ## Testing the rings -The architecture exists to make testing cheap — exploit it. Each ring maps to a kind of test; **most coverage sits in the fast inner rings**, thinning outward. +Each ring maps to a kind of test; **most coverage sits in the fast inner rings**, thinning outward. -- **Domain — pure unit tests.** No mocks, no I/O (the ring forbids I/O, so its tests need none). Assert the invariants and rules directly. -- **Service — use-case tests.** Drive the use case with in-memory fakes of the ports (the composition-root swap described in *Wiring*); assert orchestration and transaction boundaries, not the database. +- **Domain — pure unit tests.** No mocks, no I/O. Assert the invariants and rules directly. +- **Service — use-case tests.** Drive the use case with in-memory fakes of the ports; assert orchestration and transaction boundaries, not the database. - **Repo — integration tests.** Run against a real database / external sandbox; assert the mapper round-trips and the queries behave. -- **Controller — contract tests.** Assert status codes, validation rejection, auth guards, and request/response schema (see *Endpoint contract* and *Status codes*). +- **Controller — contract tests.** Assert status codes, validation rejection, auth guards, and request/response schemas. ## Verifying a change Before calling a backend change done (the root's *verified means observed* gate): -- Run the test suite for the touched module. -- Exercise the actual endpoint over HTTP — the happy path plus at least one error path. -- Confirm the status code, error shape, and correlation id match the *Endpoint contract* and *Cross-cutting* error rules already defined here. - -State what you observed (which paths you exercised, what you saw), not just that you ran it. +- Run the touched module's tests. +- Exercise the endpoint over HTTP — the happy path plus at least one error path. +- Confirm the status code, error shape, and correlation id match the *Endpoint contract* and *Error responses* rules. +- State what you observed (which paths, what you saw), not just that you ran it. ## RESTful conventions -These govern the **controller** ring — the default API contract; prefer a more specific project rule where one exists. +These govern the **controller** ring. ### Resource naming @@ -148,11 +148,11 @@ Every error response uses one envelope, produced only by the single error-mappin ``` - **`code`** — stable, machine-readable, `SCREAMING_SNAKE_CASE`, named in domain terms. Clients branch on `code`; they never parse `message`. -- **`message`** — human-readable and safe: no stack traces, SQL, or internal identifiers (what users actually read is governed by the frontend's error-copy rules). -- **`correlationId`** — the request's correlation id. It also travels as the **`x-correlation-id` response header on every response**, success or failure; the body field is the copy the frontend surfaces (see *Cross-app conventions* in `apps/frontend/CLAUDE.md`). +- **`message`** — human-readable and safe: no stack traces, SQL, or internal identifiers. +- **`correlationId`** — the request's correlation id. It also travels as the **`x-correlation-id` response header on every response**, success or failure. - Validation failures (`400`) may add `error.details`: a list of `{ "field": , "message": }` entries. -Success shapes for symmetry: a single resource is returned as the object itself (no wrapper); lists use the pagination envelope above. +Success shapes: a single resource is returned as the bare object (no wrapper); lists use the pagination envelope above. ### Endpoint contract @@ -160,45 +160,39 @@ Each endpoint defines its required permissions, request schema, response schema, ## Cross-cutting concerns -Concerns that touch every request — auth, context, logging, transactions, error mapping — are implemented as **decorators / aspects (AOP)**: declared once and applied declaratively to the ring they wrap, so a handler or use case carries only its own logic. In Node this is the framework's middleware/plugin layer — Express middleware, or Fastify plugins plus lifecycle hooks and decorators. **Scope each aspect to the subtree that needs it** — register it on the plugin/router branch it applies to, not globally, so unrelated routes stay clean. Each aspect still obeys the dependency rule — it lives in its ring and passes data inward only as plain values. +Concerns that touch every request — auth, context, logging, transactions, error mapping — are **decorators / aspects**: declared once and applied declaratively to the ring they wrap, so a handler or use case carries only its own logic. Scope each aspect to the subtree that needs it, not globally. Each aspect obeys the dependency rule — it lives in its ring and passes data inward only as plain values. -- **Auth:** guards at the controller ring reject unauthenticated requests at the edge; rule-level authorisation that depends on domain state lives in the domain or use case. +- **Auth:** guards at the controller ring reject unauthenticated requests at the edge; authorisation that depends on domain state lives in the domain or use case. - **Request context / identity:** established at the edge, passed inward as an argument — never read from a global by an inner ring. -- **Transactions:** the boundary wraps the use case (see Service). -- **Logging & audit:** one shared path carrying the request's correlation id, so a request traces end to end. Keep it out of the domain. Emit **structured records** (key/value fields, not concatenated strings) at meaningful **levels** — `error` for handled failures, `warn` for recoverable anomalies, `info` for state changes, `debug` behind a flag for diagnostics. **Never log secrets, tokens, credentials, auth headers, or PII; redact at the logging boundary** and log identifiers (e.g. a user id) rather than payloads. Log a failure **once**, where it is handled — not at every ring on the way out (re-logging the same error is the noise the root Principles forbid). -- **Audit trail:** when the app must answer *who changed what* (permission or role changes, contact-detail edits, moderation, money movement), that is a concern **distinct** from operational logging — logs rotate and aren't queryable as history. Record every meaningful state change through **one shared `record()` call** in the service ring — actor, action, target, and the before/after where it matters — to durable, queryable storage, carrying the request's correlation id. One call site per state change, invoked by the use case that owns the change; not scattered inserts, and not the log stream. -- **Errors:** the domain raises failures in domain terms; the controller ring is the single place that maps them onto transport responses, using the *Error responses* envelope — one shape app-wide. - -## Standards reference - -### Business rules - -- Validate state transitions against the rules **before** applying a status or lifecycle change — never because an external request, callback, message, or event asked for it. +- **Transactions:** the boundary wraps the use case (see *Service*). +- **Logging:** one shared path carrying the request's correlation id, so a request traces end to end. Emit structured key/value records at levels — `error` for handled failures, `warn` for recoverable anomalies, `info` for state changes, `debug` behind a flag. Never log secrets, tokens, credentials, auth headers, or PII — redact at the logging boundary and log identifiers (e.g. a user id), not payloads. Log a failure once, where it is handled. +- **Audit trail:** distinct from operational logging — logs rotate and aren't queryable as history. Record every meaningful state change (actor, action, target, and the before/after where it matters) through **one shared `record()` call** in the service ring, invoked by the use case that owns the change, to durable queryable storage, carrying the correlation id. +- **Errors:** the domain raises failures in domain terms; the controller ring is the single place that maps them onto the *Error responses* envelope — one shape app-wide. -### Integrations +## Integrations Treat every external API, callback, webhook, queue, and event as untrusted and unreliable. The integration code is a repo-ring adapter; the decisions it enforces belong to the domain. -- **Idempotency:** handle repeated requests, retries, and replays without duplicating actions or overwriting valid results. Use the primary business record id as the idempotency key unless a clearer business key exists. -- **Concurrency:** assume several workers may process the same record at once. Use conditional updates, locking, transactions, or version checks. +- **Idempotency:** handle repeated requests, retries, and replays without duplicating actions or overwriting valid results; key on the primary business record id unless a clearer business key exists. +- **Concurrency:** assume several workers may process the same record at once; use conditional updates, locking, transactions, or version checks. - **Validation:** validate structure, required fields, types, business rules, authenticity, and ownership before sending or applying anything. - **Ordering:** where order matters, process by event time, sequence/version number, or business rule — not arrival order. -- **Failure handling:** classify failures as transient, permanent, invalid, unsupported, duplicate, or unknown. Retry only transient ones, with bounded retries and backoff, and a defined final-failure path. -- **Unclear outcomes:** never treat a timeout, transport error, malformed or unexpected response, or ambiguous result as success. Preserve existing valid data and route the outcome to reconciliation or manual recovery. -- **Gate a risky integration behind a default-off flag with a no-op sink.** An integration that spends money or reaches real users (SMS/email/payment/push) ships behind a **default-off** validated-config boolean (root *Configuration*), read in **one** place, that routes to a **stdout / no-op sink** when off. Exercise it against the sink until you flip the flag on per-environment; flipping it back off is the instant rollback. (This flag underpins the optional **test-mode** and **otp-auth** add-ons — see `add-ons/`.) +- **Failure handling:** classify failures as transient, permanent, invalid, unsupported, duplicate, or unknown; retry only transient ones, with bounded retries, backoff, and a defined final-failure path. +- **Unclear outcomes:** never treat a timeout, transport error, or malformed/ambiguous response as success; preserve existing valid data and route the outcome to reconciliation or manual recovery. +- **Risky integrations** — anything that spends money or reaches real users (SMS/email/payment/push) — ship behind a **default-off** validated-config boolean (root *Configuration*), read in one place, routing to a no-op sink when off. Exercise against the sink until the flag flips on per environment; flipping it back off is the instant rollback. This flag underpins the **test-mode** and **otp-auth** add-ons (`add-ons/`). -### Security baseline +## Security baseline -The edge already validates input (the handler validates input; *Endpoint contract* defines validation rules) and places authorisation (edge guards reject unauthenticated requests; rule-level authz that depends on domain state lives in the domain / use case — see *Cross-cutting → Auth*). Add the rules that aren't yet stated: +Beyond edge validation and the auth guards above: -- **Parameterised data access:** pass query parameters as bound values; never interpolate request data into a query/filter string. -- **Secrets from the environment:** secrets and config come from the environment, never hardcoded, committed, or echoed in errors/logs; inner rings receive config as injected values, not by reading globals. -- **Ownership:** verify ownership on every client-supplied id before acting on the record — make this explicit for ordinary requests, not just the webhooks the *Integrations* rules already cover. -- **Security response headers:** send the standard HTTP hardening headers — transport security, content-type and framing protections, a referrer policy, and (where the app serves HTML) a **Content-Security-Policy** — from one shared place. Roll out a new or tightened CSP in **report-only** mode first, then promote it to enforcing once the violation reports are clean; an enforcing CSP shipped blind breaks inline styles and third-party embeds. (Exact header set + mechanism: the active stack pack.) -- **Guard server-side requests to user-supplied URLs (SSRF):** when the app fetches a URL a user or admin configured (a webhook target, an import source), restrict it to allowed schemes and **public** hosts and reject internal targets (loopback, private, link-local, cloud-metadata) **before** the request leaves — validated when the URL is saved *and* re-checked at call time. (Concrete check: the active stack pack.) -- **Secrets stored through the API are write-only:** never return a stored secret on read — expose only a "configured" indicator — and treat a blank value on update as "keep the existing secret", so re-saving a form never clears one the user didn't retype. (Concrete masking/merge: the active stack pack.) +- **Parameterised data access:** pass query parameters as bound values; never interpolate request data into a query or filter string. +- **Secrets from the environment:** never hardcoded, committed, or echoed in errors/logs; inner rings receive config as injected values, not by reading globals. +- **Ownership:** verify ownership on every client-supplied id before acting on the record — on ordinary requests, not just the webhooks *Integrations* covers. +- **Security response headers:** send the standard hardening headers — transport security, content-type and framing protections, a referrer policy, and (where the app serves HTML) a **Content-Security-Policy** — from one shared place. Roll out a new or tightened CSP **report-only** first; promote to enforcing once the violation reports are clean. Exact header set + mechanism: the active stack pack. +- **SSRF guard on user-supplied URLs:** when the app fetches a URL a user or admin configured (a webhook target, an import source), allow only permitted schemes and **public** hosts; reject loopback, private, link-local, and cloud-metadata targets before the request leaves — validated when the URL is saved *and* re-checked at call time. Concrete check: the active stack pack. +- **Secrets stored through the API are write-only:** never return a stored secret on read — expose only a "configured" indicator — and treat a blank value on update as "keep the existing secret". Concrete masking/merge: the active stack pack. ## Coding standards -- **Don't reinvent libraries** (full rule: root `CLAUDE.md` Principles, *Don't reinvent existing solutions*). Backend specifics worth repeating in-context: dates/timezones, phone canonicalisation, identifiers, CSV, and schema validation all use an established library and a single shared helper — never a hand-rolled one. -- **Schema changes are reversible migrations under `db/`** — never issue DDL or alter schema from application code; the repo ring's adapters read the schema, they don't mutate it. See `db/CLAUDE.md`. +- Dates/timezones, phone canonicalisation, identifiers, CSV, and schema validation use an established library via a single shared helper — never hand-rolled (root *Don't reinvent existing solutions*). +- Schema changes are reversible migrations under `db/` — never issue DDL or alter schema from application code; repo adapters read the schema, never mutate it. See `db/CLAUDE.md`. diff --git a/db/CLAUDE.md b/db/CLAUDE.md index 1df008e..b087017 100644 --- a/db/CLAUDE.md +++ b/db/CLAUDE.md @@ -1,22 +1,24 @@ # Database -The database contract — read before touching anything under `db/`. Repo-wide rules (principles, workflow, cross-app standards) live in the root `CLAUDE.md`. This file governs migrations and the seed/reset scripts that share this folder. +The database contract — read before touching anything under `db/`: the migrations in `db/migrations/` (its `README.md` marks the folder; they run via the root `migrate` command) and the seed/reset scripts beside them. Stack pack adopted? Read its `db.md` appendix first — precedence rules in `stacks/README.md`. -Migrations are one of the highest-risk surfaces in any project — irreversible data loss, table locks, ordering collisions, and prod/dev divergence all originate here. The rules below are checkable and client-agnostic (no specific migration tool is assumed). **If a stack pack is adopted (a single directory kept under `stacks/`), also read its `db.md` appendix before working here** — it binds these rules to the concrete tool, and its conflict register resolves any disagreement with this file, for that stack only. +## Migration rules -`db/` is the shared home for **migrations** (under `db/migrations/`) and related **seed/reset** scripts. The backend's repo-ring adapters read from the database at runtime (see `apps/backend/CLAUDE.md`); the migrations here are applied via the root `migrate` command (see root `CLAUDE.md`). +- **Ordered, timestamp-prefixed naming.** Name each file with a timestamp prefix and a short description (e.g. `20260601120000_add_orders_table`), monotonic and never reused — a timestamp, never a hand-incremented sequence, which parallel branches will both claim. Before merging, rebase onto trunk and confirm your migration still sorts after every migration already there. +- **Reversible, or justified.** Every migration pairs an `up` with a `down` OR carries an explicit irreversible-change justification comment. Never neither. +- **Never edit an applied migration.** Once merged or applied anywhere, a migration is immutable — fix forward with a new one. +- **Separate schema from data.** Keep schema migrations apart from data backfills; make backfills batched, idempotent, and resumable so an interrupted run never half-applies. +- **Expand → migrate → contract.** Split non-additive or destructive changes (DROP COLUMN/TABLE, NOT NULL on a populated table, type narrowing) across separate migrations/releases so a rollback never loses data: add the new shape, migrate onto it, remove the old shape once nothing reads it. +- **Prove the down path.** Before merging, run the migration up, then down, then up again on a throwaway scratch DB and confirm a clean round-trip — a down script present but untested proves nothing. State the evidence you observed. +- **Transactional where supported.** Run each migration in a transaction where the engine supports it, so a failure rolls back instead of leaving the schema half-changed. +- **Seed/reset are non-production only.** Seed and reset scripts are idempotent and run only against local/throwaway databases. Seed realistic, named accounts and content (not `user1`/`user2`) so manual and e2e testing exercises lifelike data; if the **test-mode** add-on is adopted, those accounts back its test-user picker (`add-ons/test-mode/`). -## Migration rules +## Schema rules -- **Ordered, timestamp-prefixed naming.** Name each file with a timestamp/sequence prefix and a short description (e.g. `20260601120000_add_orders_table`), monotonic and never reused. Use a timestamp, never a hand-incremented sequence — parallel branches must never both claim the same number. Before merging, rebase onto trunk and re-check that your migration still sorts after every migration already on trunk. -- **Reversible, or justified.** Every migration is reversible — an `up` paired with a `down` — OR carries an explicit irreversible-change justification comment. Never neither. -- **Never edit an applied migration.** Once a migration is merged or applied anywhere, treat it as immutable — fix forward with a new migration. Editing an applied migration is the single most common way an agent corrupts a shared or production database. -- **Separate schema from data.** Keep schema migrations apart from data backfills. Make backfills batched, idempotent, and resumable, so a large or interrupted run never half-applies. -- **Expand → migrate → contract for destructive changes.** For non-additive or destructive changes (DROP COLUMN/TABLE, NOT NULL on a populated table, type narrowing), split the work across separate migrations/releases so a rollback never loses data: add the new shape, migrate onto it, then remove the old shape once nothing reads it. -- **Prove the down path, not just the up.** Before merging, run the migration up, then down, then up again on a throwaway scratch DB and confirm a clean round-trip — don't rely on the down script being present but untested. State the evidence you observed. -- **Transactional where supported.** Run each migration in a transaction where the engine supports it, so a failed migration rolls back instead of leaving the schema half-changed. -- **Seed/reset are non-production only.** Seed and reset scripts are idempotent and run only against local/throwaway databases — never against a shared or production database. Prefer **realistic, named** seed accounts and content (not `user1` / `user2`) so manual and e2e testing exercises lifelike data. (If the project adopts the **test-mode** add-on, those seeded accounts also back its test-user picker — see `add-ons/test-mode/`.) +- **Money and quantities are exact types** — integer minor units or fixed-precision decimal, never floats. +- **Every table carries created/updated timestamps** in UTC-aware types. +- **Unique constraints encode business invariants.** A uniqueness violation maps to the domain conflict error — HTTP 409 at the edge. ## Shared DB across worktrees -The local DB is **global state** shared across worktrees by a fixed name (see *Working in a git worktree* in root `CLAUDE.md`). A migration, reset, or seed run in one worktree changes the schema every other worktree's app depends on. Run round-trip and destructive checks against a throwaway DB, never the shared one, while a parallel worktree depends on the current schema. +The local DB is global state, shared across worktrees by a fixed name (worktree mechanics: root `CLAUDE.md`). A migration, reset, or seed run in one worktree changes the schema every other worktree's app depends on. Never run a reset or destructive check against the shared DB while a parallel worktree depends on the current schema — use a throwaway DB. diff --git a/db/migrations/README.md b/db/migrations/README.md index eb677ba..0294a0e 100644 --- a/db/migrations/README.md +++ b/db/migrations/README.md @@ -1,3 +1,3 @@ # migrations -Migrations live here — rules in `../CLAUDE.md`. +Migration files land here — empty until the first migration (see the Day-1 checklist in the root `README.md`). Rules: `../CLAUDE.md`. From a94ae61069518c7ce403ef0924f3f6b44623b0c5 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:38:53 +0800 Subject: [PATCH 06/12] docs: spec convention escape hatch, mockup lifecycle single owner, OTP hardening specs/README admits spec-tool directories; design/README owns the mockup loop; otp-auth gains CSPRNG/single-use/bounded-TTL/max-attempts rules (F-005). Co-Authored-By: Claude Fable 5 --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- add-ons/README.md | 2 +- add-ons/otp-auth/README.md | 4 ++++ design/README.md | 15 +++++++-------- specs/README.md | 8 ++++---- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3c1bc60..01127fc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,8 +2,8 @@ -Spec: specs/ - +Spec: specs/ + ## Test plan diff --git a/add-ons/README.md b/add-ons/README.md index 985120f..1560ec4 100644 --- a/add-ons/README.md +++ b/add-ons/README.md @@ -15,7 +15,7 @@ A directory `add-ons//` with a `README.md` of agnostic guidance. `` ## Opt in — adoption is keeping the directory -Keep the add-ons you want under `add-ons/`, delete the directories you don't. Opting out *is* deleting the directory; every directory still present is adopted. The Day-1 checklist (root `README.md`) is where a fresh project chooses. +Keep the add-ons you want under `add-ons/`; opting out *is* deleting the directory — every directory still present is adopted. The Day-1 checklist (root `README.md`) is where a fresh project chooses. Activation is by instruction: the root `CLAUDE.md` tells agents to read every kept add-on's `README.md` and follow it when touching the capability it covers. Add-ons are cross-cutting (backend + frontend + db at once), so the pointer lives in the always-loaded root file rather than a per-area one. The README under `add-ons/` is the single source of truth — edit it in place; there is no generated copy. diff --git a/add-ons/otp-auth/README.md b/add-ons/otp-auth/README.md index 0cd457a..7f278fc 100644 --- a/add-ons/otp-auth/README.md +++ b/add-ons/otp-auth/README.md @@ -21,6 +21,10 @@ One-time-code auth: a user proves control of a phone or email by entering a code ## Make it robust +- **Generate codes with a CSPRNG, minimum 6 digits** — never `Math.random()` or anything timestamp-derived. +- **A code is single-use** — consume it on a successful verify; a consumed code never verifies again. +- **TTL is minutes, not hours.** +- **Cap failed verify attempts per challenge** (e.g. 5); past the cap, invalidate the challenge and require a fresh send. - **Idempotent verify.** A retry or double-submit must never create a second account or double-consume. Put a unique constraint on the natural key (target + purpose) so the race resolves to `409`, and have the client treat `409` as "already done, proceed". - **A knowable test code.** Gate a knowable code behind **test mode** (a logged real code, or a fixed code valid *only* in test mode) so the flow is walkable without a live provider. The verify path still runs — only delivery is stubbed. - **Log every send and verify** with `{purpose, masked target, test-mode, provider status, correlation id}` — never the code or full contact. diff --git a/design/README.md b/design/README.md index 3fa475b..e278447 100644 --- a/design/README.md +++ b/design/README.md @@ -1,10 +1,9 @@ # design — UI mockup / design reference **Reference only — not part of the buildable workspace.** Drop generated or -hand-made design mockups here and use them as the source for visual design, screen -inventory, copy, and flows. **Do not copy the mockup code** into the apps (its -framework is usually not the app stack). See the *UI mockup / design reference* -section in the root `CLAUDE.md`. +hand-made design mockups here; they are the source for visual design, screen +inventory, copy, and flows. **Never copy mockup code** into the apps (its +framework is usually not the app stack). This folder also holds the **design guide** — `design-guide.html` + `tokens.css`, the visual keystone confirmed before any UI work (`apps/frontend/CLAUDE.md` → @@ -14,7 +13,7 @@ the visual keystone confirmed before any UI work (`apps/frontend/CLAUDE.md` → Keep mockups findable so "point the relevant mockup at the spec" is mechanical, not a hunt. -- **Inventory table (below) is the index — keep it current** as screens are added. Columns: **screen** (semantic name) · **mockup file/folder** · **owning spec**. The *screen* name must match its name in the central route registry (`apps/frontend/CLAUDE.md`), so a screen, its mockup, and its URL cross-reference through the registry — which stays the **only** route→URL surface (the frontend file forbids a second list, so this table carries no route column). +- **The inventory table (below) is the index — keep it current** as screens are added. Columns: **screen** (semantic name) · **mockup file/folder** · **owning spec**. The *screen* name must match its name in the central route registry (`apps/frontend/CLAUDE.md`), so a screen, its mockup, and its URL cross-reference through the registry — which stays the **only** route→URL surface, so this table carries no route column. - **One file or folder per screen, named by the screen's semantic name** (matching the route registry) — never by tool export names like `screen-3-final-v2`. - **Flows** are shown by mockup ordering/links or an optional flow file — don't mandate separate flow diagrams. @@ -24,7 +23,7 @@ Keep mockups findable so "point the relevant mockup at the spec" is mechanical, ## Building from a mockup -A screen's **initial build** is verified against its design reference — never declared done from reading the code. Mockups guide that first build only: once a screen is iterated and improved it drifts from the mockup by design, so **later changes are verified against the running app, not re-checked against the mockup**. +Mockups guide a screen's **initial build only**. Verify that build against the mockup by rendering and looking — never declare it done from reading the code. After the first build the screen drifts from the mockup by design: **later changes are verified against the running app, not the mockup.** -- **With a mockup — verify, don't assume.** After first building a screen that has a mockup, run the app and view the built screen at the project's declared primary form factor plus the responsive baseline's other end (e.g. mobile + desktop), compare against the mockup, and iterate until layout, spacing, visual hierarchy, and copy match. **Capture and actually look at the rendered output** (a screenshot or equivalent) — don't reason about the code and declare it done. This is the success marker for the screen's **initial build** (ties to root `CLAUDE.md` *Goal-driven execution*). The capture/visual-diff tool is a per-project choice; the view-and-compare-against-reference step is mandatory regardless of tool. -- **Without a mockup — don't invent silently.** For a non-trivial **new** screen with no mockup, sketch the screens/copy/flow in the feature's spec under `specs/` and get it approved there before building — reuse the existing spec gate, don't create a second approval process. For minor changes to an existing screen, build to the conventions in `apps/frontend/CLAUDE.md` and note in the PR that it was "built to convention, no mockup." Never improvise UI for a non-trivial new screen with no reference. +- **With a mockup — verify, don't assume.** After first building the screen, run the app and view it at the project's declared primary form factor plus the responsive baseline's other end (e.g. mobile + desktop); compare against the mockup and iterate until layout, spacing, visual hierarchy, and copy match. **Capture and actually look at the rendered output** (a screenshot or equivalent). The capture/visual-diff tool is a per-project choice; the view-and-compare step is mandatory. +- **Without a mockup — don't invent silently.** For a non-trivial **new** screen, sketch the screens/copy/flow in the feature's spec under `specs/` and get it approved there — reuse the spec gate, no second approval process. Never improvise UI for a non-trivial new screen with no reference. For minor changes to an existing screen, build to the conventions in `apps/frontend/CLAUDE.md` and note in the PR that it was "built to convention, no mockup." diff --git a/specs/README.md b/specs/README.md index f92d5cc..691efe2 100644 --- a/specs/README.md +++ b/specs/README.md @@ -4,19 +4,19 @@ Non-trivial features start from a short written spec here before implementation. ## Convention -- One file per feature: `YYYY-MM-DD-.md` +- One dated file per feature by default: `YYYY-MM-DD-.md`. A spec tool may use numbered feature directories instead (`NNN-/` holding `spec.md` and its artifacts) — the content rules below apply per spec either way. - Tag user stories with priority: `P1` = MVP (must ship), `P2` = next, `P3` = nice-to-have -- Each story is independently shippable — avoid cross-story coupling that breaks that independence +- Each story is independently shippable — no cross-story coupling that breaks that independence - P1 stories alone form a viable MVP ## What goes in a spec 1. **Goal** — one sentence on what this builds and why -2. **User stories** — priority-tagged; each carries acceptance criteria *and how each one will be verified* (the exact command, endpoint call, or screen + states to exercise). For a UI story that **builds a new screen**, name the specific `design/` mockup file(s) it implements (screen, copy, and flow); "matches the referenced mockup" is part of done for that initial build (later iterations drift from the mockup by design — they verify against the running app). If no mockup exists for a new screen, record it under **Open questions** and resolve it before implementation rather than inventing the design. +2. **User stories** — priority-tagged; each carries acceptance criteria *and how each one will be verified* (the exact command, endpoint call, or screen + states to exercise). A story that builds a new screen names the `design/` mockup file(s) it implements; verification per `design/README.md`; no mockup → record under **Open questions** before implementation. 3. **Out of scope** — explicit list of what this spec does not cover 4. **Open questions** — decisions not yet made, with a deadline or owner 5. **UX & non-functional notes** — for UI-touching specs: primary form factor impact, states (loading/error/empty), and any perf/security constraints; one short list, not an essay ## Workflow -Feature work starts with a spec. Once approved, create a short-lived branch and open a PR that links the spec. A story is done only when every acceptance criterion has been demonstrated by its stated verification; capture that evidence in the PR's Test plan checklist. When merged, the spec stays as a record of the decision. +Once the spec is approved, create a short-lived branch and open a PR that links the spec. A story is done only when every acceptance criterion has been demonstrated by its stated verification; capture that evidence in the PR's Test plan checklist. When merged, the spec stays as a record of the decision. From f992cb8c7b8e0766d655e6cd22a96339d425ed69 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:38:53 +0800 Subject: [PATCH 07/12] =?UTF-8?q?feat(brand):=20Cavalry=20front=20door=20?= =?UTF-8?q?=E2=80=94=20lockup,=20MIT=20license,=20guide=20attribution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README rewritten as the public front door with theme-aware Cavalry lockup; design guide carries the mark and attribution from local assets only. Co-Authored-By: Claude Fable 5 --- LICENSE | 21 ++++++++ README.md | 56 +++++++++++++-------- design/brand/cavalry-lockup-dark-1600.png | Bin 0 -> 16747 bytes design/brand/cavalry-lockup-light-1600.png | Bin 0 -> 16788 bytes design/brand/cavalry-mark-dark.svg | 5 ++ design/brand/cavalry-mark-light.svg | 5 ++ design/design-guide.html | 5 +- 7 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 LICENSE create mode 100644 design/brand/cavalry-lockup-dark-1600.png create mode 100644 design/brand/cavalry-lockup-light-1600.png create mode 100644 design/brand/cavalry-mark-dark.svg create mode 100644 design/brand/cavalry-mark-light.svg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..53ee9ae --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cavalry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 24d1605..544f374 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# cavalry-template-spa + + + Cavalry + **An opinionated template for spinning up production-ready, full-stack projects — fast, and without re-litigating a single engineering decision.** -Every new Cavalry Collective project starts here. Clone it, run the Day-1 checklist once, and start shipping features the same day — with the architecture, quality gates, and conventions of a mature codebase already in force. +Every new Cavalry project starts here. Clone it, run the Day-1 checklist once, and start shipping features the same day — with the architecture, quality gates, and conventions of a mature codebase already in force. ## Why this exists @@ -13,8 +16,8 @@ The contracts are written for humans **and** for AI agents. An agent working in ## The ideology - **Opinionated where it matters, agnostic where it doesn't.** The base contracts pin the *shape* of the system — rings, layers, envelopes, gates — and deliberately not the framework. A **stack pack** (`stacks/`) then binds those contracts to one concrete stack, resolving every disagreement in an explicit conflict register. No silent contradictions. -- **Simplicity first.** The minimum code that solves the problem; every added abstraction must defeat the simpler alternative on the record. If 200 lines could be 50, it's 50. -- **Quality is a gate, not a vibe.** Nothing is "done" until it's been run and observed: four data states exercised, endpoints hit, migrations round-tripped, screens checked at 320 px. The design guide (`design/`) locks the visual system *before* the first screen is built. +- **Simplicity first.** The minimum code that solves the problem; every added abstraction must beat the simpler alternative on the record — and shorter wins. +- **Quality is a gate, not a vibe.** Nothing is "done" until it's been run and observed: four data states exercised, endpoints hit, migrations round-tripped, screens checked at the narrowest supported width. The design guide (`design/`) locks the visual system *before* the first screen is built. - **Spec-first, independently shippable slices.** Non-trivial work starts as a short written spec under `specs/`; P1 stories alone form a viable MVP. Trunk stays releasable, history stays linear. - **Instructions over machinery.** The template carries no build scripts, hooks, or generated artifacts — just precise instructions in the files agents and humans already read. What you see is the whole mechanism. @@ -35,12 +38,12 @@ That's it. There is nothing to install and no generator to run — the template | `CLAUDE.md` | Root architecture principles and workflow | | `apps/backend/` | Backend app — onion architecture (Domain → Service → Repo → Controller) | | `apps/frontend/` | Frontend SPA — layered store / services / pages / components | -| `db/` | Database & migration contract (`db/CLAUDE.md`) with reversible migrations under `db/migrations/` | -| `infra/` | Terraform infrastructure (GCP-first conventions; adaptable) | -| `design/` | UI mockups + the **design guide** (`design-guide.html` + `tokens.css`) — "Keystone", the visual keystone confirmed before UI work: design principles + full foundations as a token-driven SaaS system (Cavalry palette by default), rebranded per project; components deliberately left flexible; reference, not part of the build | +| `db/` | Database & migration contract, reversible migrations under `db/migrations/` | +| `design/` | UI mockups + the **Keystone design guide** — confirmed before any UI work (see step 10) | +| `infra/` | Terraform conventions and guardrails | | `specs/` | Feature specs — written before implementation | -| `stacks/` | Optional stack packs — appendix docs binding the agnostic contracts to one concrete stack; one chosen at instantiation, the rest deleted. See [`stacks/README.md`](stacks/README.md) | -| `add-ons/` | Optional capability add-ons — agnostic patterns you opt into at Day-1 (test mode, OTP login); the active stack pack supplies their concrete bindings. See [`add-ons/README.md`](add-ons/README.md) | +| `stacks/` | Optional stack packs — one chosen at instantiation, the rest deleted ([`stacks/README.md`](stacks/README.md)) | +| `add-ons/` | Optional capabilities — test mode, OTP login — opted into at Day-1 ([`add-ons/README.md`](add-ons/README.md)) | | `.github/workflows/` | CI and deploy stubs — fill in your toolchain commands | | `project.code-workspace` | VS Code workspace (hides agent worktrees from search and watchers) | @@ -60,7 +63,7 @@ Or choose a stack pack under `stacks/` (e.g. `nextjs-nestjs-postgres`) for a vet ## Day-1 checklist -Run this once, top to bottom, the first time you instantiate the template. Each step names the file and the marker to replace. The placeholders are grep-able: `` in the root `CLAUDE.md` command block, `FILL IN ON SETUP` in `apps/frontend/CLAUDE.md`, and `TODO: replace` in the `.github/workflows/` stubs. Step 13 checks they are all gone. +Run this once, top to bottom, the first time you instantiate the template. Each step names the file and the marker to replace. The placeholders are grep-able: `` in the root `CLAUDE.md` command block, `FILL IN ON SETUP` in `apps/frontend/CLAUDE.md`, and `TODO:` in the `.github/workflows/` stubs. Step 14 checks they are all gone. 1. **Create the repo.** Click **Use this template** → **Create a new repository** on GitHub. 2. **Clone** your new repo. @@ -71,26 +74,35 @@ Run this once, top to bottom, the first time you instantiate the template. Each - [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) — frontend layering and conventions - [`db/CLAUDE.md`](db/CLAUDE.md) — database & migration contract - [`infra/CLAUDE.md`](infra/CLAUDE.md) — Terraform authoring style and guardrails -5. **Choose a stack pack — or stay agnostic.** +5. **Start with a clean `specs/`.** If numbered spec directories from the template's own development are present under `specs/`, delete them (keep `specs/README.md`) — they document building this template, not your project. +6. **Choose a stack pack — or stay agnostic.** - **Pack path (fast):** pick the pack under `stacks/` matching your stack (e.g. `nextjs-nestjs-postgres`), then: - `rm -rf` every other `stacks/*` directory — the one pack left is the adopted one; each area's `CLAUDE.md` already points agents at its appendices (mechanism: `stacks/README.md` *Activation*). - Copy the pack README **dev** command block into the root `CLAUDE.md` "Common commands" placeholder (delete the banner); copy its **CI** block into `.github/workflows/ci.yml`. They are different blocks — never paste a dev-only migration command into CI. - Record the choice in root `CLAUDE.md` **Learnings**: `Stack: ; appendices under stacks//`. - - **Agnostic path:** keep `stacks/` for reference (or delete it) and fill in the toolchain yourself — see step 6. -6. **Choose your add-ons.** Under `add-ons/`, keep the optional capabilities you want (`test-mode`, `otp-auth`, …) and **delete the directories you don't** — every directory kept is adopted, and the root `CLAUDE.md` points agents at each kept add-on's README. The active stack pack supplies each adopted add-on's concrete bindings. See [`add-ons/README.md`](add-ons/README.md). -7. **Fill the toolchain placeholders** (agnostic path; the pack does this for you in step 5): + - **Agnostic path:** keep `stacks/` for reference (or delete it) and fill in the toolchain yourself — see step 8. +7. **Choose your add-ons.** Under `add-ons/`, keep the optional capabilities you want (`test-mode`, `otp-auth`, …) and **delete the directories you don't** — every directory kept is adopted, and the root `CLAUDE.md` points agents at each kept add-on's README. The active stack pack supplies each adopted add-on's concrete bindings. See [`add-ons/README.md`](add-ons/README.md). +8. **Fill the toolchain placeholders** (agnostic path; the pack does this for you in step 6): - Root `CLAUDE.md` "Common commands" — replace the seven ``/`TODO` commands and delete the PLACEHOLDER banner. - `.github/workflows/ci.yml` — replace the TODO steps with real install/lint/typecheck/test/build, plus the i18n key-parity check and migration up/down round-trip. - - `.github/workflows/deploy.yml` — replace the TODO step. + - `.github/workflows/deploy.yml` — replace the TODO step (keep the `event == 'push'` guard in the job condition — it prevents fork PRs from triggering a deploy). - Add a real `.env.example` (already whitelisted in `.gitignore`). -8. **Declare the primary form factor.** In `apps/frontend/CLAUDE.md`, fill in the form-factor line: +9. **Declare the primary form factor.** In `apps/frontend/CLAUDE.md`, fill in the form-factor line: ```markdown **Primary form factor (FILL IN ON SETUP):** `` ``` -9. **Rebrand & confirm the design guide — before building any screen.** The template ships **Keystone** (`design/design-guide.html` + `design/tokens.css`): design principles plus the full foundations — colour, type, spacing, layout, elevation, motion, states, content, data formatting — as a token-driven SaaS system shipping the Cavalry palette by default (components deliberately left flexible per app). Rebrand it — edit the **primitive** tier in `tokens.css`, or have your AI assistant regenerate it from your brand — then open the guide in a browser and confirm it reads as one coherent system. This is the visual keystone gate (`apps/frontend/CLAUDE.md` → *Design guide*); the app's token source and `atoms/` then implement what it shows — don't build screens against an unconfirmed system. -10. **Copy runtime config.** Copy any gitignored runtime config (`.env`, secrets) into your local checkout — it is not carried over from the template. -11. **Protect `main`.** Add a branch protection rule / ruleset requiring the CI workflow to pass before merge. Trunk must stay releasable — and on packs whose pipeline ships whatever lands on `main` (e.g. `vercel`), green-CI-before-merge *is* the deploy gate. -12. **Stand up staging (if your pack defines one).** Bring up the persistent preview/staging environment your stack pack specifies before feature work — for the `vercel` pack that is the `develop` branch plus its dedicated Neon branch (`stacks/vercel/infra.md` → *Staging environment*), migrated with the same manual runbook as prod (`stacks/vercel/db.md` → *Production & staging migrations*). -13. **Confirm green.** Push and watch the first CI run pass. Then confirm no placeholder survives — both must return nothing: `grep -rn 'FILL IN ON SETUP\|TODO: replace' . --exclude-dir=stacks --exclude-dir=specs --exclude-dir=.git --exclude=README.md` and `grep -n '^ ' CLAUDE.md`. (This README's own checklist names the markers, so it is excluded; delete it once instantiation is done if you prefer a clean tree.) +10. **Rebrand & confirm the design guide — before building any screen.** The template ships **Keystone** (`design/design-guide.html` + `design/tokens.css`): design principles plus the full foundations — colour, type, spacing, layout, elevation, motion, states, content, data formatting — as a token-driven SaaS system shipping the Cavalry palette by default (components deliberately left flexible per app). Rebrand it — edit the **primitive** tier in `tokens.css`, or have your AI assistant regenerate it from your brand — then open the guide in a browser and confirm it reads as one coherent system. This is the visual keystone gate (`apps/frontend/CLAUDE.md` → *Design guide*); the app's token source and `atoms/` then implement what it shows — don't build screens against an unconfirmed system. Replace the Cavalry brand assets under `design/brand/` with your own (and swap the lockup at the top of this README). +11. **Copy runtime config.** Copy any gitignored runtime config (`.env`, secrets) into your local checkout — it is not carried over from the template. +12. **Protect `main`.** Add a branch protection rule / ruleset requiring the CI workflow to pass before merge. Trunk must stay releasable — and on packs whose pipeline ships whatever lands on `main` (e.g. `vercel`), green-CI-before-merge *is* the deploy gate. +13. **Stand up staging (if your pack defines one).** Bring up the persistent preview/staging environment your stack pack specifies before feature work — for the `vercel` pack that is the `develop` branch plus its dedicated Neon branch (`stacks/vercel/infra.md` → *Staging environment*), migrated with the same manual runbook as prod (`stacks/vercel/db.md` → *Production & staging migrations*). +14. **Confirm green.** Push and watch the first CI run pass. Then confirm no placeholder survives — all three must return nothing: `grep -rn 'FILL IN ON SETUP' . --exclude-dir=stacks --exclude-dir=specs --exclude-dir=.git --exclude=README.md`, `grep -n 'TODO:' .github/workflows/*.yml`, and `grep -n '^ ' CLAUDE.md`. (This README's own checklist names the markers, so it is excluded; delete it once instantiation is done if you prefer a clean tree.) -> If you chose the server-first `nextjs-nestjs-postgres` pack, soften the SPA framing the base ships agnostic: root `CLAUDE.md` "the single-page app" → "the web frontend", and the **What's included** "Frontend SPA" row above → "Frontend (server-first Next.js)". The repo name still encodes "spa" and is immutable — accepted as stale. +> If you chose the server-first `nextjs-nestjs-postgres` pack, soften the SPA framing the base ships agnostic: root `CLAUDE.md` "the single-page app" → "the web frontend", and the **What's included** "Frontend SPA" row above → "Frontend (server-first Next.js)". + +## License + +MIT © [Cavalry](LICENSE). Projects created from this template may keep or replace the license — the template itself stays free to use, copy, and adapt. + +--- + +Built and maintained by **Cavalry** — an opinionated software engineering team. This template is how we start everything we ship. diff --git a/design/brand/cavalry-lockup-dark-1600.png b/design/brand/cavalry-lockup-dark-1600.png new file mode 100644 index 0000000000000000000000000000000000000000..8f2d5585a7efb6926d9f9873c1517661e9fd5a83 GIT binary patch literal 16747 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 He*1p`x6H-% literal 0 HcmV?d00001 diff --git a/design/brand/cavalry-lockup-light-1600.png b/design/brand/cavalry-lockup-light-1600.png new file mode 100644 index 0000000000000000000000000000000000000000..24010d6c87f8d207bd17bf4ab4e0beceaf798e7e GIT binary patch literal 16788 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 literal 0 HcmV?d00001 diff --git a/design/brand/cavalry-mark-dark.svg b/design/brand/cavalry-mark-dark.svg new file mode 100644 index 0000000..18d835b --- /dev/null +++ b/design/brand/cavalry-mark-dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/brand/cavalry-mark-light.svg b/design/brand/cavalry-mark-light.svg new file mode 100644 index 0000000..62f5a9b --- /dev/null +++ b/design/brand/cavalry-mark-light.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/design-guide.html b/design/design-guide.html index 55f7109..a649902 100644 --- a/design/design-guide.html +++ b/design/design-guide.html @@ -342,11 +342,14 @@
-

Keystone · design guide

+ Cavalry +

Keystone · design guide · by Cavalry

Many apps. One design philosophy.

The foundation for every product built from this template — the principles, tokens, and rules that make ten different apps feel like one practice built them. Components stay flexible per app; everything they're made of starts here.

+

Keystone is Cavalry's design foundation, shipped as this + template's default system. Rebranding it is Day-1 work; the bones stay.

Cavalry palette Token-driven From e6c60add251314e813d14197357bd0d8b48fec85 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:41:54 +0800 Subject: [PATCH 08/12] =?UTF-8?q?docs:=20README=20fresh-reader=20fixes;=20?= =?UTF-8?q?stacks=20canon=20=E2=80=94=20five=20files,=20one=20skeleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README states the JS/TS home turf, enumerates packs and add-ons, explains contract auto-loading, links Cavalry, and de-vendors the staging step. stacks/README.md now mandates five files with n/a stubs, a shared per-area skeleton incl. security bindings, replacements-only registers, standalone packs. Co-Authored-By: Claude Fable 5 --- README.md | 12 ++++++------ stacks/README.md | 36 ++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 544f374..469f90c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Every new Cavalry project starts here. Clone it, run the Day-1 checklist once, a Most project templates give you scaffolding: a folder of generated code that's stale the week after it's cut. This template ships something more durable — **contracts**. A set of `CLAUDE.md` files encode how software is built here: a backend onion with a pure domain at the centre, a layered frontend with a design-token keystone, reversible migrations, spec-first slices, and a Definition of Done where *verified means observed, not inferred*. -The contracts are written for humans **and** for AI agents. An agent working in this repo auto-loads the contract for whatever area it touches, so the hundredth feature is built to the same standard as the first — whether a person or an agent wrote it. That is the entire bet: **the biggest lever on project quality is an opinionated approach, stated where the work happens.** +The contracts are written for humans **and** for AI agents. An agent working in this repo auto-loads the contract for whatever area it touches (`CLAUDE.md` files load automatically — the root at session start, each area's the moment the agent works in that directory; every other document is read where a loaded file points at it), so the hundredth feature is built to the same standard as the first — whether a person or an agent wrote it. That is the entire bet: **the biggest lever on project quality is an opinionated approach, stated where the work happens.** ## The ideology @@ -57,9 +57,9 @@ The template is intentionally framework-agnostic. You choose: - Cloud provider and Terraform provider - Database client -Pick what fits the project. The CLAUDE.md files tell you where things go and how to structure them — not which library to use. +Pick what fits the project. The CLAUDE.md files tell you where things go and how to structure them — not which library to use. (The template is most at home in a JavaScript/TypeScript ecosystem — the toolchain verbs, parity checks, and shipped packs are JS-shaped — but the architectural contracts themselves carry to any stack.) -Or choose a stack pack under `stacks/` (e.g. `nextjs-nestjs-postgres`) for a vetted set of these choices plus copy-paste commands; the base CLAUDE.md files stay framework-agnostic. The pack is opt-in, not a mandate — see [`stacks/README.md`](stacks/README.md). +Or choose a stack pack under `stacks/` for a vetted set of these choices plus copy-paste commands. Three ship today: **`nextjs-nestjs-postgres`** (server-first Next.js · NestJS · Postgres/Prisma), **`taro-fastify-mysql-tencent`** (Taro H5 · Fastify · MySQL on Tencent Cloud), and **`vercel`** (Next.js · Fastify · Neon Postgres on Vercel). Two add-ons ship today: **`test-mode`** and **`otp-auth`**. The base CLAUDE.md files stay framework-agnostic either way — a pack is opt-in, not a mandate ([`stacks/README.md`](stacks/README.md)). ## Day-1 checklist @@ -94,15 +94,15 @@ Run this once, top to bottom, the first time you instantiate the template. Each 10. **Rebrand & confirm the design guide — before building any screen.** The template ships **Keystone** (`design/design-guide.html` + `design/tokens.css`): design principles plus the full foundations — colour, type, spacing, layout, elevation, motion, states, content, data formatting — as a token-driven SaaS system shipping the Cavalry palette by default (components deliberately left flexible per app). Rebrand it — edit the **primitive** tier in `tokens.css`, or have your AI assistant regenerate it from your brand — then open the guide in a browser and confirm it reads as one coherent system. This is the visual keystone gate (`apps/frontend/CLAUDE.md` → *Design guide*); the app's token source and `atoms/` then implement what it shows — don't build screens against an unconfirmed system. Replace the Cavalry brand assets under `design/brand/` with your own (and swap the lockup at the top of this README). 11. **Copy runtime config.** Copy any gitignored runtime config (`.env`, secrets) into your local checkout — it is not carried over from the template. 12. **Protect `main`.** Add a branch protection rule / ruleset requiring the CI workflow to pass before merge. Trunk must stay releasable — and on packs whose pipeline ships whatever lands on `main` (e.g. `vercel`), green-CI-before-merge *is* the deploy gate. -13. **Stand up staging (if your pack defines one).** Bring up the persistent preview/staging environment your stack pack specifies before feature work — for the `vercel` pack that is the `develop` branch plus its dedicated Neon branch (`stacks/vercel/infra.md` → *Staging environment*), migrated with the same manual runbook as prod (`stacks/vercel/db.md` → *Production & staging migrations*). +13. **Stand up staging (if your pack defines one).** Bring up the persistent preview/staging environment your stack pack specifies before feature work — the pack's `infra.md` names the environment and its runbook. 14. **Confirm green.** Push and watch the first CI run pass. Then confirm no placeholder survives — all three must return nothing: `grep -rn 'FILL IN ON SETUP' . --exclude-dir=stacks --exclude-dir=specs --exclude-dir=.git --exclude=README.md`, `grep -n 'TODO:' .github/workflows/*.yml`, and `grep -n '^ ' CLAUDE.md`. (This README's own checklist names the markers, so it is excluded; delete it once instantiation is done if you prefer a clean tree.) > If you chose the server-first `nextjs-nestjs-postgres` pack, soften the SPA framing the base ships agnostic: root `CLAUDE.md` "the single-page app" → "the web frontend", and the **What's included** "Frontend SPA" row above → "Frontend (server-first Next.js)". ## License -MIT © [Cavalry](LICENSE). Projects created from this template may keep or replace the license — the template itself stays free to use, copy, and adapt. +[MIT](LICENSE) © Cavalry. Projects created from this template may keep or replace the license — the template itself stays free to use, copy, and adapt. --- -Built and maintained by **Cavalry** — an opinionated software engineering team. This template is how we start everything we ship. +Built and maintained by **[Cavalry](https://cavalry.sg)** — senior engineers pairing with AI to ship software we answer for. This template is how we start everything we build. diff --git a/stacks/README.md b/stacks/README.md index 0b3dfda..47a4c43 100644 --- a/stacks/README.md +++ b/stacks/README.md @@ -1,6 +1,6 @@ # `stacks/` — stack packs -The base CLAUDE.md files are framework-agnostic on purpose. A **stack pack** binds those agnostic contracts to one concrete stack (frameworks, ORM, package manager) through appendix docs that **ride on top of** the base — they add bindings and resolve conflicts, never restate the base. One pack is chosen at instantiation; the rest are deleted. This file is the **system doc** (read once, not loaded during normal work); each pack carries its own manifest `README.md`. +The base CLAUDE.md files are framework-agnostic on purpose. A **stack pack** binds those agnostic contracts to one concrete stack (frameworks, ORM, package manager) through appendix docs that **ride on top of** the base — they add bindings and resolve conflicts, never restate the base. One pack is chosen at instantiation; the rest are deleted. This file is the system doc (read once, not loaded during normal work); each pack carries its own manifest `README.md`. ## What a pack is @@ -8,21 +8,38 @@ A pack is a directory `stacks//` of **guidance-as-text** — concrete ## Required file set -Every pack carries at least these four files (one may be thin, but all four exist); `infra.md` is the only optional fifth (see the note at the bottom): +Every pack carries all five files. **Absence is a statement**: an area that genuinely doesn't apply to the stack still ships its file, reduced to a stub that opens with the precedence line, states *what doesn't apply and why*, points at wherever the area's obligations actually live, and ends with the no-conflicts line. A missing file is a conformance failure. | File | Binds onto base file | Holds | |---|---|---| -| `README.md` | — (manifest) | identity, appendix→base mapping, suggested `` blocks, day-1 wiring, deploy-seam pointer | +| `README.md` | — (manifest) | identity, appendix→base mapping, dev + CI `` blocks, Day-1 wiring, deploy-seam pointer | | `backend.md` | `apps/backend/CLAUDE.md` | HTTP-framework bindings, DI/composition root, language-path deltas | | `frontend.md` | `apps/frontend/CLAUDE.md` | UI-framework bindings, rendering model, four-states/mutation mapping | | `db.md` | `db/CLAUDE.md` (+ repo ring) | ORM/migration bindings, schema/migration mechanics | +| `infra.md` | `infra/CLAUDE.md` | provider bindings, workload shape, deploy pipeline — or the n/a stub | + +## One shape, parallel reading + +Every area appendix follows the same skeleton, so any two packs compare side by side and a future pack starts from a known outline: + +1. **Precedence line** (verbatim, below). +2. **Binding at a glance** — the stack picks for this area, each rejected alternative named once, here and nowhere else. +3. **Structure** — how the base shape (rings, layers, folders) maps onto this stack. +4. **Bindings** — the area's concrete sections. Packs add sections only where the stack genuinely differs, and the difference is stated, never implied. +5. **Security bindings** (`backend.md` and `frontend.md`, always present) — the concrete header/CSP mechanism, the SSRF check, and secret write-only masking the base delegates to the pack. +6. **Testing** — runner and per-ring/per-layer approach. +7. **Conflict register** (last, format below). + +The manifest `README.md` follows its own fixed shape: identity (and would-be triple for platform packs) → appendix→base mapping → dev block → CI block → Day-1 wiring **including the root-CLAUDE.md Learnings entry** → deploy seam. ## Pack invariants (a pack is valid iff it satisfies all of these) - **Additions-only.** No restating base content — only (a) stack bindings and (b) explicit conflict resolutions. If a line is true without naming the stack, it does not belong. - **Register or obey.** A pack may override any base rule — even a structural one, like swapping the onion's feature-first axis for layer-first — but only through a conflict-register entry; a silent contradiction makes the pack invalid. What no pack may drop is the discipline itself: layers stay separated and dependencies point one way, whatever the axis. +- **Registers hold replacements only.** A binding that doesn't contradict the base belongs in the body, never the register — the register is the audit surface for overrides, and diluting it hides them. +- **Standalone.** A pack never references a sibling pack — siblings are deleted at instantiation, so every comparison or contrast is rewritten as a self-contained statement. - **Precedence line atop every appendix** (verbatim): `> Rides on top of the base contract; this file only adds stack bindings and resolves conflicts. Where this appendix and a base file disagree, the conflict register below wins — for this stack only.` -- **Conflict register ending every appendix.** The four registers are the single audit surface — where the appendix replaces a base statement, it is listed here, not left as a live contradiction. Each entry, ending in a checkable imperative: +- **Conflict register ending every appendix.** Bulleted entries, each ending in a checkable imperative: > **Base says:** … **In this stack:** … **Because:** … **Concretely:** … *(one DO/DON'T an agent can check or grep for)* A zero-conflict appendix states so: `_No conflicts — this appendix only adds bindings; the base contract is unchanged._` @@ -31,13 +48,12 @@ Every pack carries at least these four files (one may be thin, but all four exis ## Activation (by instruction) -Packs activate by **instruction, not machinery**. Each area's `CLAUDE.md` (`apps/backend`, `apps/frontend`, `db`, `infra`) tells the agent to read the adopted pack's matching appendix before working in that area — so a backend task pulls in `backend.md`, and only that. Adoption is structural: keep exactly **one** pack directory under `stacks/` and delete the rest, so "the adopted pack" is unambiguous. The appendix is read directly from `stacks/` — it is the single source of truth, with no generated copy to regenerate and nothing to drift. +Packs activate by **instruction, not machinery**. Each area's `CLAUDE.md` (`apps/backend`, `apps/frontend`, `db`, `infra`) tells the agent to read the adopted pack's matching appendix before working in that area — so a backend task pulls in `backend.md`, and only that. Adoption is structural: keep exactly **one** pack directory under `stacks/` and delete the rest, so "the adopted pack" is unambiguous. The appendix is read directly from `stacks/` — the single source of truth, no generated copy to drift. The pack's `README.md` is read once, at adoption, from the Day-1 checklist. ## How to add a pack -1. Create `stacks//` with the four required files. +1. Create `stacks//` with all five files, each on the skeleton above (`infra.md` may be the n/a stub). 2. Put the precedence line atop each appendix and a conflict register at the end; keep every line additions-only. -3. Write the manifest `README.md` — identity, appendix→base mapping, suggested dev + CI `` blocks, deploy-seam pointer. -4. Nothing else to wire — the per-area `CLAUDE.md` pointers pick the pack up as soon as it is the only directory under `stacks/` (see *Activation*). - -A pack MAY add an optional `infra.md` later under the same invariants (infra is cloud-shaped, not app-stack-shaped — not required in v1). +3. Write the manifest `README.md` on the manifest shape, including the Day-1 Learnings entry. +4. Add a bindings section for each add-on the template ships (see `add-ons/README.md`). +5. Nothing else to wire — the per-area `CLAUDE.md` pointers pick the pack up as soon as it is the only directory under `stacks/` (see *Activation*). From 3996f0aa2f6ae85c12e298f81d005f811a407416 Mon Sep 17 00:00:00 2001 From: DeyangChan Date: Fri, 3 Jul 2026 03:48:39 +0800 Subject: [PATCH 09/12] =?UTF-8?q?docs:=20streamline=20frontend=20and=20inf?= =?UTF-8?q?ra=20contracts=20=E2=80=94=20zero=20rules=20lost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend 5051->3643w (single-owner tier/floor/check rules, gates as the one normative digest, two rules hoisted from packs); infra 1778->1371w (GCP as replaceable default binding, Guardrails owns approval/environment rules once). Co-Authored-By: Claude Fable 5 --- apps/frontend/CLAUDE.md | 264 +++++++++++++++++++--------------------- infra/CLAUDE.md | 143 ++++++++-------------- 2 files changed, 176 insertions(+), 231 deletions(-) diff --git a/apps/frontend/CLAUDE.md b/apps/frontend/CLAUDE.md index c5350a3..fd62386 100644 --- a/apps/frontend/CLAUDE.md +++ b/apps/frontend/CLAUDE.md @@ -1,245 +1,235 @@ # Frontend -The frontend contract. Read this before touching anything under `apps/frontend/`. Repo-wide rules (principles, worktree workflow, cross-app standards) live in the root `CLAUDE.md`; this file governs how the single-page app itself is structured. **If a stack pack is adopted (a single directory kept under `stacks/`), also read its `frontend.md` appendix before working here** — it adds the concrete bindings, and its conflict register resolves any disagreement with this file, for that stack only. +The frontend contract. Read this before touching anything under `apps/frontend/`; repo-wide rules (principles, worktree workflow, cross-app standards) live in the root `CLAUDE.md`. Stack pack adopted? Read its `frontend.md` appendix first — precedence rules in `stacks/README.md`. -The frontend is organised along **two axes that never blur**: horizontal **layers** (what a piece of code *is* — store, service, page, component) and vertical **feature slices** (what business capability it serves). Components themselves follow **atomic design** — see *Component structure* below. +Two axes never blur: horizontal **layers** (what code *is* — store, service, page, component) and vertical **feature slices** (what business capability it serves). Components follow **atomic design** — see *Component structure*. ## Project structure -Mirror this shape under `apps/frontend/src/`. It is **illustrative**: the toolchain is not yet chosen (see the root `CLAUDE.md`), so treat file extensions and framework specifics as examples, not mandates. +Mirror this shape under `apps/frontend/src/`. The toolchain is not yet chosen (root `CLAUDE.md`), so file extensions and framework specifics are illustrative, not mandates. ``` src/ - store/ # state layer — one slice per domain - services/ # API clients — each domain mirrors a backend route group - pages/ # screens (atomic "pages" tier) — compose organisms, no business logic + store/ # one slice per domain + services/ # API clients — mirror backend route groups + pages/ # screens — compose organisms, no business logic components/ - atoms/ # smallest primitives, by type — Button, Input, Icon (on the headless lib) - molecules/ # small compositions of atoms, by type — FormField, SearchBar, Card + atoms/ # smallest primitives, by type (on the headless lib) + molecules/ # generic compositions, by type organisms/ - / # feature-meaningful sections, grouped by feature — BidTable, SiteHeader - templates/ # page-level layout scaffolds — the shared layout, page chrome + / # feature sections, by feature + templates/ # page-level layout scaffolds i18n/ # one dictionary per language - lib/ # genuinely shared, side-effect-light helpers - routes. # the single central route registry + lib/ # shared, side-effect-light helpers + routes. # the single route registry tokens. # the single design-token source ``` -**Grouping is set by the tier, not by preference** (full rule in *Component structure*): `atoms/` and `molecules/` are grouped **by type** and shared globally — they carry no business vocabulary; `organisms/` are grouped **by feature** — they do. A feature's vertical slice therefore spans `store/` + `services/` + `components/organisms/`, so it can still be understood, changed, and removed as a unit. Promote code into `atoms/`/`molecules/` or `lib/` only once it is genuinely shared — not in anticipation of reuse. +A feature's vertical slice spans `store/` + `services/` + `components/organisms/`, so it can be understood, changed, and removed as a unit (grouping rules: *Component structure*). Promote code into `atoms/`/`molecules/` or `lib/` only once genuinely shared — never in anticipation of reuse; co-locate a one-off helper with its only caller until reuse appears (`src/lib/` holds only genuinely shared, side-effect-light code). ## Layering -Each layer has one job, may depend only on the layers beneath it, and must never reach upward. +Each layer has one job, may depend only on the layers beneath it, and never reaches upward. Tier definitions: *Component structure*. -- **Store (`src/store/`)** — owns application state, one slice per domain. May depend on services. Must never import a page or render anything. -- **Services (`src/services/`)** — own all data fetching and mutation; each domain mirrors a backend route group. **All network access lives here**, never scattered across presentational components. May depend on `lib/`. Must never hold view state. - - **API contract.** The backend endpoint contract (see *Endpoint contract* in `apps/backend/CLAUDE.md`) is the single source of truth for request/response shapes and status codes; the service mirrors it and never invents its own shape. - - **Prefer a generated or shared contract artifact** over hand-copying when the toolchain supports it (e.g. an OpenAPI/JSON-schema document the backend emits and the frontend types against). When it doesn't, every contract change is one PR touching the backend endpoint *and* its mirroring frontend service together. - - **Validate responses against the declared shape** rather than trusting them, so a contract break surfaces as a typed error (feeding the `error` state) instead of an undefined-field render. -- **Pages (`src/pages/`)** — compose organisms into a screen (the atomic *pages* tier). **Hold no business logic;** they wire data from store/services into components. Must never fetch directly or embed reusable UI inline. -- **Templates (`src/components/templates/`)** — page-level layout scaffolds (the one shared layout, page chrome) that arrange organisms with no real data. May use organisms and primitives; hold no business logic. See *Page layout & design tokens*. -- **Organisms / feature components (`src/components/organisms//`)** — compose atoms and molecules into a feature-meaningful section. May use `atoms/`, `molecules/`, and `lib/`. Must never be imported by a primitive. -- **Shared primitives (`src/components/atoms/`, `src/components/molecules/`)** — the reusable base. May depend only on the UI library and the design tokens. Must never know about a specific feature or page. +- **Store** — application state, one slice per domain. May depend on services; never imports a page or renders anything. +- **Services** — all data fetching and mutation; each domain mirrors a backend route group; **all network access lives here**. May depend on `lib/`; never hold view state. + - The backend endpoint contract (*Endpoint contract*, `apps/backend/CLAUDE.md`) is the single source of truth for shapes and status codes; the service mirrors it, never invents its own. + - Prefer a generated or shared contract artifact (e.g. an OpenAPI/JSON-schema document the backend emits); without one, every contract change is one PR touching the backend endpoint *and* its mirroring frontend service. + - Validate responses against the declared shape so a contract break surfaces as a typed error feeding the `error` state, not an undefined-field render. +- **Pages** — compose organisms into a screen and wire data from store/services into components. No business logic; never fetch directly or embed reusable UI inline. +- **Templates** — arrange organisms with no real data; no business logic. See *Page layout & design tokens*. +- **Organisms** — may use `atoms/`, `molecules/`, and `lib/`; never imported by a primitive. +- **Shared primitives (`atoms/`, `molecules/`)** — depend only on the UI library and the design tokens; never know a specific feature or page. -Cross-cutting rules for every layer: - -- **Loading / error / empty / success states are handled consistently** — the same four states, presented the same way, on every data-backed screen. - - **An empty state is designed, not blank.** Every empty state states *why* there's nothing and offers the primary next action where one exists. Handle the cases distinctly — they differ in copy and CTA: first-run / never-created ("create your first X"), no-results / filtered-to-nothing (offer to clear filters or adjust the query), and access-restricted (explain the missing permission). A data-load **failure** is an error state, never an empty state — show a retry, not "nothing here". -- **Don't accumulate one-off helpers in `src/lib/`** — co-locate a helper with its only caller until reuse actually appears. +**Loading/error/empty/success are handled consistently on every data-backed screen** — presented the same way. An empty state is designed, not blank: state *why* there's nothing and offer the primary next action where one exists. First-run ("create your first X"), no-results/filtered-to-nothing (offer to clear filters), and access-restricted (explain the missing permission) are distinct cases — they differ in copy and CTA. A data-load **failure** is an error state with a retry, never an empty state. ## URL routing -A route is part of the app's public contract; an internal file path is an implementation detail. **Keep the two separate.** Browser URLs stay clean and human-meaningful and **never expose internal build/source paths** (no `/src/` or `/pages/` prefix in the address bar). +Browser URLs stay clean and human-meaningful and **never expose internal build/source paths** (no `/src/` or `/pages/` prefix in the address bar). -- **One central registry.** Every route lives in a single routing config (`routes.`), registered the moment its page is created — never ship a page without its route entry. Reading `routes.` is the way to audit routing; do not maintain a second route→URL list anywhere else (including this file). A CI check in the spirit of the i18n key-parity check can enforce completeness. -- **Build URLs through the registry, never by hand.** Resolve links and redirects from named routes, not by concatenating path strings — so internal structure can never leak into a URL, and renaming a route updates every link at once. +- **One central registry.** Every route lives in `routes.`, registered the moment its page is created. Audit routing by reading the registry; maintain no second route→URL list anywhere else (including this file). +- **Build URLs from named routes in the registry,** never by concatenating path strings — internal structure can't leak into a URL, and renaming a route updates every link at once. -## Design guide — the visual keystone (confirm before building UI) +## Design guide (confirm before building UI) -**Lock the visual system before building any screen.** The project's visual system lives in the **design guide** (`design/design-guide.html` — "Keystone"): the design principles plus every foundation — colour, type, spacing, layout, shape, surfaces & elevation, motion, iconography, states/focus, accessibility, content, data formatting — and the composition chapters (screen archetypes, forms, view states & feedback) — rendered live from the single design-token source (`design/tokens.css`, the seed for the app's `tokens.`). A live mirror of the tokens, not a stale screenshot. **Foundations only, by design** — components stay flexible per app and are built *from* these foundations. +**Lock the visual system before building any screen.** It lives in the design guide (`design/design-guide.html`), rendered live from the single design-token source (`design/tokens.css` — the seed for the app's `tokens.`). **Foundations only, by design** — components stay flexible per app and are built *from* these foundations. -- ***Confirm* the design guide before building screens.** It is a gate: for a new project (or a rebrand) no screen or component work starts until the guide reflects the project's brand, has been reviewed in a browser, and signed off. Once the system is established small additions don't re-gate — but a new foundational token lands in the guide first. -- **Customise by editing tokens, not screens.** A rebrand edits the **primitive** token tier — or has your AI assistant regenerate it from the brand — and the semantic tier and the whole guide re-derive. This is the "one token source, three tiers" rule below — the guide is its human-reviewable face. -- **The guide binds components without prescribing them.** Every component consumes semantic tokens, answers with the guide's state ladder and focus spec, and meets its accessibility floor; a component that violates a foundation is the defect (the DRY-gate audit under *Component structure* catches duplicates). A pattern that recurs across projects earns a specimen in the guide; a stack pack may add a Storybook against the same tokens (optional upgrade). +- **Confirm the guide first.** For a new project or rebrand, no screen or component work starts until the guide reflects the brand, is browser-reviewed, and signed off. An established system doesn't re-gate small additions — but a new foundational token lands in the guide first. +- **Customise by editing the primitive token tier, not screens;** the semantic tier and the whole guide re-derive. +- **The guide binds components without prescribing them.** Every component consumes semantic tokens, follows the guide's state ladder and focus spec, and meets its accessibility floor; a component violating a foundation is the defect. A pattern recurring across projects earns a guide specimen. -**Never-violate gates** — the build-time digest; the named guide chapter is canonical: +**Never-violate gates** — the named guide chapter is canonical: -1. Every colour, size, space, and duration resolves to a semantic token — a hex or px literal in a screen is the defect (guide → *Tokens*). -2. Pick the screen archetype before building any screen — its zones, page rhythm, and width are fixed, never re-derived per page (guide → *Screen archetypes*). -3. Surfaces follow the ladder: no card-like container inside another; separate in order whitespace → background shift → border → divider (tables/dense rows only) (guide → *Surfaces & elevation*). -4. Reuse first: archetype → documented pattern → existing screens/primitives → extend a primitive → only then new, with the PR recording why nothing fit (guide → *Components & reuse*). -5. One density app-wide, set at the token layer — never mixed within a page hierarchy (guide → *Screen archetypes*). -6. Forms and view states follow the composition patterns — the pattern outranks the component library's defaults (guide → *Forms*, *View states & feedback*). +1. Every colour, size, space, and duration resolves to a semantic token — a hex or px literal in a screen is the defect (*Tokens*). +2. Pick the screen archetype before building any screen — its zones, page rhythm, and width are fixed, never re-derived per page (*Screen archetypes*). +3. Surfaces follow the ladder: no card-like container inside another; separate in order whitespace → background shift → border → divider (tables/dense rows only) (*Surfaces & elevation*). +4. Reuse first: archetype → documented pattern → existing screens/primitives → extend a primitive → only then new, with the PR recording why nothing fit (*Components & reuse*). +5. One density app-wide, set at the token layer — never mixed within a page hierarchy (*Screen archetypes*). +6. Forms and view states follow the composition patterns — the pattern outranks the component library's defaults (*Forms*, *View states & feedback*). ## Page layout & design tokens -Consistency is a system, not a per-page effort. Two things make every screen feel like one product: a **single shared layout** and a **single token source**. A page author composes the layout and reaches for tokens — and never re-decides spacing, colour, or navigation. - -**Primary form factor (FILL IN ON SETUP):** `` plus the supported viewport range. This choice drives the default navigation pattern and which furniture the shared layout carries. +**Primary form factor (FILL IN ON SETUP):** `` plus the supported viewport range. This drives the default navigation pattern and the furniture the shared layout carries. -**One shared layout.** Every page builds on common layout components that supply the standing furniture — header / navigation, page chrome, consistent gutters and background, and the navigation pattern for the declared form factor. The page provides its content; the layout owns the frame — don't hand-roll a page shell. **The layout owns every clearance and inset; pages never re-derive them:** fixed/sticky chrome reserves its space through one clearance token (composed once with its safe-area inset), and top-spacing variants are a **prop the layout offers** — a page picks one, it never re-decides the padding. +**One shared layout** supplies the standing furniture — header/navigation, page chrome, gutters, background, and the navigation pattern for the declared form factor. The page provides content; the layout owns the frame — never hand-roll a page shell. **The layout owns every clearance and inset; pages never re-derive them:** fixed/sticky chrome reserves its space through one clearance token (composed once with its safe-area inset), and top-spacing variants are a **prop the layout offers** — a page picks one, never re-decides the padding. -**Layouts are responsive by default** — content reflows without horizontal scroll or clipping across the declared viewport range; no fixed pixel widths that break it. +**Layouts are responsive by default** — content reflows without horizontal scroll or clipping across the declared viewport range; no fixed pixel widths that break it (rules: *Responsive layout*). -**One token source, three tiers.** All spacing, colour, typography, radius, and elevation come from a single design-token source, never hardcoded per page. Structure tokens in three layers so they stay coherent and themeable: +**One token source, three tiers:** 1. **Primitive tokens** — raw, context-free values (`--red-400`, `--space-3`). -2. **Semantic tokens** — decisions that map primitives to meaning (`--color-bg`, `--gutter-screen`, `--header-clearance`). Components reference *these* (plus form-factor-specific tokens such as `--bottom-nav-clearance` only when mobile is the primary form factor). +2. **Semantic tokens** — decisions mapping primitives to meaning (`--color-bg`, `--gutter-screen`, `--header-clearance`; form-factor-specific tokens such as `--bottom-nav-clearance` only when mobile is primary). 3. **Component tokens** — per-component overrides, where a component genuinely needs them. -Pages and components consume **semantic** tokens; they never reach past them to a raw primitive value. - -**A token's committed value must match its documented scale — guard it.** Check the token file against its declared scale, in the spirit of the i18n key-parity check. +Pages and components consume **semantic** tokens; they never reach past them to a raw primitive value. A token's committed value must match its documented scale. ## Responsive layout -"Responsive by default" (above) is a promise; these rules keep it, whatever primary form factor you declared. Fix each failure with a primitive or token applied **once** — never a per-page tweak. (The concrete idioms per CSS toolchain live in the active stack pack.) +Fix each failure below with a primitive or token applied **once**, never a per-page tweak; the concrete CSS idioms live in the active stack pack. -- **Author from the smallest supported width up.** The floor is WCAG **Reflow**: no sideways scroll or lost content at **320 CSS px**, and the layout survives **200% text zoom**. +- **Author from the smallest supported width up.** The floor is WCAG Reflow: no sideways scroll or lost content at **320 CSS px**, and the layout survives **200% text zoom**. - **Prefer intrinsic sizing; reach for breakpoints last.** Fluid type/space and self-wrapping grids adapt *between* breakpoints; a reusable component adapts to **its container's** width, not the viewport's. Add a viewport breakpoint only for a genuine page-level layout change. -- **No horizontal overflow at the minimum width.** Atomic values (phone numbers, IDs, amounts) never wrap mid-token — bake no-wrap into the shared inline-value primitive; long free text wraps or truncates, never pushes width (a flex/grid child needs `min-width: 0` to be allowed to shrink); wide tables and code blocks scroll inside their own box, never the page. -- **Reserve space for fixed / sticky chrome with one semantic token** (`--header-clearance`) applied by the shared layout — never re-measured or re-padded per page. -- **Size full-bleed sections to content, not the viewport** — a content-driven min-height plus vertical padding, never `100vh`; where something must truly fill the viewport prefer `svh` over `vh`, and `dvh` only to deliberately track the browser chrome. +- **No horizontal overflow at the minimum width.** Atomic values (phone numbers, IDs, amounts) never wrap mid-token — bake no-wrap into the shared inline-value primitive; long free text wraps or truncates, never pushes width (flex/grid children need `min-width: 0` to shrink); wide tables and code blocks scroll inside their own box, never the page. +- **Pagination controls render a bounded window of page slots (~7: first, last, current ± 1, ellipsis), never the full page list** — a large page count must not widen the layout. +- **Reserve space for fixed/sticky chrome with the one semantic clearance token** applied by the shared layout — never re-measured or re-padded per page. +- **Size full-bleed sections to content, not the viewport** — a content-driven min-height plus vertical padding, never `100vh`; where something must truly fill it prefer `svh` over `vh`, `dvh` only to deliberately track browser chrome. - **Treat configurable copy as variable-length.** Any admin/CMS-editable string must survive a one-word *and* a three-line value without clipping or colliding with chrome; balance headings by default. -- **Multi-field rows collapse to full-width below the breakpoint,** each field keeping a min-width that leaves its content legible. -- **Adapt by disclosure, never by hiding meaning** — if navigation doesn't fit, collapse it into a menu; don't drop destinations or actions on small screens. +- **Multi-field rows collapse to full width below the breakpoint,** each field keeping a min-width that leaves its content legible. +- **Adapt by disclosure, never by hiding meaning** — if navigation doesn't fit, collapse it into a menu; never drop destinations or actions on small screens. ## Navigation chrome, overlays & scroll -Persistent chrome (a bottom nav, a sticky header), overlays, and client-side route changes recur as rework in an SPA — fix each at the root, not per screen. (Companion to *Responsive layout*, which owns overflow and viewport sizing; and to *One shared layout*, which owns clearance.) +Fix each of these at the root, not per screen: -- **Render overlays and fixed chrome in a top-level portal** — an ancestor's `transform` or low `z-index` otherwise drags or buries them (fixed bars sliding with page transitions; sheets rendering under the nav). -- **Reset or restore scroll in an effect keyed on the actual route/view change**, not synchronously at the navigation call; a keep-alive surface has **one explicit scroll owner**. Otherwise a newly-shown view inherits the previous one's scroll offset. -- **Global-nav visibility is a denylist of chrome-less routes, not an allowlist** — a new screen keeps the nav by default; only auth/legal/full-screen-editor routes opt out (an editor with its own sticky action bar hides the global nav so its primary action isn't clipped). -- **Under the soft keyboard, a flex column scrolls — it does not squeeze:** the scroll region is `overflow-y: auto`, non-shrinkable panels are `flex-shrink: 0`. Otherwise panels collapse to a clipped sliver when the keyboard opens. +- **Render overlays and fixed chrome in a top-level portal** — an ancestor's `transform` or low `z-index` otherwise drags or buries them. +- **Reset or restore scroll in an effect keyed on the actual route/view change,** not synchronously at the navigation call; a keep-alive surface has **one explicit scroll owner**. +- **Global-nav visibility is a denylist of chrome-less routes, not an allowlist** — a new screen keeps the nav by default; only auth/legal/full-screen-editor routes opt out. +- **Under the soft keyboard, a flex column scrolls — it does not squeeze:** the scroll region is `overflow-y: auto`, non-shrinkable panels are `flex-shrink: 0`. ## Visual quality bar -Tokens say *where* values come from; this says *which* values are good. Checkable, per screen: +Checkable, per screen: -- **Type.** One modular type scale in the token source; at most 2 font families, and on any single screen ~4 type sizes and ~2 weights. Body copy capped at ~60–75ch measure. Adding a size means adding a scale step in tokens, not a one-off value in a component. -- **Spacing.** Every margin / padding / gap resolves to an existing step on the spacing scale. Don't introduce ad-hoc values or new steps to make one screen fit; if the scale can't express it, fix the scale, not the instance. -- **Hierarchy.** Exactly one primary (filled) action per view; everything else is secondary / tertiary. One H1 per page; heading levels nest in order and never skip (h1 → h2 → h3), so the heading outline doubles as document structure for assistive tech. -- **Colour.** Use semantic intent tokens for meaning (success / warning / danger / info); never encode meaning in a raw hue or colour alone — pair it with text or an icon. Limit accent surfaces so the single primary CTA stays the most prominent element. -- **Alignment & density.** Content aligns to the shared layout's grid / gutters — no per-screen one-off gutters. Control sizing / density follows the declared primary form factor and stays consistent within a view; don't hardcode a global density. +- **Type.** One modular type scale in the token source; at most 2 font families; ~4 type sizes and ~2 weights per screen; body copy capped at ~60–75ch measure. A new size is a new scale step in tokens, never a one-off value in a component. +- **Spacing.** Every margin/padding/gap resolves to an existing step on the spacing scale. If the scale can't express it, fix the scale, not the instance. +- **Hierarchy.** Exactly one primary (filled) action per view; everything else is secondary/tertiary. One H1 per page; heading levels nest in order and never skip — the heading outline doubles as document structure for assistive tech. +- **Colour.** Semantic intent tokens for meaning (success/warning/danger/info); never encode meaning in colour alone — pair it with text or an icon. Limit accent surfaces so the single primary CTA stays the most prominent element. +- **Alignment & density.** Content aligns to the shared layout's grid/gutters — no per-screen one-off gutters. Control sizing/density follows the declared primary form factor and stays consistent within a view. ## Interaction feedback & perceived performance -- **Every actionable control shows its state from tokens.** Pressed / active, focus-visible, and disabled states are defined on the shared `atoms/`/`molecules/` primitives (not per page) and driven by semantic tokens. Hover is a pointer-device affordance; on a touch-primary form factor the pressed / active state carries the feedback — never leave the touch path without visible press feedback. (Keyboard focus-visible is owed by the headless foundation; surface it, don't suppress it.) -- **In-flight feedback stays on the control that triggered the action.** A local action disables its own control and shows an inline busy indicator there — never blank the whole screen with a top-level spinner for a local action. Reserve full-screen / section loading for a screen's initial data fetch (the `loading` state above). -- **Prefer optimistic updates for low-risk mutations** (toggles, reorders, favourites) with rollback + an error message on failure; reserve blocking spinners for genuinely blocking waits. -- **Initial content load uses skeletons that match the final layout;** short indeterminate waits use a spinner. Don't layout-shift from spinner to content. -- **Avoid indicator flicker:** delay showing a busy indicator (~150 ms) and keep it visible a small minimum once shown; debounce live search / filter input (~250 ms). Treat these as defaults a project may tune, not magic numbers. -- **Move focus deliberately after a navigational or destructive action** — to the next logical element, the confirmation, or back to the triggering control after a modal closes — so keyboard and screen-reader users aren't dropped at the top of the document. +- **Control states come from tokens on the shared primitives.** Pressed/active, focus-visible, and disabled are defined on `atoms/`/`molecules/`, never per page. Hover is a pointer-device affordance; touch-primary paths always show visible press feedback. Surface the headless foundation's focus-visible; don't suppress it. +- **In-flight feedback stays on the triggering control** — inline busy indicator plus disable; never a top-level spinner for a local action. Full-screen/section loading only for a screen's initial data fetch. +- **Prefer optimistic updates for low-risk mutations** (toggles, reorders, favourites) with rollback + an error message on failure; blocking spinners only for genuinely blocking waits. +- **Initial load uses skeletons matching the final layout;** short indeterminate waits use a spinner; no spinner-to-content layout shift. +- **Avoid indicator flicker:** delay busy indicators (~150 ms) with a small minimum visible time; debounce live search/filter input (~250 ms) — defaults a project may tune, not magic numbers. +- **Move focus deliberately after a navigational or destructive action** — to the next logical element, the confirmation, or back to the trigger after a modal closes. ## Forms -- **Validation timing.** Don't surface a field error before the user has interacted with that field. Validate a field on blur after first interaction, and the whole form on submit. Once a field shows an error, re-validate it on change so the error clears the moment it's fixed. Never error-shout on first keystroke. -- **Error placement & a11y.** Show each field's error inline, adjacent to the field, programmatically associated with it (`aria-describedby`) and conveyed by more than colour. On a failed submit, move focus to the first invalid field. -- **Destructive actions.** Require an explicit confirm step that names the consequence ("Delete 3 invoices?"). For irreversible / high-risk actions require deliberate confirmation (typed value or equivalent), never a bare button. -- **Unsaved-changes guard.** When a form holds meaningful unsaved edits, warn before discarding them — on both in-app route changes and browser unload / refresh. Don't prompt for trivial / transient inputs (e.g. a search box). -- **Submit handling.** While a submit is in flight, disable the submit control and prevent re-submission; surface progress through the same loading / error / success convention used elsewhere, not a per-form one. +- **Validation timing.** Validate a field on blur after first interaction, the whole form on submit; once a field shows an error, re-validate on change so it clears the moment it's fixed. Never error before first interaction or on first keystroke. +- **Error placement & a11y.** Field errors sit inline, adjacent to the field, `aria-describedby`-associated, and conveyed by more than colour; a failed submit moves focus to the first invalid field. +- **Destructive actions.** Require an explicit confirm naming the consequence ("Delete 3 invoices?"); irreversible/high-risk actions require deliberate confirmation (typed value or equivalent), never a bare button. +- **Unsaved-changes guard.** Warn before discarding meaningful unsaved edits — on in-app route changes and browser unload/refresh; not for trivial/transient inputs (a search box). +- **Submit handling.** Disable the submit control in flight and prevent re-submission; surface progress through the shared loading/error/success convention, not a per-form one. ## Microcopy & content -- **Capitalization is uniform.** Pick one convention project-wide and apply it everywhere — default to sentence case for all UI text except proper nouns. Don't mix title case and sentence case across buttons, headings, and labels. -- **Action labels are verb-first and specific.** Buttons and menu items name the action and its object — "Save changes", "Delete invoice", "Send invite" — not "OK", "Submit", or "Yes". -- **Error copy is user-facing and actionable.** State what happened, why if known, and what the user can do next. Blame-free; never exposes stack traces, status codes, internal identifiers, or raw exception text. (Distinct from the backend's error mapping, which shapes the transport response; this governs what the user reads.) -- **Empty / loading / success copy is concise and human** — paired with the four-state rule above; the states already exist, this governs their wording. -- **Keep user-facing copy centralized and reviewable** — out of component bodies, so all product copy can be audited in one place. In multilingual projects this is the i18n dictionaries; in single-language projects, a single strings / copy module serves the same purpose. No hardcoded display literals scattered through components. (Planned-screen copy still comes from the design mockups; these rules govern the microcopy agents would otherwise invent ad hoc — errors, empties, confirmations, labels.) +- **Capitalization is uniform** — one convention project-wide; default sentence case except proper nouns. Never mix title case and sentence case across buttons, headings, and labels. +- **Action labels are verb-first and specific** — "Save changes", "Delete invoice", "Send invite" — not "OK", "Submit", or "Yes". +- **Error copy is user-facing and actionable.** State what happened, why if known, and what the user can do next. Blame-free; never exposes stack traces, status codes, internal identifiers, or raw exception text. +- **Empty/loading/success copy is concise and human.** +- **Centralize user-facing copy** — i18n dictionaries in multilingual projects, a single strings module otherwise — so all product copy is auditable in one place; no hardcoded display literals in components. ## Component structure — atomic design -Visual and behavioural consistency comes from **reuse**, not from discipline repeated per screen. Structure every component into one of five atomic tiers, over a headless foundation you never skip — the foundation is a **dependency, not a folder**: unstyled, behavioural primitives from a headless UI library that solves focus management, keyboard handling, and widget-level ARIA for the components routed through it (not page-level a11y; see *Accessibility baseline*), with atoms built *on top of* it. - -1. **Atoms** (`components/atoms/`) — the smallest indivisible primitives, each mapping the project's tokens and conventions onto the foundation: `