plumbbob 0.3.0 → 0.4.2

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.
@@ -1,22 +1,22 @@
1
1
  ---
2
- name: pb-verify
3
- description: The verify tick — run the check, self-review the diff against intent, validate the step's done-when, pause for your approval, then checkpoint. Executor-agnostic: it reads the diff, not who wrote it.
2
+ name: verify
3
+ description: "The verify tick — run the check, self-review the diff against intent, validate the step's done-when, pause for your approval, then checkpoint. Executor-agnostic: it reads the diff, not who wrote it."
4
4
  disable-model-invocation: true
5
5
  model: opus
6
- allowed-tools: Read, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ check:*), Bash(__PLUMBBOB_BIN__ checkpoint:*), Bash(git diff:*), Bash(git status:*)
6
+ allowed-tools: Read, Bash(plumbbob status:*), Bash(plumbbob check:*), Bash(plumbbob checkpoint:*), Bash(git diff:*), Bash(git status:*)
7
7
  ---
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 status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npm i -g plumbbob && plumbbob init"`
12
12
 
13
13
  This is the **tick** — the one beat where the human is the clock. Whatever produced
14
- the current diff — `/pb-build`, your own hands, a vibe session, another harness —
14
+ the current diff — `/plumbbob:build`, your own hands, a vibe session, another harness —
15
15
  this skill verifies it the same way: **it reads the diff, not the author** (D3).
16
16
 
17
17
  ## What this skill does, in order
18
18
 
19
- 1. **Check.** Run `__PLUMBBOB_BIN__ check` (the heavy gate). If it comes back
19
+ 1. **Check.** Run `plumbbob check` (the heavy gate). If it comes back
20
20
  **red**, stop here: report what failed and do **not** pause for approval — there
21
21
  is nothing to approve yet. The human fixes it and re-invokes.
22
22
  2. **Self-review** *(a single structured read, D16)*. Read `git diff` and
@@ -31,7 +31,7 @@ this skill verifies it the same way: **it reads the diff, not the author** (D3).
31
31
  the human is the clock. Never checkpoint without it.
32
32
  5. **Checkpoint** *(only after approval)*. Commit the work — the human's
33
33
  commit-with-TIL skill for a rich message, or let `checkpoint` make the WIP commit
34
- — then run `__PLUMBBOB_BIN__ checkpoint` to record the SHA, flip the step to done,
34
+ — then run `plumbbob checkpoint` to record the SHA, flip the step to done,
35
35
  and return to DESIGN. Do **not** bump the version or touch the changelog — that is
36
36
  the human's `/version` call.
37
37
 
@@ -40,7 +40,7 @@ this skill verifies it the same way: **it reads the diff, not the author** (D3).
40
40
  - **Never skip the pause.** Check → self-review → validate, then wait. Approval is
41
41
  the only thing that triggers the checkpoint.
42
42
  - **Read the diff, not the author** (D3). Verify identically whether the code was
43
- built by `/pb-build`, by hand, vibed, or by another harness.
43
+ built by `/plumbbob:build`, by hand, vibed, or by another harness.
44
44
  - **Red means stop, not pause.** A failing check is not an approval decision; report
45
45
  it and end your turn.
46
46
  - **You review; you do not build.** If the self-review finds a problem, surface it
@@ -1,16 +1,16 @@
1
1
  ---
2
- name: pb-reset
3
- description: Close out the build — write the report (what shipped, decisions, parked/harvested items, deferred tangents), then archive intent + build-log + report and clear for a fresh goal. Report by default, no gate.
2
+ name: wrap
3
+ description: Wrap up the build — write the report (what shipped, decisions, parked/harvested items, deferred tangents), then safely archive intent + build-log + report before clearing for a fresh goal. Archive-then-clear, never destroy. Report by default, no gate.
4
4
  disable-model-invocation: true
5
5
  model: opus
6
- allowed-tools: Read, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ reset:*)
6
+ allowed-tools: Read, Write, Bash(plumbbob status:*), Bash(plumbbob wrap:*)
7
7
  ---
8
8
 
9
- # Plumbbob — reset for a new goal (the close-out)
9
+ # Plumbbob — wrap the build (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 status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npm i -g plumbbob && plumbbob init"`
12
12
 
