plumbbob 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/cli.js +4 -0
- package/dist/verbs/doctor.js +172 -0
- package/dist/verbs/setup.js +30 -8
- package/package.json +1 -1
- package/skills/pb-build/SKILL.md +1 -1
- package/skills/pb-harvest/SKILL.md +1 -1
- package/skills/pb-park/SKILL.md +1 -1
- package/skills/pb-plan/SKILL.md +1 -1
- package/skills/pb-reset/SKILL.md +1 -1
- package/skills/pb-revert/SKILL.md +1 -1
- package/skills/pb-spike/SKILL.md +1 -1
- package/skills/pb-status/SKILL.md +1 -1
- package/skills/pb-step/SKILL.md +1 -1
- package/skills/pb-verify/SKILL.md +1 -1
- package/skills/plumbbob-interrogate/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,14 @@ plumbbob setup --global
|
|
|
77
77
|
|
|
78
78
|
This copies the hook and skills under `~/.claude/` and registers them in `~/.claude/settings.json`. Restart Claude Code (or reload settings) after install.
|
|
79
79
|
|
|
80
|
+
### Verify the install
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
pnpm exec plumbbob doctor # or `plumbbob doctor` for a global install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`doctor` checks the four things that must be true — the skills are present, their bin resolves, the CLI is installed, and the post-edit hook is registered — and prints the exact fix for anything broken. Run it first if a `/pb-*` skill ever opens with an empty dashboard.
|
|
87
|
+
|
|
80
88
|
## STATE is orientation, not a gate
|
|
81
89
|
|
|
82
90
|
The one-word `.plumbbob/STATE` (`DESIGN` / `BUILD` / `SPIKE`) is read by `/pb-status` to tell you where you are and what to do next. It no longer gates edits — a wrong state is a mislabeled position on a map, not a locked door. The post-edit hook is session-gated: a repo with no `.plumbbob/STATE` behaves exactly like plain Claude Code.
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { revert } from "./verbs/revert.js";
|
|
|
12
12
|
import { spike } from "./verbs/spike.js";
|
|
13
13
|
import { reset } from "./verbs/reset.js";
|
|
14
14
|
import { setup } from "./verbs/setup.js";
|
|
15
|
+
import { doctor } from "./verbs/doctor.js";
|
|
15
16
|
const VERBS = [
|
|
16
17
|
{ name: 'start', summary: 'scaffold .plumbbob/; STATE=DESIGN; record the baseline commit' },
|
|
17
18
|
{ name: 'status', summary: 'print the session state, or NO ACTIVE SESSION' },
|
|
@@ -23,6 +24,7 @@ const VERBS = [
|
|
|
23
24
|
{ name: 'spike', summary: 'spike "<slug>" | spike done: throwaway worktree experiment' },
|
|
24
25
|
{ name: 'reset', summary: 'v2 close-out: archive intent+log+report (no gate), clear the sidecar, STATE off' },
|
|
25
26
|
{ name: 'setup', summary: 'install hooks + skills; register them (self-contained per-project by default; --global for ~/.claude)' },
|
|
27
|
+
{ name: 'doctor', summary: 'diagnose the install (skills, bin, hook) and print the fix for anything broken' },
|
|
26
28
|
];
|
|
27
29
|
// Plumbbob v2 (D1/D10/D13): the deciding/executing boundary is no longer a lock,
|
|
28
30
|
// so there is nothing to defend — every verb runs the same whether a human or the
|
|
@@ -56,6 +58,8 @@ function dispatch(verb, cwd, rest) {
|
|
|
56
58
|
return reset(cwd);
|
|
57
59
|
case 'setup':
|
|
58
60
|
return setup(cwd, rest);
|
|
61
|
+
case 'doctor':
|
|
62
|
+
return doctor(cwd);
|
|
59
63
|
default:
|
|
60
64
|
process.stderr.write(`plumbbob: unknown verb '${verb}'. Run 'plumbbob help' for the verb table.\n`);
|
|
61
65
|
return 1;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// `plumbbob doctor` — diagnose a project's install end to end and print the fix
|
|
2
|
+
// for anything broken. Read-only: it inspects, it never writes. The failure
|
|
3
|
+
// class it exists for is SILENT — a skill's status pre-injection resolving to an
|
|
4
|
+
// empty/garbled bin (e.g. the pre-0.3 `$CLAUDE_PROJECT_DIR` form, a hooks-only
|
|
5
|
+
// variable that expands empty in a skill's bash) leaves an empty dashboard with
|
|
6
|
+
// no error. doctor names the problem and the remedy instead. Functional,
|
|
7
|
+
// node builtins only (C1/C2).
|
|
8
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { basename, join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { findRepoRoot } from "../lib/git.js";
|
|
13
|
+
import { readSettings } from "../lib/settings.js";
|
|
14
|
+
// The plumbbob package's own skills/ dir (the canonical set setup installs). Off
|
|
15
|
+
// this module's URL so it resolves the same from src/ (dev) and dist/ (published).
|
|
16
|
+
function packageDir(name) {
|
|
17
|
+
return fileURLToPath(new URL(`../../${name}/`, import.meta.url));
|
|
18
|
+
}
|
|
19
|
+
// The pb-* skill directories under `dir` that actually carry a SKILL.md.
|
|
20
|
+
function listSkills(dir) {
|
|
21
|
+
try {
|
|
22
|
+
return readdirSync(dir).filter((n) => existsSync(join(dir, n, 'SKILL.md')));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Pull the bin token out of a skill's status pre-injection — the text between
|
|
29
|
+
// `!`` and ` status`. Returns null if the skill carries no injection line.
|
|
30
|
+
function injectionBin(body) {
|
|
31
|
+
const m = body.match(/!`(.+?) status\b/);
|
|
32
|
+
return m === null ? null : (m[1] ?? null);
|
|
33
|
+
}
|
|
34
|
+
// The distinct bin tokens the installed skills inject. They should be uniform; a
|
|
35
|
+
// set surfaces a partial/failed setup that left some skills on the old token.
|
|
36
|
+
function installedBins(skillsDir, skills) {
|
|
37
|
+
const bins = skills
|
|
38
|
+
.map((n) => injectionBin(readFileSync(join(skillsDir, n, 'SKILL.md'), 'utf8')))
|
|
39
|
+
.filter((b) => b !== null);
|
|
40
|
+
return [...new Set(bins)];
|
|
41
|
+
}
|
|
42
|
+
// Classify the injected bins: ok, or a list of human-readable problems. Catches
|
|
43
|
+
// the unresolved placeholder (setup never substituted) and the legacy
|
|
44
|
+
// $CLAUDE_PROJECT_DIR form (empty in a skill), and verifies an absolute bin
|
|
45
|
+
// actually exists on disk. A bare `plumbbob` resolves from PATH — accepted.
|
|
46
|
+
function binProblems(bins) {
|
|
47
|
+
const problems = [];
|
|
48
|
+
for (const bin of bins) {
|
|
49
|
+
if (bin.includes('__PLUMBBOB_BIN__')) {
|
|
50
|
+
problems.push('placeholder not substituted (setup did not finish)');
|
|
51
|
+
}
|
|
52
|
+
else if (bin.includes('$CLAUDE_PROJECT_DIR')) {
|
|
53
|
+
problems.push('legacy $CLAUDE_PROJECT_DIR bin — expands empty in a skill (pre-0.3 install)');
|
|
54
|
+
}
|
|
55
|
+
else if (bin.startsWith('/') && !existsSync(bin)) {
|
|
56
|
+
problems.push(`absolute bin not found on disk: ${bin}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return problems;
|
|
60
|
+
}
|
|
61
|
+
// True iff any registered hook command points into our hooks dir (the marker the
|
|
62
|
+
// settings module also uses for strip/merge).
|
|
63
|
+
function hasOurHook(settings) {
|
|
64
|
+
const hooks = settings.hooks ?? {};
|
|
65
|
+
return Object.values(hooks).some((list) => (list ?? []).some((e) => (e.hooks ?? []).some((h) => (h.command ?? '').includes('plumbbob/hooks/'))));
|
|
66
|
+
}
|
|
67
|
+
// The repo-scoped (self-contained) install: skills under <repo>/.claude, the CLI
|
|
68
|
+
// and hook in <repo>/node_modules, registration in either settings file.
|
|
69
|
+
function selfChecks(repoRoot, shipped) {
|
|
70
|
+
const skillsDir = join(repoRoot, '.claude', 'skills');
|
|
71
|
+
const installed = listSkills(skillsDir);
|
|
72
|
+
const bins = installedBins(skillsDir, installed);
|
|
73
|
+
const problems = binProblems(bins);
|
|
74
|
+
const localFile = join(repoRoot, '.claude', 'settings.local.json');
|
|
75
|
+
const projFile = join(repoRoot, '.claude', 'settings.json');
|
|
76
|
+
const registeredIn = [localFile, projFile].filter((f) => hasOurHook(readSettings(f)));
|
|
77
|
+
const hookScript = join(repoRoot, 'node_modules', 'plumbbob', 'hooks', 'post-edit.sh');
|
|
78
|
+
return [
|
|
79
|
+
installed.length >= shipped.length
|
|
80
|
+
? { ok: true, label: `skills installed (${installed.length}) in .claude/skills` }
|
|
81
|
+
: {
|
|
82
|
+
ok: false,
|
|
83
|
+
label: `skills incomplete (${installed.length}/${shipped.length}) in .claude/skills`,
|
|
84
|
+
fix: 'rerun: pnpm exec plumbbob setup --local',
|
|
85
|
+
},
|
|
86
|
+
problems.length === 0
|
|
87
|
+
? { ok: true, label: `skill bin resolves (${bins.join(', ') || 'n/a'})` }
|
|
88
|
+
: { ok: false, label: `skill bin broken — ${problems.join('; ')}`, fix: 'rerun: pnpm exec plumbbob setup --local' },
|
|
89
|
+
existsSync(join(repoRoot, 'node_modules', 'plumbbob'))
|
|
90
|
+
? { ok: true, label: 'plumbbob dependency present (node_modules/plumbbob)' }
|
|
91
|
+
: { ok: false, label: 'plumbbob is not a project dependency', fix: 'add it: pnpm add -D plumbbob' },
|
|
92
|
+
existsSync(join(repoRoot, 'node_modules', '.bin', 'plumbbob'))
|
|
93
|
+
? { ok: true, label: 'CLI shim present (node_modules/.bin/plumbbob)' }
|
|
94
|
+
: { ok: false, label: 'CLI shim missing (node_modules/.bin/plumbbob)', fix: 'reinstall deps: pnpm install' },
|
|
95
|
+
registeredIn.length > 0
|
|
96
|
+
? { ok: true, label: `post-edit hook registered (${registeredIn.map((f) => basename(f)).join(', ')})` }
|
|
97
|
+
: {
|
|
98
|
+
ok: false,
|
|
99
|
+
label: 'post-edit hook not registered in .claude/settings*.json',
|
|
100
|
+
fix: 'rerun: pnpm exec plumbbob setup --local',
|
|
101
|
+
},
|
|
102
|
+
existsSync(hookScript)
|
|
103
|
+
? { ok: true, label: 'hook script resolves (node_modules/plumbbob/hooks/post-edit.sh)' }
|
|
104
|
+
: {
|
|
105
|
+
ok: false,
|
|
106
|
+
label: 'hook script missing (node_modules/plumbbob/hooks/post-edit.sh)',
|
|
107
|
+
fix: 'reinstall the dep so the registered hook resolves: pnpm add -D plumbbob',
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
// The global install: skills + hook copied under ~/.claude, the CLI on PATH from
|
|
112
|
+
// `npm i -g`. The bare on-PATH bin can't be probed from here without spawning, so
|
|
113
|
+
// this scope verifies the copied artifacts and the registration.
|
|
114
|
+
function globalChecks(home, shipped) {
|
|
115
|
+
const skillsDir = join(home, '.claude', 'skills');
|
|
116
|
+
const installed = listSkills(skillsDir);
|
|
117
|
+
const problems = binProblems(installedBins(skillsDir, installed));
|
|
118
|
+
const hookScript = join(home, '.claude', 'plumbbob', 'hooks', 'post-edit.sh');
|
|
119
|
+
return [
|
|
120
|
+
installed.length >= shipped.length
|
|
121
|
+
? { ok: true, label: `skills installed (${installed.length}) in ~/.claude/skills` }
|
|
122
|
+
: { ok: false, label: `skills incomplete (${installed.length}/${shipped.length})`, fix: 'rerun: plumbbob setup --global' },
|
|
123
|
+
problems.length === 0
|
|
124
|
+
? { ok: true, label: 'skill bin resolves (plumbbob, on PATH from the global install)' }
|
|
125
|
+
: { ok: false, label: `skill bin broken — ${problems.join('; ')}`, fix: 'rerun: plumbbob setup --global' },
|
|
126
|
+
existsSync(hookScript)
|
|
127
|
+
? { ok: true, label: 'hook script present (~/.claude/plumbbob/hooks/post-edit.sh)' }
|
|
128
|
+
: { ok: false, label: 'hook script missing under ~/.claude/plumbbob/hooks', fix: 'rerun: plumbbob setup --global' },
|
|
129
|
+
hasOurHook(readSettings(join(home, '.claude', 'settings.json')))
|
|
130
|
+
? { ok: true, label: 'post-edit hook registered (~/.claude/settings.json)' }
|
|
131
|
+
: { ok: false, label: 'post-edit hook not registered (~/.claude/settings.json)', fix: 'rerun: plumbbob setup --global' },
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
export function doctor(cwd) {
|
|
135
|
+
const home = process.env.HOME ?? homedir();
|
|
136
|
+
const shipped = listSkills(packageDir('skills'));
|
|
137
|
+
const repoRoot = findRepoRoot(cwd);
|
|
138
|
+
const repoSkills = repoRoot === null ? [] : listSkills(join(repoRoot, '.claude', 'skills'));
|
|
139
|
+
const globalSkills = listSkills(join(home, '.claude', 'skills'));
|
|
140
|
+
const out = [`plumbbob doctor — ${repoRoot ?? cwd}`];
|
|
141
|
+
let checks;
|
|
142
|
+
if (repoSkills.length > 0 && repoRoot !== null) {
|
|
143
|
+
out.push('install shape: self-contained (<repo>/.claude)');
|
|
144
|
+
checks = selfChecks(repoRoot, shipped);
|
|
145
|
+
}
|
|
146
|
+
else if (globalSkills.length > 0) {
|
|
147
|
+
out.push('install shape: global (~/.claude)');
|
|
148
|
+
checks = globalChecks(home, shipped);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
out.push('install shape: none detected');
|
|
152
|
+
checks = [
|
|
153
|
+
{
|
|
154
|
+
ok: false,
|
|
155
|
+
label: 'no pb-* skills found under <repo>/.claude/skills or ~/.claude/skills',
|
|
156
|
+
fix: repoRoot === null
|
|
157
|
+
? 'global install: npm i -g plumbbob && plumbbob setup --global'
|
|
158
|
+
: 'project install: pnpm add -D plumbbob && pnpm exec plumbbob setup --local',
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
for (const c of checks) {
|
|
163
|
+
out.push(c.ok ? ` ✓ ${c.label}` : ` ✗ ${c.label}\n → ${c.fix}`);
|
|
164
|
+
}
|
|
165
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
166
|
+
out.push('');
|
|
167
|
+
out.push(failed === 0
|
|
168
|
+
? 'plumbbob: all checks passed. If a skill still misbehaves, restart Claude Code to reload settings.'
|
|
169
|
+
: `plumbbob: ${failed} problem(s) found — apply the → fixes above, then restart Claude Code.`);
|
|
170
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
171
|
+
return failed === 0 ? 0 : 1;
|
|
172
|
+
}
|
package/dist/verbs/setup.js
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
// self-contained (default when plumbbob is a project-local dep; --local /
|
|
4
4
|
// --project to force) — NOTHING is written under ~/.claude. The hooks are
|
|
5
5
|
// referenced in place at $CLAUDE_PROJECT_DIR/node_modules/plumbbob/hooks/,
|
|
6
|
-
// and the skills are copied into <repo>/.claude/skills
|
|
7
|
-
// invocation
|
|
8
|
-
//
|
|
6
|
+
// and the skills are copied into <repo>/.claude/skills/. A skill's bin
|
|
7
|
+
// invocation can NOT use $CLAUDE_PROJECT_DIR — that variable is defined only
|
|
8
|
+
// in Claude Code's hook context and expands EMPTY in a skill's bash — so
|
|
9
|
+
// setup resolves the bin at install time (see selfBin): --local bakes the
|
|
10
|
+
// absolute path to <repo>/node_modules/.bin/plumbbob (personal + untracked,
|
|
11
|
+
// so a machine path is fine); --project uses a bare `plumbbob` (committed +
|
|
12
|
+
// shared, so it stays portable — Claude Code resolves it from the project's
|
|
13
|
+
// node_modules/.bin, which it prepends to PATH). The registration lands in:
|
|
9
14
|
// --local (default) <repo>/.claude/settings.local.json — personal, untracked
|
|
10
15
|
// --project <repo>/.claude/settings.json — committable, enrolls the team
|
|
11
16
|
// This is the "run it from the project root, no global install" shape.
|
|
@@ -28,10 +33,11 @@ const HOOK_FILES = ['post-edit.sh'];
|
|
|
28
33
|
// The placeholder every skill carries in place of its `plumbbob` invocation;
|
|
29
34
|
// setup substitutes it with the form the chosen install shape resolves.
|
|
30
35
|
const BIN_PLACEHOLDER = '__PLUMBBOB_BIN__';
|
|
31
|
-
//
|
|
32
|
-
//
|
|
36
|
+
// The hook is registered in settings.json, whose `command` runs in Claude Code's
|
|
37
|
+
// hook context — the one place $CLAUDE_PROJECT_DIR is defined — so the portable
|
|
38
|
+
// project-root variable resolves correctly here. (A skill's bash does NOT get
|
|
39
|
+
// that variable; the skill bin is resolved separately, by selfBin.)
|
|
33
40
|
const SELF_HOOKS_DIR = '$CLAUDE_PROJECT_DIR/node_modules/plumbbob/hooks';
|
|
34
|
-
const SELF_BIN = '$CLAUDE_PROJECT_DIR/node_modules/.bin/plumbbob';
|
|
35
41
|
export function setup(cwd, args) {
|
|
36
42
|
const home = process.env.HOME ?? homedir();
|
|
37
43
|
const mode = resolveMode(cwd, home, args);
|
|
@@ -72,20 +78,36 @@ function installGlobal(home, settingsFile) {
|
|
|
72
78
|
// their bin invocation rewritten to the project-local binary.
|
|
73
79
|
function installSelfContained(mode) {
|
|
74
80
|
const skillsDir = join(mode.repoRoot, '.claude', 'skills');
|
|
81
|
+
const bin = selfBin(mode);
|
|
75
82
|
cpSync(packageDir('skills'), skillsDir, { recursive: true });
|
|
76
|
-
substituteSkillBins(skillsDir,
|
|
83
|
+
substituteSkillBins(skillsDir, bin);
|
|
77
84
|
writeSettings(mode.settingsFile, mergeRegistration(readSettings(mode.settingsFile), SELF_HOOKS_DIR, true));
|
|
78
85
|
const note = existsSync(join(mode.repoRoot, 'node_modules', 'plumbbob', 'hooks'))
|
|
79
86
|
? ''
|
|
80
87
|
: 'plumbbob: note — node_modules/plumbbob/ is not present in this repo yet. Add plumbbob as a dependency (e.g. `pnpm add -D plumbbob`) so the registered hooks resolve.\n';
|
|
81
88
|
const scope = mode.settingsFile.endsWith('settings.local.json') ? 'local' : 'project';
|
|
82
|
-
process.stdout.write(`plumbbob: copied skills → ${skillsDir} (bin → ${
|
|
89
|
+
process.stdout.write(`plumbbob: copied skills → ${skillsDir} (bin → ${bin}). Nothing was written under ~/.claude.\n` +
|
|
83
90
|
`plumbbob: referenced the hooks at ${SELF_HOOKS_DIR} and registered them in ${mode.settingsFile} (${scope} scope).\n` +
|
|
84
91
|
note +
|
|
85
92
|
'plumbbob: restart Claude Code (or reload settings) for the hooks to take effect.\n' +
|
|
86
93
|
'plumbbob: drive the workflow from the chat with the `pb-*` driver skills. Keep the transition verbs OUT of your settings allowlist — each driver skill self-authorizes its own verb, so a stray model-initiated transition still surfaces a permission prompt.\n');
|
|
87
94
|
return 0;
|
|
88
95
|
}
|
|
96
|
+
// Resolve the bin string baked into the copied skills. A skill's `!`...``
|
|
97
|
+
// injection and its allowed-tools prefix can NOT rely on $CLAUDE_PROJECT_DIR (a
|
|
98
|
+
// hooks-only variable that expands empty in a skill's bash context), so we
|
|
99
|
+
// resolve at install time. --local is personal and untracked, so bake the
|
|
100
|
+
// absolute path to the project-local binary — fully resolved, no runtime
|
|
101
|
+
// lookup. --project is committed and shared, so a machine-absolute path can't
|
|
102
|
+
// travel; fall back to a bare `plumbbob`, which Claude Code resolves from the
|
|
103
|
+
// project's node_modules/.bin (it prepends that to PATH). A repo path with
|
|
104
|
+
// whitespace can't sit unquoted in the injection or an allowed-tools prefix
|
|
105
|
+
// either, so use the PATH-resolved bare form there too.
|
|
106
|
+
function selfBin(mode) {
|
|
107
|
+
const committed = !mode.settingsFile.endsWith('settings.local.json');
|
|
108
|
+
const abs = join(mode.repoRoot, 'node_modules', '.bin', 'plumbbob');
|
|
109
|
+
return committed || /\s/.test(abs) ? 'plumbbob' : abs;
|
|
110
|
+
}
|
|
89
111
|
// Resolve which install shape and settings file to use. Explicit flags win;
|
|
90
112
|
// with no flag, auto-detect — a project-local plumbbob installs self-contained
|
|
91
113
|
// (personal, into settings.local.json), anything else installs global. Returns
|
package/package.json
CHANGED
package/skills/pb-build/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Edit, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBB
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — build a step (the optional engine)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
This is the **bundled executor** — one way to turn a planned step into code. It is
|
|
14
14
|
**optional** (D3): you can implement any step by hand, in a vibe session, or with
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Edit, Bash(__PLUMBBOB_BIN__ status:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — harvest the park list
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
`/pb-harvest` is the complement of `/pb-park` (D7): you parked ideas as seeds during a
|
|
14
14
|
build; now, at a boundary, you harvest them — decide what each one is.
|
package/skills/pb-park/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ park:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — park an idea (capture, don't chase)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
`/pb-park` is the **capture** half of the loop; `/pb-harvest` is where parked items
|
|
14
14
|
get triaged later (D7). Capturing the instant an idea arrives — instead of acting on
|
package/skills/pb-plan/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Edit, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBB
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — frame a goal (the whole-goal move)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
`/pb-plan` is the **whole-goal** move — it opens a session and gets the deciding out
|
|
14
14
|
of your head and onto `intent.md` *before* any code. (Planning a single increment is
|
package/skills/pb-reset/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — reset for a new goal (the close-out)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
`/pb-reset` ends the build: it captures what happened, archives it, and clears the
|
|
14
14
|
sidecar for the next goal. **Report by default** (D9) — no refuse-without-report gate,
|
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ revert:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — revert to a checkpoint (driver)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
This is a **driver skill** — a chat-side trigger for the mechanical `plumbbob revert` verb, so the whole workflow runs from the agent window instead of a terminal. It is `disable-model-invocation: true`: only the human fires it. It carries **no Edit and no Write tool** — its only action is to shell the verb and report the verb's output verbatim, including any refusal. The CLI is the source of truth: never retry a refused transition, and never edit a file to work around one.
|
|
14
14
|
|
package/skills/pb-spike/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ spike:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — spike an experiment (driver)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
This is a **driver skill** — a chat-side trigger for the mechanical `plumbbob spike` verb, so the whole workflow runs from the agent window instead of a terminal. It is `disable-model-invocation: true`: only the human fires it. It carries **no Edit and no Write tool** — its only action is to shell the verb and report the verb's output verbatim, including any refusal. The CLI is the source of truth: never retry a refused transition, and never edit a file to work around one.
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(__PLUMBBOB_BIN__ status:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — orient (the where-am-I move)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
This is a **thin driver** for `__PLUMBBOB_BIN__ status`. The dashboard above is your
|
|
14
14
|
orientation — the intent, the step list, the parked/open-question counts, and the
|
package/skills/pb-step/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Edit, Bash(__PLUMBBOB_BIN__ status:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — plan the next step (the single-increment move)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
`/pb-step` plans the **next increment** just-in-time (D6) — the smallest verifiable
|
|
14
14
|
piece of the framed goal, planned only when you reach it. (Framing the whole goal is
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ chec
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — verify a step (the tick)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
This is the **tick** — the one beat where the human is the clock. Whatever produced
|
|
14
14
|
the current diff — `/pb-build`, your own hands, a vibe session, another harness —
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Edit, Bash(__PLUMBBOB_BIN__ status:*)
|
|
|
8
8
|
|
|
9
9
|
# Plumbbob — interrogate the frame
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
|
|
11
|
+
Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npx plumbbob setup"`
|
|
12
12
|
|
|
13
13
|
## Wrong-state refusal
|
|
14
14
|
|