Cornerstone is a web-based home building project management application designed to help homeowners manage their construction project. It tracks work items, budgets (with multiple financing sources and subsidies), timelines (Gantt chart), and household item purchases.
- Target Users: 1-5 homeowners per instance (self-hosted)
- Deployment: Single Docker container with SQLite
- Requirements: See
plan/REQUIREMENTS.mdfor the full requirements document
This project uses a team of 11 specialized Claude Code agents defined in .claude/agents/:
| Agent | Role |
|---|---|
product-owner |
Defines epics, user stories, and acceptance criteria; manages the backlog |
product-architect |
Tech stack, schema, API contract, project structure, ADRs, Dockerfile |
ux-designer |
Design tokens, brand identity, component styling specs, dark mode, accessibility |
dev-team-lead |
Spec-writer, reviewer, and committer (Sonnet): decomposes work into implementation specs, reviews agent output, commits and monitors CI |
backend-developer |
API endpoints, business logic, auth, database operations (Haiku, launched by orchestrator with dev-team-lead specs) |
frontend-developer |
UI components, pages, interactions, API client (Haiku, launched by orchestrator with dev-team-lead specs) |
translator |
Non-English translations, glossary enforcement (Sonnet, launched by orchestrator with dev-team-lead Translator Specs) |
qa-integration-tester |
Unit test coverage (95%+ target), integration tests, performance testing, bug reports |
e2e-test-engineer |
Playwright E2E browser tests, page objects, smoke tests, responsive testing, dependent system integration testing |
security-engineer |
Security audits, vulnerability reports, remediation guidance |
docs-writer |
Documentation site (docs/), lean README.md, user-facing guides after UAT approval |
| Concern | Tool |
|---|---|
| Backlog, epics, stories, bugs | GitHub Projects board + GitHub Issues |
| Architecture, API contract, schema, ADRs, security audit | GitHub Wiki |
| Code review | GitHub Pull Requests |
| Source tree | Code, configs, Dockerfile, CLAUDE.md only |
| User-facing docs site | docs/ workspace (Docusaurus, GitHub Pages) |
The GitHub Wiki is checked out as a git submodule at wiki/ in the project root. All architecture documentation lives as markdown files in this submodule. The GitHub Projects board is the single source of truth for backlog management.
- Architecture — system design, tech stack, conventions
- API Contract — REST API endpoint specifications
- Schema — database schema documentation
- ADR Index — links to all architectural decision records
- ADR-NNN-Title — individual ADR pages
- Security Audit — security findings and remediation status
- Style Guide — design system, tokens, color palette, typography, component patterns, dark mode
Wiki pages are markdown files in wiki/. Sync before reading: git submodule update --init wiki && git -C wiki pull origin master. See skill files for writing workflows and page naming conventions.
- Repository:
steilerDev/cornerstone - Default branch:
main - Integration branch:
beta(feature PRs land here; promoted tomainafter epic completion)
The GitHub Projects board uses 5 statuses: Backlog, Todo, In Progress, Done, Wont-Do. All stories must be linked as sub-issues of their parent epic, and dependency relationships must be maintained. Use native gh project CLI commands for board status management (gh project item-list, gh project item-edit, gh project item-add). GraphQL mutations are still needed for addSubIssue and addBlockedBy. Board IDs and exact commands are in the skill files and agent definitions.
Important: Planning agents run first. Always launch the product-owner and product-architect agents BEFORE implementing any code. Planning only needs to run for the first story of an epic — subsequent stories reuse the established plan.
One user story per development cycle. Each cycle completes a single story end-to-end (architecture → implementation → tests → PR → review → merge) before starting the next.
Compact context between stories. After completing each story (merged and moved to Done), compact context before starting the next. Only agent memory persists between stories.
Mark stories in-progress before starting work. When beginning a story, immediately move its GitHub Issue to "In Progress" on the Projects board.
The orchestrator delegates, never implements. Must NEVER write production code, tests, or architectural artifacts. Delegate all implementation:
- Implementation specs →
dev-team-leadagent (produces specs, reviews code, commits) - Backend code →
backend-developeragent (Haiku, launched by orchestrator with dev-team-lead specs) - Frontend code →
frontend-developeragent (Haiku, launched by orchestrator with dev-team-lead specs) - Non-English translations →
translatoragent (Sonnet, launched by orchestrator with dev-team-lead Translator Specs) - Unit/integration tests →
qa-integration-testeragent (launched by orchestrator with dev-team-lead specs) - E2E browser tests →
e2e-test-engineeragent (launched by orchestrator with dev-team-lead specs) - Visual specs, design tokens, brand assets, CSS files →
ux-designeragent - Schema/API design, ADRs, wiki →
product-architectagent - Story definitions →
product-owneragent - Security reviews →
security-engineeragent - User-facing documentation (docs site + README) →
docs-writeragent
The orchestrator uses four skills to drive work. Each skill contains the full operational checklist with exact commands and agent coordination. The orchestrator delegates all work — never writes production code, tests, or architectural artifacts directly.
| Skill | Purpose | Input |
|---|---|---|
/epic-start |
Planning: PO creates stories, architect designs schema/API/ADRs | Epic description or issue number |
/develop |
Full dev cycle for one or more stories/bug fixes, bundled into a single PR | Issue number, description, semicolon-separated list, or @file |
/epic-close |
Refinement, E2E validation, UAT, then delegates to /release |
Epic issue number |
/release |
Promote beta to main: sync, PR, CI, approval loop, docs, merge |
Optional epic issue number (standalone if omitted) |
/epic-run |
Autonomous end-to-end epic: plan, develop all stories, close | Epic description or issue number |
Every epic follows a two-phase validation lifecycle. Development phase (/develop): PO defines acceptance criteria, QA + E2E + security review each story/bug PR — PRs auto-merge after CI green + all reviewers approved. Epic validation phase (/epic-close): refinement, E2E coverage confirmation, UAT scenarios fed to e2e-test-engineer, promotion, then docs update. Use /epic-run to execute the entire lifecycle in a single session. The only human gate is promotion from beta → main, where the user reviews a comprehensive summary with change inventory, validation report, and manual validation checklist. If the user provides feedback via /tmp/notes.md, fixes are applied autonomously (PO groups items into issues, /develop fixes each group) and the promotion PR is re-created — looping until the user approves. Documentation runs after approval to reflect the final state.
- User approval required for promotion — the user is the final authority on
beta→mainpromotion - Automated before manual — all automated tests must be green before the user validates
- Iterate until right — failed validation triggers a fix-and-revalidate loop (user writes feedback to
/tmp/notes.md, system fixes autonomously and re-presents) - Acceptance criteria live on GitHub Issues — stored on story issues, summarized on promotion PRs
- Security review required — the
security-engineermust review every story PR - Test agents own all tests —
qa-integration-testerowns unit and integration tests;e2e-test-engineerowns Playwright E2E browser tests. Developer agents do not write tests. - Flat delegation model — the orchestrator launches all agents directly. The
dev-team-leadproduces implementation specs, reviews agent output, and handles commits/CI. The orchestrator routes specs tobackend-developer,frontend-developer,translator,qa-integration-tester, ande2e-test-engineer.
All commits follow Conventional Commits:
- Types:
feat:,fix:,docs:,chore:,refactor:,test:,build:,ci: - Scope optional but encouraged:
feat(work-items):,fix(budget):,docs(adr): - Breaking changes: Use
!suffix orBREAKING CHANGE:footer - Every completed task gets its own commit with a meaningful description
- Link commits to issues: When a commit resolves work tracked in a GitHub Issue, include
Fixes #<issue-number>in the commit message body (one per line for multiple issues). Note:Fixes #Nonly auto-closes issues when the commit reachesmain(notbeta). - Always commit, push to a feature branch, and create a PR after work is complete. Lint, format, and audit fixes are handled by the CI auto-fix bot on
beta. Do not leave work uncommitted or unpushed. Never push directly tomainorbeta.
Do NOT run npm test, npm run lint, npm run typecheck, or npm run build manually. Lint, format, and audit issues are handled by the CI auto-fix bot (.github/workflows/auto-fix.yml), which runs on every push to beta and creates a fix PR if needed. Unfixable lint issues are surfaced during /epic-close (step 2b: Lint Health Check).
- CI auto-fix bot:
npm run lint:fix+npm run format+npm audit fix(runs onbetapush, creates PR if changes needed) - CI Quality Gates: typecheck + test + build (runs on every PR)
To validate your work: commit and push. After pushing, always wait for the required CI gates to pass before proceeding to the next step.
gh pr checks --watch does not support GitHub Rulesets (only legacy branch protection). Use the polling loops below to watch the required gate checks by name.
Step 1 — Check for merge conflicts. CI may not run (or silently hang) if the PR has conflicts. Always verify mergeability first:
state=$(gh pr view <PR> --repo steilerDev/cornerstone --json mergeable -q '.mergeable'); if [ "$state" != "MERGEABLE" ]; then echo "PR is not mergeable (state: $state) — resolve conflicts before waiting for CI"; exit 1; fiIf the state is CONFLICTING, rebase onto the target branch, force-push, and re-check. If the state is UNKNOWN, wait a few seconds and retry — GitHub may still be computing mergeability.
Step 2 — Poll for required gate checks.
Beta PRs (require Quality Gates only — expected ~5 minutes):
echo "Waiting for Quality Gates..."; SECONDS=0; while true; do if [ $SECONDS -ge 300 ]; then echo "TIMEOUT: Quality Gates did not complete within 5 minutes"; exit 1; fi; bucket=$(gh pr checks <PR> --repo steilerDev/cornerstone --json name,bucket -q '.[] | select(.name == "Quality Gates") | .bucket' 2>/dev/null); case "$bucket" in pass) echo "Quality Gates passed"; break ;; fail) echo "Quality Gates FAILED"; exit 1 ;; *) sleep 30 ;; esac; doneMain PRs (require Quality Gates + E2E Gates — expected ~15 minutes):
echo "Waiting for Quality Gates + E2E Gates..."; SECONDS=0; while true; do if [ $SECONDS -ge 900 ]; then echo "TIMEOUT: CI gates did not complete within 15 minutes"; exit 1; fi; qg=$(gh pr checks <PR> --repo steilerDev/cornerstone --json name,bucket -q '.[] | select(.name == "Quality Gates") | .bucket' 2>/dev/null); e2e=$(gh pr checks <PR> --repo steilerDev/cornerstone --json name,bucket -q '.[] | select(.name == "E2E Gates") | .bucket' 2>/dev/null); if [ "$qg" = "fail" ] || [ "$e2e" = "fail" ]; then echo "CI FAILED (QG=$qg, E2E=$e2e)"; exit 1; fi; if [ "$qg" = "pass" ] && [ "$e2e" = "pass" ]; then echo "All gates passed"; break; fi; sleep 30; doneReplace <PR> with the PR number. The polling loop handles the "checks not yet reported" edge case — an empty bucket means we retry after 30s. Timeouts prevent agents from polling indefinitely if CI hangs.
The only exception is the QA agent running a specific test file it just wrote (e.g., npx jest path/to/new.test.ts) to verify correctness before committing — but never npm test (the full suite).
The sandbox environment is resource-constrained. When running tests locally:
- Never run the full test suite (
npm test) — only run tightly scoped, specific test files (e.g.,npx jest path/to/specific.test.ts) - Always use a single worker — pass
--maxWorkers=1to Jest for any local test execution - Rely on CI for full suite validation
When gh or git push commands fail with a GitHub rate-limit error (primary API limit, secondary abuse limit, or HTTP 403/HTTP 429 with a rate-limit message), retry with exponential backoff instead of aborting:
- Detect: stderr contains
rate limit,secondary rate limit,abuse detection,was blocked,HTTP 403, orHTTP 429 - Backoff schedule: 30s → 60s → 120s → 240s → 480s (cap 480s, max 6 attempts)
- Honor
Retry-After/X-RateLimit-Resetheaders when present (use the larger of the header value and the current backoff step) - Only retry transient rate-limit failures. Permission errors, merge conflicts, and other non-transient failures must not be retried
- Log each retry attempt with its wait duration so the user can see progress
Apply the same policy when polling CI gates — if gh pr checks fails with a rate-limit error, the polling loop's normal sleep already absorbs short-lived throttling; for persistent rate-limit errors, extend the sleep per the backoff schedule above.
All agents must clearly identify themselves:
- Commits:
Co-Authored-By: Claude <agent-name> (<model>) <noreply@anthropic.com>— see each agent's definition file for the exact trailer. - GitHub comments: prefix with
**[agent-name]**(e.g.,**[backend-developer]** This endpoint...) - Orchestrator: when committing work produced by an agent, use that agent's name in the trailer.
The orchestrator launches all implementation agents directly using specs produced by the dev-team-lead. The dev-team-lead never launches sub-agents — it operates in three modes (spec, review, commit) and never modifies production files.
The orchestrator runs a trailer verification after every commit:
- Commit trailers must include appropriate co-authors for production file changes
- Files under
server/orshared/→ must havebackend-developertrailer - Files under
client/(exceptclient/src/i18n/de/andclient/src/i18n/glossary.json) → must havefrontend-developertrailer - Files under
client/src/i18n/de/orclient/src/i18n/glossary.json→ must havetranslatortrailer - Files under
e2e/→ must havee2e-test-engineertrailer
Commits that change production files without the appropriate Haiku co-author trailers are rejected and re-committed with corrected trailers.
Production files: any file under server/, client/, or shared/.
Never commit directly to main or beta. All changes go through feature branches and pull requests.
- Branch naming:
<type>/<issue-number>-<short-description>(e.g.,feat/42-work-item-crud,fix/55-budget-calc) - Never push a
worktree-<anything>branch. Worktree branches carry auto-generated names. Before pushing, always rename the branch to match the naming convention above:git branch -m <type>/<issue-number>-<short-description>. If the scope of work is not yet clear, determine it before pushing — do not publish placeholder branch names.
Sessions run in git worktrees. The user starts each session in a worktree manually. If the branch has a randomly generated name, rename it once scope is clear: git branch -m <type>/<issue-number>-<short-description>.
Rebase onto beta at session start. Worktrees are created from main. Before doing any work in a fresh session, rebase to beta: git rebase origin/beta. Skip only if the branch is already based on beta.
NEVER cd to the base project directory to modify files. All file edits, git operations, and commands must be performed from within the git worktree assigned at session start. The base project directory may have other sessions' uncommitted changes. This applies to subagents too — all file reads, writes, and exploration must use the worktree path.
See the skill files (.claude/skills/) for the full operational checklists. The typical lifecycle is: /epic-start (once per epic) → /develop (once per story, or batched for multiple small items) → /epic-close (once per epic after all stories merged). Alternatively, /epic-run chains all three phases in a single session (only pauses for promotion approval). Use /release standalone to promote beta to main without a prior epic definition.
Cornerstone uses a two-tier release model:
| Branch | Purpose | Release Type | Docker Tags |
|---|---|---|---|
beta |
Integration branch — feature PRs land here | Beta pre-release (e.g., 1.7.0-beta.1) |
1.7.0-beta.1, beta |
main |
Stable releases — beta promoted after epic completion |
Full release (e.g., 1.7.0) |
1.7.0, 1.7, latest |
Merge strategies:
-
Feature PR ->
beta: Squash merge (clean history) -
beta->main(epic promotion): Merge commit (preserves individual commits so semantic-release can analyze them) -
Hotfixes: Cherry-pick any
mainhotfix back tobetaimmediately. See/releasefor merge-back, release summary, and DockerHub sync details.
Both main and beta require PRs with passing Quality Gates. main additionally requires E2E Gates. Force pushes and deletions are blocked on both branches.
Full E2E tests (16 shards × 3 viewports) run on all PRs for visibility. Quality Gates covers static analysis, unit tests, Docker build, and E2E smoke tests — it does not wait for full E2E shards, so beta PRs can merge quickly. E2E Gates is a separate required check on main only — it waits for all E2E shards and blocks promotion if any fail. On main-targeted PRs, E2E shards also use fail-fast: the first non-recoverable failure stops the shard (maxFailures: 1) and cancels remaining shards.
| Layer | Technology | Version | ADR |
|---|---|---|---|
| Server | Fastify | 5.x | ADR-001 |
| Client | React | 19.x | ADR-002 |
| Client Routing | React Router | 7.x | ADR-002 |
| Database | SQLite (better-sqlite3) | -- | ADR-003 |
| ORM | Drizzle ORM | 0.45.x | ADR-003 |
| Bundler (client) | Webpack | 5.x | ADR-004 |
| Styling | CSS Modules | -- | ADR-006 |
| Testing (unit/integration) | Jest (ts-jest) | 30.x | ADR-005 |
| Testing (E2E) | Playwright | 1.59.x | ADR-005 |
| Language | TypeScript | ~6.0 | -- |
| Runtime | Node.js | 24 LTS | -- |
| Container | Docker (DHI Alpine) | -- | -- |
| Monorepo | npm workspaces | -- | ADR-007 |
Full rationale for each decision is in the corresponding ADR on the GitHub Wiki.
cornerstone/
package.json # Root workspace config, shared dev dependencies
CLAUDE.md # This file
Dockerfile # Multi-stage Docker build
plan/ # Requirements document
wiki/ # GitHub Wiki (git submodule) — architecture, ADRs, API contract
shared/ # @cornerstone/shared — TypeScript types
src/types/ # API types, entity types
server/ # @cornerstone/server — Fastify REST API
src/
routes/ # Route handlers by domain
plugins/ # Fastify plugins (auth, db, etc.)
services/ # Business logic
db/schema.ts # Drizzle schema definitions
db/migrations/ # SQL migration files
client/ # @cornerstone/client — React SPA
src/
components/ # Reusable UI components
pages/ # Route-level pages
hooks/ # Custom React hooks
lib/ # Utilities, API client
e2e/ # @cornerstone/e2e — Playwright E2E tests
containers/ # Testcontainers setup
fixtures/ # Test fixtures and helpers
pages/ # Page Object Models
tests/ # Test files by feature/epic
docs/ # @cornerstone/docs — Docusaurus site
src/ # Markdown content (guides, getting-started, development)
@cornerstone/shared <-- @cornerstone/server
<-- @cornerstone/client
@cornerstone/e2e (standalone — runs against built app via testcontainers)
@cornerstone/docs (standalone — Docusaurus, deployed to GitHub Pages)
shared (tsc) -> client (webpack build) -> server (tsc)
The docs workspace is NOT part of the application build (npm run build). Build it separately with npm run docs:build.
- Always use the latest stable (LTS if applicable) version of a package when adding or upgrading dependencies
- Pin dependency versions to a specific release — use exact versions rather than caret ranges (
^) to prevent unexpected upgrades - Avoid native binary dependencies for frontend tooling. Tools like esbuild, SWC, Lightning CSS, and Tailwind CSS v4 (oxide engine) ship platform-specific native binaries that crash on ARM64 emulation environments. Prefer pure JavaScript alternatives (Webpack, Babel, PostCSS, CSS Modules). Native addons for the server (e.g., better-sqlite3) are acceptable since the Docker builder can install build tools. esbuild has been fully eliminated from the dependency tree.
- Zero known fixable vulnerabilities. Run
npm auditbefore committing dependency changes. All fixable vulnerabilities must be resolved. - Always regenerate the lockfile with
npm install, notnpm install --package-lock-only—--package-lock-onlycan silently nest a dependency under a workspace directory instead of hoisting it to the rootnode_modules/, breaking TypeScript type resolution for other workspace consumers. After anypackage.jsonedit, run a fullnpm installto produce a correct lockfile.
| Context | Convention | Example |
|---|---|---|
| Database columns | snake_case | created_at, budget_category_id |
| TypeScript variables/functions | camelCase | createdAt, getBudgetCategory |
| TypeScript types/interfaces | PascalCase | WorkItem, BudgetCategory |
| File names (TS modules) | camelCase | workItem.ts, budgetService.ts |
| File names (React components) | PascalCase | WorkItemCard.tsx, GanttChart.tsx |
| API endpoints | kebab-case with /api/ prefix | /api/work-items, /api/budget-categories |
| Environment variables | UPPER_SNAKE_CASE | DATABASE_URL, LOG_LEVEL |
- Strict mode enabled (
"strict": truein tsconfig) - Use
typeimports:import type { Foo } from './foo.js'(enforced by ESLintconsistent-type-imports) - ESM throughout (
"type": "module"in all package.json files) - Include
.jsextension in import paths (required for ESM Node.js) - No
anytypes without justification (ESLint warns on@typescript-eslint/no-explicit-any) - Prefer
interfacefor object shapes,typefor unions/intersections
- ESLint: Flat config (
eslint.config.js), TypeScript-ESLint rules, React plugin for client code - Prettier: 100 char line width, single quotes, trailing commas, 2-space indent
- Run
npm run lintto check,npm run lint:fixto auto-fix - Run
npm run formatto format,npm run format:checkto verify
- All endpoints under
/api/prefix - Standard error response shape:
{ "error": { "code": "MACHINE_READABLE_CODE", "message": "Human-readable", "details": {} } } - HTTP status codes: 200 (OK), 201 (Created), 204 (Deleted), 400 (Validation), 401 (Unauthed), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 500 (Server Error)
Before creating a new UI component, check if an existing shared component can be used or extended. The shared component library lives in client/src/components/ and shared styles in client/src/styles/shared.module.css.
Shared components (must be used instead of creating alternatives):
Badge— status indicators, severity badges, outcome badges (parameterized by variant map)SearchPicker— search-as-you-type dropdowns for entity selection (work items, household items, etc.)Modal— dialog overlays with backdrop, escape key, focus managementSkeleton— loading placeholder with configurable line countEmptyState— empty data display with icon, message, and optional actionFormError— consistent error banner and field-level error display
Rules:
- New UI that resembles an existing shared component MUST use or extend that component
- If a shared component doesn't quite fit, extend it with new props — don't create a parallel implementation
- Every new component must be built as a reusable shared component — no one-off implementations. If a UI pattern doesn't fit an existing shared component, create a new shared component in
client/src/components/that can be reused by future features - New shared components require UX designer visual spec approval
- All CSS values must use design tokens from
tokens.css— no hardcoded colors, spacing, radii, or font sizes - Stylelint enforces token usage automatically
- Unit & integration tests: Jest with ts-jest (co-located with source:
foo.test.tsnext tofoo.ts) - API integration tests: Fastify's
app.inject()method (no HTTP server needed) - E2E tests: Playwright (runs against built app)
- E2E test files live in
e2e/tests/(separate workspace, not co-located with source) - E2E tests run against desktop, tablet, and mobile viewports via Playwright projects
- Test environment managed by testcontainers: app, OIDC provider, upstream proxy
- E2E test files live in
- Test command:
npm test(runs all Jest tests across all workspaces via--experimental-vm-modulesfor ESM) - Coverage:
npm run test:coverage— 95% unit test coverage target on all new and modified code - Test files use
.test.ts/.test.tsxextension - No separate
__tests__/directories -- tests live next to the code they test - E2E page coverage requirement: Every page/route in the application must have E2E test coverage. Fully implemented pages need comprehensive tests (CRUD flows, validation, responsive layout, dark mode). Stub/placeholder pages need at minimum a smoke test verifying the page loads and renders its heading.
Coverage is tracked and enforced through three complementary mechanisms:
1. CI Coverage Reports (automated)
Every PR triggers coverage collection across 6 Jest shards. The Coverage Report CI job merges shard results and uploads a coverage-report artifact containing coverage-summary.json (per-file and total percentages) and coverage-final.json (raw Istanbul data). Coverage reports are retained for 30 days.
- Test shards run with
--coverage --coverageReporters=json - The merge script (
scripts/merge-coverage.mjs) combines shard data and prints a text summary in CI logs - To inspect coverage for a PR: download the
coverage-reportartifact from the CI run
2. Test File Parity (enforced by dev-team-lead)
During [MODE: review], the dev-team-lead verifies that every new or modified production file has a corresponding test file. Production files added without test files result in VERDICT: CHANGES_REQUIRED with a fix spec routed to qa-integration-tester. This prevents untested code from entering the codebase.
3. Local Coverage Verification (enforced by qa-integration-tester)
The QA agent runs coverage on each new test file before committing:
npx jest path/to/file.test.ts --coverage --coverageReporters=text --maxWorkers=1This verifies 95%+ coverage on the corresponding source file before the code leaves the agent.
- Node.js >= 24
- npm >= 11
- Docker (for container builds)
git submodule update --init # Initialize wiki submodule
npm install # Install all workspace dependencies
npm run dev # Start server (port 3000) + client dev server (port 5173)| Command | Description |
|---|---|
npm run dev |
Start both server and client in watch mode |
npm run dev:server |
Start only the Fastify server (node --watch) |
npm run dev:client |
Start only the Webpack dev server |
npm run build |
Build all packages (shared -> client -> server) |
npm test |
Run all tests |
npm run lint |
Lint all code |
npm run format |
Format all code |
npm run typecheck |
Type-check all packages |
npm run test:e2e:smoke |
Run E2E smoke tests (desktop/Chromium only) |
npm run db:migrate |
Run pending SQL migrations |
npm run docs:dev |
Start docs site dev server (port 3001) |
npm run docs:build |
Build docs site to docs/build/ |
npm run docs:screenshots |
Capture app screenshots into docs/static/img/screenshots/ |
Docusaurus 3.x site deployed to GitHub Pages at https://steilerDev.github.io/cornerstone/. Deployed via the docs-deploy job in .github/workflows/release.yml on stable releases (screenshots are auto-captured from the released Docker image). Content: docs/src/ (user guides, end users) · wiki/ (architecture/ADRs, agents) · README.md (GitHub visitors) · CLAUDE.md (AI agents).
Hand-written SQL files in server/src/db/migrations/ with a numeric prefix (e.g., 0001_create_users.sql). Run npm run db:migrate to apply. The runner (server/src/db/migrate.ts) tracks applied migrations in _migrations and runs new ones in a transaction.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
HOST |
0.0.0.0 |
Server bind address |
DATABASE_URL |
/app/data/cornerstone.db |
SQLite database path |
LOG_LEVEL |
info |
Log level (trace/debug/info/warn/error/fatal) |
NODE_ENV |
production |
Environment |
CLIENT_DEV_PORT |
5173 |
Webpack dev server port (development only) |
EXTERNAL_URL |
(none) | Public-facing base URL (e.g., https://myhouse.example.com) for reverse-proxy setups |
PAPERLESS_URL |
(none) | Paperless-ngx instance base URL |
PAPERLESS_API_TOKEN |
(none) | Paperless-ngx API authentication token |
PAPERLESS_EXTERNAL_URL |
(none) | Browser-facing URL for Paperless-ngx links (falls back to PAPERLESS_URL if unset) |
PAPERLESS_FILTER_TAG |
(none) | Tag name for automatic document pre-filtering |
BACKUP_DIR |
/backups |
Backup destination directory (must be outside app data directory) |
BACKUP_CADENCE |
(none) | Cron expression for automatic backups (e.g., 0 2 * * * for daily at 2 AM) |
BACKUP_RETENTION |
(none) | Maximum number of backup archives to retain (oldest deleted when exceeded) |
Production images use Docker Hardened Images (DHI). See Dockerfile and docker-compose.yml for build/deploy details.
README.md: The> [!NOTE]block at the top ofREADME.mdis a personal note from the repository owner. Agents must NEVER modify, remove, or rewrite this note block. Other sections ofREADME.mdmay be edited as needed.
Any agent making a decision that affects other agents (e.g., a new naming convention, a shared pattern, a configuration change) must update this file so the convention is documented in one place.
When a code change invalidates information in agent memory (e.g., fixing a bug documented in memory, changing a public API, updating routes), the implementing agent must update the relevant agent memory files.
When tests fail during development, a structured diagnostic protocol determines whether the failure is in the test, the production code, or the spec — preventing wasted fix loops (e.g., weakening a correct test to make broken code pass).
- Source-of-truth hierarchy: Spec/Contract > Production code > Test code
- Rule: Correct tests must not be weakened to accommodate buggy code; correct code must not be broken to satisfy a wrong test
- Protocol owner: The
dev-team-leadruns the diagnostic decision tree during[MODE: review]when test failures are present in the review input. See the dev-team-lead agent definition for the full classification table and escalation rules. - Test agents report, not diagnose:
qa-integration-testerande2e-test-engineersubmit structured failure reports but do not determine whether the fault lies in code or tests — that judgment belongs to the dev-team-lead.
The application supports multiple locales (English and German) via i18next and react-i18next. All agents must follow these conventions:
- Frontend: All user-facing strings must use
t()from react-i18next — never hardcode text in JSX. Translation files live inclient/src/i18n/{lang}/{namespace}.json. Dev agents write English (en) keys only. - Translator owns non-English locales: The
translatoragent handles all non-English translations and enforces glossary compliance. Dev agents do not write German or other non-English translations. - Backend: API error responses must use
ErrorCodeenum values (machine-readable codes), not human-readable messages. The frontend translates error codes into locale-specific messages viatranslateApiError(). TheCURRENCYenv var (default:EUR) is exposed viaGET /api/config. - Formatting: Use
formatDate,formatCurrency,formatPercentfromclient/src/lib/formatters.ts— these read the locale from i18next automatically. Never use rawtoLocaleDateString()orIntl.NumberFormatdirectly. - Testing: QA must verify translation keys exist in both locales. E2E tests must verify locale detection and switching behavior.
- Specs: Dev-team-lead specs must include i18n requirements — translation namespace, English keys to add, and a Translator Spec section for the translator agent.
- Glossary:
client/src/i18n/glossary.json— single source of truth for domain term translations (multi-locale) - Dev agents write English only: frontend-developer adds
enkeys. Never writes non-English translations. - Translator owns all non-English locales: translates new keys + enforces glossary compliance
- Glossary scope: Domain-specific terms only (Work Item, Invoice, etc.), not common UI words
- Glossary updates: Translator proposes additions for new domain terms; product-owner approves terminology
- Adding a locale: Add locale code to
glossary.json_meta.locales, add translations for all terms, createclient/src/i18n/{locale}/directory with namespace files, register inclient/src/i18n/index.ts