13
- `/pb-reset` ends the build: it captures what happened, archives it, and clears the
13
+ `/plumbbob:wrap` 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,
15
15
  and no separate docs phase.
16
16
 
@@ -23,11 +23,11 @@ and no separate docs phase.
23
23
  - **Final status** — done or partial, and what is left.
24
24
  - **Deferred tangents** — the harvested items that become future work.
25
25
  This is the "yeah, I did that" artifact. Write it by default; the human may edit it.
26
- 2. **Archive & clear.** Run `__PLUMBBOB_BIN__ reset`, which appends the checkpoint SHAs
26
+ 2. **Archive & clear.** Run `plumbbob wrap`, which appends the checkpoint SHAs
27
27
  to the report, archives intent + build-log + report to
28
28
  `.plumbbob/archive/<date>-<slug>/`, and clears the sidecar (STATE last). Git is not
29
29
  touched.
30
- 3. **Point at the next goal** — `/pb-plan` to frame the next one.
30
+ 3. **Point at the next goal** — `/plumbbob:plan` to frame the next one.
31
31
 
32
32
  ## The hard contracts
33
33
 
@@ -4,8 +4,8 @@ step boundaries. The antidote to "my plan got lost in the noise."
4
4
 
5
5
  Steps : where you are. One step in flight at a time.
6
6
  Park list : where ideas go so you do not chase them. CAPTURE, never act inline.
7
- Triage : the boundary ritual that keeps you on one branch.
8
- Log : the audit trail. Feeds /plumbbob-report, then gets archived.
7
+ Harvest : the boundary ritual that keeps you on one branch.
8
+ Log : the audit trail. Feeds the /plumbbob:wrap report, then gets archived.
9
9
  -->
10
10
 
11
11
  # Build log — {{TITLE}}
@@ -16,7 +16,8 @@ step boundaries. The antidote to "my plan got lost in the noise."
16
16
  ## Steps
17
17
 
18
18
  *(Mirror of intent.md's Steps, with live status. Only ONE step is in flight. A step
19
- is done only after `plumbbob done` — check green + checkpoint taken.)*
19
+ is done only after a checkpoint — check green + checkpoint taken, via `/plumbbob:verify` or
20
+ `/plumbbob:build`.)*
20
21
 
21
22
  - ☐ 1. <step>
22
23
 
@@ -24,31 +25,30 @@ is done only after `plumbbob done` — check green + checkpoint taken.)*
24
25
 
25
26
  > Mid-step, every new problem / idea / "ooh what if" lands HERE, untouched, and you
26
27
  > go straight back to the step. Acting the instant an idea arrives is the disease.
27
- > Capture is one line (`/park` composes it, or raw `plumbbob park`). Triage happens
28
- > only at the boundary.
28
+ > Capture is one line (`/plumbbob:park` composes it). Harvest happens only at the boundary.
29
29
 
30
- ## Triage *(run at each step boundary, after green)*
30
+ ## Harvest *(run `/plumbbob:harvest` at each step boundary, after green)*
31
31
 
32
32
  Classify each parked item as exactly ONE. Naming it before acting is what keeps you
33
33
  from sprawling across branches.
34
34
 
35
- | Class | Meaning | Action |
36
- |------------------|-------------------------------------------|-------------------------------|
37
- | **blocker** | Plan was wrong/incomplete; can't proceed | `revert`, fold into intent.md |
38
- | **tangent** | A different path, not clearly better | Defer or kill. Default here. |
39
- | **pivot signal** | Evidence the whole approach is wrong | Stop. Replan deliberately. |
35
+ | Class | Meaning | Action |
36
+ |------------------|-------------------------------------------|---------------------------------|
37
+ | **blocker** | Plan was wrong/incomplete; can't proceed | `/plumbbob:revert`, fold into intent |
38
+ | **tangent** | A different path, not clearly better | Defer or kill. Default here. |
39
+ | **pivot signal** | Evidence the whole approach is wrong | Stop. Replan deliberately. |
40
40
 
41
41
  > Reality check: almost everything that *feels* like a pivot is a tangent. Require a
42
42
  > failed assumption, not a shinier idea, before you pivot.
43
43
 
44
- Triage results this boundary:
44
+ Harvest results this boundary:
45
45
 
