Skip to content

Latest commit

 

History

History
416 lines (337 loc) · 23.3 KB

File metadata and controls

416 lines (337 loc) · 23.3 KB

CLI reference

The plumbbob CLI is the mechanical layer the /plumbbob:* skills shell out to — in normal use you never type it by hand. This page documents the full surface anyway, for power users, for driving PlumbBob from another harness, and for understanding what each skill actually runs.

plumbbob <verb> [args]      # also available as `pb`

It is a lean CLI — node builtins plus one deliberate dependency, checkride (C2, amended; D32) — that runs natively on Node ≥ 22.18. Every verb is a pure function that writes to stdout/stderr and returns an exit code; the only process.exit is the bin entry.

Verbs at a glance

Verb Synopsis Effect
start start <title> [--slug <name>] [--local] [--allow-dirty] scaffold builds/<slug>/, record baseline, open the session
status status [--build <slug>] print the orientation dashboard (or NO ACTIVE SESSION)
build build [<n>] [--build <slug>] write step n's seam + STEP (goes in-flight)
handoff handoff [<n>] [--build <slug>] print the standardized hand-off block; read-only
check check [--bail] [--changed] [--all] [--only a,b] [--skip a,b] [--include a,b] run the heavy gate; no state change
checkpoint checkpoint [<n>] [--plan] [-m <msg>] [--body <<'BODY'…] gate on green, commit, record SHA, mark step done
revert revert [--to <n>] [--build <slug>] git reset --hard to a checkpoint SHA
park park <text> append a line to the park list
spike spike <slug> [opt…] [--build <slug>] | spike report <slug> | spike done throwaway worktree experiment
agent agent list | agent run <name> [--step N] [--mode …] [--agent <path>] [--build <slug>] list user-authored agents, or run one through the doorway
use use <slug> re-point the active-build cursor and resume that build
finish finish [--body <<'BODY'…] [--build <slug>] append checkpoints to the report, make the final commit, close the session
init init [--uninstall] [--force] link plumbbob into Claude Code as the skills-dir plugin
doctor doctor [--migrate] diagnose the plugin link; migrate a legacy flat sidecar
turn turn UserPromptSubmit hook machinery; not a user verb
help help [<verb>] | --help | -h print the verb table, or one verb's flags
version version | --version | -v print the CLI version

status, build, handoff, revert, spike, finish, and agent accept --build <slug> to target a specific build; without it, the verb resolves the active build from the cursor (D28, see the layout). The other verbs act on the cursor's build only.

Every verb answers --help (or -h) with its own synopsis, arguments, and flags — plumbbob checkpoint --help, or equivalently plumbbob help checkpoint. The flags a verb declares are the only ones it accepts: an unrecognized flag is a refusal (exit 1), never a silently-ignored token, so a typo cannot fall through into a commit. turn and park are the two exceptions to the refusal — turn is a hook that must never wedge a prompt, and park's argument is free text.

Session verbs

start

plumbbob start "<title>" [--slug <name>] [--local] [--allow-dirty]

Scaffolds the sidecar, records the baseline HEAD, and opens the session. By default it plants a tracked build folder at .plumbbob/builds/<slug>/ — the slug derived from the title as YYYY-MM-DD-<title-slug>, date-prefixed so builds/ lists chronologically (--slug overrides it verbatim, no prefix) — holding intent.md, build-log.md, and checkpoints (baseline <sha>); it writes the tracked settings.json — seeded empty as {}, which is exactly "all defaults", since absent check already means checkride is the gate and absent auto already means false (D24/D32); the file is yours once it exists, so a re-start never touches it — and the untracked STATE sentinel — whose content is the active-build cursor, pointed at the new build (D28) — and narrows the repo's info/exclude to the control-plane patterns (D17/D26). --local opts out into the old fully-untracked flat layout — everything under .plumbbob/ excluded (D26). Refuses (exit 1) on an empty title, a slug that collides with an existing build, a non-git directory, a repo with no commits, an already-active session, or a dirty tree — --allow-dirty overrides the dirty-tree refusal and records the current HEAD as the baseline (D22).

status

plumbbob status [--build <slug>]

Prints the orientation dashboard — title, the derived phase, the step list with the next step's done-when and seam, the last checkpoint, and the parked / open-question counts — then a single suggested next move (D8 / D15). Read-only; prints NO ACTIVE SESSION and exits 0 when there is no session.

build

plumbbob build [<n>] [--build <slug>]

Reads step n's seam from intent.md and writes SEAM (the path list) and STEP (the number) — the STEP file is what makes the dashboard read BUILD. The seam is orientation, not a lock. Refuses (exit 1) with no session, a non-numeric or < 1 step, or a seam it cannot parse (seams are exact paths or dir/ grants, never globs — D23).

handoff

