# CLAUDE.md — working agreements for this repository This file is read by Claude Code on every task. It defines how we work, what good looks like, and where to find the source of truth. **Read `PRD.md`, the active Gitea milestone, and the relevant slice issue before starting any task.** The primary remote for this repository is the **Respellion Gitea instance**. There is no GitHub remote. Tooling, CI, issue tracking, releases, and the container registry all live in Gitea. Treat any reference to GitHub-only features as a bug to be corrected. --- ## 1. Mission Build a reference application that demonstrates Respellion's Common Ground architecture pattern. Quality and architectural clarity beat feature throughput. Every commit must teach. ## 2. Engineering principles (in order) 1. **TDD.** Red → green → refactor. The red test commit precedes the implementation commit. Never write production code without a failing test first. 2. **DDD.** The Domain Service owns aggregates, invariants, and the ubiquitous language. Cross-aggregate consistency is eventual. Anti-corruption boundaries are explicit and minimal. 3. **BDD at the use-case level.** Each user-visible flow has a Gherkin scenario in `tests/acceptance/`. Scenarios are written in business language (Dutch terms allowed inline where they're the actual domain term). 4. **Loose coupling over convenience.** Upstream Common Ground modules are reached only via their documented APIs. No direct DB access to OpenZaak, Objecten, or any other peer module. Ever. If you're about to break this rule, stop and propose an ADR. 5. **Walking skeleton first, vertical slices after.** Each slice is independently deployable and demoable. ## 3. Definition of Done A change is done only when **all** of these hold: - A linked Gitea issue exists (the slice or bug being addressed). - Failing test written and committed first. - Implementation makes the test pass. - Refactor commit follows if structure improved. - Conventional Commit messages used, referencing the Gitea issue (see §6). - All Gitea Actions CI jobs green: lint, unit, integration, mutation (ratchet), e2e, container build + push, compose-up smoke test. - `docker compose up` from a fresh clone reaches green health checks within 3 minutes. - Docs touched if behaviour, contracts, or operations changed — versioned docs in `docs/`, operational notes in the Gitea Wiki. - ADR added in `docs/architecture/` if a non-obvious decision was made. - Demo note appended to `docs/demo-script.md` if the slice is user-visible. - The Gitea issue is closed by the merging PR's commit footer (`closes #NN`). ## 4. TDD discipline — concrete rules - **Red commit:** `test(scope): describe the behaviour being added (refs #NN)`. Test must fail with a clear assertion message. - **Green commit:** `feat(scope): implement X (refs #NN)` — the smallest change that makes the test pass. No unrelated edits. - **Refactor commit:** `refactor(scope): describe the structural change (refs #NN)`. Tests stay green. - **No skipped or ignored tests** committed to `main`. If a test is temporarily disabled, the commit is `chore(test): skip X — reason (refs #NN)` and is reverted within the same slice. - **One assertion concept per test.** Multiple `Assert.*` calls are fine if they verify the same behaviour from different angles; otherwise split. - **Test names describe behaviour**, not method names: `submitting_without_diploma_keeps_zaak_in_ingediend` not `Test_Submit_1`. ## 5. Mutation testing - Stryker.NET (backend) and Stryker (Angular) run on every PR via Gitea Actions. - The first slice establishes a **baseline mutation score**. CI fails on regression below baseline (ratchet). - Goal direction: increase baseline only deliberately, as a slice's stated outcome. Never lower it. ## 6. Commits, branching, issues, and PRs - **Conventional Commits** taxonomy: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `arch` (architectural change with ADR), `perf`, `build`, `ci`. - **Scope** is the affected service or library: `feat(domain): ...`, `test(acl): ...`, `arch(workflow): ...`. - **Every commit references its issue:** `(refs #NN)` in the subject. The PR that merges the slice closes the issue via `closes #NN` in the squash-commit body. - **Trunk-based development.** Short-lived branches off `main`, squash merges. No long-lived feature branches. - **Branch naming:** `/-`, e.g. `feat/14-acl-default-fill`, `fix/27-projection-idempotency`. - **Changelog** generated by `git-cliff` from commits, refreshed on tag; published as a Gitea Release. - **CalVer** versioning: `YYYY.MM.PATCH`. - **Breaking changes:** footer `BREAKING CHANGE: ...` plus an ADR. ## 7. Gitea is the system of record - **User stories, bugs, and ADR proposals live in Gitea Issues.** The `BACKLOG.md` file is a curated mirror of the active milestone, not the source of truth. - **Milestones** group slices into iterations (Iteration 0, 1, 2, …). - **Labels:** `type:slice`, `type:bug`, `type:adr-proposal`, `type:chore`; area labels `area:acl`, `area:domain`, `area:portal-self-service`, `area:portal-openbaar`, `area:portal-behandel`, `area:portal-beheer`, `area:infra`, `area:workflow`, `area:event-subscriber`, `area:projection`, `area:bff`, `area:docs`. - **Project boards** visualise each milestone with `Todo / In progress / In review / Done`. - **Issue templates** in `.gitea/ISSUE_TEMPLATE/`: - `slice.md` — for backlog slices; encodes the Definition of Done as a checklist. - `bug.md` — for defects. - `adr-proposal.md` — for decisions that need recording before coding starts. - **PR template** in `.gitea/PULL_REQUEST_TEMPLATE.md` enforces the DoD checklist. - **Don't open a PR without a linked issue.** If the work is genuinely throwaway, open the issue first and close it on merge. ## 8. Architectural rules — non-negotiable 1. **The ACL is the only code that talks to ZGW APIs.** BFF, Domain Service, and portals never construct ZGW URLs. 2. **The Workflow Client is the only code that talks to Flowable.** BPMN models never embed OpenZaak knowledge — they ask the Workflow Client to execute external tasks. 3. **Portals talk only to the BFF.** Never directly to Domain Service, ACL, OpenZaak, or any other backend. 4. **The Read Projection is rebuildable.** It is a derived artefact and must remain so. No write-only state lives in the projection. 5. **No direct DB access across services.** Each service owns its schema. 6. **Idempotency at every event boundary.** The Event Subscriber must tolerate duplicate and out-of-order NRC events. 7. **No GitHub-only assumptions.** Workflow files live in `.gitea/workflows/`. Marketplace actions are referenced by absolute URL and pinned. CI features that require GitHub APIs (e.g. GitHub-only `actions/upload-artifact` quirks) are replaced with Gitea-compatible alternatives or documented as known gaps in `docs/runbooks/gitea-actions-gotchas.md`. ## 9. Layering inside .NET services - `Domain/` — aggregates, value objects, domain events. No external dependencies. Pure C#. - `Application/` — use cases (commands, queries, handlers). Depends on Domain. - `Infrastructure/` — adapters: EF Core, HTTP clients, Flowable, Keycloak. - `Api/` — minimal API endpoints + DTOs. Depends on Application. - **Dependency direction:** Api → Application → Domain. Infrastructure implements ports defined in Application. ## 10. Frontend rules - **Nx monorepo** with four apps and shared libs. - **NL Design System** components first; only roll our own if NL DS doesn't cover it (recorded in `docs/frontend-decisions.md`). - **Standalone components + signals.** No NgModules in new code. - **Angular Testing Library** for component tests; **Playwright** for e2e. - **API client generated** from BFF's OpenAPI spec; never hand-written. - **No business logic in components.** Logic lives in services and stores; components render. - **Accessibility (WCAG 2.1 AA)** is part of Definition of Done for portal slices. ## 11. Tests pyramid for this repo - **Unit (fast, in-memory):** dominant. Domain, application handlers, Angular services. - **Integration (containers via Testcontainers):** ACL ↔ OpenZaak, Event Subscriber ↔ NRC, Workflow Client ↔ Flowable, projection updates. - **Acceptance (Gherkin via Reqnroll):** one scenario per user-visible flow. Drives the slice. - **E2E (Playwright):** the walking-skeleton happy path, expanded slice by slice. - **Mutation:** Stryker on every PR with ratchet. ## 12. Docs are part of the work — two surfaces - **`docs/` (versioned, MkDocs):** PRD, architecture, ADRs, glossary, synthetic-data spec, runbooks, demo-script. Source of truth. PR-reviewed. Published via a Gitea Actions workflow to Gitea Pages. - **Gitea Wiki (living):** day-to-day runbooks that change often, known-issues, environment URLs, on-call rota. The Wiki points at `docs/` for everything authoritative. - ADRs follow the Nygard template. One decision per ADR (`docs/architecture/adr-NNNN-title.md`). The first ADR is provided as a template — read it before writing new ones. ## 13. When in doubt - If a task pushes against any of §8's architectural rules, **stop and open an `adr-proposal` issue in Gitea** before coding. - If a slice issue looks bigger than 1–2 days of focused work, **split it** in Gitea — close it with a comment pointing at the replacement issues — and update `BACKLOG.md` to match. - If a test is hard to write, **the design is wrong** — refactor the production code to be testable. - If you're tempted to add a dependency, **justify it in the PR description** with three sentences: what it gives us, what we'd write to replace it, what risk it adds. - If you can't explain a piece of code in one sentence, it's too clever — rewrite or extract. ## 14. Definition of an ADR-worthy moment You owe an ADR when any of these apply: - A new dependency is added. - A boundary between services is changed. - A coupling rule from §8 is bent (even slightly). - A non-obvious technical decision is made that future-you might question. - A Gitea Actions workflow uses a non-standard mechanism (custom runner, escape hatch, GitHub-only fallback). ADRs are short — a page is plenty. Open an `adr-proposal` Gitea issue first; the ADR file lands in the PR that implements the decision. ## 15. CI behaviour (Gitea Actions) - **Workflow files** live in `.gitea/workflows/`. They follow GitHub-Actions-compatible YAML, with these conventions: - `uses:` references are absolute URLs and pinned to a tag, e.g. `uses: https://github.com/actions/checkout@v4`. - Self-hosted runner labels (e.g. `runs-on: respellion-linux`) are declared in `docs/runbooks/ci.md`. - Secrets come from Gitea repository or organisation secrets — never inline. - Container images push to the Gitea Container Registry: `gitea.respellion.local/big-reference/:`. - **PRs must be green to merge. No exceptions.** - **Flaky tests are fixed, not retried.** - **A red `main` is a P0** — pause feature work until green. - **The compose-up smoke test runs in CI and gates merges.** ## 16. Working agreements with Claude Code - Read this file, `PRD.md`, the active Gitea milestone, and the relevant slice issue before starting a task. - Pick the smallest open issue that produces a demoable outcome. Don't batch. - Always commit a failing test before the implementation that makes it pass. - Don't create files outside the layout described in `PRD.md` §9 without proposing a change first via an issue. - Don't add dependencies casually. See §13. - Reference the Gitea issue in every commit (`refs #NN`) and close it on the merging commit (`closes #NN`). - If asked to "just make it work," default to the slice's Definition of Done. If that's not what was wanted, ask. - When a task is ambiguous, propose 2–3 options with trade-offs in the issue and stop. Don't guess. - Never push directly to `main`. Never bypass the PR review gate. --- **This file is a living document.** Improve it as the repo matures. Treat changes to `CLAUDE.md` as architecturally significant — open a Gitea issue, then a PR, then get review.