46
46
  - (none yet)
47
47
 
48
48
  ## Log
49
49
 
50
50
  *(Append-only. One decision or event per line, dated. What you point at to say "I
51
- did that — the LLM helped, but those were my calls." `/plumbbob-report` reads this;
52
- `plumbbob finish` archives it under `.plumbbob/archive/`.)*
51
+ did that — the LLM helped, but those were my calls." `/plumbbob:wrap` reads this for the
52
+ report; `plumbbob wrap` archives it under `.plumbbob/archive/`.)*
53
53
 
54
54
  - <date> — <decision / event / what shipped this step>
@@ -33,25 +33,29 @@ open. If the implementor (you-later, or the LLM) has to guess, the doc failed.
33
33
  ## Decisions
34
34
 
35
35
  *(One line each. Settled, not re-litigated in the chat. Grows as you resolve the
36
- holes the interrogation surfaces, and as blockers fold in during BUILD.)*
36
+ holes `/plumbbob:refine` surfaces, and as blockers fold in during BUILD.)*
37
37
 
38
38
  - D1: <decision> — *because* <the one reason that mattered>
39
39
 
40
40
  ## Constraints
41
41
 
42
- *(Hard rules the build must honor. Triage and review read against these.)*
42
+ *(Hard rules the build must honor. `/plumbbob:verify` and `/plumbbob:refine` read against these.)*
43
43
 
44
44
  - C1: <e.g. functional/procedural only; no new dependencies>
45
45
 
46
46
  ## Steps
47
47
 
48
- *(The build plan. Each step carries its own seamthe paths `plumbbob build <n>`
49
- writes into `.plumbbob/SEAM` and the seam-guard enforces. Keep each step small
50
- enough to verify in one review pass. A blocker may rewrite a step's seam: fold the
51
- new decision, revise the seam here, then `build <n>` again.)*
48
+ *(The build plan. `/plumbbob:plan` authors the **whole list up front** each step a small,
49
+ verifiable increment with its own **done-when** and **seam** (the paths it will touch,
50
+ which `/plumbbob:build` records in `.plumbbob/SEAM` for orientation awareness, not a lock
51
+ in v2). Then drive `/plumbbob:build` until done. Later steps may be fuzzier than the first;
52
+ sharpen the next one just-in-time with `/plumbbob:step` (empty input auto-syncs it), and use
53
+ `/plumbbob:refine` to repair the whole plan when a blocker rewrites it.)*
52
54
 
53
55
  1. [ ] <step> — **done when:** <criterion, ideally a test or check result>
54
56
  - seam: `<file>`, `<file>`
57
+ 2. [ ] <step> — **done when:** <criterion>
58
+ - seam: `<file>`
55
59
 
56
60
  ## Open questions
57
61
 