plumbbob handoff [<n>] [--build <slug>]

Prints the standardized hand-off block — the "state / choice / what's next" the human sees at each step boundary. Read-only, no state change. The moment is derived, not passed: a step in flight yields the pause block, none yields the post-checkpoint boundary block. An explicit <n> overrides which step it reports on; otherwise it uses the in-flight step, else the last checkpointed one. Owning the block here rather than as prose in the build skill keeps the skill from drifting out of sync with status, which renders the same next-step detail. Refuses (exit 1) only with no session.

check

plumbbob check [--bail] [--changed] [--all] [--only a,b] [--skip a,b] [--include a,b]

Runs the heavy gate with no state change (D16 / D24 / D32). The gate is checkride, run in-process: each slot (types, lint, struct, dead, test, docs, links, spell) resolves to the tool the repo already configures, raw output lands in .check/, and a red run names the failing slots with their .check/<slot> pointers. A check key in the settings ladder (settings.local.jsonsettings.json, D27) overrides checkride with a shell command, spawned exactly as before — that is how non-checkride repos gate.

The flags narrow a checkride run for the iteration loop (--bail --only types,lint); they map straight onto checkride's own flags and are warned-and-ignored on the override path. checkpoint's gate takes no flags — the commit gate is always the full run.

Exits with the check's code: 0 green, 1 red — including a run where every slot skipped, which refuses rather than passing vacuously — and 2 when the gate itself broke (e.g. a malformed checkride.config.json). Refuses (exit 1) with no session.

checkpoint

plumbbob checkpoint [<n>] [-m <msg>] [--body <<'BODY' … BODY]
plumbbob checkpoint --plan  [--body …]

The executor-agnostic commit tick (D3). Resolves the step — explicit <n>, else the in-flight STEP, else the first undone step in intent.md — then gates on a green check, commits any pending work (or records the existing HEAD if the tree is already clean) with a CLI-owned Conventional subject <type>(<scope>): <description> — scope from the build slug, type from the step title (author prefix honored, else feat) — appends step <n> <sha> to checkpoints, flips the step to [x], and clears SEAM/STEP — dropping the dashboard back to the DESIGN boundary. -m <msg> overrides the subject. The commit body leads with a plumbbob step N marker, then a --body heredoc on stdin (skill-composed, proportional); without it a deterministic fallback carries done-when + seam + diffstat (D68/D35). --plan instead commits only the build's artifact folder as chore(<scope>): plan and records a plan <sha> line, giving the plan its own commit so the first step's diff doesn't absorb the scaffold (D36). Refuses (exit 1) with no session, no resolvable step, or a red check.

revert

plumbbob revert [--to <n>] [--build <slug>]

git reset --hard to a recorded checkpoint SHA: the last step by default, --to <n> for a specific step, or the baseline as the fallback. The build folder is now tracked (D26), so before the reset revert snapshots builds/<slug>/ to a temp dir and restores it after — park lines and intent edits survive even when reverting to a baseline that predates the folder (C4). Untracked files inside the seam are removed, files outside it are left alone. Clears SEAM/STEP, dropping back to the DESIGN boundary. Refuses (exit 1) with no session, an invalid --to, or a step with no recorded checkpoint.

park

plumbbob park "<text>"

Appends <text> as a raw line under ## Park list in build-log.md and prints parked: <text> (D7). This is the dumb capture path — composing the tidy tagged line is the /plumbbob:park skill's job. Refuses (exit 1) with no session, empty text, or no ## Park list section.

spike

plumbbob spike "<slug>" [opt…] [--build <slug>]   # open
plumbbob spike report "<slug>"                    # scaffold a report, no worktrees
plumbbob spike done                               # close

Opens a throwaway experiment for a genuine fork (D18): a sibling git worktree and spike/<slug>-<opt> branch per option (default options a and b), created outside the repo root, and drops the SPIKE marker. spike done removes every spike worktree and branch and clears the marker. Refuses (exit 1) with no session, a step already in flight, an empty slug, or a worktree path that already exists; done refuses when no spike is open.

agent

plumbbob agent list
plumbbob agent run <name> [--step N] [--mode before|build|after] [--agent <path>] [--build <slug>]
plumbbob agent run        --mode before|build|after [--step N]

The doorway to user-authored agents (D39 — the full author contract is agents.md). An agent is any executable that speaks a versioned JSON-stdin / JSON-stdout / prose-stderr envelope; these two subcommands list and run them, with no code path to advance the loop — no checkpoint, no step flip, no chaining (C6, the identity invariant).

agent list prints every resolvable agent — name, origin tier (project /personal), slots, and description — walking .plumbbob/agents/<name>/ (tracked) then ~/.plumbbob/agents/<name>/ (personal), project shadowing personal (D41). A malformed agent.json lists as an ✗ … invalid: line rather than hiding. Refuses (exit 1) outside a git repository.

