Skip to main content

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 in packages/core/test.
  • apps/api handles persistence, access control, AI calls and retention. Routes stay thin; logic goes in src/services.
  • apps/web holds the UI and every export. Data fetching goes through src/lib/api.ts, and TanStack Query keys live in src/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​

  1. Every figure has provenance. A new metric needs a MetricKey in metric-provenance.ts, rooted in a source in the reference data's sources, or explicitly labelled an assumption. Show it with <ProvenanceBadge>, and include it in export methodology sections. See Provenance.
  2. 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.
  3. Queries run through asUser so row-level security applies. Use asService only 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.
  4. Schema changes are new migration files (apps/api/migrations/NNNN_name.sql), mirrored in src/db/schema.ts. Never edit an applied migration. New tables get RLS policies in the same migration.
  5. Audit sensitive actions with audit(), in the same transaction as the change. New actions are added to the AuditAction union and to ACTION_LABEL in components/AuditTable.tsx.
  6. 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).
  7. 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.
  8. 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.ts spins 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 in apps/web/test; journeys go in apps/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: printWidth 110, double quotes, trailing commas (.prettierrc). Run npm run format.
  • Validate every request body with a zod schema from packages/core/src/schemas.ts, read through body() (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).