Dev Environment Lifecycle (IaC)
TL;DR. Declare a project’s dev environment in
dev.json;/dev-upbrings it to a fully testable state — server up, migrations run, seed data loaded, test users provisioned, health checked. Warp-drive calls it automatically before the first chunk and between chunks.
Declarative dev environment management that ensures apps are always running, seeded, and testable — both on-demand and during warp-drive autonomous coding.
Overview
Section titled “Overview”Every project declares its dev environment in dev.json at the project root. The dev-up command reads this manifest and orchestrates the full lifecycle: server management, migrations, seed data, and test user provisioning. It’s idempotent, composable, and integrates directly into warp-drive.
Quick Start
Section titled “Quick Start”# 1. Copy the template to your projectcp ~/.claude/templates/dev.json ./dev.json
# 2. Customize for your project (edit server command, port, auth adapter, etc.)vi dev.json
# 3. Set up seed datamkdir -p seed/cp ~/.claude/templates/seed/users.json ./seed/cp ~/.claude/templates/seed/001-base-data.sql ./seed/cp ~/.claude/templates/seed/002-sample-entities.sql ./seed/# Customize the SQL for your schema
# 4. Run it~/.claude/scripts/dev-lifecycle/dev-up.sh . --verbosedev.json Manifest
Section titled “dev.json Manifest”{ "server": { "command": "npm run dev", // Command to start the dev server "port": 5173, // Port the server listens on "health": "/api/health", // Health check endpoint path "restart": "on-failure", // Restart strategy: "on-failure" | "always" | "never" "env": {} // Additional environment variables }, "migrations": { "command": "npm run migrate:dev", // Migration command (null to skip) "auto_run": true // Run migrations automatically }, "seed": { "directory": "seed/", // Path to seed scripts "runner": "node", // Script runner: "node" | "npx tsx" | custom "order": "alphabetical", // Execution order: "alphabetical" | "numeric" "d1_database": "db", // D1 database name for SQL seeds (if applicable) "auto_generate": true // Auto-generate seed data for uncovered tables during dev-up }, "auth": { "adapter": "d1", // Auth adapter: "d1" | "sqlite" | "supabase" | "script" | "custom" "users_file": "seed/users.json", // Path to test user definitions "table": "users", // Database table name for users "db_file": "dev.db", // SQLite database file path (for the "sqlite" adapter) "provision_command": null // Custom provisioning command (for "custom" adapter) }, "access": { "localhost": "http://localhost:5173", // Local access URL "farm_01": null // Remote dev server URL (if accessible) }}The full schema lives at schemas/dev.schema.json and is validated by make check. The block above is the canonical single-environment shape; templates/dev.json is the scaffold dev-up copies into new projects.
Multi-environment profiles & promotion (#447)
Section titled “Multi-environment profiles & promotion (#447)”Beyond the dev-only fields above, dev.json may declare higher environments and a promotion policy. Both blocks are optional — a project without them is a single-environment (dev-only) project, and existing manifests keep validating unchanged.
{ "environments": { "test": { // also: "prod", or any named env (e.g. "staging") "url": "https://app-test.example.workers.dev", "deploy": { "adapter": "cloudflare", // "cloudflare" | "node" | "drupal" | "script" | "none" "command": null, // override deploy command (script adapter) "target": "app-test" // deploy target (e.g. Worker/env name) }, "db": { "binding": "DB", // binding name in this environment "database": "app-db-test", // database identifier "adapter": "d1" // "d1" | "sqlite" | "supabase" | "postgres" | "none" }, "env": { "ENVIRONMENT": "test" } // env-specific variables } }, "promotion": { "ceiling": "prod", // how far promotion may go: "pr" | "external" | "test" | "prod" (default "external") "strategy": "ci" // how promotion is triggered: "manual" | "auto" | "ci" (refined in #448) }}environments.{test,prod,…}— per-environment deploy config, public URL, database binding, and env vars. The deploy-adapter interface is expanded in #450; the test-env provisioning + prod→test mirror in #451.promotion— the project’s promotion-pipeline policy.ceiling(required when the block is present) caps how far automated promotion may go:pr(stop at a pull request),external(merge to git but no deploy — the default),test/prod(merge and deploy up to that environment). The merge/deploy decision isf(automation_level, ceiling), enforced byscripts/promotion/promotion.js; see automation-behavior.md for the matrix. Defined in #448.
Dev-up recipes (#449)
Section titled “Dev-up recipes (#449)”dev.json is a committed, vetted file — generated once from a stack-keyed recipe, then owned by the project. dev-up uses the committed file; it does not regenerate it on every run. This replaced the old on-the-fly generation in bin/dev-up so warp-drive gets a stable, project-specific bring-up.
How it works
Section titled “How it works”Recipes live in templates/dev-recipes/ — one JSON file per stack (cloudflare-d1, sveltekit, drupal-ddev, node, …). Each is a dev.json template plus a _recipe metadata block:
{ "_recipe": { "name": "cloudflare-d1", "stacks": ["cloudflare-workers"], "priority": 20 }, "server": { "command": "npx wrangler dev", "port": "${PORT}", ... }, "seed": { "d1_database": "${D1_DATABASE}", ... }, ...}The engine (scripts/dev-lifecycle/dev-recipe.js):
- Detects the stack by reusing the fleet readiness audit’s detector (
scripts/fleet/readiness.js::detectStack) — there is no second stack detector to keep in sync. - Selects the highest-
priorityrecipe whose_recipe.stacksintersect the detected stack (so a Cloudflare+Node project pickscloudflare-d1, priority 20, overnode, priority 5). - Substitutes placeholders (
${PORT},${D1_DATABASE}) from lightly-detected values, strips_recipe, and writes a schema-validdev.json.
When no recipe matches (e.g. a CLI/tooling repo with no dev server), the engine writes nothing and dev-up skips the dev environment — no spurious dev.json.
Commands
Section titled “Commands”| Command | Effect |
|---|---|
dev-up [dir] |
Use the committed dev.json; seed one once from the recipe if missing |
dev-up --regen [dir] |
Rewrite dev.json from the stack recipe (overwrites) |
dev-recipe.js detect|render|write [--force]|list [dir] |
Inspect/drive recipes directly |
Adding a new stack recipe
Section titled “Adding a new stack recipe”Adding a stack is a pure data change — no engine edits:
- Drop a new
templates/dev-recipes/<name>.jsonfile. - Give it a
_recipeblock:name, thestacksit applies to (matchingdetectStacktokens such assveltekit,drupal,go,python), and apriority(higher wins ties; specific stacks should outrank genericnode). - Author the
dev.jsonbody, using${PORT}/${D1_DATABASE}where detection should fill values.
The engine discovers it automatically on the next run; the new recipe’s rendered output is validated against dev.schema.json by the test suite.
Port Allocation (#197)
Section titled “Port Allocation (#197)”Moved — see Port Allocation.
Scripts
Section titled “Scripts”All scripts live in ~/.claude/scripts/dev-lifecycle/ and follow the IaC prime directive: standalone, idempotent, parameterized, with --help.
| Script | Purpose | Usage |
|---|---|---|
dev-up.sh |
Full lifecycle orchestrator | dev-up.sh [DIR] [--check|--verbose|--skip-*] |
health-check.sh |
Lightweight health probe | health-check.sh [DIR] [--port N --path /path] |
provision-users.sh |
Test user provisioning | provision-users.sh [DIR] [--adapter TYPE] |
check-seed-coverage.sh |
Seed data coverage check | check-seed-coverage.sh [DIR] |
dev-up.sh
Section titled “dev-up.sh”The main orchestrator. Runs the full lifecycle:
- Server: Check health → start if not running → kill & restart if unhealthy
- Migrations: Run migration command if configured and
auto_runis true - Seed data: Execute all scripts in
seed/directory alphabetically - Test users: Provision users via the configured auth adapter
- Report: Output JSON status and access URL
Flags:
--check— Health check only (no start/seed/users). Fast gate for warp-drive.--skip-seed— Skip seed data scripts--skip-users— Skip user provisioning--skip-server— Skip server management (run seed/users only)--verbose— Show detailed step-by-step output
health-check.sh
Section titled “health-check.sh”Lightweight standalone health probe. Returns JSON:
{"status": "healthy", "port": 5173, "url": "http://localhost:5173"}provision-users.sh
Section titled “provision-users.sh”Reads seed/users.json and provisions users via pluggable adapters:
| Adapter | How it works |
|---|---|
d1 |
Generates SQL, executes via wrangler d1 execute --local |
sqlite |
Direct sqlite3 INSERT OR REPLACE |
supabase |
Delegates to supabase db reset --local |
script |
Runs project-local seed/provision-users.sh |
custom |
Runs auth.provision_command from dev.json |
check-seed-coverage.sh
Section titled “check-seed-coverage.sh”Advisory check: warns if migration/schema files changed but no seed files were modified. Used by warp-drive during the coding phase to remind about additive seed data.
Seed Data Convention
Section titled “Seed Data Convention”See ~/.claude/templates/seed/README.md for full details.
Key rules:
- Files run in alphabetical order. Use numeric prefixes:
001-base.sql,002-entities.sql - Every script MUST be idempotent (INSERT OR REPLACE, UPSERT, etc.)
- Seed entities in ALL lifecycle states your domain defines — not just fresh/active records
- New features add their own seed file in the same commit (e.g.,
010-feature-invoicing.sql)
Standardized Test Users
Section titled “Standardized Test Users”Defined in seed/users.json. The superuser credentials are consistent across all projects:
| Role | Password | Purpose | |
|---|---|---|---|
| superuser | admin@test.local |
admin123 |
Full access, consistent across projects |
| editor | editor@test.local |
editor123 |
Read/write/publish access |
| viewer | viewer@test.local |
viewer123 |
Read-only access |
| guest | guest@test.local |
guest123 |
Minimal/no access |
Projects should customize the role-specific users to match their permission model while keeping the superuser unchanged.
Warp-Drive Integration
Section titled “Warp-Drive Integration”When a project has dev.json, warp-drive automatically manages the dev environment:
| Phase | Action |
|---|---|
| prerequisites | Full dev-up --verbose — start everything before coding begins |
| coding | check-seed-coverage — advisory reminder if schema changes lack seed data |
| chunk_complete | dev-up --check — fast health gate before next chunk |
| chunk_complete (recovery) | Full dev-up --verbose if health check fails |
Dev health failure is treated as a blockable event. If recovery fails after 2 attempts, warp-drive escalates per its error protocol.
Projects without dev.json skip all dev lifecycle steps — the integration is opt-in.
Structured checks (checks) (#586)
Section titled “Structured checks (checks) (#586)”A project can declare the checks the warp-drive inner loop runs, turning the
testing phase’s feedback from an LLM-supplied free-text string into a typed
contract. Add a checks array to dev.json (or a sibling checks.json):
{ "checks": [ { "name": "unit", "command": "npm test", "kind": "test" }, { "name": "types", "command": "tsc --noEmit", "kind": "typecheck" }, { "name": "lint", "command": "npm run lint", "kind": "lint" } ]}Each check has a name, a command, and a kind (test | typecheck |
lint | build | smoke). The runner executes them and parses each result by
kind:
node ~/.claude/scripts/warp-drive/run-checks.js --cwd . # exit 0 = all pass, 1 = a check failedtest— failing test names + count (vitest / jest / node:test).typecheck—tscdiagnostics as{file, line, message}.lint— eslint (stylish + compact) diagnostics.build/smoke/ unknown — a raw output tail.
Parsers degrade gracefully — an unrecognised runner yields a tail, never a
crash. The runner emits a typed last_check_result (schema:
schemas/last-check-result.schema.json) with a stable failure signature;
warp-drive persists it to state and the stall guard compares it instead of an
LLM string. The smoke kind lets a change-scoped functional assertion (not just
“server up”) gate a chunk.
Backward compatible: with no checks block the runner reports fallback: true and warp-drive runs the detected test command as before. See the
warp-drive testing phase for the loop wiring.
Loop memory — hidden-rules store (#588)
Section titled “Loop memory — hidden-rules store (#588)”The loop records the quirks it discovers (flaky tests, legacy quirks, constraints, gotchas) in a per-project episodic-memory store so the next run starts smarter — the Reflexion pattern. Unlike journal/lessons/trace-mining (which flow outward to humans and the BoB repo), this store is read back by the same project’s loop and is agent-consumed, not filed as issues.
The store lives at .claude/loop-notes.json (schema:
schemas/loop-notes.schema.json) and is git-ignored by default — it is
machine state. A team that wants shared hidden rules can remove the .gitignore
line and commit it.
# record a discovered rule (kind: flaky-test | legacy-quirk | constraint | gotcha)node ~/.claude/scripts/warp-drive/loop-notes.js add --kind flaky-test \ --summary "test/api.test.js > fetches catalog" --evidence "passed on retry" --source-chunk 2node ~/.claude/scripts/warp-drive/loop-notes.js list # inspect the storenode ~/.claude/scripts/warp-drive/loop-notes.js flaky # flaky-test identifiersCapture is bounded (capped, oldest evicted) and de-duped by signature
(kind + normalized summary; a repeat refreshes last_seen_at and increments
count). Two consumers read it back:
- Session start — the
loop-notes-inject.shSessionStart hook injects the notes asadditionalContext, so the next run does not re-learn a known quirk. - Check-runner — a
flaky-testnote (itssummaryis the failing test id) lets the structured check-runner quarantine that test: a failing run whose failures are all known-flaky is retried once and, if it then passes, surfaced as quarantined rather than treated as a hard failure (still surfaced, never silently ignored).
Adopting in a Project
Section titled “Adopting in a Project”- Copy
~/.claude/templates/dev.jsonto your project root - Customize the server command, port, and health endpoint
- Create
seed/directory with your initial seed scripts - Copy
~/.claude/templates/seed/users.jsonand customize roles - Set the auth adapter in dev.json to match your stack
- Run
dev-up . --verboseto verify
The template at ~/.claude/templates/dev.json has sensible defaults for SvelteKit + Cloudflare Workers projects.
Docs Site Lifecycle (#153)
Section titled “Docs Site Lifecycle (#153)”Moved to how-to — see Docs-Site Lifecycle.