Skip to content

Project Orchestration Handbook

The encyclopedic guide to running projects with BoB (Big ol' Brain). For humans and agents.

This handbook is the human-facing how-to. For under-the-hood architecture and internals, see project-management-reference.md. For deep dives on individual subsystems, see the See Also section.


1. The Big Picture

What BoB is

BoB (Big ol' Brain) is a global Claude Code tooling framework. Skills, agents, slash commands, hooks, scripts, and templates live in a single source-of-truth git repo and get distributed to every project via deploy + symlink.

When someone says Bob, BoB, big brain, or big ol' brain, they mean this framework. See bob-identity.md for the full identity definition.

Source of truth → runtime

BOB_SOURCE (~/projects/bigbrain)        <- Source of truth (git repo)

    │   scripts/deploy.sh   (sync)

BOB_HOME (~/.claude/)                   <- Runtime (Claude Code reads here)

    │   cdi  (per-project init: symlinks)

.claude/  (in each project)             <- Project-level entry points
LayerLocationPurpose
BOB_SOURCE~/projects/bigbrainEdit here. One git repo. Single source of truth.
BOB_HOME~/.claudeWhere Claude Code reads at runtime. Synced from src.
Project<repo>/.claude/Per-project symlinks to BOB_HOME items + local items

The Prime Directive

Everything BoB manages must be declarative, version-controlled, reproducible, idempotent, and observable — with every piece of state owned by exactly one source of truth.

Full text and approved patterns: prime-directive.md.

Work tracking lives in GitHub Issues

Every project's vision lives in docs/vision.md. Everything below the vision lives in GitHub Issues — capabilities, requirements, bugs, decisions, lessons, session summaries, and TODOs. There are no REQ-*.md, BUG-*.md, SOL-*.md, or PM index files. If someone tries to create one, stop them.

Component types at a glance

TypeSource pathLoading model
Universal skillBOB_SOURCE/skills/Auto-loaded everywhere by Claude Code
Universal commandBOB_SOURCE/commands/Symlinked into each project's .claude/commands/ by cdi
Universal agentBOB_SOURCE/agents/Symlinked into each project's .claude/agents/ by cdi
Universal runbookBOB_SOURCE/runbooks/Referenced by absolute path; not symlinked
Registry skillBOB_SOURCE/registry/skills/Provisioned per-project via manifest (provisions/<project>.json)
Registry commandBOB_SOURCE/registry/commands/Same — opt-in per project
Registry agentBOB_SOURCE/registry/agents/Same — opt-in per project
HookBOB_SOURCE/hooks/Wired up in ~/.claude/settings.json by event
ScriptBOB_SOURCE/scripts/Invoked by absolute path from skills, commands, hooks, or shell
TemplateBOB_SOURCE/templates/Copied (not symlinked) into projects, then customized
Provision manifestBOB_SOURCE/provisions/Declares which registry items a project gets

2. Quick Start

Moved to a tutorial — see Getting Started.

3. Setup & Lifecycle

3.1 First-time install on a machine

Moved — see Getting Started.

3.2 Deploying changes from BOB_SOURCE → BOB_HOME

deploy.sh is the only sanctioned way to update ~/.claude/. Direct edits to ~/.claude/ are anti-pattern — you'd lose them on the next deploy.

bash
scripts/deploy.sh --dry-run      # Preview what would change
scripts/deploy.sh                # Apply
scripts/deploy.sh --first-run    # First-deploy mode (cleans up legacy .git/ in BOB_HOME)
scripts/deploy.sh --force        # Skip confirmation prompts

Machine-specific files are protected and never overwritten:

  • ~/.claude/settings.json
  • ~/.claude/settings.local.json
  • ~/.claude/CLAUDE.local.md

3.3 Per-project initialization (cdi)

bash
cd <project>
cdi                              # Idempotent — safe to re-run

Creates <project>/.claude/ with symlinks to:

  • commands/ from ~/.claude/commands/
  • agents/ from ~/.claude/agents/
  • hooks/ referenced in settings.json
  • Templates (copied, not symlinked)

Skills are not symlinked — they're auto-loaded globally from ~/.claude/skills/.

3.4 Provisioning registry items (cdprov + /provision)

Each project has a manifest at BOB_SOURCE/provisions/<project>.json declaring which registry skills, commands, and agents it uses (in addition to the universal set).

bash
cdprov                           # Apply manifest — create symlinks to match
cdprov --status                  # What's in manifest vs. what's actually linked
cdprov --diff                    # Dry run — planned link changes + Recommendation (inferred): why: per item, would-remove
cdprov --refresh                 # Re-sync symlinks (drop stale, add missing)
cdprov --init                    # Generate a manifest — profile inferred from vision + code by default (#1878)
cdprov --init --auto             # Same, one shot: write + refresh, no prompts; prints a why: line per item (#1879)
cdprov --init --no-infer         # Stack-only input (no capabilities) — honoured by --diff and reconcile too
cdprov reconcile [--apply] [--allow-remove]   # Re-infer and apply the manifest delta (#1880) — see below

The inference inputs, evidence model, precedence and limits behind --init, --diff and reconcile are explained once in the orchestrator guide — Inference from vision and code, with a flag table for the whole pipeline.

Inside Claude (/provision):

/provision status
/provision add skill cloudflare-dev
/provision add command deploy
/provision add agent code-reviewer
/provision remove skill seo-auditing
/provision diff
/provision init                  # Bootstrap a manifest for a new project

Manifest shape (provisions/<project>.json):

json
{
  "_meta": { "project": "myproj", "stack": ["cloudflare-workers", "d1"] },
  "skills":   ["cloudflare-dev", "d1-expert"],
  "commands": ["deploy", "research"],
  "agents":   ["code-reviewer", "code-debugger"],
  "runbooks": []
}

Validate against schemas/manifest.schema.json.

Intent-mutating vs pass-through (#784). Every cd* invocation is classified by a single seam (scripts/lib/intent-classify.sh, sourced by provision.sh and cdi): an invocation is intent-mutating iff it edits provisions/<project>.json — the manifest is the source of truth and symlinks are a deterministic projection of it (#782). Only intent-mutating invocations are routed through the stage-on-branch review gate (#785, below); pass-through invocations (reads and pure projections) always run unchanged.

InvocationClassificationWhy
cdprov add / removeintent-mutatingEdits the manifest's item lists
cdprov init / interviewintent-mutatingCreates / (re)writes the manifest
cdprov status / diff / checkpass-throughReads only
cdprov reconcilepass-throughDry run: re-infers and prints the manifest delta (#1880)
cdprov reconcile --applyintent-mutatingApplies the delta — the same staged PR / fast-path route as add/remove
cdprov refreshpass-throughPure projection manifest → symlinks (the post-merge apply, #789 — stays ungated)
cdprov prunepass-throughTouches dangling symlinks, never the manifest
cdi init — manifest stepintent-mutatingWrites provisions/<project>.json (OQ-05)
cdi init — structural steppass-through.claude/ dir, universal symlinks, dev.json template — applies immediately (OQ-05)
cdlpass-throughSymlink-only today: links a global item without touching the manifest (OQ-10). --manifest is reserved — a future manifest-aware cdl classifies intent-mutating
cdb / cdg / cdspass-throughDashboards / generators: pure reads
anything unrecognizedintent-mutatingFail-safe: an unknown invocation gets reviewed, never silently applied

The stage-on-branch gate (#785) — gated by default. An intent-mutating cdprov invocation does not write the manifest to disk or any symlink to the target project. Instead (scripts/lib/config-branch.sh, hung off the #784 seam's route_intent):

  1. Any dirty BOB_SOURCE working tree is stashed (and restored when the flow finishes).
  2. A review branch config/<project>-<action>-<YYYYMMDD> is cut from the default branch. Same-day collisions append an 8-hex hash of the manifest diff — deterministic, so re-running an identical change resolves to the same branch and reports "already staged" instead of duplicating.
  3. The manifest edit is committed on that branch (chore(config): <action> for <project>), pushed, and opened as a PR.
  4. The command prints the PR URL and exits 0. The target project's .claude/ is byte-identical to before the command — a rejected PR needs no on-disk revert.

What the PR contains: the manifest diff, the symlink projection that cdprov refresh will apply post-merge (each create/remove line, in the same naming the provisioner uses — skills as directories, commands/agents/runbooks as .md), and a link to each affected registry item's definition. The PR is the canonical answer to "what did BoB just change?".

Applying (post-merge projection, #789): merge the PR, then run cdprov refresh from the target project — refresh is the ungated post-merge projection. The PR body's "Apply (post-merge)" section carries the exact copy-paste command for the staging machine (git -C <bob_source> pull --ff-only && cdprov refresh <target-dir>); on another fleet machine, substitute its own paths. Operator-run refresh is the default (OQ-03) — the merge applies nothing by itself, and no CI or fleet job triggers the projection; a CI/fleet auto-trigger is a documented follow-on that would need per-machine target paths (see #415/#446 fleet plumbing). Partial failures are recoverable by re-running refresh: a target path that is a real file is skipped as [local], a permission error on one link is reported as [failed] without aborting the rest of the run, and the summary says what to fix. Re-running re-attempts only what is missing (existing correct links are skipped) and the manifest is never rolled back — it stays the declared truth the next refresh converges toward. BOB_CONFIG_GATE=off remains the internal (test/CI) escape hatch; the user-facing bypass is --now (#788, below).

Config PRs are never auto-merged — with one guarded exception (#788, #1478). Staging is the default automatic step: config_branch_stage opens the review PR and stops. Warp-drive's L3 direct-merge path merges feature branches (feature/issue-NN-*); it never touches config/* branches. A config change therefore waits for a human to merge its PR, then cdprov refresh projects it — except the single-item fast path below, which is the one code path that merges a config PR, and only under its guards. The immediate-apply alternative remains an explicit, per-invocation opt-in (--now), never the default.

Single-item fast path (#1478). A single cdprov add/remove of a registry item is mechanical, low-risk, and fully schema-validated — a review PR there adds latency without adding review value (the motivating case: a one-line flightplan add took a multi-step PR round-trip). So at automation Level 2+ (resolved through the same promotion decider --now uses; an unresolvable level fails safe to review), the just-staged PR is auto-merged and the apply steps are chained end-to-end: label config-fastpath → squash-merge → git pull --ff-only in BOB_SOURCE → deploy.sh --forcecdprov refresh. "Provision add" then means provisioned, not "merged, now run two more commands". The guards (in config_branch_fastpath): the PR must change exactly provisions/<project>.json, the semantic delta must be exactly one item across the four item arrays (a textual line count would lie — jq's array formatting rewrites the neighbouring comma line), and both manifest versions must pass the structural schema. Any guard miss degrades gracefully: rc 3, nothing mutated, the PR stays open for normal review — which is also how a #791-accumulated multi-item PR is handled. Level 1 keeps today's behavior (PR opened, human merges), and BOB_FASTPATH=0 opts out at any level. The audit trail is preserved (Option A): the PR exists, labelled, with the merge commit reachable from the manifest history. A failure at any chained step names the step (MERGE / PULL / DEPLOY / REFRESH) and prints the exact resume command; a landed merge is never rolled back — resume forward, don't restart.

The --now bypass (#788). cdprov <intent-mutating-action> --now skips the review PR and applies the change immediately: it commits the manifest edit straight to the default branch (logged [bypass: --now], via config_branch_apply_now) and projects the symlinks in the same run — today's pre-gate behaviour, now behind a deliberate flag. It shares all of the stage path's carve-outs and safety (BOB_HOME / protected-file refusal, no-delta no-op, dirty-tree stash/restore, staged-set invariant). --now is refused at automation Level 1 with promotion ceiling pr (exit 2, with a message pointing at /automation or /promotion): that stance mandates review, so an immediate apply is disallowed. At any other level×ceiling --now applies. The level+ceiling are resolved through the promotion decider (scripts/promotion/promotion.js); tests pin them via BOB_NOW_LEVEL / BOB_NOW_CEILING.

Warp-drive owns BOB_SOURCE — the concurrency guard (#790, resolves OQ-06). When a warp-drive session is active on BOB_SOURCE itself, an intent-mutating invocation refuses to cut a config branch on top of it (rc 5 from config_branch_stage, non-zero cdprov exit) — the session owns the repo's branch state (checkouts, stashes, integration/feature branches), and staging underneath it would race those git operations. The signal is an explicit, documented sentinel, never an inference: the warp-drive state file at <BOB_SOURCE>/.claude/.warp-drive-state.json (warp_owns_bob_source in scripts/lib/config-branch.sh), which the state machine creates at session init and deletes at session_ended/reset. The refusal prompt surfaces the session's phase/issue/branch and the ways forward: finish or merge that work first (warp status, /stop-warp-drive), clear stale state from a crashed session (state-machine.js reset), or override this one invocation with --now (config_branch_apply_now is deliberately unguarded — the operator explicitly accepts the risk, contained by the stash/restore + staged-set invariant). A warp-drive session on a target project never trips this — its state file lives in that project's repo, not the config repo.

Batching policy — per-project accumulating config PR (#791, resolves OQ-01). Multiple intent-mutating commands in one session produce one config PR per project, not N. Every command still stages eagerly — there is no local buffer, so no flush trigger exists or is needed (each command pushes immediately; merging the PR is the flush). But when an open config PR already exists for the project (_cb_pending_branch: candidate config/<project>-* branches, gh-confirmed open — a merged/closed PR's leftover branch is not pending), the command appends its manifest edit as a new commit on that PR's branch instead of cutting a sibling PR. Edits compound: the append is computed against the pending manifest, and cdprov add/remove read their edit base from it too (config_branch_pending_manifest), so the second add's PR diff contains both items rather than two conflicting single-item diffs. Re-adding an item already on the pending PR is a clean no-op (already staged on the pending config PR), as is a no-delta append (rc 4). The alternative — a session accumulator with a cdprov flush — was rejected: it would hold un-versioned pending state outside git (against the Prime Directive) and add a second thing to forget; here the open PR is the accumulator. If gh cannot answer the pending-PR probe, staging fails open to the eager per-command path. --now and the ungated apply route never read the pending base — they operate on the real manifest.

cdprov add / remove and the no-op guard (#786). cdprov add <type> <name> / cdprov remove <type> <name> are the item-level manifest mutations (types: skill, command, agent, runbook; the single-arg form cdprov add <name> infers the type — from the registry for add, from the project manifest for remove). Both flow through the gate above. Before any branch is cut, a no-op guard decides whether the invocation produces a manifest delta at all:

  • item already in the project manifest → already provisioned — no change
  • item already provided globally by BOB_HOME (#412 dedup, via the same item_is_global check the provisioner uses) → already provisioned (global dedup) — no change
  • remove of an item the manifest doesn't declare → not provisioned — no change

Each no-op exits 0 having created zero git refs — no branch, no PR, nothing to clean up (a fat-fingered cdprov add already-there costs nothing). add/remove require a per-project manifest (provisions/<project>.json); the _default.json fallback is a read-time convenience, never a write target. config_branch_stage keeps its own empty-diff backstop (rc 4) for the init/interview paths.

cdprov reconcile — re-derive the manifest after the vision or code moves (#1880). A manifest written once by cdprov --init drifts as a long-lived project changes: a new capability lands in docs/vision.md, a CI workflow appears, a stack is dropped. cdprov reconcile (alias cdprov --diff --infer) re-runs the #1878 inference and prints the delta the #1879 explain.js document computes against the current manifest — + <kind> <name> — would-add: <why> for recommended-but-undeclared items, - <kind> <name> — would-remove: <reason> for declared items nothing supports — and is a dry run by default (nothing written, no PR; --json emits the explain document). cdprov reconcile --apply builds the new manifest and hands it to exactly the machinery above: the #785 staged review PR, with the #1478 fast path on top — so a single-item delta at automation L2+ is auto-merged, deployed and refreshed end-to-end, a multi-item delta leaves the normal review PR open (the fast-path guard miss), and L1 always stages a PR; --now commits to the default branch, and the #791 pending-PR base applies (the delta is computed against the pending manifest and compounds onto it). It is idempotent: a reconcile immediately after an applied one computes an empty delta and exits 0 having created zero git refs. Removals are never applied by defaultwould-remove items are withheld (and re-reported on the next run) unless --apply --allow-remove is passed or an interactive run answers yes; _meta.manual items are never removal candidates at all. Note a would-remove whose reason is not a registry item with orchestrator metadata is unevidenced rather than unwanted — check before allowing it. Tests: make test-cdprov-reconcile.

BOB_HOME + protected-file carve-outs (#787). The gate never touches the BOB_HOME surface. provision.sh already routes a ~/.claude target to the verify-only path (#411) before the gate is reached; config_branch_stage enforces the same carve-outs itself as defense-in-depth, reusing bob-home-detect.sh (no duplicate detection): it refuses when the staging root resolves to BOB_HOME (covering the BOB_SOURCE-unset fallback, where it would otherwise cut branches inside ~/.claude — operator is pointed at deploy.sh), refuses the _bob-home(.machine/.role) global manifest as a staging target (edited via a normal BOB_SOURCE PR), and refuses a protected basename as a manifest path. A staged-set invariant before the commit guarantees a config branch carries exactly provisions/<project>.json — a touched settings.json, settings.local.json, or CLAUDE.local.md can never ride along (the stash/restore cycle hands it back to the working tree untouched).

3.4a Universal vs registry — and how to promote between tiers

Every command, skill, and agent lives in exactly one of two tiers. This is the single most important distinction to internalise — most "why isn't my command showing up?" confusion comes from not knowing which tier an item is in.

TierSource folderWho gets itHow it lands in a project
Universalcommands/, skills/, agents/, runbooks/Every project, automaticallycdi symlinks commands/agents in; skills auto-load globally
Registryregistry/commands/, registry/skills/, …Only projects that opt inListed in provisions/<project>.json, then cdprov links it

Registry is an à-la-carte menu: with 30+ registry skills and ~30 registry commands, you don't want cloudflare-dev loaded into a non-Cloudflare project. So registry items are opt-in, declared per-project. Universal items carry a context cost in every project, so default new items to registry and promote only the ones that are genuinely needed everywhere.

The end-to-end flow — what links what:

edit in BOB_SOURCE  →  deploy.sh (sync to ~/.claude)  →  cdi    (universal symlinks)
                                                       →  cdprov (registry symlinks, per manifest)

deploy.sh copies files into the runtime dir but links nothing into projects. cdi links the universal set. cdprov links the registry items a project's manifest names. A registry item that no manifest references is linked into no project — even after deploy.

Where the links point (#1475): registry symlinks target the deployed registry — LINK_ROOT = ${BOB_HOME:-~/.claude}/registry — not the source checkout (decision #1484). Manifest reads/writes resolve through BOB_SOURCE (#1220), but link creation and comparison are pinned to LINK_ROOT, so cdprov refresh produces byte-identical targets no matter which environment invoked it. Consequences: an item merged to source but not yet deployed is reported with a "run deploy.sh" hint instead of being linked into the checkout, and moving existing links to a new root is always called out explicitly ([retarget] lines plus a summary count) — a mass retarget can never happen silently. Regression coverage: make test-link-root. Extended to every link BoB writes (#1924): cdi's universal command/hook/agent links and the git commit-msg hook, and cdprov's universal retargets, all target $BOB_HOME too — a source-only item is reported [pending] … run deploy.sh, never linked into the checkout — and make check-abs-paths fails the build on any literal home path in a synced or ledgered file, or any absolute-target symlink in a synced tree (see check-abs-paths).

Authored vs. derived — what a consuming repo commits (#1479): commit what is authored in the project (CLAUDE.md, dev.json, seed/, .claude/settings.json, real-file commands/agents/skills); gitignore what is provisioned (every symlink cdprov materializes from the manifest, plus settings.local.json). Provisioned links are derived state — tracking them creates a second owner that fights the manifest, and they embed absolute user paths that dangle for any other clone. cdprov refresh maintains a managed block (# --- BoB-provisioned … ---) in the project's ignore file, rebuilt from the manifest on every provisioning write so it can never drift from the provision list. Placement is per-project via the manifest's _meta.ignore_placement: the committed .gitignore (default) or .git/info/exclude (no BoB fingerprints in client repos); _meta.track_provisioned: true is the explicit opt-out. Two consequences to know: drift detection moves to BoB tooling — a stale link no longer shows in git status; cdprov status flags [unlinked] items and the fleet snapshot carries unlinked_declared — and fresh clones materialize links with cdi + cdprov refresh (links do not arrive via checkout; a just-cloned project showing [unlinked] items is pre-refresh state, not breakage). Coverage: make test-ignore-block.

Promote registry → universal (the item should be in every project):

bash
cd ~/projects/bigbrain
git mv registry/commands/<name>.md commands/<name>.md   # the tier IS the folder
scripts/deploy.sh                                        # sync to ~/.claude
# then re-run `cdi` in each existing project to pick up the new symlink;
# new projects get it automatically. Remove the item from any provisions/*.json
# that listed it — it's now universal, so an explicit opt-in is redundant.

Demote universal → registry (the item is only needed by some projects): reverse the move (git mv commands/<name>.md registry/<name>.md), deploy.sh, then add the item to the manifest of each project that still needs it and cdprov --refresh. Projects not naming it lose the symlink on their next cdi/refresh.

The tier of an item is defined entirely by which folder holds it — promotion and demotion are just a git mv plus a re-link. There is no separate registration step.

3.5 Updating BoB

bash
cd ~/projects/bigbrain
git pull                         # Get upstream changes
scripts/deploy.sh --dry-run      # Always preview first
scripts/deploy.sh                # Apply
make check                       # Verify nothing broke

3.6 Local overrides (gitignored)

  • <project>/.claude/CLAUDE.local.md — project notes that don't get checked in.
  • ~/.claude/CLAUDE.local.md — global notes that don't sync.

These are loaded into context but never deployed or committed.

3.7 Verification (Makefile)

make is the IaC verification layer. Run any of these from BOB_SOURCE:

TargetWhat it checks
make checkAll of: deps + schemas + symlinks + hooks + provisions
make check-depsnode, jq, git, make versions
make check-schemasAll *.json configs against JSON Schema definitions
make check-symlinksEvery symlink across registered projects resolves
make check-hooksEvery hook script in settings.json exists and is executable
make check-provisionsEach project's actual symlink state matches its manifest
make testSchema validator unit tests + warp-drive state machine tests + integration
make cicheck + test (what GitHub Actions runs)
make doctorDiagnostic dump for triaging weirdness
make helpPrint all targets

Current test counts (run make test for the live numbers):

  • Schema validator: 34 unit tests
  • Warp-drive state machine: 99 transition tests
  • Integration tests for all check scripts: 13

make ci is what GitHub Actions runs on push and PR to master. CI skips check-symlinks and check-provisions because those scan local project directories that don't exist on the runner.

Doc-link sweep: the doc-keeper auditor's broken_relative_link rule (scripts/doc-keeper/audit.js, run via make docs-check) validates every relative .md/.txt link across README.md, CLAUDE.md, and docs/**/*.md (excluding docs/archive/**, which is frozen historical content). It supersedes the former standalone check-doc-links.sh, removed in #462 as dead tooling.

3.8 Cross-machine audit (#415)

BoB runs on a set of machines — the fleet — declared in machines.json (resolver: scripts/fleet/machine.js whoami). scripts/fleet/audit.js audits any managed project on any fleet machine from any other, over Tailscale: audit.js snapshot publishes this machine's project/provision/health condition to $BOB_FLEET_SNAPSHOTS (default ~/.claude/fleet/snapshots/<id>.json), audit.js pull <id|tailscale-name> runs the audit on a remote machine over Tailscale SSH, and audit.js view aggregates live + last-known-cached state across the fleet, marking staleness and unreachable targets. Each snapshot also carries promotion readiness (#446), mirror condition (mirror-remote.md), and harness-defect counts (#1838). Optional host layer (#1871, #1925): when this machine's machines.json entry declares a host_provider: {id, command} (Fleet on the laptops, thefarm on farm-01 — declared, never discovered from PATH; $BOB_FLEET_HOST_PROVIDER is an override only, and an empty value opts out), the snapshot runs <command> status --json and carries a host section consumed by contract: host-status/1 (schemas/host-status.schema.json, owned here, deployed to BOB_HOME/schemas/; fleet-status/1 is a profile of it). Every provider-defined section lands in host.sections (+ host.section_order) summarised to {ok, drift} — Fleet's five are also still addressable as host.<name> — plus host.drift_count, and view rolls them up as a "Host drift" table whose columns are the union of sections reported across machines, with a PROVIDER column. No provider means no host key; a declared provider that is missing, fails or times out (BOB_FLEET_HOST_TIMEOUT_MS, default 20000) is recorded as host.error, never a failed snapshot, and summary.host_drift (#1921) is carried either way. The provider's document wins over its exit code — a provider exits 3 on drift with the full document on stdout (na: true sections are not-measured rather than drift), and a verdict: error document surfaces its error as host.error (paulirv/fleet#7). A declared command may be $HOME/$BOB_HOME-relative (#1924) — expanded for the spawn, recorded as declared. Inbound audits require the target to accept SSH — macOS has Remote Login off by default.

"fleet" vs. "Fleet". BoB's fleet is the machine layer above — machines.json, scripts/fleet/{audit,machine,readiness,mirror,runner}.js, BOB_FLEET_SNAPSHOTS. It is not the Fleet project (~/projects/fleet, provisioned via provisions/fleet.json): the host-environment layer beneath BoB for the operator's Tailscale devices (Homebrew bundle, iTerm2 profiles, repo sync, SSH reachability, fleet status / fleet travel). Fleet composes BoB — it reuses machines.json, scripts/fleet/*, deploy.sh, and mirror.js and never re-declares _bob-home.*; see Fleet's vision at ~/projects/fleet/docs/vision.md. Both terms are defined in the glossary.


4. The Product Hierarchy

Vision & Strategy (docs/vision.md)              /vision
└── Business case (GitHub Issue, business-case)  /business-case   — justify investment (above cap)
    └── Capability  (GitHub Issue, label: cap)   /capability
        └── Requirement (GitHub Issue, req)      /requirement
            └── Warp-drive chunks                automatic — individual commits

Use case (GitHub Issue, label: use-case)         /use-case        — a scenario; Realizes → cap/req
└── → Capability (broad) or Requirement (narrow)
LevelFormatCommandContains
Visiondocs/vision.md/visionWhy, who, where, principles, non-goals, roadmap. Living doc.
Business caseGitHub Issue (business-case)/business-caseProblem, options, cost/benefit, risk, recommendation, success metrics
CapabilityGitHub Issue (cap)/capabilityUser stories, success metrics, requirements checklist
RequirementGitHub Issue (req)/requirementDescription, acceptance criteria (checkboxes), priority, notes
Use caseGitHub Issue (use-case)/use-caseActor, goal, scenario, success criteria; decomposes to cap/req
BugGitHub Issue (bug)gh issue create --label bugBug reports
TODOGitHub Issue (todo)auto-created by warp-drive timeoutHuman action items

Justification and scenario nodes

Two issue types bracket the capability spine without being part of the linear cap → req decomposition:

  • Business case (business-case, /business-case) sits above capability — it justifies investment before capabilities are spawned. Its recommended option is realized by one or more capabilities, each carrying Part of #NN (where NN is the business case) in its Notes, exactly as a requirement links to its capability. The vision-doc fold-in of an approved recommendation is deferred to grooming; /business-case never edits docs/vision.md.
  • Use case (use-case, /use-case) captures a concrete scenario (actor, goal, main/alternate flows, success criteria) and decomposes into the work that realizes it — a capability (broad: multiple actors/steps) or requirement(s) (narrow: one actor/goal). Each realizing issue carries Realizes #NN (where NN is the use case) in its Notes, and inherits the use case's area:<slug> so the whole scenario tree is one warp-drivable workstream.

Query the traceability with gh issue list --label use-case, gh issue list --label business-case, and gh issue list --search "Realizes #NN".

Labels

LabelMeaning
business-caseBusiness case — investment justification above capability
capCapability
reqRequirement
use-caseUse case — a scenario that decomposes into cap/req
bugBug report
todoHuman action item
approvedReady to work — picked up by /warp-drive
in-progressBeing worked on — on a cap, machine-derived from child-req state (see Capability status)
blockedWaiting on something
completedDone-signal: on a todo, the human performed the action; on a closed cap, every child req closed (derived — closed without it means abandoned)
implementedCode done, awaiting verification
serial-onlyExcluded from cdfork --from-issues (pinch points)
area:<slug>Workstream tag — batches issues for one module/feature/capability (see Area labels)
decisionArchitecture decision (created by /journal decision)
lessonLesson learned (created by /journal lesson)
session-summarySession summary (created by /session-summary)
p1-critical / p2-high / p3-medium / p4-lowPriority

The approval workflow

  1. /capability creates an issue (label cap).
  2. Break it into requirements with /requirement (label req, with Part of #NN linking back to the capability).
  3. Add approved when a requirement is ready to work.
  4. /warp-drive picks the highest-priority req + approved issue not in progress.
  5. When done, warp-drive flags it implemented and moves on.

Capability status (derived, never hand-set)

Capability status is derived state (#989), machine-reconciled from child-requirement issue state by scripts/warp-drive/cap-status.js/groom runs it as the reconciler of last resort, and /requirement invokes its reopen gate when linking a new child req. Three states, no new labels:

StateEncodingDerivation
Not startedOpen cap, no status labelNo child req closed or in-progress (absence of a label is deliberate — one less invariant)
In progressOpen cap + in-progress≥1 child req closed or itself in-progress; removed again when none are
CompleteClosed cap + completedEvery child req (checklist ∪ Part of #NN, issue state as truth) is closed

A cap closed without completed means abandoned/cancelled — the reconciler never produces that state and never touches closed caps, mirroring the todo lifecycle exactly. Filter recipes:

bash
gh issue list -l cap,completed -s closed        # completed capabilities
gh issue list -l cap,in-progress                # in-progress capabilities
gh issue list -l cap --search '-label:in-progress'   # not-started capabilities

Area labels (batching by workstream)

Within a single repo, issues for distinct workstreams — a new module, a feature, a capability rollout — are intermixed. An area:<slug> label tags every issue belonging to one workstream so the whole cluster can be listed and warp-driven as a unit without hand-picking issue numbers (#260).

  • Convention. Slugs are kebab-case workstream names: area:billing, area:baz, area:seebod. Labels use a reserved colour (#5319E7) and an Area: <Name> description so the namespace is visually distinct and observable. Scope is within a single repo — each project tracks its own issues on its own repo.

  • Hierarchy fit. A capability owns its area:<slug> (derived from its title); its child requirements inherit the same label so the Capability → Requirement tree is warp-drivable as one unit.

  • Idempotent provisioning (Prime Directive). Create/reconcile a label on demand with the helper — it never duplicates or clobbers:

    bash
    ~/.claude/scripts/area-labels.sh ensure billing      # create/refresh area:billing (accepts a name too)
    ~/.claude/scripts/area-labels.sh list                # list existing area:* labels
    ~/.claude/scripts/area-labels.sh slugify "Billing & Invoicing"   # -> billing-invoicing
  • Status overview per workstream (the gh issue list recipe):

    bash
    ~/.claude/scripts/area-labels.sh issues billing            # open issues in the area
    ~/.claude/scripts/area-labels.sh issues billing --state all
    # equivalent raw form:
    gh issue list --label "area:billing" --state open
  • Warp-drive batching. /warp-drive --area <slug> narrows discovery to req + approved + area:<slug> — see the warp-drive guide.

  • Expectation & drift check (#527). Every open cap and req should carry an area:<slug> label (other issue types — bugs, todos — take one when a workstream is meaningful, but it is not required). /capability and /requirement assign one at creation; to catch anything that slipped through, run the audit (also wired into /what-next Step 5):

    bash
    ~/.claude/scripts/area-labels.sh audit   # lists open cap/req missing area:* (exit 3 on drift)

What NOT to create

The following filename patterns are forbidden. They predate the GitHub Issues migration. Do not regenerate them:

  • REQ-*.md, SOL-*.md, BUG-*.md, RISK-*.md, INC-*.md, CR-*.md, UXR-*.md, STATUS-*.md
  • REQUIREMENTS-INDEX.md, SOLUTIONS-INDEX.md, backlog.md, traceability-matrix.md
  • .claude/project-management/, .claude/requirements/, .claude/journal/decisions/ (or lessons/, sessions/)

If a tool emits any of these, treat it as a bug and fix the tool.

Pinch points (mark serial-only)

Some work cannot be parallelized. Mark these requirements serial-only so cdfork --from-issues skips them:

  • Config / settings sync
  • Lockfile bumps
  • DB migrations
  • Compiled-asset rebuilds
  • Deploy steps

5. Dev Environment Lifecycle

Every project can declare its dev environment in dev.json. The /dev-up command (and bin/dev-up from a terminal) reads it and brings the environment to a fully testable state — server, migrations, seed data, test users, health check.

Full reference: dev-lifecycle.md.

5.1 The dev.json manifest

Lives at the project root. Sample at templates/dev.json. Schema at schemas/dev.schema.json.

json
{
  "server":     { "command": "npm run dev", "port": 5173, "health": "/api/health" },
  "migrations": { "command": "npm run migrate:dev", "auto_run": true },
  "seed":       { "directory": "seed/", "runner": "node", "order": "alphabetical" },
  "auth":       { "adapter": "d1", "users_file": "seed/users.json" },
  "access":     { "localhost": "http://localhost:5173" }
}

5.1a Port allocation (#197)

cdi allocates every project a deterministic 10-port band so multiple dev servers coexist without collisions. Two-tier, single source of truth:

  • Ledger (authoritative): BOB_SOURCE/provisions/ports.json, version-controlled, managed by scripts/port-ledger.js. base = 5200 + slot*10; slot from an FNV-1a hash of the project name. Sub-offsets: +0 vite, +1 wrangler, +2 cds dashboard, +3..9 reserved.
  • Projection (derived): cdi writes a marked, regeneratable block into dev.json (_bob_ports + server.port) and the vite/wrangler configs — never hand-edited; lossless and idempotent.

Name-hash is the default proposal; the ledger only records and arbitrates collisions (deterministic next free band). Explicit non-default pins (e.g. nanaawards 5180) are retrofit-backfilled and preserved. The legacy cds~/.claude/dashboard-ports.json registry is folded into the ledger and retired. Inspect with cdb --ports / cds --ports. Full reference: dev-lifecycle.md → Port Allocation.

Fleet backfill (#198). Projects provisioned before #197 have no band yet. make port-migrate (or node scripts/port-fleet-migrate.js) is a one-shot, idempotent migration that walks every provisioned project (provisions/*.json), allocates each its band via the same allocator as cdi, writes the ledger, and regenerates the marked projection blocks. It backfills only un-allocated projects, never reassigns an existing band, aborts loudly before any write on a pin collision, and is a verified no-op on a second run. Always review make port-migrate-dry first. deploy.sh runs it idempotently (honouring --dry-run) and non-fatally on each deploy.

5.2 /dev-up and friends

CommandPurpose
/dev-upFull lifecycle (server + migrations + seed + users + health)
/dev-up --checkHealth probe only
/dev-up --skip-seed / --skip-users / --skip-serverPartial run
/dev-up --regen-seedDiscard cached seed and regenerate
bin/dev-upSame, from a regular terminal
bin/dev-healthShortcut for dev-up --check

5.3 Seed convention

  • Seed scripts live in seed/ — alphabetical execution order.
  • Seed data must cover all lifecycle states the domain defines (not just fresh records).
  • New features include their seed updates in the same commit.
  • Seed coverage gate: scripts/dev-lifecycle/check-seed-coverage.sh.

5.4 Standardized test users

seed/users.json declares test users, provisioned via pluggable auth adapters.

AdapterUse when…
d1Cloudflare D1 (SQLite)
sqliteLocal SQLite
supabaseSupabase auth
scriptCustom provisioning script
customInline command in provision_command

The superuser is consistent across every project: admin@test.local / admin123.

5.5 Integration with warp-drive

When dev.json exists, warp-drive automatically:

  1. Calls dev-up before the first chunk
  2. Verifies health between chunks
  3. Auto-recovers on dev failure
  4. Treats dev health failure as a blockable event

6. Manual Workflow

The day-to-day cycle when you're at the keyboard.

/start-work          → branch off
… code …
/commit              → create a commit
/journal             → optional: capture a decision/lesson
/pr                  → optional: open a PR
/finish-work         → merge + cleanup
/session-summary     → close the session as a GitHub Issue

/start-work

Creates a new branch with a sensible name. Argument: feature | fix | docs | refactor | tooling | chore.

/commit

Stages and commits with a generated message. The commit skill enforces:

  • No commits to master/main (use /start-work first)
  • Pre-commit hooks run (don't bypass with --no-verify)
  • Commit message reflects the why, not just the what

/pr

Pushes branch and opens a PR with requirement traceability — links back to the GitHub Issue(s) the branch addresses.

Subactions: /pr create, /pr template, /pr link.

/finish-work

Merges current branch to master (Level 3) or opens a PR (Level 2), runs cleanup, deletes the local branch.

/journal

Create a journal entry as a GitHub Issue. Three types:

SubcommandLabel
/journal decision <name>decision
/journal lesson <name>lesson
/journal session <name>session-summary

/session-summary

End-of-session writeup as a GitHub Issue. Captures what shipped, what's next, decisions, lessons.

Built-in Claude Code commands you'll use alongside

CommandPurpose
/reviewCode review of pending changes
/security-reviewSecurity review of pending changes
/initInitialize a CLAUDE.md from existing codebase
/clean_goneClean up local branches that are gone on the remote
/commit-push-prCommit, push, and open PR in one go (built-in plugin)
/loopRun a slash command on a recurring interval
/scheduleCron-style scheduling for recurring agents

7. Autonomous Workflow (Warp Drive)

7.1 Automation levels

LevelNameStart withDefault behavior
1Supervisedclaude / -a1Confirm every tool that can change state
2Trusted Devclaude -a2Auto-approve safe ops; PRs instead of direct merges
3Autonomousclaude -a3Minimize prompts; merge directly to master
4Full-Autoclaude -a4Superset of L3: reversible (two-way-door) decisions auto-made + recorded, irreversible (one-way-door) decisions notify-and-wait or stop; requires the full-auto profile and a cost ceiling to start

Switch mid-session with /automation level <N>. See profiles/{supervised,trusted,autonomous,full-auto}.json for the exact permission profiles. Level 4 is a superset of Level 3 — it inherits the autonomous allow-list and always-blocked floor verbatim, then adds an irreversible-action floor; it only activates when active_level is explicitly 4 and the full-auto profile is present, and it refuses to launch without a spend ceiling. Full behavior table: automation-behavior.md.

7.2 /warp-drive — the autonomous development loop

This replaces /auto-loop. /auto-loop is legacy and will be removed.

/warp-drive               # Discover next approved requirement, work it
/warp-drive 42            # Work on issue #42 specifically
/stop-warp-drive          # Graceful stop

Before the first cycle, prerequisites run two blocking health gates side by side: dev health (dev-up) and the provisioning preflight (#1267) — provision-preflight.js check grades the project's manifest adequate / inadequate (missing orchestrator-recommended items, named) / absent / unknown (detect or engine error — failure-safe, never a silent proceed) / skipped (non-BoB or opt-out). A non-adequate verdict is a blockable event resolved per automation level: L1 blocks and asks, L2 requires confirm/deny of the proposed cdprov fix (timeout = block), L3 auto-provisions via cdprov and reports the applied diff at session start and in the session summary. The same gate runs at the other session-start seams — /start-work step 11 and /flightplan Step 0 — so no session begins coding against tooling that can't support it. Declines and operator overrides are session-scoped (provision-preflight.js waive, cleared at session end).

The preflight's mid-session complement is gap detection (#1134). When a session is about to hand-roll a procedure, gap-detect.js match --task "<what you're doing>" asks whether an unprovisioned registry item already covers it — matching frontmatter metadata with a deliberately conservative bar (a false "you need skill X" nudge is worse than a missed one) and honouring the #412 dedupe (provisioned and machine-global items are never nudged). A hit emits the exact cdprov add <kind>/<name> --now fix, resolved per automation level: L1 asks, L2 confirms, L3 applies and reports — the provision change is always a visible manifest edit. Its vision-side twin (#1881) is gap-detect.js divergence: instead of a free-text task it takes the project's inferred profile (vision + README + code, #1878) and nudges — same dedupe, same fix: line, same exit codes — for every registry item the #1879 explain document would-add but the manifest does not declare, each reading vision now mentions <evidence> — registry item <name> covers it. /vision runs it after writing docs/vision.md and /groom once per reconciliation pass (step 2c); both print the nudges verbatim and never apply them — the manifest is edited only by cdprov add / cdprov reconcile --apply (#1880). Gaps that exist nowhere (no registry item at all) are the authoring-gap stream: sessions record them into the evidence ledger (gap-analysis.js observe), and the trace-mining pass batches recurring ones into issue-first authoring recommendations (§9.3) — never auto-authored.

What it does each cycle:

  1. Discover work (gh issue list --label approved --label req)
  2. Plan implementation
  3. Code (chunked: max 3 acceptance criteria per commit)
  4. Test
  5. Update issue (flip ACs, add labels)
  6. Commit + journal
  7. Merge (L3/L4) or open PR (L2)
  8. Ask "continue?" → loop or stop

At Level 4 the loop runs the same cycle, but the human-in-the-loop pauses are resolved by the reversibility decision engine instead of prompting: reversible calls are auto-made and recorded as decision issues, one-way doors notify-and-wait or stop. The continue step auto-continues (bounded), an in-session QA loop gates promotion, and a long run checkpoints and resumes in a fresh context so quality holds over hours — all under a required cost ceiling. See automation-behavior.md §Level 4.

Full deep-dive: warp-drive.md.

7.3 The warp CLI (terminal observation)

Run from any shell — no Claude session needed:

bash
warp status               # Phase, current chunk, recent activity
warp stop                 # Abort the loop from outside
warp config               # Show _workflow settings (incl. session_merge)
warp help                 # All commands

Integration-branch streams (#268). Accumulate several requirements onto one branch and ship them once, instead of a merge per requirement:

bash
warp session start integration/feature-x   # create + check out the session branch
# ...run /warp-drive over the cluster; each requirement merges into the session branch...
warp session status                         # show resolved branch config
warp finalize --dry-run                     # preview the ship
warp finalize                               # L2: one consolidated PR / L3: rebase+merge to main
warp session end                            # clear the session branch (git branch left intact)

The merging phase switches to local-accumulate automatically when a session branch is set and _workflow.session_merge is local (the default); set it to pr to keep per-requirement PRs. See the warp-drive guide.

7.4 /what-next — discover work without committing

Reads project state and proposes the next sensible work item. Use it when you're not sure where to pick up.

/what-next

7.5 /stop-warp-drive

Inside Claude — graceful stop with proper cleanup. Don't kill the session with Ctrl-C; let the skill flush state cleanly.

7.6 Remote Decision Bridge (/rdb)

When you're away from the keyboard, RDB routes all decisions to Telegram via ask_remote/notify_remote.

/rdb on                   # Decisions go to Telegram
/rdb off                  # Back to terminal prompts
/rdb status               # Current state

Going AFK? Saying "I'm stepping away" is treated as /rdb on automatically.

7.7 Warp-drive launch checklist

  • [ ] claude -a3 -rdb (or -a2 -rdb)
  • [ ] /warp-drive started
  • [ ] Phone has Telegram open
  • [ ] Laptop plugged in and won't sleep
  • [ ] At least one req + approved issue exists

7.8 Error handling

  • Warp-drive does 3 strikes on failed fixes before escalating.
  • Failures escalate to a Telegram prompt (RDB) or terminal (no RDB).
  • "Type 'skip'" or "do whatever you think is best" are valid responses.
  • Repeated escalations create a todo-labeled issue for human follow-up.

7.9 Declarative loop primitive (generic "do X until Y")

Warp-drive is a loop hardcoded to the dev cycle. For any other iterate-until-done loop — fix-until-tests-pass, refactor-until-lint-clean, or "keep running this agent against this check until it passes" — use the declarative loop primitive (#424): a manifest (goal + agent + evaluator + stop_condition + guardrails) run by a single reusable runner.

bash
node ~/.claude/scripts/loop/run.js ~/.claude/templates/loops/fix-until-tests-pass.json

Guardrails are mandatory (max_iterations at minimum) and breaches halt as blockable events, reusing warp-drive's budget_exceeded semantics. Cost telemetry reuses the warp-drive token/cost model. Provision it as the skills/loop-primitive registry item. Full reference: loop-primitive.md.


8. Parallel Work (cdfork)

cdfork fans out N parallel /warp-drive sessions, one per branch, in isolated git worktrees + tmux windows. Tmux + git are the state — no daemon, no DB. If the orchestrator dies, your worktrees and commits survive.

Full reference: cdfork.md.

8.1 Subcommands

CommandPurpose
cdfork fork <branch>...Spawn one warp-drive per named branch
cdfork fork --from-issues [N]Auto-pick N approved requirements; fan out
cdfork status [--json]List worktrees + tmux + warp-drive state
cdfork drop <branch> [--force]Tear down a worktree + tmux window
cdfork drop --all [--force]Tear down every cdfork worktree for this repo
cdfork pair --contract <p> --backend <r> --frontend <r> --branch <n>Cross-repo contract-driven mode
cdfork-pair ...Standalone shape of cdfork pair
cdfork helpShow command summary

8.2 Worktree layout

For a repo at /path/to/<repo>/:

/path/to/<repo>.worktrees/<branch>/

Sibling, not nested — keeps Composer, Bundler, npm, etc. from getting confused.

8.3 When to use cdfork vs. a single warp-drive

Use cdfork when…Use single /warp-drive when…
Multiple independent issues are approvedSingle issue or tightly-coupled work
Work clusters don't touch the same filesIssues touch shared config/migrations
You want to maximize throughput while AFKLinear progression matters
You have CPU + RAM to spareSingle-threaded resource constraint

8.4 What cdfork --from-issues actually does

  1. Runs gh issue list --label approved --label req (excluding serial-only)
  2. Picks the top N (default 3) by priority + age
  3. Derives a branch name per issue (e.g. req-42-add-auth)
  4. Spawns one worktree + tmux window + warp-drive per branch

8.5 cdfork pair (cross-repo)

Designed for headless splits where backend + frontend share a contract. Anchor use case: nanawall.com Drupal-headless migration.

bash
cdfork pair \
  --contract  ~/contracts/api-spec.yaml \
  --backend   ~/Sites/nanawall-api \
  --frontend  ~/Sites/nanawall-web \
  --branch    feat/product-listing

Two worktrees, two tmux windows, both warp-drives anchored to the same contract path.

8.6 Prerequisites

ToolWhyInstall
gitWorktreesbrew install git
tmuxWindow managementbrew install tmux (NOT installed by default)
claudeSpawned in each windowClaude Code CLI
gh--from-issuesbrew install gh
jqStatus JSON, issue filteringbrew install jq

Preflight runs before any worktree is touched and fails fast on missing deps.


9. Reporting & Continuous Improvement

9.1 Per-event reporting (GitHub Issues)

WhatCommandLabel
Architecture decision/journal decision <name>decision
Lesson learned/journal lesson <name>lesson
Session writeup/session-summary or /journal session <name>session-summary
Bug reportgh issue create --label bugbug
Human action itemauto-created by warp-drive timeouttodo

9.2 /rebob — full state reconciliation

Think git fsck for project management. Examines ground truth (git log, file system, codebase) and reconciles all PM artifacts against it.

/rebob

Use it after long absences, before/after major refactors, or when state feels off.

9.3 /trace-mining — outer improvement loop

Analyzes past session journals to extract failure patterns, identify recurring harness gaps, and propose targeted improvements.

/trace-mining

Output feeds back into BoB — surfacing missing skills, broken hooks, or workflow friction. Pair with gh issue list --label lesson to mine accumulated learnings.

Trace-mining also hosts the authoring-gap analysis (#1134): gap-analysis.js analyze reads the session evidence ledger (#1131) for recurring improvised procedures and no-trigger tasks, clusters lesson issues by shared theme, and applies a configurable recurrence threshold (_workflow.gap_recurrence_count, default 3 occurrences across ≥2 sessions). Cross-project recurrence flags a registry candidate; single-project recurrence a project-docs candidate. gap-analysis.js recommend --apply files the qualifying batch as authoring-gap issues carrying the ledger evidence, routed toward the skill-creator path — zero skills are ever auto-authored, and a dismissed recommendation stays suppressed until new evidence accrues.

9.4 Registry-only continuous-improvement commands

Provision and use as needed (not universal — opt-in per project):

  • /research — Manage research projects (questions, sources, findings, decisions). Also available as a universal skill (research).
  • /retrospective — Standard / start-stop-continue / 4Ls retros.
  • /standup — Daily standup format.
  • /status — Status reports at quick / weekly / monthly cadence.

9.5 Automatic self-healing (warp-drive session end)

At session_ending, warp-drive runs scripts/warp-drive/trace-mine-session.sh to close the improvement loop without manual effort. Since #295 it mines the warp-drive state history, not the issues you filed:

  • What counts as a finding — genuine execution friction recorded by the state machine: tests_failed / merge_failed / push_failed events, retry_count, more than one coding↔testing cycle (coding_cycles), no_work, and phase stalls (longest inter-transition gap over max_phase_minutes). Each finding is tagged with an action bucket: coding reliability (root-cause), merge/process (config), harness/capability (stall), or process.
  • No friction → no issue. A clean session is skipped silently ({"skipped":true,...}) — the common case. This is the key difference from the old behavior, which always emitted an issue that merely re-listed the chunk reports/decisions filed during the session.
  • Context, not findings. Issues filed during the session (decision, risk, lesson, bug-deferred) and the session summary are listed under a Context section for cross-reference — they are no longer presented as findings.
  • When friction is found, the miner files a warp-drive-labelled issue on the BoB repo titled self-healing: session friction from <project> #<req>.

This complements the on-demand /trace-mining (§9.3): the automatic miner flags per-session friction; /trace-mining analyses patterns across many sessions.

9.6 Model tiering in multi-agent workflows

When you orchestrate work with the Workflow tool (agent() / parallel() / pipeline()), make asymmetric model tiering the default: spend a cheap, fast model on wide exploration and reserve a strong model for selective judgment. This is a convention, not a new capability — agent(prompt, {model, effort}) already accepts per-call overrides. Source: the "Self-Improving Loop" external-validation lesson (#264) — cheap breadth, expensive judgment.

The convention.

Stage roleExamplesDefault tier
Finder / explorer / sweep — wide, shallow, parallel, disposablefind bugs, grep logs, enumerate call-sites, map a subsystemcheap: model: 'haiku' or low effort
Verify / judge / critic / synthesize — narrow, deep, decisiveadversarially refute a finding, score competing designs, write the final answerstrong: model: 'opus' (or omit to inherit) + high effort

Economic rationale. Finders run in bulk and most of what they surface is noise — paying premium per-token to generate candidates is wasteful. Judgment runs on the small filtered set where correctness actually matters, so that is where the strong model earns its cost. A pool of cheap finders feeding a few expensive verifiers gets most of the quality of an all-strong fleet at a fraction of the spend.

Inheritance caveat. When you pass no model/effort, the agent inherits the session model (the resolved main-loop model) and session effort — which is almost always the right default. Only override when you're confident a different tier fits the stage: drop finders to haiku/low, lift the hardest verify/judge stages to high effort. Don't set model "just because"; an unnecessary override is how you accidentally run a 200-agent sweep on the expensive tier.

Canonical example — find → verify with explicit per-stage tiers:

js
// Cheap, parallel finders sweep each dimension; each finding is then verified
// by an expensive, skeptical judge. The pipeline runs verify-as-soon-as-found.
const results = await pipeline(
  DIMENSIONS,
  // FIND (cheap breadth): wide net, low cost, tolerant of false positives
  d => agent(d.prompt, {
    label: `find:${d.key}`, phase: 'Find',
    model: 'haiku', effort: 'low',          // <-- cheap tier
    schema: FINDINGS_SCHEMA,
  }),
  // VERIFY (expensive judgment): adversarially refute each candidate
  review => parallel(review.findings.map(f => () =>
    agent(`Adversarially verify — try to REFUTE: ${f.title}`, {
      label: `verify:${f.file}`, phase: 'Verify',
      model: 'opus', effort: 'high',        // <-- strong tier
      schema: VERDICT_SCHEMA,
    }).then(v => ({ ...f, verdict: v }))
  ))
)
const confirmed = results.flat().filter(Boolean).filter(f => f.verdict?.isReal)

The same shape applies to judge panels (cheap candidate generation → strong scoring), loop-until-dry discovery (cheap finders → strong dedup/synthesis), and multi-modal sweeps (cheap per-modality search → strong completeness critic). Keep the breadth cheap; keep the gate strong.


10. Skill Glossary

Moved to reference — see Glossary.

11. Agent Glossary

Moved to reference — see Glossary.

12. Slash Command Glossary

Moved to reference — see Glossary.

13. Shell Command Glossary

Moved to reference — see Glossary.

14. Hooks Reference

Moved to reference — see Hooks Reference.

15. Files & Layout

15.1 BOB_SOURCE structure

BOB_SOURCE/
├── skills/              # Universal skills (auto-loaded)
├── commands/            # Universal slash commands
├── agents/              # Universal subagents
├── runbooks/            # Universal runbooks
├── registry/
│   ├── skills/          # Opt-in skills
│   ├── commands/        # Opt-in commands
│   ├── agents/          # Opt-in agents
│   └── runbooks/        # Opt-in runbooks
├── provisions/          # Per-project manifests (one JSON per project)
├── machines.json        # Machine fleet manifest (the BoB machine layer — not the Fleet project, ~/projects/fleet)
├── hooks/               # All hooks; wired in settings.json
├── scripts/             # CLI scripts, helpers, engines
├── bin/                 # PATH-friendly entrypoints (cdi, cdb, cdp, cdl, cdg, cds, cdprov, warp, …)
├── templates/           # Copied (not symlinked) into projects
├── schemas/             # JSON Schema for manifest, projects, settings
├── profiles/            # Permission profiles per automation level
├── docs/                # Guides (this file lives here)
├── tests/               # `make test` targets
├── claude-init.sh       # DEPRECATED passthrough → bin/cdi (one release, #1193)
├── claude-dashboard.sh  # DEPRECATED passthrough → bin/cdb
├── claude-promote.sh    # DEPRECATED passthrough → bin/cdp
├── claude-link.sh       # DEPRECATED passthrough → bin/cdl
├── settings.json        # Default settings shipped to BOB_HOME
├── CLAUDE.md            # Default project instructions
├── README.md
├── Makefile
└── bob-identity.md

15.2 Project-level structure

A typical project has:

my-project/
├── .claude/                # Created by cdi — symlinks into BOB_HOME
│   ├── commands/           # Symlinks
│   ├── agents/             # Symlinks
│   ├── settings.json       # Project-specific overrides (optional)
│   └── settings.local.json # Machine-specific, gitignored
├── CLAUDE.md               # Project instructions (committed)
├── CLAUDE.local.md         # Local notes (gitignored)
├── docs/
│   └── vision.md           # The living vision doc
├── dev.json                # Dev environment manifest
├── seed/                   # Seed scripts + users.json
└── ...

15.3 Forbidden patterns

Do not create these — they predate the GitHub Issues migration:

.claude/project-management/         ✗
.claude/requirements/               ✗
.claude/journal/decisions/          ✗  (use /journal decision)
.claude/journal/lessons/            ✗  (use /journal lesson)
.claude/journal/sessions/           ✗  (use /journal session)
REQ-*.md SOL-*.md BUG-*.md          ✗
RISK-*.md INC-*.md CR-*.md          ✗
UXR-*.md STATUS-*.md                ✗
REQUIREMENTS-INDEX.md               ✗
SOLUTIONS-INDEX.md                  ✗
backlog.md                          ✗
traceability-matrix.md              ✗

Derived state is not a forbidden pattern. The rule above bans local PM files — human-authored work records that compete with GitHub Issues as a source of truth. It does not ban tooling-written caches. .claude/forge/ (the #1749 issue read cache) sits under .claude/ and persists across sessions, but it fails every test that makes a PM file a problem: no human writes it, nothing reads it as a source of truth, it is gitignored through the cdprov managed ignore block, and deleting the whole directory costs a re-fetch and nothing else. The distinction to apply is authored vs. derived, not where the file lives or how long it survives.

15.4 BOB_HOME (~/.claude/) — runtime only

Synced from BOB_SOURCE by deploy.sh. Don't edit directly. Files protected from overwrite:

  • ~/.claude/settings.json
  • ~/.claude/settings.local.json
  • ~/.claude/CLAUDE.local.md

15.5 Documentation filename conventions (docs/)

Files under docs/ follow one mechanical naming rule so the directory greps and globs predictably, the docs site renders stable URLs, and contributors (and warp-drive) never have to guess casing.

RuleDecision
Caselowercase only
Word separator- (kebab-case)
Character set[a-z0-9-] only — no underscores, spaces, accents, em-dashes
Extension.md for prose, .json for data. .txt / .markdown are not allowed
Date stampsISO 8601 suffix: <topic>-YYYY-MM-DD.md (e.g. doc-audit-2026-05-07.md)
Acronymslowercased — pm-cheatsheet, iac-audit, bob-identity
Subdirectorieslowercase, plural where applicable: audits/, archive/, research/
Suffix patterns*-guide.md for "explain X", *-reference.md for reference docs
Frontmatter titleindependent of the filename — the docs-site engine uses title for display, the slug for the URL

Reserved names (exempt): README.md, index.md, _index.md, CHANGELOG.md, CONTRIBUTING.md, LICENSE. These follow conventional uppercase and are skipped by the linter.

Canonical regex (basename minus extension): ^[a-z0-9]+(-[a-z0-9]+)*$ with extension in {md, json}.

Why:

  • Slug collapse — under flat serving PM-CHEATSHEET.md and pm-cheatsheet.md can produce the same URL (and engines like Starlight lowercase slugs); lowercasing eliminates the collision and keeps links engine-portable.
  • Case-insensitive filesystems — macOS APFS treats Foo.md and foo.md as the same file. A single casing rule avoids silent git mv no-ops and cross-machine drift. (When renaming, git mv -f through a temp name clears the no-op hurdle.)
  • Greppability — one rule means callers can link and search without second-guessing casing.

Not part of the convention: numbered ordering prefixes (01-foo.md) — use the docs-site engine's nav-ordering config (e.g. Starlight's sidebar.order frontmatter) instead; mixing the two is the worst outcome. There is no filename-length budget — long descriptive names like project-orchestration-handbook.md are fine.

Scope & enforcement: This convention covers docs/ (excluding docs/archive/**, which is frozen historical record). It is enforced two ways:

  • make check-doc-naming (script: scripts/checks/check-doc-naming.sh) — wired into make check; fails CI on any non-reserved docs/** file outside the rule.
  • The doc-keeper auditor emits a filename-violation drift category so violations also surface in /doc-audit reports.

The same lowercase-kebab rule is applied to the code-side item directories (agents/, registry/{skills,agents,commands}/, runbooks/, provisions/) by the sibling linter make check-filename-conventions (scripts/checks/check-filename-conventions.sh, #238-241), with framework-reserved carve-outs (SKILL.md, _default.json, …). Skill bundle assets (LICENSE.txt, *.pdf, scripts inside a skill dir) are not policed — only item/dir names are.

15.6 Test & inspection artifacts (.bob-artifacts/)

Ephemeral test/inspection output — Playwright screenshots, DOM snapshots, scratch captures — goes in a single per-project directory: .bob-artifacts/. Never the repo root or scattered /tmp paths (#253).

  • cdi idempotently adds .bob-artifacts/ and .playwright-mcp/ (the Playwright MCP default output dir) to the project's root .gitignore, so these artifacts never get accidentally committed.
  • The webapp-testing skill writes screenshots there; verify/run flows should follow the same convention.
  • Backfill an existing project (or clean stray root screenshots) with scripts/clean-artifacts.sh <project> — idempotent: it relocates loose root-level screenshots into .bob-artifacts/ and ensures the .gitignore entries.

16. Troubleshooting

16.1 Diagnostic commands

SymptomRun this
"Is BoB healthy?"make check
"Are my project's symlinks ok?"cdb then cdb --check <project>
"What is provisioned in this project?"cdprov --status
"Does the manifest match reality?"cdprov --diff
"Why is warp-drive stuck?"warp status
"Is the dev env healthy?"bin/dev-health or /dev-up --check
"Hook script missing?"make check-hooks
"JSON config invalid?"make check-schemas
"Stale state across the system?"/rebob
"Lessons accumulating? Patterns?"/trace-mining

16.2 State files

FileOwnerNotes
<project>/.warp-drive-state.jsonwarp-drive engineCurrent chunk, phase, history
<project>/.autoloop-state.json(legacy) auto-loopWill be removed
~/.claude/settings.local.jsonmachine_rdb.enabled lives here

16.3 Common problems

ProblemFix
Hooks aren't firingmake check-hooks; verify ~/.claude/settings.json references existing scripts
Symlinks point to nowherecdprov --refresh (re-link) or cdprov --prune (drop dangling/cross-machine; dry-run first); fleet-wide: make repair-fleet then make check-fleet (#303)
Warp-drive won't startCheck warp status; ensure at least one req + approved issue exists
Telegram silent (RDB on)Check ~/.claude/settings.local.json for _rdb.enabled: true; type something in terminal
/auto-loop did weird thingsStop using /auto-loop — switch to /warp-drive
Got prompt to create REQ-NNNN.mdStop. That's a regression. File a bug — work tracking is GitHub Issues
Tests stuck in fix loopWarp-drive 3-strikes-out automatically; let it escalate
cdfork worktree won't dropcdfork drop <branch> --force; if that fails, manually git worktree prune
Deploy refuses to overwrite somethingThat's by design — settings.json, settings.local.json, CLAUDE.local.md are protected

17. Automated Versioning

Every BoB-managed project can be automatically versioned — conventional-commit-driven semver bumps, git tags, CHANGELOG.md, and GitHub Releases — provisioned from one source of truth rather than hand-copied CI (capability #275).

Tool: commit-and-tag-version (maintained fork of standard-version). release-please was considered and deferred (see the versioning guide).

Enable it — versioning is an opt-in manifest capability (like gh_project); it copies template files rather than symlinking:

jsonc
// provisions/<project>.json
"versioning": { "enabled": true, "main_branch": "main" }
bash
cdprov refresh    # copies auto-release.yml + .versionrc.json + commitlint config,
                  # merges package.json (release script + devDependency)

Local fallback (#1691): when Actions is unavailable, bob-release cuts the identical release from a workstation, and the local delivery runbook covers the full no-Actions path (CI gate, release cut, docs deploy).

The orchestrator recommends versioning by default for every project and picks a push trigger-path filter per project type (docroot/** for cms, the full source surface for framework, src/**+package.json for apps/libraries, no filter for research/mixed). An existing versioning block is always preserved on re-run, so enabled:false opt-outs and customizations survive.

A merge to the main branch then bumps the version, rewrites the changelog, tags vX.Y.Z, and publishes a GitHub Release — loop-guarded against the chore(release) commit. Conventional-commit enforcement ships as config (#278).

Heads-up: the workflow pushes the release commit/tag directly to the main branch via GITHUB_TOKEN; enabling branch protection without a bot exception breaks it. Full detail, bump rules, and the per-type path table: versioning.md.


18. See Also

  • bob-identity.md — What "BoB" means
  • prime-directive.md — IaC principles, approved patterns, anti-patterns
  • warp-drive.md — Full warp-drive deep-dive
  • cdfork.md — Full cdfork deep-dive
  • dev-lifecycle.md — Full dev env lifecycle reference (also covers the docs-site lifecycle, #153)
  • orchestrator.md — Registry metadata contract, recommendation engine, interview question bank (#157 family)
  • gh-projects.md — GitHub Projects v2 integration: the cdproj CLI, profiles, canonical fields, manifest opt-in, warp-drive/command integration, backfill (#210 family)
  • versioning.md — Automated semantic versioning: commit-and-tag-version, the versioning manifest block, per-project-type trigger paths, provisioning, branch-protection caveat (#275 family)
  • vision.md — BoB's own vision document
  • project-management-reference.md — Under-the-hood architecture (note: predates GitHub Issues migration; due for its own audit)
  • branch-config.md — Branch detection / configuration
  • automation-behavior.md — How automation levels behave
  • backup-recovery.md — Backup & recovery
  • new-machine-setup.md — Setup on a fresh machine
  • verification-system.mdmake check deep-dive
  • token-monitoring.md — Token usage observability
  • pm-conventions.md — PM conventions (redirect stub; original archived 2026-05-07)
  • doc-audit-2026-05-07.md — Documentation audit methodology and gap list (use as a template for the next reconciliation cycle)
  • archive/ — Frozen historical PM docs (2026-02-pm/, 2026-05-pm/). Each archive subdir is dated and immutable; superseded docs leave a redirect stub at the original path. Excluded from the published docs site; browse on GitHub.

Last updated: 2026-05-07 — reconciled with doc audit (#144). Changelog: disambiguated cdr (Claude Disaster Recovery, not legacy reqs-index) in §13.1; surfaced live test counts and the doc-link sweep script in §3.7; added doc audit and archive subtree to §17 See Also.