82 lines
3.7 KiB
Markdown
82 lines
3.7 KiB
Markdown
# Engineering standards
|
|
|
|
This file is the contract for every contributor to this repository, human or
|
|
agent. `CLAUDE.md` is a symlink to it: one canonical standard, read by every
|
|
tool. `ruff`, `mypy`, `eslint`, `prettier`, and `vue-tsc` enforce mechanically
|
|
what they can; this document carries the rest.
|
|
|
|
## Architecture
|
|
|
|
Monorepo with a ports-and-adapters seam. The dependency direction is enforced
|
|
by review:
|
|
|
|
```
|
|
api -> pipeline -> ports <- adapters domain imports nothing app-level
|
|
```
|
|
|
|
- `backend/app/domain/`: pure models and logic (`Track`, `TasteProfile`,
|
|
fuzzy matching, profile compression). Imports nothing app-level.
|
|
- `backend/app/ports/`: `Protocol` definitions (`MusicCatalog`,
|
|
`Recommender`, `PlaylistWriter`).
|
|
- `backend/app/adapters/`: implementations. `spotify/` (auth, thin httpx
|
|
client, DTO mapping), `anthropic/` (both LLM calls), `demo/`
|
|
(fixture-replay implementations of the same ports).
|
|
- `backend/app/pipeline/`: the orchestrator composes the stages; stages never
|
|
import each other.
|
|
- Spotify JSON never escapes `adapters/spotify/mapping.py`.
|
|
- Pipeline tunables (counts, thresholds, budgets, model id, effort, TTLs) live
|
|
in `app/config.py` (pydantic-settings). No magic numbers in code.
|
|
- LLM prompts are versioned template files under `app/prompts/`, not inline
|
|
strings.
|
|
- `eval/scenarios.yaml` is one source of truth for three consumers: golden
|
|
eval queries, demo fixture keys, and UI suggestion chips.
|
|
|
|
## Naming
|
|
|
|
Human-readable, descriptive, full words.
|
|
|
|
- Python: `snake_case` functions/variables, `PascalCase` classes,
|
|
`UPPER_SNAKE` constants. Functions verb-first (`resolve_candidate`),
|
|
variables noun-first (`grounded_tracks`), booleans as predicates
|
|
(`is_grounded`, `should_stream`).
|
|
- TypeScript/Vue: `camelCase` variables/functions, `PascalCase` components and
|
|
`.vue` filenames, composables `use`-prefixed, types without `I`-prefix.
|
|
- Abbreviations: only universally idiomatic ones (`id`, `url`, `api`, `db`,
|
|
`llm`, `sse`), never invented ones.
|
|
- Single-letter names only in lambdas and comprehensions whose whole scope is
|
|
a few lines.
|
|
- Modules are named for what they contain, singular nouns. `utils.py`,
|
|
`helpers.py`, and `misc.py` are banned names.
|
|
|
|
## Sizing, DRY, modularity
|
|
|
|
- Soft cap ~300 lines per module, hard cap 500. Split by responsibility.
|
|
Functions target 40 lines or less.
|
|
- One responsibility per module: if it needs "and" to describe, split it.
|
|
- DRY by the rule of three: extract on the third occurrence, not the second.
|
|
No premature abstraction, no helpers for one-shot operations, no designing
|
|
for hypothetical future requirements.
|
|
- Validate at boundaries only (user input, Spotify responses, LLM output).
|
|
Trust internal code; no defensive re-checking between our own modules unless it's a clear necessity.
|
|
|
|
## Comments and docstrings
|
|
|
|
Sparse and load-bearing:
|
|
|
|
- One-line docstring on every module and public function/class. Add
|
|
Args/Returns only where the signature genuinely doesn't tell the story.
|
|
- Inline comments state only what the code cannot: constraints, API quirks,
|
|
non-obvious whys. Never narrate the next line.
|
|
- Banned: commented-out code, drive-by TODOs, decorative section banners.
|
|
- Type hints everywhere; `mypy --strict` is the enforcement.
|
|
|
|
## Enforcement
|
|
|
|
- Backend: `ruff` (lint + format) everywhere. `mypy --strict` on `domain/`,
|
|
`ports/`, `pipeline/`; default strictness on `adapters/` and `api/`, since
|
|
SDK and streaming typing archaeology is not where the effort goes. Every
|
|
`# type: ignore` carries an error code and a reason.
|
|
- Frontend: `eslint` + `prettier` + `vue-tsc --noEmit`; no `any`. The stream
|
|
event types are a discriminated union mirrored against the Pydantic
|
|
response schemas.
|
|
- Commit messages describe the change, conventional-commit style.
|