@@ -1,83 +0,0 @@
1
- // Claude Code settings.json hook registration (D27). A pure-TS JSON merge (zero
2
- // runtime deps, node builtins only — C2): strip our entries, then re-add them, so
3
- // a re-run is byte-identical. The same logic serves all three D27 scopes — only
4
- // the settings file path and the command-path representation differ, both chosen
5
- // by the `setup` verb. Functional/procedural, no classes (C1).
6
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
- import { dirname } from 'node:path';
8
- // A registration entry is "ours" iff one of its hook commands points into our
9
- // hooks dir. This marker matches every command form we emit: the global
10
- // `~/.claude/plumbbob/hooks/...` copy and the self-contained
11
- // `$CLAUDE_PROJECT_DIR/node_modules/plumbbob/hooks/...` reference both contain
12
- // `plumbbob/hooks/`, so uninstall finds either.
13
- const OURS_MARKER = 'plumbbob/hooks/';
14
- // The edit matcher covers all four editing tools (D5); v2 uses it for the
15
- // post-edit feedback hook (the only edit-time hook left).
16
- const EDIT_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';
17
- // The single hook Plumbbob v2 registers: the PostToolUse light feedback (D11/D25).
18
- // v1's PreToolUse muzzle + bash-guard are gone (D1/D13); the merge still STRIPS any
19
- // leftover PreToolUse entry from an older install, it just no longer adds one. Two
20
- // command forms:
21
- // global — a direct absolute path to the +x copy under ~/.claude.
22
- // self (viaSh) — `sh "<dir>/<file>"` against the package's hooks/ under
23
- // node_modules, addressed through `$CLAUDE_PROJECT_DIR` so the
24
- // committed/portable form carries no machine-absolute path and
25
- // needs no execute bit (pnpm's store may not preserve one).
26
- function registrationEntries(hooksDir, viaSh) {
27
- const dir = hooksDir.endsWith('/') ? hooksDir.slice(0, -1) : hooksDir;
28
- const command = (file) => (viaSh ? `sh "${dir}/${file}"` : `${dir}/${file}`);
29
- const entry = (matcher, file) => ({
30
- matcher,
31
- hooks: [{ type: 'command', command: command(file) }],
32
- });
33
- return { PostToolUse: [entry(EDIT_MATCHER, 'post-edit.sh')] };
34
- }
35
- function isOurs(entry) {
36
- return (entry.hooks ?? []).some((h) => h.command.includes(OURS_MARKER));
37
- }
38
- function stripOurs(list) {
39
- return (list ?? []).filter((e) => !isOurs(e));
40
- }
41
- // Merge our registration into a parsed settings object (strip-then-add). Returns
42
- // a new object; every unrelated key and unrelated hook is preserved, and key
43
- // order is stable across runs so a second merge is byte-identical.
44
- export function mergeRegistration(settings, hooksDir, viaSh = false) {
45
- const entries = registrationEntries(hooksDir, viaSh);
46
- const existing = settings.hooks ?? {};
47
- const hooks = { ...existing };
48
- // Strip any leftover v1 PreToolUse muzzle/bash-guard from an older install, but
49
- // add back only the PostToolUse feedback hook — v2 registers no PreToolUse.
50
- if (existing.PreToolUse !== undefined) {
51
- hooks.PreToolUse = stripOurs(existing.PreToolUse);
52
- }
53
- hooks.PostToolUse = [...stripOurs(existing.PostToolUse), ...entries.PostToolUse];
54
- return { ...settings, hooks };
55
- }
56
- // Remove only our entries from the two events we manage, leaving everything else
57
- // untouched (uninstall, and the idempotent strip half of a re-install).
58
- export function stripRegistration(settings) {
59
- const existing = settings.hooks;
60
- if (existing === undefined) {
61
- return settings;
62
- }
63
- const hooks = { ...existing };
64
- if (existing.PreToolUse !== undefined) {
65
- hooks.PreToolUse = stripOurs(existing.PreToolUse);
66
- }
67
- if (existing.PostToolUse !== undefined) {
68
- hooks.PostToolUse = stripOurs(existing.PostToolUse);
69
- }
70
- return { ...settings, hooks };
71
- }
72
- export function readSettings(path) {
73
- try {
74
- return JSON.parse(readFileSync(path, 'utf8'));
75
- }
76
- catch {
77
- return {};
78
- }
79
- }
80
- export function writeSettings(path, settings) {
81
- mkdirSync(dirname(path), { recursive: true });
82
- writeFileSync(path, `${JSON.stringify(settings, null, 2)}\n`);
83
- }
@@ -1,141 +0,0 @@
1
- // `plumbbob setup` — the installer. Two install shapes:
2
- //
3
- // self-contained (default when plumbbob is a project-local dep; --local /
4
- // --project to force) — NOTHING is written under ~/.claude. The hooks are
5
- // referenced in place at $CLAUDE_PROJECT_DIR/node_modules/plumbbob/hooks/,
6
- // and the skills are copied into <repo>/.claude/skills/ with their bin
7
- // invocation pointed at $CLAUDE_PROJECT_DIR/node_modules/.bin/plumbbob. The
8
- // registration lands in:
9
- // --local (default) <repo>/.claude/settings.local.json — personal, untracked
10
- // --project <repo>/.claude/settings.json — committable, enrolls the team
11
- // This is the "run it from the project root, no global install" shape.
12
- //
13
- // global (--global, or the auto default when plumbbob is NOT a project-local
14
- // dep) — the original behavior: copy the hooks to ~/.claude/plumbbob/hooks/
15
- // and the skills to ~/.claude/skills/, register absolute command paths in
16
- // ~/.claude/settings.json, and leave the skills calling a bare `plumbbob`.
17
- //
18
- // `--uninstall` strips the registration from whichever settings file the same
19
- // resolution selects; installed/copied files are left in place. The hooks are
20
- // session-gated, so any registration is safe in every repo (C7).
21
- import { chmodSync, cpSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
22
- import { homedir } from 'node:os';
23
- import { join, sep } from 'node:path';
24
- import { fileURLToPath } from 'node:url';
25
- import { findRepoRoot } from "../lib/git.js";
26
- import { mergeRegistration, readSettings, stripRegistration, writeSettings } from "../lib/settings.js";
27
- const HOOK_FILES = ['post-edit.sh'];
28
- // The placeholder every skill carries in place of its `plumbbob` invocation;
29
- // setup substitutes it with the form the chosen install shape resolves.
30
- const BIN_PLACEHOLDER = '__PLUMBBOB_BIN__';
31
- // Self-contained installs address everything through Claude Code's project-root
32
- // variable so committed/portable files carry no machine-absolute path.
33
- const SELF_HOOKS_DIR = '$CLAUDE_PROJECT_DIR/node_modules/plumbbob/hooks';
34
- const SELF_BIN = '$CLAUDE_PROJECT_DIR/node_modules/.bin/plumbbob';
35
- export function setup(cwd, args) {
36
- const home = process.env.HOME ?? homedir();
37
- const mode = resolveMode(cwd, home, args);
38
- if (mode === null) {
39
- process.stderr.write('plumbbob: a project-scoped install writes <repo>/.claude/, but this is not a git repository. ' +
40
- 'Run setup from inside the repo, or use --global to install under ~/.claude.\n');
41
- return 1;
42
- }
43
- if (args.includes('--uninstall')) {
44
- writeSettings(mode.settingsFile, stripRegistration(readSettings(mode.settingsFile)));
45
- process.stdout.write(`plumbbob: removed the hook registration from ${mode.settingsFile}. The installed/copied hooks and skills were left in place.\n`);
46
- return 0;
47
- }
48
- return mode.kind === 'global' ? installGlobal(home, mode.settingsFile) : installSelfContained(mode);
49
- }
50
- // The original shape: hooks + skills copied under ~/.claude, absolute command
51
- // paths, skills calling a bare `plumbbob` (which the global npm install puts on
52
- // PATH).
53
- function installGlobal(home, settingsFile) {
54
- const hooksDir = join(home, '.claude', 'plumbbob', 'hooks');
55
- const skillsDir = join(home, '.claude', 'skills');
56
- cpSync(packageDir('hooks'), hooksDir, { recursive: true });
57
- for (const file of HOOK_FILES) {
58
- chmodSync(join(hooksDir, file), 0o755);
59
- }
60
- cpSync(packageDir('skills'), skillsDir, { recursive: true });
61
- substituteSkillBins(skillsDir, 'plumbbob');
62
- writeSettings(settingsFile, mergeRegistration(readSettings(settingsFile), hooksDir));
63
- process.stdout.write(`plumbbob: installed hooks → ${hooksDir}, skills → ${skillsDir}.\n` +
64
- `plumbbob: registered the hooks in ${settingsFile} (global scope).\n` +
65
- 'plumbbob: restart Claude Code (or reload settings) for the hooks to take effect.\n' +
66
- "plumbbob: the skills call a bare `plumbbob`; the npm install puts it (and `pb`) on PATH. From a dev checkout add `alias plumbbob='node <repo>/src/cli.ts'`.\n");
67
- return 0;
68
- }
69
- // The self-contained shape: nothing under ~/.claude. Hooks are referenced in
70
- // place inside node_modules; only the skills are copied into the repo (Claude
71
- // Code discovers skills under .claude/skills/, never from node_modules), with
72
- // their bin invocation rewritten to the project-local binary.
73
- function installSelfContained(mode) {
74
- const skillsDir = join(mode.repoRoot, '.claude', 'skills');
75
- cpSync(packageDir('skills'), skillsDir, { recursive: true });
76
- substituteSkillBins(skillsDir, SELF_BIN);
77
- writeSettings(mode.settingsFile, mergeRegistration(readSettings(mode.settingsFile), SELF_HOOKS_DIR, true));
78
- const note = existsSync(join(mode.repoRoot, 'node_modules', 'plumbbob', 'hooks'))
79
- ? ''
80
- : '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
- const scope = mode.settingsFile.endsWith('settings.local.json') ? 'local' : 'project';
82
- process.stdout.write(`plumbbob: copied skills → ${skillsDir} (bin → ${SELF_BIN}). Nothing was written under ~/.claude.\n` +
83
- `plumbbob: referenced the hooks at ${SELF_HOOKS_DIR} and registered them in ${mode.settingsFile} (${scope} scope).\n` +
84
- note +
85
- 'plumbbob: restart Claude Code (or reload settings) for the hooks to take effect.\n' +
86
- '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
- return 0;
88
- }
89
- // Resolve which install shape and settings file to use. Explicit flags win;
90
- // with no flag, auto-detect — a project-local plumbbob installs self-contained
91
- // (personal, into settings.local.json), anything else installs global. Returns
92
- // null when a project scope is required but cwd is not in a git repo.
93
- function resolveMode(cwd, home, args) {
94
- const globalFile = join(home, '.claude', 'settings.json');
95
- if (args.includes('--global')) {
96
- return { kind: 'global', settingsFile: globalFile };
97
- }
98
- const wantsProject = args.includes('--project');
99
- const wantsLocal = args.includes('--local');
100
- const auto = !wantsProject && !wantsLocal && isProjectLocal(cwd);
101
- if (!wantsProject && !wantsLocal && !auto) {
102
- return { kind: 'global', settingsFile: globalFile };
103
- }
104
- const repoRoot = findRepoRoot(cwd);
105
- if (repoRoot === null) {
106
- return null;
107
- }
108
- const file = wantsProject ? 'settings.json' : 'settings.local.json';
109
- return { kind: 'self', settingsFile: join(repoRoot, '.claude', file), repoRoot };
110
- }
111
- // plumbbob is project-local when its package directory lives inside this repo's
112
- // node_modules (a `pnpm add` / `npm install` dependency), as opposed to a global
113
- // install or a dev checkout.
114
- function isProjectLocal(cwd) {
115
- const repoRoot = findRepoRoot(cwd);
116
- if (repoRoot === null) {
117
- return false;
118
- }
119
- return packageRoot().startsWith(join(repoRoot, 'node_modules') + sep);
120
- }
121
- // Rewrite the bin placeholder in every copied skill (both the `!`...`` status
122
- // pre-injection and the allowed-tools patterns) to the resolved invocation.
123
- function substituteSkillBins(skillsDir, bin) {
124
- for (const name of readdirSync(skillsDir)) {
125
- const file = join(skillsDir, name, 'SKILL.md');
126
- if (!statSync(join(skillsDir, name)).isDirectory() || !existsSync(file)) {
127
- continue;
128
- }
129
- const src = readFileSync(file, 'utf8');
130
- if (src.includes(BIN_PLACEHOLDER)) {
131
- writeFileSync(file, src.split(BIN_PLACEHOLDER).join(bin));
132
- }
133
- }
134
- }
135
- // The plumbbob package root (parent of hooks/ and skills/), off this module's URL.
136
- function packageRoot() {
137
- return fileURLToPath(new URL('../../', import.meta.url));
138
- }
139
- function packageDir(name) {
140
- return join(packageRoot(), name);
141
- }
@@ -1,49 +0,0 @@
1
- ---
2
- name: pb-build
3
- description: The optional engine — read the current planned step from intent, implement it (its done-when, seam, Decisions, Constraints), then verify it through to the approval pause. Skip it entirely to implement by hand, vibed, or with another harness.
4
- disable-model-invocation: true
5
- model: opus
6
- allowed-tools: Read, Edit, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ build:*), Bash(__PLUMBBOB_BIN__ check:*), Bash(__PLUMBBOB_BIN__ checkpoint:*), Bash(git diff:*)
7
- ---
8
-
9
- # Plumbbob — build a step (the optional engine)
10
-
11
- Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
12
-
13
- This is the **bundled executor** — one way to turn a planned step into code. It is
14
- **optional** (D3): you can implement any step by hand, in a vibe session, or with
15
- another harness and go straight to `/pb-verify` instead — plumbbob does not care how
16
- the diff appeared. When you do run it, it reads the plan, writes the step, and
17
- carries straight through to the verify pause.
18
-
19
- ## What this skill does, in order
20
-
21
- 1. **Pick the step.** Use the number you were invoked with (e.g. `/pb-build 4`), else
22
- the next undone, planned step in `.plumbbob/intent.md`. If there is no planned step
23
- to build, stop and tell the human to `/pb-step` first.
24
- 2. **Enter the step.** Run `__PLUMBBOB_BIN__ build <n>` (records the in-flight STEP +
25
- SEAM so `/pb-status` shows the step in flight; in v2 the seam is awareness, not a
26
- lock).
27
- 3. **Read the plan.** Read the step's **done-when**, its **seam**, and the
28
- **Decisions** and **Constraints** in `intent.md`. Build to *that* — the deciding
29
- already happened, off the chat.
30
- 4. **Implement** the step, and only that step, staying within the declared seam. A
31
- new problem or "ooh what if" that surfaces mid-build is a `/pb-park`, **not** an
32
- edit — capture it and stay on the step. If you genuinely cannot finish without
33
- touching more than the seam, that is scope drift: surface it to the human rather
34
- than sprawling.
35
- 5. **Verify, through to the pause.** Run the verify tick exactly as `/pb-verify`
36
- does: `__PLUMBBOB_BIN__ check` → self-review the diff against the done-when, the
37
- Decisions, and the Constraints (a single structured read, D16) → validate → **PAUSE
38
- for the human's approval** → only on approval, checkpoint with
39
- `__PLUMBBOB_BIN__ checkpoint`. Do **not** bump the version or changelog — that is
40
- the human's `/version` call.
41
-
42
- ## The hard contracts
43
-
44
- - **Optional, never required.** The loop works without this skill; `/pb-verify`
45
- checkpoints a hand-built or vibed diff just the same (D3).
46
- - **Build the decided step, not a new one.** Implement what `intent.md` settled. A
47
- new idea mid-build is a `/pb-park`, not an edit.
48
- - **Always end at the pause.** Implement → verify → wait for approval. Never
49
- checkpoint without it; the human is the clock.
@@ -1,38 +0,0 @@
1
- ---
2
- name: pb-plan
3
- description: Frame a fresh goal — scaffold the session and author the intent's Frame, Decisions, and Constraints before any code. Steps stay empty; they come one at a time from /pb-step.
4
- disable-model-invocation: true
5
- model: opus
6
- allowed-tools: Read, Edit, Write, Bash(__PLUMBBOB_BIN__ status:*), Bash(__PLUMBBOB_BIN__ start:*)
7
- ---
8
-
9
- # Plumbbob — frame a goal (the whole-goal move)
10
-
11
- Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
12
-
13
- `/pb-plan` is the **whole-goal** move — it opens a session and gets the deciding out
14
- of your head and onto `intent.md` *before* any code. (Planning a single increment is
15
- the separate `/pb-step` move; do not confuse the two.)
16
-
17
- ## What this skill does
18
-
19
- 1. **Scaffold.** If there is no active session, run `__PLUMBBOB_BIN__ start "<title>"`
20
- to create `.plumbbob/` (STATE=DESIGN, baseline recorded). If a session already
21
- exists, say so and edit the existing `intent.md` rather than starting over.
22
- 2. **Frame** (`.plumbbob/intent.md`), with the human: the **Problem** in plain words,
23
- the **smallest thing** that solves it, what **done looks like**, and what you are
24
- **explicitly NOT doing**. This is the human's convergence — propose wording, but
25
- the human decides every line.
26
- 3. **Decisions & Constraints.** Record the settled calls (one line each, with the
27
- *because*) and the hard rules the build must honor. An unresolved hole goes to
28
- **Open questions**, never guessed into a Decision.
29
- 4. **Leave `## Steps` empty.** Steps are planned just-in-time (D6) — one at a time
30
- with `/pb-step` — so do not write the build plan here.
31
-
32
- ## The hard contracts
33
-
34
- - **Deciding before code.** `/pb-plan` writes `intent.md` only — never source.
35
- - **The human converges.** You surface options and draft wording; the human picks.
36
- An unresolved hole is an Open question, not a guessed Decision.
37
- - **Size to the work.** A small change fills Frame + a couple of Decisions and stops;
38
- ceremony on a one-liner is the failure mode, not thoroughness.
@@ -1,37 +0,0 @@
1
- ---
2
- name: pb-step
3
- description: Plan the next increment — propose one small step with a done-when criterion and a seam, get the human's OK, and append it to intent's ## Steps. One by default; several only if the human already knows them.
4
- disable-model-invocation: true
5
- model: opus
6
- allowed-tools: Read, Edit, Bash(__PLUMBBOB_BIN__ status:*)
7
- ---
8
-
9
- # Plumbbob — plan the next step (the single-increment move)
10
-
11
- Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
12
-
13
- `/pb-step` plans the **next increment** just-in-time (D6) — the smallest verifiable
14
- piece of the framed goal, planned only when you reach it. (Framing the whole goal is
15
- the separate `/pb-plan` move.)
16
-
17
- ## What this skill does
18
-
19
- 1. **Read the plan.** Read `intent.md`'s Frame, Decisions, Constraints, and the steps
20
- already done, to see what the *next* increment should be.
21
- 2. **Propose one step.** Draft a single step:
22
- - a one-line **title**,
23
- - a **done-when** criterion — ideally a test or check result, something
24
- `/pb-verify` can actually validate,
25
- - a **seam**: the specific files it should touch (exact paths, or a `dir/` grant).
26
- Keep it small enough to verify in one review pass.
27
- 3. **Get the human's OK**, then **append** it to `## Steps` in the standard format —
28
- `N. [ ] <title> — **done when:** <criterion>` with a `- seam:` sub-line. Default
29
- to **one** step; plan several only when the human already knows them.
30
-
31
- ## The hard contracts
32
-
33
- - **One verifiable increment.** Each step carries a done-when `/pb-verify` can check
34
- and a seam small enough to review in one pass.
35
- - **Append to `## Steps` only**, in the standard format `status` and `build` parse —
36
- never the Roadmap, never loose prose.
37
- - **The human approves the step** before it lands. You propose; they decide.
@@ -1,33 +0,0 @@
1
- ---
2
- name: plumbbob-interrogate
3
- description: DESIGN-phase frame interrogation — attack the plan for holes and append them as Open questions, without deciding anything.
4
- disable-model-invocation: true
5
- model: opus
6
- allowed-tools: Read, Edit, Bash(__PLUMBBOB_BIN__ status:*)
7
- ---
8
-
9
- # Plumbbob — interrogate the frame
10
-
11
- Current session state (injected when this skill runs): !`__PLUMBBOB_BIN__ status`
12
-
13
- ## Wrong-state refusal
14
-
15
- This skill runs in **DESIGN only**. Read the state injected above and **stop before touching anything** if it is not `DESIGN`:
16
-
17
- - `STATE: BUILD` or `STATE: REVIEW` — you are mid-step. Return to DESIGN first: finish the step with `plumbbob done`, or drop a half-built step with `plumbbob revert`. Then re-invoke.
18
- - `STATE: SPIKE` — close the experiment with `plumbbob spike done` first.
19
- - `STATE: FINISH` — the session is wrapping up; there is nothing left to interrogate.
20
- - `NO ACTIVE SESSION` — start one with `plumbbob start "<title>"`.
21
-
22
- When the state is wrong, refuse in one line naming the verb above, and edit nothing.
23
-
24
- ## What this skill does
25
-
26
- Hand the frame (the **Frame** and **Architecture sketch** in `intent.md`) a cold, adversarial read and **attack it for holes** — ambiguities, unhandled edge cases, hidden assumptions, and collisions with the existing code. This is the one place early divergence is wanted, and it is divergence in the **problem space only**: surface what is unclear, never propose a solution and never pick between options.
27
-
28
- ## The one hard contract
29
-
30
- - Append every hole as a bullet under `intent.md`'s `## Open questions` section, and **nowhere else**.
31
- - **Never append to `## Decisions`** — resolving a hole is the human's convergence, not yours (D13). You surface; you do not decide.
32
- - One question per hole, phrased as a question, each standing on its own.
33
- - Once the Open questions are appended, **end your turn**. Do not answer them, do not start the next step, do not slide from interrogating into deciding.