agent run composes the step's StepContext from intent.md + settings, spawns the manifest command via sh -c at the repo root with the agent's own directory in PLUMBBOB_AGENT_DIR (D49) and the context JSON on stdin, streams the child's stderr live, and — on a clean run (exit 0 + a valid envelope) — lands any parked[] through the park verb (D44), appends the envelope to the step-scoped builds/<slug>/handoff.json ledger (untracked, cleared at checkpoint — D47), prints the human summary on stderr, and re-emits the machine envelope on its own stdout for the calling skill (D46).

  • --step N picks the step (else the in-flight STEP; without either it refuses).
  • A name runs exactly that agent and fails loud on a miss or an undeclared --mode slot (D54 — you asked for it by name). No name + --mode <slot> runs the step's harness-bound agents for that slot (D42); a bound agent a teammate lacks degrades to a warning and is skipped (D54).
  • --mode names the slot; it must be one the manifest declares. A single-slot agent infers it; a multi-slot agent requires it.
  • --agent <path> points at an explicit agent directory (top of the ladder); it still needs a name for the run label.
  • Ctrl-C kills the child and reports rather than orphaning it (D58); a positive agentTimeout (below) arms a kill timer (D51).

Exits 0 on a clean run, 1 on a failed one — a non-zero child exit (reported verbatim, the envelope of a failed child is not trusted), garbage on stdout (out of contract), a timeout kill, an interrupt, an explicit-name miss, or an undeclared-slot refusal. A batch of bound agents returns non-zero if any that actually ran failed.

use

plumbbob use <slug>

Re-points the active-build cursor at the named build and resumes it — the one verb for both switching between builds and picking one back up (D30). Validates that builds/<slug>/ exists, then rewrites the cursor in STATE (leaving the session sentinel intact). It warns (but allows) leaving a build that still has a step in flight — that surviving STEP/SEAM is the payoff of per-build markers. Refuses (exit 1) with an empty slug or a slug with no build folder; status with no cursor lists the available builds instead of refusing.

finish

plumbbob finish [--body <<'BODY' … BODY] [--build <slug>]

The close-out (D9/D29). Appends the checkpoint SHAs to the build's report.md (the report itself is written by the /plumbbob:finish skill; a missing one is noted, never a refusal), makes the final commit (subject chore(<scope>): finish, mirroring the step-checkpoint Conventional shape; body leads with a plumbbob finish marker, then an optional --body heredoc), and clears the control state (STATE, the cursor, the in-flight markers). No separate archive copy — the tracked build folder already is the record and merges into main with the branch, so it rides into the PR (D26). There is no refuse-without-report gate. Refuses (exit 1) only with no session.

turn

plumbbob turn

Hook machinery, not a user verb. Registered as the UserPromptSubmit hook, it reads the hook payload from stdin — it takes no arguments — ticks the human-turn ledger that the checkpoint latch reads, and emits any nudge as additionalContext JSON on stdout. It always exits 0 and never refuses an unrecognized flag: a hook that failed would wedge every prompt in the session.

Install verbs

PlumbBob has two co-equal install paths: the marketplace plugin (Claude Code installs the published npm package for you — skills and this CLI on PATH via bin/ — so it needs no init) and the skills-dir link these verbs manage. See install.md for the choice; the two are mutually exclusive.

init

plumbbob init [--uninstall] [--force]

Links plumbbob into Claude Code as the skills-dir plugin: it symlinks the package into ~/.claude/skills/plumbbob, where Claude Code loads it in place (skills as /plumbbob:*, the post-edit hook auto-registered from hooks/hooks.json). Idempotent, global-only, and it never writes settings.json. --uninstall drops the link. Refuses (exit 1) if the path exists and is not a plumbbob link, or if a marketplace plumbbob plugin is already installed — the two register the same plugin name and collide over the /plumbbob:* namespace (skills can drop to flat names like /plumbbob:status); --force overrides that guard (the dev-install path uses it). Restart Claude Code (or /reload-plugins) to activate.

doctor

plumbbob doctor [--migrate]

Three diagnostics under one verb.

Plugin link (read-only). Across both install paths it verifies the skills-dir link resolves to a package carrying the manifest, the skills, and the hook; it also recognizes a marketplace-only install as a valid, passing state, and flags the double-install collision when both are present, printing the exact fix for anything missing. Run it first if a /plumbbob:* skill opens an empty dashboard. Also available in-session as /plumbbob:doctor — the only way to reach it on a marketplace install, where the CLI is on PATH only inside a Claude Code session.

