Removes the old planner/decision-critic/role-agents/domain-agents system (step-injector scripts, stale scopes, sprint-era workflow) and replaces it with 16 ground-truth-verified skills under .claude/skills/: change control, debugging playbook, failure archaeology, architecture contract, domain reference, OCR/Gemini pipeline, config and secrets, build and env, run and operate, diagnostics (with tested scripts), validation and QA, docs and writing, launch readiness, deploy-safety campaign, proof and analysis toolkit, and research frontier. RULE 0/1/2, the temporal-contamination rule, and the decision stress-test protocol are carried forward into the new skills; the retired content remains in git history. Co-Authored-By: Claude Fable 5 <[email protected]>
17 KiB
name, description
| name | description |
|---|---|
| mvp-build-and-env | Load when starting from a fresh MotoVaultPro checkout or when any build/test/install command fails locally. Triggers - "how do I run this", "npm test fails", "Could not resolve a module for a custom reporter", "tdd-guard-jest", "Jest did not exit one second after the test run", jest hangs, "Configuration file not found at /app/config/production.yml", "make setup fails", secrets are directories, "mobile-setup nothing to be done", pytest/paddleocr install, Dockerfile build questions, "why is my migration not on staging". Covers what works on the dev machine vs container-only, the exact local test loop per workspace, Docker build anatomy, and known environment traps. |
Build and Environment Reality
All commands below were verified by running them or reading the exact source on 2026-07-07. Where docs contradict code, code wins; this file states code behavior.
When to use / When NOT to use
Use this skill when you have a fresh checkout and need to build, lint, type-check, or run tests, or when a local command fails in a confusing way.
Do NOT use this skill for:
- Deploying, rollback, staging/prod operations, backups:
mvp-run-and-operate - What counts as test evidence, adding tests, mobile+desktop validation:
mvp-validation-and-qa - Issue/branch/PR workflow and review rules:
mvp-change-control - Runtime failures of a deployed stack:
mvp-debugging-playbook - Config keys, secrets catalog, feature tiers:
mvp-config-and-secrets
1. The development model (read this first)
Development on this project is done by AI sessions working in this repo; the human owner reviews PRs. There is NO fully working local dev loop and none is expected. The loop is:
- Edit code locally. Run unit tests + lint + type-check + build locally (per workspace, below).
- Open a PR. CI builds 3 Docker images and deploys them to staging. That is the entire gate — no tests, no lint (canonical statement:
mvp-validation-and-qasection 1). The only compile check CI performs istscinside the Dockerfiles. - End-to-end verification happens ON STAGING (https://staging.motovaultpro.com) after the PR pipeline deploys. See
mvp-validation-and-qafor the evidence bar.
Consequence: anything your local commands do not catch, nothing catches before staging. Run the local gates every time; they are the only gates.
2. What works on the dev machine (verified 2026-07-07)
Root package.json has NO scripts and no workspaces. npm test / npm run lint at repo root fail with "Missing script". Always cd into backend/, frontend/, or ocr/.
Bootstrap
make install # runs npm install in frontend/ AND backend/ (Makefile:220)
# or individually:
cd backend && npm install
cd frontend && npm install
Dev-safe make targets (Makefile:218-254; these never touch Docker)
| Target | Does |
|---|---|
make install |
npm install in frontend + backend |
make type-check |
npm run type-check in frontend + backend |
make lint |
npm run lint in frontend + backend |
make build-local |
npm run build in frontend + backend (outputs frontend/dist, backend/dist) |
Every other make target (setup, start, rebuild, migrate, clean, ...) drives Docker. Per root CLAUDE.md, make setup/make rebuild are for staging/prod-style builds, NOT development — and on this machine make setup does not produce a working stack anyway (section 3).
backend/ — everything works
cd backend
npm run lint # eslint src (flat config eslint.config.js)
npm run type-check # tsc --noEmit
npm run build # tsc --project tsconfig.build.json -> dist/
npm test -- --forceExit # all unit tests
npm test -- --forceExit --testPathPattern=src/features/fuel-logs # one feature
npm run test:feature --feature=fuel-logs # same, needs --forceExit caveat too
--forceExit is MANDATORY locally. Without it jest prints "Jest did not exit one second after the test run" and hangs indefinitely (verified: killed after 8+ min; with --forceExit the same suite finishes in ~2 s). Cause: backend/src/core/config/database.ts creates a pg Pool eagerly and unit tests leave open handles. Do not "fix" a hang by waiting.
Expect a RED baseline on main: 15 of 25 unit suites fail pre-existing (6 die loading real config, 7 fail ts-jest compilation, 2 contain the 2 genuinely failing tests; Tests: 2 failed, 147 passed, 149 total, re-verified 2026-07-09 — baseline detail homed in mvp-deploy-safety-campaign Phase 0.3). Judge your change against that baseline, not against zero.
Why unit tests work at all locally: they mock core/config/config-loader and core/config/redis at the top of the test file (see src/features/stations/tests/unit/station-matching.test.ts:5-15). The real config-loader is an eager singleton (config-loader.ts:281: export const appConfig = configLoader.load()) that throws Configuration file not found at /app/config/production.yml if imported unmocked outside a container. If you see that error in a test, the test (or something it imports) is loading real config — mock it like the existing unit tests do.
Integration tests (src/features/*/tests/integration/) are container-only AND destructive — see section 3.
Broken script: npm run migrate:feature references src/_system/migrations/run-feature.ts, which does not exist (only run-all.ts does). It fails on invocation.
frontend/ — build tools work, npm test is broken outside the container
cd frontend
npm run lint # works
npm run type-check # works
npm run build # works: tsc --project tsconfig.build.json && vite build
npm run dev # vite dev server on :3000 (UI only; API calls need a backend)
npm test fails outside the container with:
Error: Could not resolve a module for a custom reporter. Module name: tdd-guard-jest
Two independent causes in frontend/jest.config.ts:28-36:
- The
tdd-guard-jestreporter is declared only in the ROOTpackage.jsondevDependencies, and no rootnode_modules/exists on a fresh checkout (root has no scripts, nobody runsnpm installthere), so the module cannot resolve fromfrontend/. - The reporter config hardcodes
projectRoot: '/home/egullickson/motovaultpro'— a Linux path from the container/CI host, wrong on this macOS checkout even if the module resolved.
WORKING FALLBACK (verified 2026-07-07, executes and reports results):
cd frontend
npx jest src/path/to/File.test.tsx --reporters=default # single file
npx jest --reporters=default --testPathPattern=fuel-logs # by pattern
npx jest --reporters=default # full suite
Trap: --reporters is greedy. npx jest --reporters=default src/Foo.test.tsx (positional AFTER the flag) swallows the path as a second reporter name and fails with "Could not resolve a module for a custom reporter. Module name: src/Foo...". Put the positional path FIRST, or use --testPathPattern.
Expect the fallback run to be RED at baseline on main: Tests: 17 failed, 196 passed, 213 total across 14 failing suites (re-verified 2026-07-09; the baseline numbers are homed in mvp-deploy-safety-campaign Phase 0.3). Diff your run against that baseline — do not attribute the pre-existing failures to your change, and do not report "only 1 known failure". There is NO in-container alternative: the shipped frontend image is the nginx production stage (no node/npm) and frontend/.dockerignore excludes *.test.* from every stage, so docker compose exec mvp-frontend npm test (still documented in docs/TESTING.md) has never been able to work. This fallback plus staging verification is the whole frontend test story today.
Also note: frontend/test/ contains test files outside jest roots: ['<rootDir>/src'] — they never run anywhere. See mvp-validation-and-qa.
ocr/ — tests exist; running them locally is heavyweight and UNVERIFIED
cd ocr
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # includes paddleocr, google-cloud-vision, google-genai — multi-GB, slow
python -m pytest tests/
Verified facts only: ocr/requirements.txt lists pytest>=7.4.0 and pytest-asyncio>=0.21.0; 16 test files exist under ocr/tests/; tests import from app.main import app; there is no venv, no pytest.ini, and no conftest.py in the repo. The pip install and pytest run above were NOT executed during authoring (paddleocr download is too heavy) — UNVERIFIED as a complete recipe. CI never runs OCR tests either (ocr/Dockerfile has no test step), so OCR test results always come from a manual run.
3. Container-only, and why
Backend integration tests — DESTRUCTIVE, backup first (non-negotiable)
backend/src/features/*/tests/integration/*.test.ts need a real Postgres + Redis and real config (CONFIG_PATH, SECRETS_DIR — container paths). docs/TESTING.md:24-32 documents make shell-backend then npm test, but that recipe is STALE-DANGEROUS: the shipped backend image is the production stage (npm ci --omit=dev — no jest; only dist/ plus migration SQL copied), so npm test inside it fails with "jest: not found". As-shipped, the integration tests are runnable nowhere; the viable paths are a builder-stage image (docker build --target builder ...) or a host run against an ephemeral Postgres/Redis — see mvp-deploy-safety-campaign 1B.
They are DESTRUCTIVE by design: beforeAll executes the feature's real migration SQL and afterAll drops the tables, e.g. vehicles.integration.test.ts:38 runs DROP TABLE IF EXISTS vehicles CASCADE and drops update_updated_at_column() CASCADE (which other tables' triggers depend on); admin.integration.test.ts:72-73 drops admin_audit_logs and admin_users. They run against whatever database the container config points at — the shared dev DB motovaultpro.
Owner non-negotiable: never run integration tests against a database you care about without a fresh backup first (make db-backup or ./scripts/export-database.sh). Restore path: ./scripts/import-database.sh. See mvp-run-and-operate.
Note: config/app/ci.yml ("CI-specific configuration for backend tests") exists but nothing in the repo references it — it also points at mvp-postgres/db motovaultpro, so it does not make the tests safe.
Full local stack — currently cannot work on this machine
make setup (compose up --build + migrations) is labeled staging/prod-build only in root CLAUDE.md, and on this checkout it will not produce a healthy stack because four "secret files" exist only as Docker-created EMPTY DIRECTORIES (Docker auto-creates a directory when a bind-mount source file is missing; canonical description of this trap: mvp-config-and-secrets section 2):
secrets/app/stripe-secret-key.txt <- directory
secrets/app/stripe-webhook-secret.txt <- directory
secrets/app/auth0-ocr-client-id.txt <- directory
secrets/app/auth0-ocr-client-secret.txt <- directory
The backend's Zod secrets schema (config-loader.ts:114-125) requires stripe_secret_key and stripe_webhook_secret as non-optional strings; reading a directory fails, validation throws, and the backend crash-loops at startup. The OCR container bind-mounts the two auth0-ocr paths (docker-compose.yml:213-214). To ever make the local stack work: rm -rf the four directories, create real .txt files (see the .txt.example siblings), then make setup. Until then, local full-stack is off the table — which is consistent with the dev model in section 1.
Verify current state: ls -la secrets/app/ | grep '^d' (any .txt entry that is a directory is a broken mount point).
4. Docker build anatomy
Three Dockerfiles; CI (.gitea/workflows/staging.yaml) builds all three on every push to main and every PR sync, tags :<7-char-sha> AND :latest, and pushes to the registry.
| Image | Dockerfile | Compile gate | Registry |
|---|---|---|---|
| backend | backend/Dockerfile (2-stage) |
RUN npm run build (tsc) at line 31 |
git.motovaultpro.com/egullickson/backend |
| frontend | frontend/Dockerfile (4-stage) |
RUN npm run build (tsc + vite) at line 35 |
git.motovaultpro.com/egullickson/frontend |
| ocr | ocr/Dockerfile (1-stage) |
none (pip install only, no tests) | git.motovaultpro.com/egullickson/ocr |
These tsc runs are the ONLY compile gate CI has. A PR is "green" when the 3 images build and staging boots healthy — nothing else.
Migrations ship inside the backend image: backend/Dockerfile:58-61 sets ENV MIGRATIONS_DIR=/app/migrations and copies src/features and src/core there; the container CMD (Dockerfile:84) runs node dist/_system/migrations/run-all.js && npm start on every start. Therefore a NEW MIGRATION REACHES STAGING/PROD ONLY VIA AN IMAGE REBUILD — merging SQL into the repo does nothing until CI builds and deploys a new backend image. A new feature's migrations also require an entry in MIGRATION_ORDER in backend/src/_system/migrations/run-all.ts or they silently never run.
Frontend build-time vs runtime config: all VITE_* values are baked at image build via compose/CI build args (docker-compose.yml:60-64, ARGs at frontend/Dockerfile:21-25). Exception: the Google Maps key/map-id are injected at container RUNTIME by frontend/scripts/load-config.sh from /run/secrets, not at build.
PR builds clobber :latest — deploy implications and blue-green mechanics are in mvp-run-and-operate.
5. Environment traps table
| Trap | Reality | Do instead |
|---|---|---|
Root npm test/npm run lint |
Root package.json has NO scripts (root CLAUDE.md is wrong here; code wins) |
cd into backend/ or frontend/ |
| Root dependencies | test@^3.3.0 (stray/accidental package, supply-chain smell), plus jest, @playwright/test, tdd-guard-jest with no root node_modules — all dead weight |
Do not npm install at root expecting anything; do not add root deps |
make mobile-setup |
Advertised in help and .PHONY but HAS NO RULE — "Nothing to be done" |
Mobile testing procedure: mvp-validation-and-qa |
make clean |
docker compose down -v --rmi all — DESTROYS DB VOLUMES wherever run |
Non-negotiable: fresh backup first (make db-backup) |
| Backend jest hang | pg pool open handles; jest never exits | Always npm test -- --forceExit locally |
Frontend npm test |
Broken outside container (tdd-guard-jest reporter + hardcoded Linux projectRoot) | npx jest <path> --reporters=default; positional path BEFORE the flag |
| Integration tests | DROP TABLE ... CASCADE on the shared dev DB |
Container-only, backup first, never against a DB you care about |
make setup locally |
4 secrets are empty directories; backend Zod validation crash-loops | Treat local full-stack as unavailable; verify on staging |
frontend/.env.local |
Affects npm run dev only; deployed images use build args baked by CI |
Change Gitea CI variables / compose build args for deployed values |
| New migration "not on staging" | Migrations live inside the backend image at /app/migrations |
Merge -> CI image build -> deploy; check _migrations table |
npm run migrate:feature (backend) |
Points at nonexistent run-feature.ts |
npm run migrate (runs all, idempotent per file) |
Provenance and maintenance
Authored 2026-07-07 against commit e729d42 (main). All commands in sections 2 and the frontend fallback were executed on the dev machine that day, except the OCR venv/pytest recipe (labeled UNVERIFIED). Volatile facts and how to re-check them:
| Fact | Re-verify with |
|---|---|
Root package.json still has no scripts + stray test dep |
cat package.json |
| Backend scripts unchanged (test/lint/type-check/build, broken migrate:feature) | cat backend/package.json and ls backend/src/_system/migrations/ |
| Frontend jest still broken (reporter + projectRoot) | grep -n -A6 reporters frontend/jest.config.ts |
| Frontend fallback still works | cd frontend && npx jest --reporters=default --listTests |
| Backend jest still hangs without forceExit | cd backend && npx jest src/features/stations/tests/unit/station-matching.test.ts (should finish; if it hangs, trap still live) |
| 4 secrets still empty directories | ls -la secrets/app/ | grep '^d' |
make mobile-setup still ruleless |
grep -n "mobile-setup:" Makefile || echo missing |
| Integration tests still DROP TABLE | grep -rn "DROP TABLE" backend/src --include="*.test.ts" |
| Migrations still image-packaged | grep -n "MIGRATIONS_DIR|migrations" backend/Dockerfile |
| CI still runs zero tests/lint | grep -rn "npm test|npm run lint" .gitea/workflows/ || echo none |
| Registry image names | grep -n "egullickson/" .gitea/workflows/staging.yaml | head |
| OCR pytest deps present | grep -n "pytest" ocr/requirements.txt |