The Prime Directive: Infrastructure as Code
Everything BoB manages must be declarative, version-controlled, reproducible, idempotent, and observable — with every piece of state owned by exactly one source of truth.
This is BoB’s Prime Directive. It is the single governing principle behind every design decision in the framework. If a pattern can’t be expressed as code in a repository, it doesn’t belong in BoB.
Core Principles
Section titled “Core Principles”1. Declarative Manifests Over Imperative Steps
Section titled “1. Declarative Manifests Over Imperative Steps”Configuration is declared in structured files (JSON, YAML, Markdown), not performed as a sequence of manual steps. The system reads manifests and converges to the desired state.
Why: Declarative manifests are self-documenting, diffable, and reviewable. Imperative steps are fragile, forgettable, and unauditable.
2. Version Control as Source of Truth
Section titled “2. Version Control as Source of Truth”Every configuration file, script, template, and manifest lives in the BoB source repo (BOB_SOURCE, a git repository) and is deployed to ~/.claude/ (BOB_HOME) at runtime. Changes are tracked, reversible, and attributable. No configuration lives only in memory, environment variables, or local state.
Why: If it’s not in git, it doesn’t exist. Version control provides history, rollback, and collaboration.
3. Reproducibility
Section titled “3. Reproducibility”Given the same repository state, any machine can reach the same working configuration. Clone the repo, run cdi, and the environment is ready. No “works on my machine” exceptions.
Why: Reproducibility enables portability across machines, disaster recovery, and onboarding.
4. Idempotency
Section titled “4. Idempotency”Running a setup or provisioning operation multiple times produces the same result as running it once. Scripts check before acting. Manifests describe desired state, not actions to take.
Why: Idempotent operations are safe to retry, safe to automate, and safe to run on schedule.
5. Canonical State Ownership
Section titled “5. Canonical State Ownership”Every piece of mutable state has exactly one owner — a single file or system that is the authoritative source. Other systems may read that state, but only the owner may write it. Ownership boundaries are explicit and documented.
Why: Ambiguous ownership causes conflicting writes, stale reads, and subtle bugs. When two systems can both mutate the same state, neither can be trusted.
Ownership map:
| State | Owner | Readers |
|---|---|---|
| Project tooling needs | provisions/<project>.json |
provision.sh, cdprov, cdb |
| Dev environment shape | dev.json (project root) |
dev-up, warp-drive |
| Global settings | settings.json |
Claude Code, hooks |
| Local overrides | settings.local.json (gitignored) |
Claude Code |
| Work items | GitHub Issues | warp-drive, commands, agents |
| Decision/lesson log | GitHub Issues (decision, lesson) |
reports, retrospectives |
| Seed data | seed/ directory |
dev-up, test suites |
6. Observability
Section titled “6. Observability”All state transitions must be visible. When BoB provisions a project, seeds a database, or makes a decision, the outcome is recorded in a durable, inspectable medium — git history, GitHub Issues, or structured command output.
Why: If you can’t prove what happened, you can’t debug what went wrong. Observability turns “it broke” into “this specific transition failed at this point.”
Existing mechanisms: Git commit history (who changed what, when). GitHub Issues with decision and lesson labels (why). Warp-drive session summaries (what happened during autonomous work). cdprov --check output (current compliance state).
Principle: New tooling should produce structured output that answers: what changed, from what state, to what state, and why.
7. Convergence Under Failure
Section titled “7. Convergence Under Failure”Operations must converge toward the declared state even from inconsistent starting points. Partial failures must not leave the system in an unrecoverable state. When an operation fails partway through, re-running it should make forward progress rather than compounding the damage.
Why: Idempotency guarantees safety on repeat. Convergence guarantees safety on partial failure. Both are needed for operations that can be interrupted (network drops, process kills, human cancellation).
Design rules:
- Prefer atomic operations (symlink swap) over multi-step sequences
- When multi-step is unavoidable, make each step independently idempotent
- Never leave temporary state behind — clean up on both success and failure
cdprov --refreshis the reconciliation path: it reads the manifest and converges, regardless of current state
Approved Patterns
Section titled “Approved Patterns”These are the established IaC patterns in BoB. New tooling should follow these conventions.
Provision Manifests
Section titled “Provision Manifests”Each project declares what it needs from the registry in a JSON manifest.
~/.claude/provisions/bodmail.json{ "_meta": { "project": "bodmail", "path": "/Users/paulirving/Sites/bodmail", "stack": ["sveltekit", "cloudflare-workers", "d1"] }, "skills": ["code-expert", "webapp-testing"], "commands": ["status"], "agents": ["code-reviewer", "lead-dev-architect"]}The manifest is the single source of truth for what a project needs. Running cdprov reads it and creates/removes symlinks accordingly.
Dev Environment Manifests
Section titled “Dev Environment Manifests”Each project that needs a dev environment declares it in dev.json at the project root.
{ "server": { "command": "npm run dev", "port": 5173, "health_endpoint": "/api/health" }, "database": { "type": "d1", "migrations": "drizzle" }, "seed": { "directory": "seed", "command": "npm run seed" }, "auth": { "adapter": "d1", "users_file": "seed/users.json" }}The dev-up script reads this manifest and brings the environment to a fully testable state: start server, run migrations, seed data, provision users, health check.
Seed Data Scripts
Section titled “Seed Data Scripts”Seed data lives in a seed/ directory with idempotent scripts. Data covers all lifecycle states the domain defines — not just fresh records.
seed/├── users.json # Test users (admin@test.local / admin123)├── 01-base-data.sql # Reference data├── 02-sample-emails.sql # Entities in various states└── run.sh # Idempotent seed runnerTemplates
Section titled “Templates”Reusable starting points live in ~/.claude/templates/. They’re copied (not symlinked) to projects, then customized.
~/.claude/templates/├── dev.json # Dev environment manifest template└── seed/ └── users.json # Standard test users templateGlobal Settings
Section titled “Global Settings”settings.json declares hooks, permissions, and status line configuration. It’s version-controlled and shared across all projects.
Skills, Commands, and Agents
Section titled “Skills, Commands, and Agents”Each is a self-contained markdown file (or directory with supporting files) that follows a consistent structure. Universal items live in ~/.claude/{skills,commands,agents}/. Domain-specific items live in ~/.claude/registry/{skills,commands,agents}/ and are provisioned per-project.
Anti-Patterns
Section titled “Anti-Patterns”These patterns violate the Prime Directive. Avoid them; refactor them when found.
Manual Configuration
Section titled “Manual Configuration”Anti-pattern: Telling someone to “edit this file” or “run these commands” to set up a project.
# BAD: setup instructions in a README1. Create a .env file with DATABASE_URL=...2. Run `npm install`3. Manually create the database4. Copy the hook files from ~/.claude/hooks/ into .claude/hooks/Fix: Declare the configuration in a manifest (dev.json, provisions/<project>.json) and let tooling handle it. If steps can’t be automated, they belong in a runbook with a script that does as much as possible.
Undeclared Dependencies
Section titled “Undeclared Dependencies”Anti-pattern: A project works because something was manually installed or configured outside its declared manifests.
# BAD: project requires a global npm package nobody documented# "Oh, you need to `npm install -g wrangler` first"
# BAD: hook references a script that doesn't exist in the repo"command": "/Users/someone/.claude/hooks/my-custom-hook.sh"Fix: Every dependency belongs in a manifest. Global tools go in check-deps.sh. Project tools go in package.json. Hook scripts go in ~/.claude/hooks/ (version-controlled).
Imperative-Only Setup
Section titled “Imperative-Only Setup”Anti-pattern: Setup is a script that runs a sequence of commands without checking current state first.
# BAD: not idempotent — fails on second runmkdir seedcp template.sql seed/createdb myprojectpsql myproject < seed/template.sqlFix: Check before acting. Use mkdir -p, CREATE TABLE IF NOT EXISTS, upserts instead of inserts. Make every script safe to run twice.
Unversioned State
Section titled “Unversioned State”Anti-pattern: Configuration exists only in local files, environment variables, or cloud dashboards — never committed to git.
# BAD: .env file with secrets is the only place API keys are documented# BAD: Cloudflare Workers settings configured via dashboard, not wrangler.toml# BAD: Database schema managed by clicking around in a GUIFix: Use .env.example (committed) for structure, secrets management for values. Use wrangler.toml (committed) for Workers config. Use migration files (committed) for schema changes.
Snowflake Configurations
Section titled “Snowflake Configurations”Anti-pattern: Each project has a unique, hand-crafted setup that doesn’t follow any shared conventions.
# BAD: project A uses .env, project B uses config.yaml, project C uses env.sh# BAD: project A seeds with SQL, project B with a Node script, project C manuallyFix: Follow the established conventions. Use dev.json for dev environment. Use provisions/<project>.json for tooling. Use seed/ for seed data. Templates exist to make this easy.
Orphaned Artifacts
Section titled “Orphaned Artifacts”Anti-pattern: Old symlinks, removed hooks, renamed commands, or deleted templates that leave dangling references.
# BAD: symlink points to a file that was deleted months ago.claude/hooks/old-hook.sh -> ~/.claude/hooks/old-hook.sh (dangling)Fix: When renaming or removing items, clean up all references. Run make check-symlinks to catch orphans. The provisioning system (cdprov) handles this for manifested items.
Shared Ownership of State
Section titled “Shared Ownership of State”Anti-pattern: Multiple systems can write the same state, or a piece of state has no clear owner.
# BAD: both settings.json and settings.local.json define the same hook# Which one wins? Depends on merge order — not declared anywhere.
# BAD: a script writes directly to provisions/<project>.json# AND the user edits it manually AND cdprov also modifies itFix: Assign a single owner for each piece of state and document it. Other systems read; only the owner writes. If a piece of state doesn’t have a clear owner, it’s a design smell — resolve it before building on top of it.
Silent Operations
Section titled “Silent Operations”Anti-pattern: A script runs, exits 0, and produces no output about what it did. When something breaks later, there’s no trail to follow.
# BAD: provisioning silently creates symlinksln -sf "$source" "$target"
# BAD: seed script runs SQL with no indication of what was insertedsqlite3 "$DB" < seed.sqlFix: Log what changed. At minimum, report the transition: “linked code-expert -> ~/.claude/registry/skills/code-expert”, “seeded 12 emails across 4 lifecycle states”. Structured output (JSON, table) is preferred over free-text.
Non-Convergent Failure Modes
Section titled “Non-Convergent Failure Modes”Anti-pattern: An operation that, when interrupted and re-run, makes things worse instead of better.
# BAD: appends to a config file without checking if the entry existsecho "NEW_SETTING=true" >> .env
# BAD: creates a resource without checking if it already exists,# then fails on the duplicate instead of convergingcreatedb myproject # fails on second run with "already exists"Fix: Check current state before acting. Use upserts, CREATE IF NOT EXISTS, mkdir -p, and symlink-swap patterns. Design for the re-run case, not just the first-run case.
Guardrails
Section titled “Guardrails”Automated enforcement prevents drift from the Prime Directive.
Compliance Check
Section titled “Compliance Check”Run cdprov --check (or provision.sh --check <project-dir>) to audit a project’s IaC compliance. The check detects:
| Check | What it catches |
|---|---|
| Undeclared items | Local files in .claude/{skills,commands,agents}/ not in the manifest |
| Modified templates | Items that should be symlinks but are local files (someone copied instead of linking) |
| Manifest drift | Registry symlinks present that aren’t declared in the manifest |
| Broken symlinks | Links pointing to deleted or moved targets |
| Missing registry items | Manifest declares items that don’t exist in the registry |
Each warning includes a fix instruction. Exit code equals the number of warnings (0 = fully compliant).
Resolving Warnings
Section titled “Resolving Warnings”| Warning | Fix |
|---|---|
| “local file not tracked in manifest” | Add the item to the manifest, or delete the local file |
| “should be a symlink but is a local file” | Delete the local file, run cdprov --refresh to restore the symlink |
| “provisioned but not in manifest” | Add to manifest or run cdprov --refresh to remove |
| “broken symlink” | Run cdprov --refresh to repair, or delete manually |
| “declared in manifest but missing from registry” | Add the item to the registry, or remove from manifest |
Verification Pipeline
Section titled “Verification Pipeline”The full CI pipeline (make ci) validates:
- Dependencies (
check-deps): Required tools at correct versions - Schemas (
check-schemas): All JSON configs valid against schemas - Symlinks (
check-symlinks): All symlinks across projects resolve - Hooks (
check-hooks): All declared hook scripts exist and are executable - Provisions (
check-provisions): Project state matches manifests
Convergence Test
Section titled “Convergence Test”Any provisioning or setup operation should pass the convergence test:
- Run from clean state — succeeds
- Run again immediately — succeeds with no changes (idempotency)
- Manually break something (delete a symlink, corrupt a file) — re-run succeeds and repairs (convergence)
If step 3 fails, the operation is idempotent but not convergent — it needs a reconciliation path.