Codify project standards in docs/STANDARDS.md (feature-sliced NestJS modules, pure-logic/IO split, reuse-don't-reimplement, co-located Vitest, 300/500 LOC caps, README-per-feature). Add src/README.md as the module index. Point CLAUDE.md at both. src/markets/ is the reference module. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.5 KiB
Engineering Standards — Quinn Prospector
The house rules for this repo. Project-specific; inherits the workspace-wide
TypeScript standards (~/.claude/instructions/typescript-code-standards.md) and
the no-stubs / SOLID / DRY commandments. When in doubt, match the existing
src/markets/ module — it is the reference implementation of these rules.
1. Feature-sliced architecture (backend src/)
One NestJS feature module per domain (markets/, prospects/, reports/,
campaigns/, …). A module owns one bounded concern and exposes it through:
<feature>/
<feature>.module.ts — NestJS wiring (imports, providers, exports)
<feature>.controller.ts — HTTP surface only (no logic)
<feature>.service.ts — I/O + orchestration (DB, other services)
<pure>.ts — pure domain logic, one concern per file
dto/*.ts — class-validator request DTOs
*.test.ts — co-located Vitest, next to the unit it tests
index.ts — public API manifest (the only cross-module entry)
README.md — what/why for the feature (see §4)
- No cross-feature reach-in. Import another feature only through its
index.ts/ its exported*Module. Never deep-import its internals. - New domain → new module, not a new file bolted onto an existing one.
2. Separate pure logic from I/O
Domain logic is pure functions in their own files (registry.ts,
locality.ts, tour-window.ts, timezone.ts, stats.ts). The *.service.ts
does I/O (SQL, HTTP) and calls the pure layer. This is why the aggregations are
exhaustively unit-testable with no database.
- Pure files: no
@Injectable, no repo, noDate.now()baked in — takenowas a parameter so tests are deterministic. - Reuse, don't re-implement.
markets/stats.tscomposesreports/reports.ts#buildReportrather than copying the funnel logic. Extract shared logic to a pure module both features import.
3. Type & quality discipline
strict: true, noany, noas unknown as, no@ts-ignore.- Explicit return types on every exported function.
- Discriminated unions over boolean flags; make illegal states unrepresentable.
- No stubs / TODOs / fallbacks in production code — complete or fail loudly.
- No dead code — delete it; no
_unused, no "kept for migration" re-exports. - File size: soft 300 LOC, hard 500 (tests exempt). Split god files.
- Validate all inputs at the edge (DTOs +
class-validator); handle every error.
4. Filesystem as modular documentation
Documentation lives next to the code it describes, not in a far-off wiki.
- Every feature module has a
README.mdstating: what it does, the domain model + key decisions (and what was deliberately rejected), a file table (one concern per row), and the HTTP surface.src/markets/README.mdis the template. src/README.mdis the module index — one line per feature, kept current.- Add/refresh a module's README when you build or materially change it. Do not mass-generate hollow READMEs — an empty doc is worse than none.
- Comments explain why, not what. Code is the source of truth for "what".
5. Testing
- Vitest, co-located
*.test.ts. Full-sentenceit('…')names. - Cover the pure layer exhaustively (it's cheap and deterministic).
- Deterministic: pass
now/seeds in; no wall-clock, no network in unit tests. - Green bar before commit:
npm run typecheck && npm test && npm run build.
6. Persistence & migrations
- Schema changes are numbered SQL migrations in
migrations/(000N_<name>.sql). - TypeORM entities in
src/entities/mirror the schema; export viasrc/entities/index.ts. - Mutating an existing table → read-modify-write the full row; never blind upsert.
7. Git
- Atomic, scoped commits:
git commit -- <paths>, never blindgit add -A. - Conventional-commit subject; end with the
Co-Authored-Bytrailer. - Verify before commit (§5), push after (fast-forward only).
- Branch: work on
maindirectly (this repo's convention).
8. Frontend (web/)
- Vite + React, served same-origin by this backend; installs as a Chrome PWA.
- One view per
web/src/views/*.tsx, wired inApp.tsx(NAV + router + titles). - All HTTP through
web/src/api.ts(typedapiFetch); no inlinefetch. - Reuse existing CSS classes (
card,funnel,vchart,bars) before adding new. designs/*.htmlare the visual/behavior contract — match them.