Platform
Contributing
Branch model, commit conventions, code standards, and the PR checklist for contributing to Hatched.
Branch model
mainis the always-deployable trunk. CI runs against every PR; a green build is a prerequisite for merge.- Feature branches:
feat/<scope>-<short-description>, e.g.feat/badges-daily-limit. - Fix branches:
fix/<scope>-<short-description>. - Chore / docs:
chore/<...>,docs/<...>.
Commit conventions
Short, imperative, lowercase scope prefix. One change per commit when practical; a refactor that spans 20 files and has no logical single-commit unit is fine as a single commit if it's genuinely atomic.
feat(badges): add weekly earning cap
fix(rule-engine): serialize custom-counter writes
chore(ci): drop --passWithNoTestsReference issues / PRs in the body, not the subject.
Code standards
- TypeScript strict across all projects. Don't
anyescape. - No backwards-compat hacks — the project has zero users. Delete code that won't be missed; renaming a variable is cheaper than aliasing it forever.
- No half-finished implementations — merge only what is wired up end to end.
- Don't narrate what code does in comments. Comments explain why a
non-obvious choice was made (a subtle invariant, a workaround, an external
constraint). Remove dead
// TODOcomments — open a ticket instead. - Entity → DTO boundary is enforced. Controllers must not import entity classes; they return DTOs produced by a module-local mapper.
Tests are the contract
- Every new feature that touches money (coins, tokens, badges), auth, or the
rule-engine requires an e2e test under
apps/api/test/. - Invariant tests (
invariant-idempotency,invariant-tenant-isolation,invariant-concurrency) are load-bearing — do not weaken them. If a PR has to touch them, add a commit that explains the new invariant first. - Widget changes get a build-level assertion (widgets are produced) plus a
manual click-through in
apps/demo. - Dashboard CRUD pages ship with a Playwright smoke.
Running the gates locally
pnpm install
pnpm lint # fail on any warning
pnpm build # every workspace builds
pnpm --filter @hatched/api test # unit tests
pnpm --filter @hatched/api test:e2e # e2e (needs local Postgres + Redis)CI runs exactly these. If they are red locally, don't push.
Capability naming
When adding a new capability / feature gate:
- Add the key to
CustomerFeaturesin@hatched/shared/types. - Add a default in
apps/api/src/database/migrations/…_default_feature_flags.sql(or equivalent default provider). - Add a label + description in
apps/dashboard/src/components/capability-disabled-state.tsx. - Update the settings page capability toggles.
- Add the capability to
AudienceBriefper-audience overrides if it applies per-segment.
Always use the word capability in UI copy; feature belongs to internal code paths.
PR checklist
Before requesting review:
pnpm lint && pnpm buildgreen.- New behavior has an e2e or unit test.
- If the change affects a public contract (API DTO, widget prop, SDK
method), the type is sourced from
@hatched/sharedand all call sites compile. - If the change introduces an error path, it throws a subclass of
HatchedExceptionwith a stablecode. - Migration files are numbered strictly ascending and unique.
- No secrets or hex-looking literals committed in source.
- Docs updated when behavior in
README,ARCHITECTURE, orOBSERVABILITYgoes stale.
PR description should answer why this change, not what — the diff already shows the what.