Appearance
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| Layer | Location | Purpose |
|---|---|---|
| BOB_SOURCE | ~/projects/bigbrain | Edit here. One git repo. Single source of truth. |
| BOB_HOME | ~/.claude | Where 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
| Type | Source path | Loading model |
|---|---|---|
| Universal skill | BOB_SOURCE/skills/ | Auto-loaded everywhere by Claude Code |
| Universal command | BOB_SOURCE/commands/ | Symlinked into each project's .claude/commands/ by cdi |
| Universal agent | BOB_SOURCE/agents/ | Symlinked into each project's .claude/agents/ by cdi |
| Universal runbook | BOB_SOURCE/runbooks/ | Referenced by absolute path; not symlinked |
| Registry skill | BOB_SOURCE/registry/skills/ | Provisioned per-project via manifest (provisions/<project>.json) |
| Registry command | BOB_SOURCE/registry/commands/ | Same — opt-in per project |
| Registry agent | BOB_SOURCE/registry/agents/ | Same — opt-in per project |
| Hook | BOB_SOURCE/hooks/ | Wired up in ~/.claude/settings.json by event |
| Script | BOB_SOURCE/scripts/ | Invoked by absolute path from skills, commands, hooks, or shell |
| Template | BOB_SOURCE/templates/ | Copied (not symlinked) into projects, then customized |
| Provision manifest | BOB_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 promptsMachine-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-runCreates <project>/.claude/ with symlinks to:
commands/from~/.claude/commands/agents/from~/.claude/agents/hooks/referenced insettings.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 belowThe 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 projectManifest 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.
| Invocation | Classification | Why |
|---|---|---|
cdprov add / remove | intent-mutating | Edits the manifest's item lists |
cdprov init / interview | intent-mutating | Creates / (re)writes the manifest |
cdprov status / diff / check | pass-through | Reads only |
cdprov reconcile | pass-through | Dry run: re-infers and prints the manifest delta (#1880) |
cdprov reconcile --apply | intent-mutating | Applies the delta — the same staged PR / fast-path route as add/remove |
cdprov refresh | pass-through | Pure projection manifest → symlinks (the post-merge apply, #789 — stays ungated) |
cdprov prune | pass-through | Touches dangling symlinks, never the manifest |
cdi init — manifest step | intent-mutating | Writes provisions/<project>.json (OQ-05) |
cdi init — structural step | pass-through | .claude/ dir, universal symlinks, dev.json template — applies immediately (OQ-05) |
cdl | pass-through | Symlink-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 / cds | pass-through | Dashboards / generators: pure reads |
| anything unrecognized | intent-mutating | Fail-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):
- Any dirty BOB_SOURCE working tree is stashed (and restored when the flow finishes).
- 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. - The manifest edit is committed on that branch (
chore(config): <action> for <project>), pushed, and opened as a PR. - 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 --force → cdprov 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_globalcheck the provisioner uses) →already provisioned (global dedup) — no change removeof 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 default — would-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.
| Tier | Source folder | Who gets it | How it lands in a project |
|---|---|---|---|
| Universal | commands/, skills/, agents/, runbooks/ | Every project, automatically | cdi symlinks commands/agents in; skills auto-load globally |
| Registry | registry/commands/, registry/skills/, … | Only projects that opt in | Listed 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 broke3.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:
| Target | What it checks |
|---|---|
make check | All of: deps + schemas + symlinks + hooks + provisions |
make check-deps | node, jq, git, make versions |
make check-schemas | All *.json configs against JSON Schema definitions |
make check-symlinks | Every symlink across registered projects resolves |
make check-hooks | Every hook script in settings.json exists and is executable |
make check-provisions | Each project's actual symlink state matches its manifest |
make test | Schema validator unit tests + warp-drive state machine tests + integration |
make ci | check + test (what GitHub Actions runs) |
make doctor | Diagnostic dump for triaging weirdness |
make help | Print 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 viaprovisions/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 reusesmachines.json,scripts/fleet/*,deploy.sh, andmirror.jsand 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)| Level | Format | Command | Contains |
|---|---|---|---|
| Vision | docs/vision.md | /vision | Why, who, where, principles, non-goals, roadmap. Living doc. |
| Business case | GitHub Issue (business-case) | /business-case | Problem, options, cost/benefit, risk, recommendation, success metrics |
| Capability | GitHub Issue (cap) | /capability | User stories, success metrics, requirements checklist |
| Requirement | GitHub Issue (req) | /requirement | Description, acceptance criteria (checkboxes), priority, notes |
| Use case | GitHub Issue (use-case) | /use-case | Actor, goal, scenario, success criteria; decomposes to cap/req |
| Bug | GitHub Issue (bug) | gh issue create --label bug | Bug reports |
| TODO | GitHub Issue (todo) | auto-created by warp-drive timeout | Human 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 carryingPart of #NN(whereNNis 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-casenever editsdocs/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 carriesRealizes #NN(whereNNis the use case) in its Notes, and inherits the use case'sarea:<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
| Label | Meaning |
|---|---|
business-case | Business case — investment justification above capability |
cap | Capability |
req | Requirement |
use-case | Use case — a scenario that decomposes into cap/req |
bug | Bug report |
todo | Human action item |
approved | Ready to work — picked up by /warp-drive |
in-progress | Being worked on — on a cap, machine-derived from child-req state (see Capability status) |
blocked | Waiting on something |
completed | Done-signal: on a todo, the human performed the action; on a closed cap, every child req closed (derived — closed without it means abandoned) |
implemented | Code done, awaiting verification |
serial-only | Excluded from cdfork --from-issues (pinch points) |
area:<slug> | Workstream tag — batches issues for one module/feature/capability (see Area labels) |
decision | Architecture decision (created by /journal decision) |
lesson | Lesson learned (created by /journal lesson) |
session-summary | Session summary (created by /session-summary) |
p1-critical / p2-high / p3-medium / p4-low | Priority |
The approval workflow
/capabilitycreates an issue (labelcap).- Break it into requirements with
/requirement(labelreq, withPart of #NNlinking back to the capability). - Add
approvedwhen a requirement is ready to work. /warp-drivepicks the highest-priorityreq+approvedissue not in progress.- When done, warp-drive flags it
implementedand 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:
| State | Encoding | Derivation |
|---|---|---|
| Not started | Open cap, no status label | No child req closed or in-progress (absence of a label is deliberate — one less invariant) |
| In progress | Open cap + in-progress | ≥1 child req closed or itself in-progress; removed again when none are |
| Complete | Closed cap + completed | Every 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 capabilitiesArea 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 anArea: <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-invoicingStatus overview per workstream (the
gh issue listrecipe):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 openWarp-drive batching.
/warp-drive --area <slug>narrows discovery toreq + approved + area:<slug>— see the warp-drive guide.Expectation & drift check (#527). Every open
capandreqshould carry anarea:<slug>label (other issue types — bugs, todos — take one when a workstream is meaningful, but it is not required)./capabilityand/requirementassign one at creation; to catch anything that slipped through, run the audit (also wired into/what-nextStep 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-*.mdREQUIREMENTS-INDEX.md,SOLUTIONS-INDEX.md,backlog.md,traceability-matrix.md.claude/project-management/,.claude/requirements/,.claude/journal/decisions/(orlessons/,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 byscripts/port-ledger.js.base = 5200 + slot*10;slotfrom an FNV-1a hash of the project name. Sub-offsets:+0vite,+1wrangler,+2cds dashboard,+3..9reserved. - Projection (derived):
cdiwrites a marked, regeneratable block intodev.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
| Command | Purpose |
|---|---|
/dev-up | Full lifecycle (server + migrations + seed + users + health) |
/dev-up --check | Health probe only |
/dev-up --skip-seed / --skip-users / --skip-server | Partial run |
/dev-up --regen-seed | Discard cached seed and regenerate |
bin/dev-up | Same, from a regular terminal |
bin/dev-health | Shortcut 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.
| Adapter | Use when… |
|---|---|
d1 | Cloudflare D1 (SQLite) |
sqlite | Local SQLite |
supabase | Supabase auth |
script | Custom provisioning script |
custom | Inline 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:
- Calls
dev-upbefore the first chunk - Verifies health between chunks
- Auto-recovers on dev failure
- 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-workfirst) - 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:
| Subcommand | Label |
|---|---|
/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
| Command | Purpose |
|---|---|
/review | Code review of pending changes |
/security-review | Security review of pending changes |
/init | Initialize a CLAUDE.md from existing codebase |
/clean_gone | Clean up local branches that are gone on the remote |
/commit-push-pr | Commit, push, and open PR in one go (built-in plugin) |
/loop | Run a slash command on a recurring interval |
/schedule | Cron-style scheduling for recurring agents |
7. Autonomous Workflow (Warp Drive)
7.1 Automation levels
| Level | Name | Start with | Default behavior |
|---|---|---|---|
| 1 | Supervised | claude / -a1 | Confirm every tool that can change state |
| 2 | Trusted Dev | claude -a2 | Auto-approve safe ops; PRs instead of direct merges |
| 3 | Autonomous | claude -a3 | Minimize prompts; merge directly to master |
| 4 | Full-Auto | claude -a4 | Superset 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 stopBefore 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:
- Discover work (
gh issue list --label approved --label req) - Plan implementation
- Code (chunked: max 3 acceptance criteria per commit)
- Test
- Update issue (flip ACs, add labels)
- Commit + journal
- Merge (L3/L4) or open PR (L2)
- 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 commandsIntegration-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-next7.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 stateGoing 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-drivestarted - [ ] Phone has Telegram open
- [ ] Laptop plugged in and won't sleep
- [ ] At least one
req+approvedissue 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.jsonGuardrails 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
| Command | Purpose |
|---|---|
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 help | Show 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 approved | Single issue or tightly-coupled work |
| Work clusters don't touch the same files | Issues touch shared config/migrations |
| You want to maximize throughput while AFK | Linear progression matters |
| You have CPU + RAM to spare | Single-threaded resource constraint |
8.4 What cdfork --from-issues actually does
- Runs
gh issue list --label approved --label req(excludingserial-only) - Picks the top N (default 3) by priority + age
- Derives a branch name per issue (e.g.
req-42-add-auth) - 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-listingTwo worktrees, two tmux windows, both warp-drives anchored to the same contract path.
8.6 Prerequisites
| Tool | Why | Install |
|---|---|---|
git | Worktrees | brew install git |
tmux | Window management | brew install tmux (NOT installed by default) |
claude | Spawned in each window | Claude Code CLI |
gh | --from-issues | brew install gh |
jq | Status JSON, issue filtering | brew 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)
| What | Command | Label |
|---|---|---|
| Architecture decision | /journal decision <name> | decision |
| Lesson learned | /journal lesson <name> | lesson |
| Session writeup | /session-summary or /journal session <name> | session-summary |
| Bug report | gh issue create --label bug | bug |
| Human action item | auto-created by warp-drive timeout | todo |
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.
/rebobUse 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-miningOutput 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_failedevents,retry_count, more than one coding↔testing cycle (coding_cycles),no_work, and phase stalls (longest inter-transition gap overmax_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 titledself-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 role | Examples | Default tier |
|---|---|---|
| Finder / explorer / sweep — wide, shallow, parallel, disposable | find bugs, grep logs, enumerate call-sites, map a subsystem | cheap: model: 'haiku' or low effort |
| Verify / judge / critic / synthesize — narrow, deep, decisive | adversarially refute a finding, score competing designs, write the final answer | strong: 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.md15.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.
| Rule | Decision |
|---|---|
| Case | lowercase 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 stamps | ISO 8601 suffix: <topic>-YYYY-MM-DD.md (e.g. doc-audit-2026-05-07.md) |
| Acronyms | lowercased — pm-cheatsheet, iac-audit, bob-identity |
| Subdirectories | lowercase, plural where applicable: audits/, archive/, research/ |
| Suffix patterns | *-guide.md for "explain X", *-reference.md for reference docs |
Frontmatter title | independent 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.mdandpm-cheatsheet.mdcan 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.mdandfoo.mdas the same file. A single casing rule avoids silentgit mvno-ops and cross-machine drift. (When renaming,git mv -fthrough 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 intomake check; fails CI on any non-reserveddocs/**file outside the rule.- The doc-keeper auditor emits a
filename-violationdrift category so violations also surface in/doc-auditreports.
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).
cdiidempotently 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-testingskill writes screenshots there;verify/runflows 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.gitignoreentries.
16. Troubleshooting
16.1 Diagnostic commands
| Symptom | Run 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
| File | Owner | Notes |
|---|---|---|
<project>/.warp-drive-state.json | warp-drive engine | Current chunk, phase, history |
<project>/.autoloop-state.json | (legacy) auto-loop | Will be removed |
~/.claude/settings.local.json | machine | _rdb.enabled lives here |
16.3 Common problems
| Problem | Fix |
|---|---|
| Hooks aren't firing | make check-hooks; verify ~/.claude/settings.json references existing scripts |
| Symlinks point to nowhere | cdprov --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 start | Check 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 things | Stop using /auto-loop — switch to /warp-drive |
Got prompt to create REQ-NNNN.md | Stop. That's a regression. File a bug — work tracking is GitHub Issues |
| Tests stuck in fix loop | Warp-drive 3-strikes-out automatically; let it escalate |
cdfork worktree won't drop | cdfork drop <branch> --force; if that fails, manually git worktree prune |
| Deploy refuses to overwrite something | That'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
cdprojCLI, profiles, canonical fields, manifest opt-in, warp-drive/command integration, backfill (#210 family) - versioning.md — Automated semantic versioning:
commit-and-tag-version, theversioningmanifest 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.md —
make checkdeep-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.