docs(standards): add CODE_STANDARDS.md + CONTRIBUTING.md
The living rulebook (formatting, modules, API/wire + error contract, security, logging, data integrity, testing) so conventions are codified and persist. CONTRIBUTING points at it and the CI gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5aaf292b2e
commit
88674fa2f5
|
|
@ -0,0 +1,27 @@
|
|||
# Contributing
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm install # also installs git hooks (lefthook) via the prepare script
|
||||
```
|
||||
|
||||
## Before you commit
|
||||
|
||||
Hooks run automatically (format + lint on commit, typecheck on push). To run the full gate
|
||||
yourself:
|
||||
|
||||
```bash
|
||||
npm run ci # format:check + lint + typecheck (client+server) + check:server + tests + build
|
||||
```
|
||||
|
||||
## Standards
|
||||
|
||||
All conventions — formatting, error handling, API/wire format, security, logging, testing —
|
||||
live in **[docs/CODE_STANDARDS.md](docs/CODE_STANDARDS.md)**. Follow the canonical pattern for
|
||||
each; new code must pass the CI gate.
|
||||
|
||||
## Commits
|
||||
|
||||
Conventional-commit style (`feat:`, `fix:`, `chore:`, `refactor:`, `ci:`, `style:`, `test:`).
|
||||
Keep mechanical reformatting in its own commit.
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# Code Standards
|
||||
|
||||
The living rulebook for this codebase. One canonical way to do each thing, enforced in
|
||||
CI so it can't drift. Sections marked **(enforced)** fail CI; **(target)** are being
|
||||
rolled out. Update this doc when a standard changes.
|
||||
|
||||
## Tooling & enforcement (enforced)
|
||||
|
||||
- **Formatting:** Prettier (`.prettierrc.json`) — single quotes, semicolons, 2-space,
|
||||
`printWidth: 100`, trailing commas. Run `npm run format`; CI runs `format:check`.
|
||||
- **Lint:** `npm run lint` (`eslint .`) covers **client and server**. Focused rule set
|
||||
(react-hooks on the client; `js/recommended` + TS-unused on the server). Errors block CI;
|
||||
warnings are advisory (ratchet down over time).
|
||||
- **Typecheck:** `npm run typecheck` (client, strict + `noUncheckedIndexedAccess`) and
|
||||
`npm run typecheck:server` (server). Both run in CI.
|
||||
- **Local hooks (lefthook):** pre-commit auto-formats staged files + lints; pre-push
|
||||
typechecks. Installed by `npm install` (the `prepare` script).
|
||||
- **Dead code:** `npm run knip` (advisory) for unused exports/deps.
|
||||
- **Full gate:** `npm run ci` = format:check + lint + typecheck (×2) + check:server +
|
||||
tests (server + client) + build. The CI pipeline runs the same.
|
||||
|
||||
## Modules & files
|
||||
|
||||
- Server is **CommonJS TypeScript `.cts`**, run by Node via type-stripping (no build step).
|
||||
Every migrated module is `require`d **with the explicit `.cts`/`.mts` extension**
|
||||
(extensionless does not resolve). Each `.cts` leads with an `import type …` line so
|
||||
type-stripping activates.
|
||||
- Client is ESM `.ts`/`.tsx` with the `@/*` path alias.
|
||||
- Money is **integer cents** in the DB and on the wire; convert to dollars only at the edge
|
||||
via the money helpers. Branded `Cents`/`Dollars` types where practical.
|
||||
|
||||
## HTTP API conventions
|
||||
|
||||
- **Wire format:** JSON, **snake_case** field names (mirrors DB columns), money as integer
|
||||
cents.
|
||||
- **Error responses (target):** every error body is `{ error, message, code, field? }` with
|
||||
the correct HTTP status. Throw the factories from `utils/apiError.cts`
|
||||
(`ValidationError`, `NotFoundError`, `ConflictError`, …) and let the single terminal error
|
||||
handler format them — do not hand-roll `res.status().json({ error })`. Express 5 forwards
|
||||
rejected async handlers automatically, so route bodies should not wrap everything in
|
||||
`try/catch`. Clients read `message`/`code` + status.
|
||||
- **Success envelope (target):** reads return the bare resource/list; mutations return
|
||||
`{ success: true, … }`.
|
||||
- **Validation (target):** validate inputs with the shared validators (throwing
|
||||
`ValidationError`); do not scatter ad-hoc `parseInt`/range checks.
|
||||
|
||||
## Security (target; see also SECURITY.md)
|
||||
|
||||
- Every **mutating** route carries CSRF + auth + an appropriate rate-limit + input
|
||||
validation. Admin routes require `requireAdmin`.
|
||||
- Every user-scoped query filters `user_id` **and** `deleted_at IS NULL`. Never interpolate
|
||||
user input into SQL — use bound parameters.
|
||||
- Never log secrets, tokens, or raw financial values.
|
||||
- Fail fast at boot on missing required config (e.g. `TOKEN_ENCRYPTION_KEY` in production).
|
||||
|
||||
## Logging (target)
|
||||
|
||||
- One `log` helper with levels; env-driven level; redaction of secrets/PII. Replace raw
|
||||
`console.*` over time.
|
||||
|
||||
## Data integrity
|
||||
|
||||
- Every **multi-statement write** runs inside `db.transaction()`.
|
||||
- Migrations are append-only versioned entries in `db/migrations/versionedMigrations.cts`
|
||||
(idempotent; guard column adds with `PRAGMA table_info`).
|
||||
|
||||
## Testing
|
||||
|
||||
- Server: `node:test` in `tests/*.test.js`. Client: Vitest (`*.test.ts[x]`). E2E: Playwright
|
||||
(`npm run test:e2e`, probe subset `test:e2e:probe`). Add coverage for new handlers; prove
|
||||
money invariants with property tests where practical.
|
||||
|
||||
## Future / modernization (tracked, not yet adopted)
|
||||
|
||||
Kysely (typed SQL) · Zod (validation) · pino (logging) · native `--env-file` + validated
|
||||
config · `node:sqlite` · ts-rest + OpenAPI · Vite 6/7. Adopt where each makes the code more
|
||||
uniform; see the standardization plan.
|
||||
Loading…
Reference in New Issue