bob ready
bob ready — the one command for starting or resuming a project
Section titled “bob ready — the one command for starting or resuming a project”bob ready is the one command for starting a project or picking one up on any
machine (#3158). Give it a new directory, an existing checkout or an
owner/repo, and it ends with the project on the forge, on bob viz and
warp-ready — or NOT READY with the exact fix for each blocker. It never asks a
question, a second run changes nothing, and nothing another project is doing
on the machine stops it: it never deploys BoB (#3160), and a live run elsewhere
blocks none of its steps.
It began as a façade over five steps each remembered separately — bob init,
bob provision refresh, label bootstrap, port band, dev.json / checks.json
sanity (#1922). It now also creates the repo, registers the project with bob
viz, and gives a project it creates its CI locus, automation level, remote
decision bridge and mirror.
bob ready ~/projects/newthing # a missing or empty directory: created, git init, forge repo, … READYbob ready acme/shop # clone into this machine's projects_root, then the samebob ready # the project you are inbob ready ./notes --new # a folder that already has files: --new creates its forge repobob ready ~/projects/shop --start # READY, then bob warp start, printing the attach commandbob ready ~/projects/shop --vision intent.txt # also write the first docs/vision.md from your text (#4033)bob ready [project-dir | owner/repo] [--new] [--start] [--vision <file|->] [--dry-run] [--json] [--name <project-name>]bob ready --all [--dry-run] [--json] # every declared project checked out here (#2068)bob ready … # same engine, PATH wrappermake ready ARGS=<project-dir> # same engine, from the BOB_SOURCE checkoutExit 0 READY · 1 NOT READY · 2 usage error. Engine:
scripts/ready/ready.js.
Every project on this machine: --all
Section titled “Every project on this machine: --all”bob ready --all grades every project the registry declares — the derived
projects.json, a projection of provisions/*.json _meta (#2066) — that is
checked out on this machine, running the same steps below per project under the
entry’s manifest name. A non-dry run writes each project’s
.claude/.project-ready.json, so one command backfills the verdicts the fleet
snapshot and bob dash status read.
| Registry entry | Reported as |
|---|---|
active: false |
not listed — declared archival |
| path not checked out here | ABSENT on one summary line — never graded, never a failure |
the BoB entry ($HOME/.claude) |
SKIPPED — BOB_HOME is a deploy target graded by bob doctor; bob init / bob provision refresh never converge it |
| present | READY / NOT READY with its first blocker (and how many more) |
bob ready --all — 3 of 5 declared project(s) present on farm-01 PROJECT VERDICT FIRST BLOCKER bigbrain READY — shop NOT READY ports: port ledger has a collision (shop) (+1 more) site READY —
absent here (1): mobile skipped (1): BoB — BOB_HOME is graded by bob doctor
NOT READY — 1 of 3 present project(s): shop (bob ready <project-dir> for the full step list)Exit 1 when any present project is NOT READY, 2 when projects.json is
unreadable (or --all is given a project-dir / --name). --json emits
project-ready-all/1: verdict, ok, summary (declared, present,
ready, not_ready, absent, skipped) and projects[] (name, path,
dir, present, verdict, first_blocker, blockers, reason for
ABSENT/SKIPPED, and the per-project project-ready/1 document as report).
The snapshot’s summary.project_ready also counts ungraded — present
projects with no verdict yet — and audit.js view names the fix,
bob ready --all.
Starting a project: the target (#3158)
Section titled “Starting a project: the target (#3158)”| You pass | bob ready does |
|---|---|
| a path that does not exist, or an empty directory | creates it, git init, puts the unborn HEAD on main, then links the forge repo <default_org>/<name> if it exists or creates it — private unless operator.json project_defaults.visibility is public |
| a directory with files but no forge repo | the same local steps, but creates no forge repo unless you pass --new (a typo’d path never makes a GitHub repo); without it the repo step blocks with bob ready <dir> --new |
an existing checkout with an origin |
nothing in the repo step (no-op) |
owner/repo that is not a local path |
gh repo clone into this machine’s projects_root (machines.json, default ~/projects), then carries on |
Anything only a human can do is a blocker naming the command: gh auth login
when the forge refuses, bob operator init <dir> --link when no operator
profile names the forge. When the repo step blocks, every later step is
skipped, never run against a half-made repo.
What a project it creates also gets. These steps act only on a project this
run created, cloned or first provisioned, so bob ready --all never raises an
existing project’s automation level, moves its CI or adds an MCP server to it:
| Step | From | Result |
|---|---|---|
automation |
operator.json project_defaults.automation_level |
the level set with switch-level.sh <n> --yes |
ci |
operator.json project_defaults.ci ({locus, runner_label?}) |
written into the manifest as _meta.ci, then CI_RUNNER_LABEL projected by ci-locus.js reconcile --apply (a public repo is never sent to farm) |
rdb |
a machine that declares project_rdb: true in machines.json |
the remote-decision bridge copied from $BOB_HOME/.mcp.json (or the source checkout’s) into the project’s .mcp.json, with BRIDGE_PROJECT set, ignored by git and mode 600 |
mirror |
mirrors.json enabling a mirror for it |
bob mirror reconcile --apply creates it and wires the remote |
Without a project_defaults entry each step says what to declare as an
advisory and changes nothing. Every project also gets logs: a .logs/
directory, ignored through .git/info/exclude so the working tree stays clean.
The first manifest lands without waiting. bob provision --init --auto
commits a project’s first manifest straight to the operator repo’s default
branch at every automation level, and finishes any config/<project>-* branch
it makes redundant — see the single-item fast path.
Steps, in order
Section titled “Steps, in order”| Step | Runs | ok / no-op means |
Blocker names |
|---|---|---|---|
repo |
git, gh repo view/create/clone (new-project.js) |
a git checkout with an origin |
bob ready <dir> --new · gh auth login · bob operator init <dir> --link |
host |
host-drift.js (bob doctor, #1921) |
this machine’s BOB_HOME is usable — measured against the commit it was deployed from, never the source working tree (#3160), and host-scoped sections only, so a stale link in the project is never reported as host drift (#1953). Host drift is an advisory: a link in BOB_HOME that does not resolve, the forge shim or the shell guard are named with their fix on an Advisory: line and the step stays ok. How far source has moved since the deploy is named in the detail as information |
only a hard finding blocks: a surface that was never deployed, node / jq / gh missing, host-drift.js erroring, or BOB_HOME differing from its recorded commit — scripts/deploy.sh, or the fix the failing section names |
version |
reads dev.json bob_version and $BOB_HOME/.bob-version (the bob-version/1 marker deploy.sh writes, #2046) — no tool invoked (#2047) |
no pin declared (any deployed BoB is accepted); a channel pin (stable / nightly) matches the marker’s channel; a tag pin (vX.Y.Z / nightly-YYYY-MM-DD) equals the marker’s tag. A missing or off-schema marker is not measured (rendered n/a) — never a blocker; deploy.sh writes the marker |
bob update (stable) · bob update --channel nightly · bob update --to <tag> — the detail names both sides: BOB_HOME runs 1.273.0 (v1.273.0) [stable], project pins tag v1.270.0 — fix: bob update --to v1.270.0 |
links |
host-drift.js --section project_symlinks (#1953) |
every link under the project’s own .claude/ resolves on this machine |
per item: bob provision refresh (declared) · bob init -y (universal) · rm <link> (undeclared leftover) |
init |
bob init -y <dir> |
.claude/ scaffolded, universal items linked, project registered |
bob init -y <dir> |
provision |
bob provision refresh <dir>, or bob provision --init --auto <dir> when no provisions/<name>.json exists — a first manifest is committed to the operator repo’s default branch at every level (#3158) |
manifest projection converged (no [unlinked] items) |
the failing bob provision command |
logs |
— | .logs/ exists and git ignores it |
— |
automation |
switch-level.sh (a project it creates) |
a level is set, or the project is not new | the failing switch-level.sh |
permissions |
reads .claude/settings.local.json and the deployed profile for its level ($HOME/.claude/profiles/<supervised|trusted|autonomous|full-auto>.json, the files switch-level.sh copies from); re-applies with switch-level.sh <level> --yes when they differ (#3702) |
the applied allow / deny lists equal the profile’s as sets, or no level is set. A profile changed since the level was set (new deny rules, #3618) reaches a project only when it is re-applied: --dry-run reports would-change, naming what differs and the command; a real run re-applies it, the level unchanged, and says what it changed. No readable deployed profile is not measured (rendered n/a) — never a blocker |
cd <dir> && bash …/switch-level.sh <level> --yes, when re-applying failed or left the lists different |
labels |
bootstrap-labels.sh check, then ensure only if labels are missing. The check never waits for a quota reset: it reads gh label list, then the REST labels endpoint (a separate quota), then the forge cache’s last-known list (#3195) |
the canonical label set is on the repo’s forge; the detail names the source when it was REST or the cache. A rate limit with no source left is not measured (rendered n/a, naming the quota and its reset time) — never a blocker. A forge that cannot be reached is not measured too (#3160): the label set was not read, so nothing is known to be missing — the Advisory: line names gh auth login / a GitHub remote |
the failing bootstrap-labels.sh ensure — only when labels are known to be missing and could not be created |
ports |
port-fleet-migrate.js --dry-run --json as a collision gate, then port-project-sync.js sync |
a band exists in provisions/ports.json and dev.json carries its _bob_ports projection. A collision in another project’s band is an advisory here (#3160) — it is that project’s to fix, named with make port-migrate |
make port-migrate on a collision that names this project |
required |
for a project marked _workflow.unattended only: merge-on-green.sh --required-of <default branch> --json, the same read that judges an unattended PR (branch protection’s required checks, else .github/required-checks.json on that branch, #3625, #3696) |
the default branch names its required checks; the detail names them and where they come from. A project that is not unattended is no-op with no forge read. Advisory (never a blocker): an unattended project whose default branch names none, because merge-on-green would leave every one of its PRs open. The Advisory: line names the file and the body to add: .github/required-checks.json containing {"required": ["<the check that reports on every pull request>"]}. A read that fails (forge down, no remote) is not measured |
— |
manifests |
dev.json against schemas/dev.schema.json; checks.json shape (name / command / kind, optional paths / tier / timeout / quarantine) |
manifests parse and validate (absent dev.json is fine — a CLI repo has no dev server). Advisory (never a blocker, #1979): a browser-driven suite (test:e2e* / *visual* scripts, a Playwright config) with no checks block is reported as checks: undeclared (wholesale fallback) with an Advisory: line — declare scoped checks so warp-drive runs only what the change touched |
the manifest to fix |
ci |
writes _meta.ci (a project it creates), then ci-locus.js reconcile --apply |
_meta.ci declared and the repo variable in sync, or nothing declared. A reconcile that cannot reach the forge is an advisory |
— |
viz |
projects-derive.js --write into the operator repo’s projects.json (#3432), which the fleet server reads, audit.js snapshot (queued-deploy drain off), bob viz --json (starts the fleet server, or restarts a stale one), then feed-has.js polls the live /fleet.json |
the project is listed under this machine on bob viz — a refresh of the page shows its tile. A snapshot that fails to publish is an advisory: this machine’s tile is read live | bob viz <source> when the feed still does not list it after one refresh interval (20 s, BOB_READY_FEED_WAIT_S) |
rdb |
copies the bridge entry (a project it creates, on a project_rdb machine) |
.mcp.json carries the bridge, or the machine does not declare it |
where to put the bridge entry |
mirror |
mirror.js status, then reconcile --apply (a project it creates) |
the mirror is wired, disabled or undeclared. Declared but unwired on an existing project is an advisory | — |
vision |
only with --vision <file> (- reads stdin), vision.js (#4033) |
changed: docs/vision.md written from the text. skipped: one already exists — it is never overwritten, and the detail says so |
— (an empty or unreadable file is a usage error, exit 2, before any step runs) |
work |
docs/vision.md, gh issue list --label approved (not in a dry run) |
informational, never a blocker: with neither a vision nor approved work the summary’s next command is /vision, then /capability |
— |
Every step runs even after a blocker, so one pass reports every blocker — except
after a blocked repo step, when there is no checkout to run them on.
READY ends with where to look and what to run. The summary prints the bob
viz URL and the next command — bob warp start (or /warp-drive in a Claude
session), or /vision then /capability when there is nothing to work on yet.
--start runs bob warp start for you once the project is READY with approved
work and prints the session and its attach command (bob warp attach <project>,
#3163). A start that fails exits 1 with its fix; READY stays the verdict.
--vision writes the first vision (#4033). The operator’s text — from the
shell, or from Control’s New project form, which runs the same command — becomes
docs/vision.md when the project has none. The file follows the /vision
template (templates/docs/vision.md): its
title with the project name, > Last updated: today, every template heading in
order, the text under ## Vision, and every other heading left empty to fill.
It carries a vision-draft marker line; while the marker is there the next
step is /vision — here and in bob status --scope project — and /vision
removes it once it has worked the sections through. An existing
docs/vision.md is never touched: the vision step is skipped and says so.
Advisories (#3160). An advisory is named with its fix and changes neither
the verdict nor the exit code. project-ready/1 lists them as advisories[]
(step, detail, fix); the step that raised one carries it as advisory.
Host drift never blocks project work: before #3160 BOB_HOME was compared with
bigbrain’s working tree, so a commit, an uncommitted edit or a feature-branch
checkout there made every other project on the machine NOT READY. What still
blocks is what would make the run wrong: an unusable BOB_HOME, and a declared
bob_version pin that is not met.
Pinning a project to a BoB version (#2047). A public launch must not
inherit a harness change it did not ask for. Declare the BoB the project
expects in dev.json and the version step refuses a run on any machine
whose deployed BoB does not satisfy it, naming the exact bob update that
would:
{ "bob_version": "v1.273.0" } // exact tag — or "stable" / "nightly" for a channelschemas/dev.schema.json pattern-checks the value (make check-schemas
validates the committed fixture schemas/fixtures/dev.bob-version.json).
bob preflight inherits the verdict as project:version through the same
project-ready/1 blockers — one step, no new contract. See
Pinning a project.
Contract
Section titled “Contract”- No prompts, ever. Every tool is invoked in its non-interactive form with stdin closed. Anything that would need a human is a blocker with the command to run, not a question.
- Parallel-safe (#3158). The steps that write machine-wide files —
provision(the operator repo),ports(the ledger) and the viz step’s registration and snapshot — hold one lock,${XDG_STATE_HOME:-~/.local/state}/bob/ready.lock, so twobob readyruns on different projects never lose each other’s writes. A lock whose owner has died, or is 15 minutes old, is taken over; a live owner is waited for (BOB_READY_LOCK_WAIT_S, default 180 s), then the step blocks naming the project, pid and step holding it.projects.jsonand the port ledger are written through a temp file and a rename. A live run in another project blocks nothing; a live run in this one skipsinit(#3099). - Idempotent. A second run on a ready project changes nothing and reports
each step
ok/no-op. Change detection is a fingerprint of the project’s.claude/tree (and, for ports,dev.json+ the ledger) taken around each mutating step —changedis observed, not assumed. --dry-runreports what each step would do (wouldlines) and runs nothing that writes; only read-only probes (host-drift.js,bob provision --status,bootstrap-labels.sh check, the collision gate) execute.- macOS and Linux alike. node + bash only; no iTerm2 or brew assumptions;
paths resolve via
$BOB_SOURCE/$BOB_HOME/$HOME. - No
BOB_SOURCEexport needed (#2273). The BoB source is resolved byscripts/lib/bob-source-resolve.js:$BOB_SOURCE, then the engine’s own checkout, then~/projects/bigbrain. Only with no checkout at all does a deployed engine fall back to its own tree. The step tools are handed that source asBOB_SOURCE, so a first manifest write never lands on the deploy target. --jsonemits a versionedproject-ready/1document:verdict,ok,bob_source(the source the run read),steps[](name,status,detail,fix, andadvisoryon a step that passes with a recommendation; theversionstep addspin,pin_kind,measuredand, when measured,deployed: {version, channel, tag}),blockers[],advisories[],viz_url,next({command, in_session}, or{command: "/vision", then: "/capability", why}) and, with--start,start({started, session, attach}or{started: false, reason, fix}). A step whose evidence is absent reportsstatus: skippedwith anot measureddetail — distinct fromblocked.- The verdict persists. A non-dry run writes
<project>/.claude/.project-ready.json(verdict,at,machine,blockers). The fleet snapshot reads it asproject_readyper project and rolls it up per machine;audit.js viewrenders a “Project ready” table naming NOT READY projects with their blocking steps;bob dash statusprints aready: READY/ready: NOT READY (step…)line per graded project. Nothing re-runs the steps to show a verdict.
Sample output
Section titled “Sample output”A new project:
bob ready — hello (/Users/you/projects/hello) changed repo created the directory, git init, created acme/hello (private) and linked origin ok host BOB_HOME matches the deployed commit c275c81 (4 sections) ok version no bob_version pin in dev.json — any deployed BoB is accepted ok links no .claude/ links to grade changed init cdi scaffolded/relinked .claude/ changed provision first manifest generated by bob provision --init --auto and landed on the operator repo's default branch — no review PR at any level (#3158) changed logs created .logs/; ignored through .git/info/exclude changed automation automation level 2 (operator default) ok labels standard label set present changed ports band allocated/projected (base 5360) ok manifests no dev.json (CLI/library project — dev-up not declared); no checks.json (warp-drive falls back to the detected test command) changed ci declared _meta.ci locus farm; repo variable reconciled changed viz registered in projects.json, snapshot published, listed by the live feed — http://farm-01.tail3bfead.ts.net:6952/bob changed rdb added the remote-decision-bridge to .mcp.json (from /Users/you/.claude/.mcp.json; ignored by git) changed mirror mirror farm@farm-01:~/git-mirrors/hello.git created and its remote wired ok work no docs/vision.md; no approved issues — nothing for a run to pick up yet
READY — hello is warp-drive-ready on this machine. bob viz: http://farm-01.tail3bfead.ts.net:6952/bob next: /vision, then /capability — no docs/vision.md and no approved issues yetAn existing project with a blocker:
bob ready — shop (/Users/you/projects/shop) no-op repo git repo with origin git@github.com:acme/shop.git ok host BOB_HOME converged with source (4 sections) ok version BOB_HOME runs 1.273.0 (v1.273.0) [stable] — satisfies the project's channel pin stable ok links 12 project .claude/ link(s) resolve on this machine no-op init .claude/ already initialised changed provision cdprov refresh re-linked declared items ok labels standard label set present BLOCKED ports port ledger has a collision (shop) Fix: make port-migrate (in BOB_SOURCE) — resolves the collision loudly ok manifests dev.json valid; checks.json: 3 check(s) declared
NOT READY — 1 blocker(s): ports (fixes above)Where it is called from
Section titled “Where it is called from”- A human, after cloning a project on any machine (
bob initalone is no longer the whole story — see New machine setup). - Fleet
repos syncas its post-clone BoB hook, and thefarm’sbobworkload for farm checkouts (paulirv/fleet#15, #1920 R5). - The warp-drive preflight gate (#1923) consumes the same verdict:
bob preflight(scripts/warp-drive/host-preflight.js) runsbob doctorandbob ready --dry-runas onewarp-preflight/1document — non-mutating by default,--convergeappliesbob readyfirst and then re-checks (kickoff-only: refused once a run has done work, and never adeploy.sh/ settings rewrite, #1997) — and on failure files onetodoper machine + cause (a host cause in bigbrain, a project cause in the project repo; a recurrence comments, a later pass closes it — #3161) naming its fix. It never proceeds past a failed check; exit 0 pass · 1 fail · 3 engine error (fails closed).
Testing
Section titled “Testing”make test-ready drives the façade against stubbed step tools
($BOB_READY_TOOLS names a directory of same-named executables), so the
contract above — order, classification, idempotence, dry-run, verdict, exit
codes, persistence, the bob dash status line — is what is under test; each underlying
tool keeps its own suite.
tests/test-ready-viz.js drives the real CLI and the real feed probe against a
live HTTP feed that reads projects.json per request. tests/test-ready-new.js
starts projects from nothing with real git and a scratch operator: a new
directory, a second run, a clone, --new, a forge auth failure, an existing
forge repo, no project_defaults, a dry run, --start, and --vision (the
template’s headings in order, the text under ## Vision, an existing file never
overwritten, an empty file refused).
tests/test-ready-parallel.js runs two real bob ready processes at once
against deliberately racy registry and ledger stubs, with a negative control
that proves the race is real, beside a live run in a third project.
make test-ready-all drives --all over a throwaway $HOME whose registry
mixes a READY project, a NOT READY one, an absent path, an active: false
checkout and the BoB entry: the table, the exit code (1 mixed, 0 all READY,
2 usage / unreadable registry), per-project persistence, dry run,
project-ready-all/1, and the audit.js view ungraded hint (#2068).