Contributing
These conventions come from AGENTS.md at the repository root, which applies to anyone changing the code, human or AI. Read the README and Architecture first.
Where code goes
- Domain logic belongs in
packages/core. It must be pure: no I/O, no DOM, no Node APIs. Anything the server and browser both compute (scoring, summaries, rollout, the workbook model, validation) lives here, so they can't disagree. Add a test beside it inpackages/core/test. apps/apihandles persistence, access control, AI calls and retention. Routes stay thin; logic goes insrc/services.apps/webholds the UI and every export. Data fetching goes throughsrc/lib/api.ts, and TanStack Query keys live insrc/lib/data.ts.- Reference data and prompts are data. A new table or constant the model reads belongs in
ReferenceData, not a code constant. See Add a reference data field.
Invariants: don't break these
- Every figure has provenance. A new metric needs a
MetricKeyinmetric-provenance.ts, rooted in a source in the reference data'ssources, or explicitly labelled an assumption. Show it with<ProvenanceBadge>, and include it in export methodology sections. See Provenance. - Names and emails never enter
dataset_employees, logs, AI prompts, summaries or unnamed exports. Manager emails are resolved in the browser. Identities are stored only encrypted (services/identities.ts), and read only with PII access and an audited purpose. - Queries run through
asUserso row-level security applies. UseasServiceonly for the existing reviewed service paths (provisioning, reading published prompts, cache writes, benchmarks, AI metering reads, the nightly and manual purge, seeding). If we add one, say why in a comment and cover it with a test. - Schema changes are new migration files (
apps/api/migrations/NNNN_name.sql), mirrored insrc/db/schema.ts. Never edit an applied migration. New tables get RLS policies in the same migration. - Audit sensitive actions with
audit(), in the same transaction as the change. New actions are added to theAuditActionunion and toACTION_LABELincomponents/AuditTable.tsx. - Benchmarks never publish a figure from fewer than
MIN_BENCHMARK_ENGAGEMENTS(five, counted after excluding the compared engagement), and every published figure is rounded (BENCHMARK_ROUNDING). - AI output is validated, cited and labelled, with a deterministic fallback. AI is optional: every feature must work, and say so, when AI is off or the budget is spent.
- Published reference data and prompt versions are immutable. Datasets are pinned to a reference version; never change scoring for an existing dataset except through an explicit, audited rescore.
Tests
- Core: unit tests for every model change, including edge cases (empty input, missing SOC, cycles).
- API:
test/harness.tsspins up PGlite with the real migrations, seed data and RLS, with helpers to provision users and engagements and ingest rows. Security-relevant changes need a test that tries the forbidden thing (test/security.test.ts). - Web: pure helpers (
src/lib,src/exports) get Vitest tests inapps/web/test; journeys go inapps/web/e2e.
npm test and npm run typecheck must pass before every commit. See Testing.
UI and copy
Follow docs/DESIGN.md, which translates the Davies Brand Guidelines 2023:
- colours only through the Tailwind tokens in
apps/web/src/styles.css(teal, salmon, sky, mint, grey and their tints), and never the logo colours; - Calibri (Carlito fallback), left-aligned text, Lucide icons at a 1.5 stroke;
- primary buttons dark teal with white text; only white or salmon on teal; salmon never as a text colour or text background (it fails AA), only for rules, markers and large text;
- flat colour, no gradients;
- every screen handles loading, empty, error (with retry) and permission-limited states, and works at 375px wide;
- every control has a visible focus state; motion is reduced when the OS asks.
Copy is British English, active voice, "we" rather than "you", short words, contractions fine, with no unsupported claims. Keep assumption and AI labels honest: AI text is always marked as AI-written, with its confidence and sources.
Code style
- TypeScript strict mode everywhere (
tsconfig.base.json). - Prettier:
printWidth110, double quotes, trailing commas (.prettierrc). Runnpm run format. - Validate every request body with a zod schema from
packages/core/src/schemas.ts, read throughbody()(which also caps the size while streaming). - Comments explain why, not what. Many modules open with a short comment on their design constraints; keep those current.
Commits
Small, logical commits with a message that says what changed and why. Run npm test and npm run typecheck first. Record user-visible changes in CHANGELOG.md.
When a change affects how consultants or administrators use the Workbench, update this documentation site too (docusaurus/docs/user), and check npm run build in docusaurus/ still passes (broken links fail the build).