Sidecar layout. When run inside a repo carrying a legacy flat sidecar — the pre-restructure layout with a config file, an archive/ folder, or a flat active session — doctor reports it and offers --migrate. plumbbob doctor --migrate moves the archive entries and the active session into tracked builds/<slug>/ folders (the active one becomes the cursor; the rest are "done" simply by not being it), turns config into settings.json, narrows the excludes, and stages the whole move without committing — the commit is yours (D31).

Check gate (D32). Reports how the heavy gate will resolve in this repo: a configured check override is named as-is; otherwise checkride's own doctor prints the slot/adapter table (✓ types ← tsc, ○ spell — no tool detected, …) so you can see what a green gate actually covers before trusting it.

Exits 0 when everything passes, 1 when a check fails or an un-migrated legacy sidecar is present.

The .plumbbob/ sidecar

The sidecar splits into a tracked artifact plane and an untracked control plane (D17/D26). The artifact plane — settings.json and every builds/<slug>/ folder — is committed, so a build's record (intent, log, checkpoints, report) rides its branch into the PR. The control plane — STATE, settings.local.json, and each build's in-flight markers — stays git-excluded; a session is live iff STATE is present.

.plumbbob/
  STATE                    # untracked — session sentinel (presence = live) AND the active-build cursor (its content, D28)
  settings.json            # tracked   — project defaults: {"check": "…", "auto": false}
  settings.local.json      # untracked — personal overlay only: {"auto": true, …} (no cursor — that lives in STATE)
  agents/                  # tracked   — optional: user-authored agents, one dir per agent (D41; see agents.md)
    <name>/agent.json      #            — the manifest; personal agents live under ~/.plumbbob/agents/
  builds/
    <slug>/                # derived slugs are YYYY-MM-DD-<title-slug>, so ls sorts chronologically
      intent.md            # tracked   — canonical intent (rides the branch into the PR)
      build-log.md         # tracked   — live ledger + park list
      checkpoints          # tracked   — "baseline <sha>", "plan <sha>", "step N <sha>"
      report.md            # tracked   — written at finish
      harness.json         # tracked   — optional: per-step agent slot bindings (D42; see agents.md)
      STEP                 # untracked — the in-flight step number (its presence is the BUILD phase)
      SEAM                 # untracked — the in-flight step's declared paths (awareness, not a lock)
      SPIKE                # untracked — marker, present while a spike fork is open
      handoff.json         # untracked — step-scoped agent-run ledger, cleared at checkpoint (D47)

Which build a verb acts on resolves --build <slug> → the active-build cursor in STATE → the sole build in builds/ → a refusal with a hint (D28). plumbbob start --local opts back into the old fully-untracked flat layout — intent.md/build-log.md/checkpoints at the sidecar root, the whole .plumbbob/ excluded — for repos that will not track tool folders (D26). A repo scaffolded by a pre-restructure plumbbob keeps that legacy flat layout until plumbbob doctor --migrate moves it here (D31).

The control plane also carries the latch's flat files at the sidecar root — TURN (the human-turn counter) and GRANT (a one-turn self-approval) — plus each build's TICK entry stamp. What every one of them holds, how they are git-excluded, and why they are not in .gitignore or your home directory: state-and-git.md.

Settings

Settings resolve through a four-rung ladder (D27): a CLI flag → settings.local.json (untracked personal overlay) → settings.json (tracked project defaults) → a built-in default. The known keys:

// settings.json  (tracked — shared project defaults)
{ "auto": false }                        // no "check" key: checkride is the gate (D32)
{ "check": "npm test", "auto": false }   // or override the gate with any shell command
{ "agents": { "after": ["reviewer"] } }  // project-wide slot bindings — the bottom of the ladder (D57)
{ "agentTimeout": 120 }                  // kill a user-authored agent after N seconds (0/absent = off, D51)

// settings.local.json  (untracked — personal, per-worktree)
{ "auto": true }

check overrides the heavy gate (a shell command run in the repo root; its exit code is the result); absent, the gate is checkride (D24/D32). auto is whether the agent approves in your place. The per-worktree active-build cursor is not a setting — it is STATE's content, so plumbbob never rewrites this hand-editable overlay (D28). agents sets project-wide slot bindings for user-authored agents — the bottom rung under a build's harness.json and the --agent flag (D57). agentTimeout (seconds) arms a kill timer for a spawned agent; absent or 0 means no timeout, since the human is present and Ctrl-C works (D51). Both files are optional JSON — a missing or malformed one contributes nothing rather than wedging the tool.

Exit codes

  • 0 — success. For check (and checkpoint's gate), 0 means the heavy check was green.
  • 1 — a refusal or failure: a guard tripped (no session, a step in flight, bad argument), a red check, or an unknown verb. check propagates the underlying command's non-zero code.
  • 2 — the gate itself broke (D32): checkride couldn't run at all (e.g. a malformed checkride.config.json). Fix the harness before trusting green or red.

See also