Files
motovaultpro/.claude/skills/mvp-build-and-env/SKILL.md
T
Eric GullicksonandClaude Fable 5 c239bb9347
Deploy to Staging / Build Images (push) Successful in 5m32s
Deploy to Staging / Deploy to Staging (push) Successful in 44s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 4s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
chore: replace AI skill library with 16 verified mvp-* skills
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]>
2026-07-09 20:21:42 -05:00

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:

  1. Edit code locally. Run unit tests + lint + type-check + build locally (per workspace, below).
  2. 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-qa section 1). The only compile check CI performs is tsc inside the Dockerfiles.
  3. End-to-end verification happens ON STAGING (https://staging.motovaultpro.com) after the PR pipeline deploys. See mvp-validation-and-qa for 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:

  1. The tdd-guard-jest reporter is declared only in the ROOT package.json devDependencies, and no root node_modules/ exists on a fresh checkout (root has no scripts, nobody runs npm install there), so the module cannot resolve from frontend/.
  2. 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