prospector/docs/STANDARDS.md
Natalie b9c8c0879e docs(standards): house engineering standards + module index, filesystem-as-docs
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>
2026-06-29 08:09:35 -04:00

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, no Date.now() baked in — take now as a parameter so tests are deterministic.
  • Reuse, don't re-implement. markets/stats.ts composes reports/reports.ts#buildReport rather than copying the funnel logic. Extract shared logic to a pure module both features import.

3. Type & quality discipline

  • strict: true, no any, no as 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.md stating: 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.md is the template.
  • src/README.md is 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-sentence it('…') 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 via src/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 blind git add -A.
  • Conventional-commit subject; end with the Co-Authored-By trailer.
  • Verify before commit (§5), push after (fast-forward only).
  • Branch: work on main directly (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 in App.tsx (NAV + router + titles).
  • All HTTP through web/src/api.ts (typed apiFetch); no inline fetch.
  • Reuse existing CSS classes (card, funnel, vchart, bars) before adding new.
  • designs/*.html are the visual/behavior contract — match them.