Engineering conventions
These come from
CLAUDE.md
— that file is the canonical spec for both human and agent
contributors. Read it. The condensed list below covers what most
PRs trip over.
Runtime + toolchain
Section titled “Runtime + toolchain”- Bun only. No
npm/pnpm/yarn. Usebun install,bun run,bun test,bun build. Reach for Bun primitives (Bun.file,Bun.serve,Bun.$) over Node equivalents when one fits. - Type-check with
tsgo(@typescript/native-preview). Every workspace’stype-checkscript must calltsgo --noEmit. Don’t regress to plaintsc. - Lint with Biome. One
biome.jsonat root. No ESLint, no Prettier, no parallel formatter.bun lint:fixis the only formatting/linting command.
Imports
Section titled “Imports”- Top-level imports only. No
await import(...), norequire(). If a module needs lazy initialisation, restructure boot order so dependencies are statically resolvable. - Existing
await importcalls inapps/backend/{api,worker,data-provider}/src/index.tspredate this rule. Refactor them when you touch those files.
OOP + DI
Section titled “OOP + DI”- Domain logic lives in
@Service()-decorated classes. SOLID, one responsibility per class, compose over inherit. - Class-field DI, not constructor-param injection. See Dependency injection pattern for why — it’s a Bun-specific runtime requirement, not a style choice.
- DRY across packages. Two places reaching for the same logic →
promote into a
packages/*. No copy-paste between apps.
- Tests live in
tests/next tosrc/, mirroring the source tree. E.g.packages/business/domain/tests/services/HoldingService.test.tsforpackages/business/domain/src/services/HoldingService.ts. - Existing inline
*.test.tsfiles (next to source) should migrate to the mirroredtests/layout when their surrounding code is touched. - See Testing patterns for the
stubbed-DI helper and the
withTestDbtransaction wrapper.
Async work
Section titled “Async work”- Async work goes through BullMQ on Redis, consumed by
apps/backend/worker. The api enqueues; it doesn’t process long-running work inline. - Scheduled jobs use the advisory-lock wrapper so overlapping fires of the same job name no-op. See Adding a scheduled job.
Comments
Section titled “Comments”- Default to no comments. Code is documentation; well-named identifiers do the explaining.
- Add a comment only when the WHY is non-obvious — a hidden constraint, a subtle invariant, a workaround for a specific bug, behaviour that would surprise a reader.
- Never explain WHAT the code does.
- Never reference the current task, fix, or callers (“used by X”, “added for the Y flow”) — those belong in the PR description and rot as the codebase evolves.
Suppression
Section titled “Suppression”- No
@ts-ignore/@ts-expect-error/biome-ignorewithout a one-line justification. If you can’t articulate the reason, fix the underlying problem instead.
Dead code
Section titled “Dead code”- No dead code, no stubs, no half-finished implementations.
If a feature is removed, delete the code. Don’t leave commented
blocks,
// TODO: implement, or “kept for backwards compatibility” shims when nothing actually needs them.
Env var ownership
Section titled “Env var ownership”- Apps own app-level env vars. App’s
envSchemavalidates them at boot. - Packages own package-level env vars (e.g.
@scani/securityownsENCRYPTION_KEY,@scani/storageownsS3_*,@scani/emailownsFASTMAIL_API_TOKEN/SMTP_URL). Each has its ownsrc/config.tswith aloadX()loader andresetX()for tests. - Apps must NOT redeclare a package’s env vars. The package owns validation; the app just sets the variable and trusts the package’s loader.
See Environment variables reference for the full split.
Dependency hygiene
Section titled “Dependency hygiene”bun run deps:lint— syncpack: workspace alignment, single version per external dep, caret ranges.bun run deps:fix— auto-fix.bun run deps:unused— knip: surfaces unused exports / files / deps.- CI runs both whenever lockfile/config files change.
Before pushing
Section titled “Before pushing”Always:
bun run type-checkbun lint:fixbun test --preload ./packages/business/domain/test-preload.ts \ packages/ --timeout 30000If you touched deps:
bun run deps:lintbun run deps:unusedSee also
Section titled “See also”- How to contribute
- Dependency injection pattern
- Testing patterns
- The canonical spec:
CLAUDE.md