CLAUDE.md#
Guidance for Claude Code when working in this repository.
About this project#
This is ai-assisted-todolist: a browser-based todo list app (Quarkus backend,
React + TypeScript frontend, PostgreSQL persistence). It is primarily a demo
project — its main purpose is to build up experience with AI-assisted
software development, not to ship production software. Favor clarity and
learning value in explanations and commits over maximal speed.
About the developer#
Gunnar Hilling. Experienced software developer, especially with Java and Quarkus. Do not over-explain Java/Quarkus/backend fundamentals — assume that knowledge. Frontend (React/TypeScript) and AI-assisted workflows are areas where more context and explanation are welcome.
Ask, don't guess#
If a requirement, API contract, data model, or design decision is unclear or underspecified, stop and ask rather than guessing or picking a default silently. This applies especially to:
- Domain rules and business logic for the todo model (states, transitions, validation rules).
- Auth/OIDC provider behavior and configuration.
- Anything where multiple reasonable implementations exist and the choice affects the design.
Guessing silently and moving on is the failure mode to avoid here, even if it slows things down.
Workflow#
- Always work on a git branch, never commit directly to
main. - Every change reaches
mainthrough a pull request; themain-branchruleset enforces it and rejects direct pushes. No approving review is required, so you can merge your own pull request once CI is green. - Delete a branch once its pull request is merged — locally and on
origin— unless told otherwise. Because pull requests are squash-merged, a merged branch does not show up ingit branch --merged main, so stale branches are easy to lose track of if they are not cleaned up straight away. - Anything that can reasonably be enforced automatically (tests, coverage, style/lint checks, build) should be enforced by the standard GitHub Actions CI/CD runs, not left as a manual convention.
- Project-level documentation lives in
/docand is the source of truth; the rootREADME.mdis a short entry point that links into it. Update/docin the same change as the behaviour it describes, never as a follow-up — the README had already drifted into documenting endpoints and task states that no longer existed. - The API contract is generated and drift-gated, never written twice. The backend's
OpenAPI document and the per-type JSON Schemas under
doc/api/come fromdoc/api/generate.py; the frontend's wire types and response validators underfrontend/src/generated/come fromnpm run generate:api. Backend CI and Frontend CI each regenerate their half and fail on any difference, so neither can go stale and neither is hand-edited. A wire change starts at the backend record. - Code-level documentation is Javadoc and TSDoc in the code itself, saying what
a type is for. The conventions are in
backend/CLAUDE.mdandfrontend/CLAUDE.md; on the backend they are enforced by Checkstyle. - Deployment artifacts live under
/deployment, never at the repository root:deployment/docker/holds the Compose stacks anddeployment/aws-tofu/the AWS infrastructure. The AWS code is OpenTofu, not Terraform — the binary istofu, anddeployment/aws-tofu/README.mdsays what that changes. - The two AWS environment roots are byte-identical apart from
terraform.tfvars, anddeployment/aws-tofu/check-environments-match.pyfails CI when they are not. Every resource belongs inmodules/environment/; anything that must differ betweenqaandprodbecomes a module variable. Never add a resource to an environment root. deployment/aws-tofu/account/is the exception, and is a root with resources in it: it holds what there is one of per AWS account, such as the GitHub OIDC provider. Apply it before either environment — they look the OIDC provider up by URL and cannot plan until it exists.- Infrastructure is applied by a human, never by CI. The deploy identities are OIDC roles
scoped to redeploying the application; there is deliberately no credential anywhere that can
run
tofu apply.doc/decisions.mdexplains why that is a guarantee rather than a policy. - Never commit secrets (API keys, OIDC client secrets, real credentials,
etc.). Only local, testing-only placeholder values belong in the repo
(e.g.
.env.example-style files or dev/test configuration); real secrets are supplied via environment variables / CI secrets only. - Never hand-edit a version. A release is a git tag (
v1.2.3); the backend'spom.xmlcarries${revision}and the release build overrides it from the tag.mainstays1.0.0-SNAPSHOTpermanently, and the frontend'spackage.jsonversion is unused because the package is never published.doc/releasing.mdhas the whole procedure. - A SNAPSHOT dependency fails every backend build, not just a release
(
requireReleaseDepsatvalidate); the npm counterpart isnpm run check:depsinfrontend/. Do not move either into a release-only path — the point is to fail when the dependency is added. - Dependency updates come from Renovate, not by hand. Patch and minor
updates automerge once every check is green; majors wait for Gunnar. The
platformAutomerge: falsein.github/renovate.jsonis load-bearing: themain-branchruleset requires no status checks, so GitHub's own auto-merge would merge before anything had run. Theschedulethere is deliberate and restricts pull request creation only — Renovate acting outside it is expected, because it can be run directly from the Mend dashboard, so never remove it as leftover setup debris. - The project is licensed Apache-2.0 (
LICENSE, verbatim). There are deliberately no per-file license headers — seedoc/decisions.md.
Issues#
- A piece of work Claude should finish without a conversation first is written with the
Story issue form (
.github/ISSUE_TEMPLATE/story.yml): what and why, done when, constraints, decisions — yours or mine, out of scope. - Prefer the outcome to the mechanism. A named mechanism that turns out not to work costs a
round trip; an outcome does not. Issue #70 asked for
Optional<T>, which breaks the OpenAPI document, so the ask became a question instead of a change. - Say which decisions are delegated. Ask-don't-guess above means an unmarked decision becomes a question and the run stops there, which is right — but most decisions do not need Gunnar, and saying so is what lets a story be finished in one go.
- Anything that is not a story — a bug, a question, a note to self — uses the blank form.
Development methodology#
- TDD for implementation. Write a failing test first, then the minimal code to make it pass, then refactor. This applies to both backend (JUnit/Quarkus test framework) and frontend (whatever test runner is configured) work. Don't write production code without a test driving it.
- DDD for planning. When planning a feature or change, think in terms of the domain first: ubiquitous language, entities, value objects, aggregates, and bounded contexts, before jumping to REST endpoints, database schema, or UI components. Surface domain modeling questions to Gunnar rather than assuming an answer.
- Never change or disable an existing unit test without asking Gunnar first. This includes editing its assertions/setup, deleting it, or marking it skipped/disabled — always ask before touching a test that already exists, even if it appears to be blocking other work.