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.
- package/.claude-plugin/plugin.json +11 -0
- package/README.md +201 -47
- package/dist/cli-core.js +83 -0
- package/dist/cli.js +6 -78
- package/dist/lib/archive.js +1 -1
- package/dist/lib/orient.js +35 -15
- package/dist/verbs/check.js +1 -1
- package/dist/verbs/checkpoint.js +1 -1
- package/dist/verbs/doctor.js +74 -0
- package/dist/verbs/init.js +81 -0
- package/dist/verbs/{reset.js → wrap.js} +7 -7
- package/hooks/hooks.json +12 -0
- package/package.json +4 -3
- package/skills/build/SKILL.md +70 -0
- package/skills/{pb-harvest → harvest}/SKILL.md +6 -6
- package/skills/{pb-park → park}/SKILL.md +6 -6
- package/skills/plan/SKILL.md +85 -0
- package/skills/refine/SKILL.md +45 -0
- package/skills/{pb-revert → revert}/SKILL.md +5 -5
- package/skills/{pb-spike → spike}/SKILL.md +5 -5
- package/skills/{pb-status → status}/SKILL.md +5 -5
- package/skills/step/SKILL.md +49 -0
- package/skills/{pb-verify → verify}/SKILL.md +8 -8
- package/skills/{pb-reset → wrap}/SKILL.md +8 -8
- package/templates/build-log.md +14 -14
- package/templates/intent.md +10 -6
- package/dist/lib/settings.js +0 -83
- package/dist/verbs/setup.js +0 -141
- package/skills/pb-build/SKILL.md +0 -49
- package/skills/pb-plan/SKILL.md +0 -38
- package/skills/pb-step/SKILL.md +0 -37
- package/skills/plumbbob-interrogate/SKILL.md +0 -33
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// `plumbbob doctor` — diagnose the global plugin link end to end. Read-only: it
|
|
2
|
+
// inspects, it never writes. After `plumbbob init`, plumbbob lives as a symlink at
|
|
3
|
+
// ~/.claude/skills/plumbbob pointing at the installed package; Claude Code loads it
|
|
4
|
+
// in place as a plugin (skills `/plumbbob:*`, the post-edit hook from hooks.json).
|
|
5
|
+
// doctor verifies the link resolves to a package carrying the manifest, the skills,
|
|
6
|
+
// and the hook — and names the fix for anything missing. The failure class it
|
|
7
|
+
// exists for is SILENT (a `/plumbbob:status` that opens an empty dashboard because the
|
|
8
|
+
// plugin never linked). Functional, node builtins only (C1/C2).
|
|
9
|
+
import { existsSync, lstatSync, readdirSync, readlinkSync } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
// The plumbbob package's own skills/ dir (the canonical set), off this module's
|
|
14
|
+
// URL so it resolves the same from src/ (dev) and dist/ (published).
|
|
15
|
+
function packageDir(name) {
|
|
16
|
+
return fileURLToPath(new URL(`../../${name}/`, import.meta.url));
|
|
17
|
+
}
|
|
18
|
+
// Skill directories (those carrying a SKILL.md) under `dir`.
|
|
19
|
+
function listSkills(dir) {
|
|
20
|
+
try {
|
|
21
|
+
return readdirSync(dir).filter((n) => existsSync(join(dir, n, 'SKILL.md')));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Resolve the dir the plugin link points at: a symlink's target, or the path
|
|
28
|
+
// itself if a real directory was copied there. null if nothing is linked.
|
|
29
|
+
function linkedPackage(link) {
|
|
30
|
+
try {
|
|
31
|
+
const st = lstatSync(link);
|
|
32
|
+
if (st.isSymbolicLink()) {
|
|
33
|
+
return readlinkSync(link);
|
|
34
|
+
}
|
|
35
|
+
return st.isDirectory() ? link : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function buildChecks(link, pkg, shipped) {
|
|
42
|
+
const installed = listSkills(join(pkg, 'skills'));
|
|
43
|
+
return [
|
|
44
|
+
{ ok: true, label: `linked (${link} → ${pkg})` },
|
|
45
|
+
existsSync(join(pkg, '.claude-plugin', 'plugin.json'))
|
|
46
|
+
? { ok: true, label: 'plugin manifest present (.claude-plugin/plugin.json)' }
|
|
47
|
+
: { ok: false, label: 'plugin manifest missing — the link does not point at a plumbbob package', fix: 're-link: plumbbob init' },
|
|
48
|
+
installed.length >= shipped.length && shipped.length > 0
|
|
49
|
+
? { ok: true, label: `skills present (${installed.length}) — load as /plumbbob:*` }
|
|
50
|
+
: { ok: false, label: `skills incomplete (${installed.length}/${shipped.length})`, fix: 're-link: plumbbob init' },
|
|
51
|
+
existsSync(join(pkg, 'hooks', 'hooks.json'))
|
|
52
|
+
? { ok: true, label: 'post-edit hook present (hooks/hooks.json, auto-registers)' }
|
|
53
|
+
: { ok: false, label: 'hook missing (hooks/hooks.json)', fix: 're-link: plumbbob init' },
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
export function doctor() {
|
|
57
|
+
const home = process.env.HOME ?? homedir();
|
|
58
|
+
const link = join(home, '.claude', 'skills', 'plumbbob');
|
|
59
|
+
const shipped = listSkills(packageDir('skills'));
|
|
60
|
+
const pkg = linkedPackage(link);
|
|
61
|
+
const checks = pkg === null ? [{ ok: false, label: `not linked — no plugin at ${link}`, fix: 'run: plumbbob init' }] : buildChecks(link, pkg, shipped);
|
|
62
|
+
const out = ['plumbbob doctor — global plugin install'];
|
|
63
|
+
for (const c of checks) {
|
|
64
|
+
out.push(c.ok ? ` ✓ ${c.label}` : ` ✗ ${c.label}\n → ${c.fix}`);
|
|
65
|
+
}
|
|
66
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
67
|
+
out.push('');
|
|
68
|
+
out.push(failed === 0
|
|
69
|
+
? 'plumbbob: all checks passed. If a skill still misbehaves, restart Claude Code (or /reload-plugins).'
|
|
70
|
+
: `plumbbob: ${failed} problem(s) — apply the → fixes, then restart Claude Code.`);
|
|
71
|
+
out.push('plumbbob: skills shell a bare `plumbbob` — ensure it is on PATH (`npm i -g plumbbob`). Sessions are per-project via `plumbbob start`.');
|
|
72
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
73
|
+
return failed === 0 ? 0 : 1;
|
|
74
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// `plumbbob init [--uninstall]` — link plumbbob into Claude Code as an in-place
|
|
2
|
+
// skills-directory plugin. This is the whole install: it symlinks the installed
|
|
3
|
+
// package into ~/.claude/skills/plumbbob, where Claude Code discovers it as
|
|
4
|
+
// `plumbbob@skills-dir` — the skills load namespaced (`/plumbbob:*`) and the
|
|
5
|
+
// post-edit hook auto-registers from hooks/hooks.json. Global-only by design:
|
|
6
|
+
// plumbbob is a personal tool (like firecrawl/gh), and install scope is NOT
|
|
7
|
+
// session scope — sessions stay per-project via `plumbbob start`. Idempotent +
|
|
8
|
+
// reversible (`--uninstall` drops the link); it NEVER writes settings.json.
|
|
9
|
+
// Functional, node builtins only (C1/C2).
|
|
10
|
+
import { lstatSync, mkdirSync, readlinkSync, rmSync, symlinkSync } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
// The installed package root (parent of .claude-plugin/, skills/, hooks/, dist/),
|
|
15
|
+
// off this module's URL — the global install when run as the published bin, the
|
|
16
|
+
// checkout in dev. No trailing slash, so it compares clean against a readlink.
|
|
17
|
+
function packageRoot() {
|
|
18
|
+
return fileURLToPath(new URL('../../', import.meta.url)).replace(/\/+$/, '');
|
|
19
|
+
}
|
|
20
|
+
function linkPath(home) {
|
|
21
|
+
return join(home, '.claude', 'skills', 'plumbbob');
|
|
22
|
+
}
|
|
23
|
+
// lstat-based existence: true even for a broken symlink (existsSync follows the
|
|
24
|
+
// link and would miss one whose target moved).
|
|
25
|
+
function present(path) {
|
|
26
|
+
try {
|
|
27
|
+
lstatSync(path);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// The symlink's target (no trailing slash), or null if `path` is not a symlink.
|
|
35
|
+
function symlinkTarget(path) {
|
|
36
|
+
try {
|
|
37
|
+
return lstatSync(path).isSymbolicLink() ? readlinkSync(path).replace(/\/+$/, '') : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function init(args) {
|
|
44
|
+
const home = process.env.HOME ?? homedir();
|
|
45
|
+
const link = linkPath(home);
|
|
46
|
+
const target = packageRoot();
|
|
47
|
+
if (args.includes('--uninstall')) {
|
|
48
|
+
return uninstall(link);
|
|
49
|
+
}
|
|
50
|
+
mkdirSync(join(home, '.claude', 'skills'), { recursive: true });
|
|
51
|
+
if (present(link)) {
|
|
52
|
+
const current = symlinkTarget(link);
|
|
53
|
+
if (current === null) {
|
|
54
|
+
process.stderr.write(`plumbbob: ${link} already exists and is not a plumbbob link. Move or remove it, then re-run \`plumbbob init\`.\n`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
if (current === target) {
|
|
58
|
+
process.stdout.write(`plumbbob: already linked (${link} → ${target}). Nothing to do.\n`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
rmSync(link); // a stale link from an earlier install location — repoint it
|
|
62
|
+
}
|
|
63
|
+
symlinkSync(target, link);
|
|
64
|
+
process.stdout.write(`plumbbob: linked ${link} → ${target}.\n` +
|
|
65
|
+
'plumbbob: Claude Code loads it as a plugin — skills as `/plumbbob:*`, the post-edit hook auto-registered from hooks.json. Restart Claude Code (or /reload-plugins) to activate.\n' +
|
|
66
|
+
'plumbbob: nothing else under ~ is touched and settings.json is left alone. Sessions are per-project — run `plumbbob start "<goal>"` in any repo, then `plumbbob doctor` to verify.\n');
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
function uninstall(link) {
|
|
70
|
+
if (!present(link)) {
|
|
71
|
+
process.stdout.write(`plumbbob: nothing to uninstall — no link at ${link}.\n`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (symlinkTarget(link) === null) {
|
|
75
|
+
process.stderr.write(`plumbbob: ${link} is not a plumbbob link (a real directory/file) — leaving it untouched.\n`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
rmSync(link);
|
|
79
|
+
process.stdout.write(`plumbbob: unlinked ${link}. Restart Claude Code to drop the plugin.\n`);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// `plumbbob
|
|
2
|
-
//
|
|
3
|
-
// (the `/
|
|
1
|
+
// `plumbbob wrap` (D9) — the v2 close-out, replacing the v1 four-verb
|
|
2
|
+
// finish ceremony. It archives intent + build-log + report
|
|
3
|
+
// (the `/plumbbob:wrap` skill writes the report by default) under .plumbbob/archive/,
|
|
4
4
|
// clears the active files, and deletes the control state (STATE last). Unlike v1
|
|
5
5
|
// `finish` there is NO refuse-without-report gate — guidance offers the artifact, it
|
|
6
6
|
// does not wall the exit. Archive-then-clear, never destroy (C4); git untouched (C5).
|
|
@@ -9,7 +9,7 @@ import { join, relative } from 'node:path';
|
|
|
9
9
|
import { findRepoRoot } from "../lib/git.js";
|
|
10
10
|
import { buildLogPath, checkpointsPath, hasSession, intentPath, seamPath, sidecarDir, stepPath } from "../lib/sidecar.js";
|
|
11
11
|
import { archiveSession, reportPath } from "../lib/archive.js";
|
|
12
|
-
export function
|
|
12
|
+
export function wrap(cwd) {
|
|
13
13
|
const root = findRepoRoot(cwd);
|
|
14
14
|
if (root === null || !hasSession(root)) {
|
|
15
15
|
process.stderr.write('plumbbob: no active session. Run `plumbbob start "<title>"` first.\n');
|
|
@@ -20,7 +20,7 @@ export function reset(cwd) {
|
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
process.stderr.write('plumbbob: note — no report.md found; archiving intent + build-log without one ' +
|
|
23
|
-
'(/
|
|
23
|
+
'(/plumbbob:wrap normally writes the report first). No gate (D9).\n');
|
|
24
24
|
}
|
|
25
25
|
const archived = archiveSession(root);
|
|
26
26
|
// Clear the active files — now safely archived — then the control state, STATE
|
|
@@ -31,8 +31,8 @@ export function reset(cwd) {
|
|
|
31
31
|
rmSync(seamPath(root), { force: true });
|
|
32
32
|
rmSync(stepPath(root), { force: true });
|
|
33
33
|
rmSync(join(sidecarDir(root), 'STATE'), { force: true });
|
|
34
|
-
process.stdout.write(`plumbbob:
|
|
35
|
-
'Run `/
|
|
34
|
+
process.stdout.write(`plumbbob: wrap — archived to ${relative(root, archived)}. Sidecar cleared. ` +
|
|
35
|
+
'Run `/plumbbob:plan` (or `plumbbob start "<title>"`) to frame the next goal.\n');
|
|
36
36
|
return 0;
|
|
37
37
|
}
|
|
38
38
|
// Append the recorded checkpoints (baseline + each `step n <sha>`) to the report so
|
package/hooks/hooks.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Attention-first build process:
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Attention-first build process: 11 skills + a CLI that keep you the decider — guidance, not enforcement.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
7
7
|
"claude",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"pb": "dist/cli.js"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
+
".claude-plugin",
|
|
27
28
|
"dist",
|
|
28
29
|
"hooks",
|
|
29
30
|
"skills",
|
|
@@ -57,6 +58,6 @@
|
|
|
57
58
|
"check:astgrep": "ast-grep scan",
|
|
58
59
|
"check:test": "vitest run",
|
|
59
60
|
"check:knip": "knip",
|
|
60
|
-
"check:md": "markdownlint --config .markdownlint.jsonc \"**/*.md\" --ignore node_modules"
|
|
61
|
+
"check:md": "markdownlint --config .markdownlint.jsonc \"**/*.md\" --ignore node_modules --ignore research"
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build
|
|
3
|
+
description: The optional engine — read the next planned step from intent, implement it (its done-when, seam, Decisions, Constraints), then verify it through to the approval pause. Skip it to build by hand/vibed/another harness. `--auto` self-approves and chains to done.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
model: opus
|
|
6
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob build:*), Bash(plumbbob check:*), Bash(plumbbob 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 status 2>/dev/null || echo "plumbbob CLI not found - install the dep and re-run: npm i -g plumbbob && plumbbob init"`
|
|
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 `/plumbbob: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
|
+
Since `/plumbbob:plan` lays down the whole step list up front, the happy path is to fire
|
|
20
|
+
`/plumbbob:build` once per step until done — each run builds the next undone step and stops
|
|
21
|
+
at the pause for your approval. **Re-firing `/plumbbob:build` is itself the clock tick.**
|
|
22
|
+
|
|
23
|
+
## What this skill does, in order
|
|
24
|
+
|
|
25
|
+
1. **Pick the step.** Use the number you were invoked with (e.g. `/plumbbob:build 4`), else
|
|
26
|
+
the next undone, planned step in `.plumbbob/intent.md`. If there is no planned step
|
|
27
|
+
to build, stop and tell the human to `/plumbbob:step` first.
|
|
28
|
+
2. **Enter the step.** Run `plumbbob build <n>` (records the in-flight STEP +
|
|
29
|
+
SEAM so `/plumbbob:status` shows the step in flight; in v2 the seam is awareness, not a
|
|
30
|
+
lock).
|
|
31
|
+
3. **Read the plan.** Read the step's **done-when**, its **seam**, and the
|
|
32
|
+
**Decisions** and **Constraints** in `intent.md`. Build to *that* — the deciding
|
|
33
|
+
already happened, off the chat.
|
|
34
|
+
4. **Implement** the step, and only that step, staying within the declared seam. A
|
|
35
|
+
new problem or "ooh what if" that surfaces mid-build is a `/plumbbob:park`, **not** an
|
|
36
|
+
edit — capture it and stay on the step. If you genuinely cannot finish without
|
|
37
|
+
touching more than the seam, that is scope drift: surface it to the human rather
|
|
38
|
+
than sprawling.
|
|
39
|
+
5. **Verify, through to the pause.** Run the verify tick exactly as `/plumbbob:verify`
|
|
40
|
+
does: `plumbbob check` → self-review the diff against the done-when, the
|
|
41
|
+
Decisions, and the Constraints (a single structured read, D16) → validate → **PAUSE
|
|
42
|
+
for the human's approval** → only on approval, checkpoint with
|
|
43
|
+
`plumbbob checkpoint`. Do **not** bump the version or changelog — that is
|
|
44
|
+
the human's `/version` call.
|
|
45
|
+
|
|
46
|
+
## `--auto` — let the agent be the clock (opt-in)
|
|
47
|
+
|
|
48
|
+
`/plumbbob:build --auto` is the explicit escape hatch when the human wants unattended
|
|
49
|
+
progress instead of approving each step. It does the same work, but **the agent reviews
|
|
50
|
+
and approves in the human's place**, and it **chains**:
|
|
51
|
+
|
|
52
|
+
- Build the next step → `check` → self-review → **if the check is green AND the
|
|
53
|
+
self-review finds no done-when / Decision / Constraint mismatch, checkpoint** and move
|
|
54
|
+
straight on to the next planned step. Repeat.
|
|
55
|
+
- **Stop and hand back to the human** the moment any of these is true: the check is red,
|
|
56
|
+
the self-review finds a mismatch (surface exactly what, and do not checkpoint it), a
|
|
57
|
+
new decision is needed, or no planned steps remain.
|
|
58
|
+
|
|
59
|
+
`--auto` is the only path that checkpoints without a human pause, and only because the
|
|
60
|
+
human asked for it by name. The default — no flag — always ends at the pause.
|
|
61
|
+
|
|
62
|
+
## The hard contracts
|
|
63
|
+
|
|
64
|
+
- **Optional, never required.** The loop works without this skill; `/plumbbob:verify`
|
|
65
|
+
checkpoints a hand-built or vibed diff just the same (D3).
|
|
66
|
+
- **Build the decided step, not a new one.** Implement what `intent.md` settled. A
|
|
67
|
+
new idea mid-build is a `/plumbbob:park`, not an edit.
|
|
68
|
+
- **Default ends at the pause.** Implement → verify → wait for approval; never
|
|
69
|
+
checkpoint without it. Only an explicit `--auto` lets the agent approve in your place,
|
|
70
|
+
and it still halts on a red check or any mismatch.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: harvest
|
|
3
3
|
description: Triage the park list at a step boundary — propose one class (blocker/tangent/pivot) per parked item, write only after the human confirms each, record under ## Harvest, and fold a confirmed blocker into intent.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
model: opus
|
|
6
|
-
allowed-tools: Read, Edit, Bash(
|
|
6
|
+
allowed-tools: Read, Edit, Bash(plumbbob status:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Plumbbob — harvest the park list
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`
|
|
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
|
+
`/plumbbob:harvest` is the complement of `/plumbbob:park` (D7): you parked ideas as seeds during a
|
|
14
14
|
build; now, at a boundary, you harvest them — decide what each one is.
|
|
15
15
|
|
|
16
16
|
## When to run it — wrong-state refusal
|
|
@@ -19,7 +19,7 @@ Harvest at a **step boundary**: after a step is checkpointed and you are back in
|
|
|
19
19
|
DESIGN, not mid-step. Read the state injected above:
|
|
20
20
|
|
|
21
21
|
- `NO ACTIVE SESSION` — **refuse**; tell the human to `plumbbob start "<title>"` first.
|
|
22
|
-
- `BUILD` — a step is in flight; **refuse** and suggest finishing it with `/
|
|
22
|
+
- `BUILD` — a step is in flight; **refuse** and suggest finishing it with `/plumbbob:verify`
|
|
23
23
|
before harvesting. Chasing parked items mid-step is the disease parking prevents.
|
|
24
24
|
- `DESIGN` (and other settled states) — go ahead.
|
|
25
25
|
|
|
@@ -40,7 +40,7 @@ one line of reasoning, then **wait for the human to confirm or override**. Write
|
|
|
40
40
|
**only after** per-item confirmation:
|
|
41
41
|
|
|
42
42
|
- Record each confirmed class in `build-log.md`'s `## Harvest` section.
|
|
43
|
-
- **Flip the harvested item** from `- [ ]` to `- [x]` in the Park list, so `/
|
|
43
|
+
- **Flip the harvested item** from `- [ ]` to `- [x]` in the Park list, so `/plumbbob:status`
|
|
44
44
|
stops counting it as open.
|
|
45
45
|
- A confirmed **blocker** also folds its decision into `intent.md`.
|
|
46
46
|
- Never reclassify or resolve an item the human hasn't confirmed, and default every
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: park
|
|
3
3
|
description: Compose one tidy tagged park line, get the human's OK in-turn, then capture it by shelling `plumbbob park` — never by editing a file. The capture half of the park/harvest loop.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
model: haiku
|
|
6
|
-
allowed-tools: Bash(
|
|
6
|
+
allowed-tools: Bash(plumbbob status:*), Bash(plumbbob park:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Plumbbob — park an idea (capture, don't chase)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`
|
|
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
|
+
`/plumbbob:park` is the **capture** half of the loop; `/plumbbob:harvest` is where parked items
|
|
14
14
|
get triaged later (D7). Capturing the instant an idea arrives — instead of acting on
|
|
15
15
|
it — is the whole point: it protects the step in flight.
|
|
16
16
|
|
|
@@ -29,12 +29,12 @@ later. Then:
|
|
|
29
29
|
|
|
30
30
|
1. **Show the composed line to the human** and wait for **in-turn approval** — they
|
|
31
31
|
confirm it as-is or edit the wording.
|
|
32
|
-
2. **Only after** that approval, capture it by running `
|
|
32
|
+
2. **Only after** that approval, capture it by running `plumbbob park "<the
|
|
33
33
|
approved line>"` via Bash.
|
|
34
34
|
|
|
35
35
|
## The one hard contract
|
|
36
36
|
|
|
37
37
|
The capture itself is the **dumb CLI**, never an edit. This skill carries **no Edit and
|
|
38
38
|
no Write tool** on purpose: you may not append to `build-log.md` yourself. Compose, get
|
|
39
|
-
approval, then shell `
|
|
39
|
+
approval, then shell `plumbbob park` — that is the only write path. If approval
|
|
40
40
|
never comes, capture nothing.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: "Frame a fresh goal and author the whole plan — Frame, Decisions, Constraints, and all Steps — before any code. Three input modes: no arg interviews you; a file path absorbs a spec; any other text expands your inline intent."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
model: opus
|
|
6
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob start:*)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Plumbbob — plan a goal (the whole-goal move)
|
|
10
|
+
|
|
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
|
+
|
|
13
|
+
`/plumbbob: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. By default it authors the
|
|
15
|
+
**complete plan, including all the Steps**, so the happy path afterward is just
|
|
16
|
+
`/plumbbob:build` until done. (Revising a single increment later is the separate `/plumbbob:step`
|
|
17
|
+
move; do not confuse the two.)
|
|
18
|
+
|
|
19
|
+
## Three input modes (disambiguated for you — no quotes needed)
|
|
20
|
+
|
|
21
|
+
Look at the argument the human gave and pick the mode yourself:
|
|
22
|
+
|
|
23
|
+
1. **No argument → interview.** Walk the human through a short, friendly Q&A to draw
|
|
24
|
+
the plan out of their head (see *The interview* below).
|
|
25
|
+
2. **The argument is a path to a file that exists → absorb the spec.** Read that file
|
|
26
|
+
and distill it into `intent.md`, **retaining enough detail that `intent.md` stands
|
|
27
|
+
on its own** — don't just link to the source. Add a one-line provenance
|
|
28
|
+
(`*Source: <path>*`) and, for anything sizable, a `## Source` appendix preserving
|
|
29
|
+
the original text. (Probe with the `Read` tool; if it isn't a real file, fall to
|
|
30
|
+
mode 3.)
|
|
31
|
+
3. **Any other text → expand the inline intent.** Treat the text as the human's
|
|
32
|
+
rough plan, expand it into the full `intent.md`, and ask only about what is
|
|
33
|
+
genuinely ambiguous.
|
|
34
|
+
|
|
35
|
+
All three modes converge on the **same artifact**: a complete, standalone `intent.md`
|
|
36
|
+
an agent can follow with `/plumbbob:build`. The argument only seeds how you get there.
|
|
37
|
+
|
|
38
|
+
## What this skill does
|
|
39
|
+
|
|
40
|
+
1. **Scaffold.** If there is no active session, run `plumbbob start "<title>"`
|
|
41
|
+
to create `.plumbbob/` (STATE=DESIGN, baseline recorded). If a session already
|
|
42
|
+
exists, say so and edit the existing `intent.md` rather than starting over.
|
|
43
|
+
2. **Frame** (`.plumbbob/intent.md`), with the human: the **Problem** in plain words,
|
|
44
|
+
the **smallest thing** that solves it, what **done looks like**, and what you are
|
|
45
|
+
**explicitly NOT doing**. This is the human's convergence — propose wording, but
|
|
46
|
+
the human decides every line.
|
|
47
|
+
3. **Decisions & Constraints.** Record the settled calls (one line each, with the
|
|
48
|
+
*because*) and the hard rules the build must honor. An unresolved hole goes to
|
|
49
|
+
**Open questions**, never guessed into a Decision.
|
|
50
|
+
4. **Author the Steps.** Write the **full build plan** under `## Steps` — each step a
|
|
51
|
+
small, verifiable increment in the exact format the parser reads:
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
1. [ ] <title> — **done when:** <criterion, ideally a test or check result>
|
|
55
|
+
- seam: `<file>`, `<file>`
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every step needs a **done-when** `/plumbbob:verify` can check and a **seam** (the exact
|
|
59
|
+
paths it touches). Later steps may be fuzzier than the first — that's fine; they get
|
|
60
|
+
sharpened just-in-time when you reach them with `/plumbbob:step`. Keep each small enough to
|
|
61
|
+
verify in one review pass.
|
|
62
|
+
5. **Offer to stress-test it.** Suggest `/plumbbob:refine` to attack the frame for holes (or
|
|
63
|
+
to repair the plan as it drifts). Optional, the human's call.
|
|
64
|
+
|
|
65
|
+
## The interview (mode 1)
|
|
66
|
+
|
|
67
|
+
Make it easy and non-intrusive:
|
|
68
|
+
|
|
69
|
+
- **Triage size first.** A tiny change earns a 3-line Frame and one Step, fast — never
|
|
70
|
+
ceremony on a one-liner. Scale the questions to the work.
|
|
71
|
+
- **Propose, don't interrogate.** Offer concrete suggestions the human can **accept as-is
|
|
72
|
+
without typing** ("done-when: the 6th request in 60s returns 429 — good?"), while
|
|
73
|
+
taking arbitrary detail when they want to give it, including pointers to other files.
|
|
74
|
+
- **Let them double back.** They will revise as the picture sharpens; that's expected.
|
|
75
|
+
They can also edit `intent.md` by hand at any time, or call `/plumbbob:refine` to repair it.
|
|
76
|
+
|
|
77
|
+
## The hard contracts
|
|
78
|
+
|
|
79
|
+
- **Deciding before code.** `/plumbbob:plan` writes `intent.md` only — never source.
|
|
80
|
+
- **The human converges.** You surface options and draft wording; the human picks.
|
|
81
|
+
An unresolved hole is an Open question, not a guessed Decision.
|
|
82
|
+
- **Stands on its own.** Whatever the input mode, the finished `intent.md` carries
|
|
83
|
+
enough detail to be followed without the chat or the external source.
|
|
84
|
+
- **Size to the work.** A small change fills Frame + a couple of Decisions + a step or
|
|
85
|
+
two and stops; ceremony on a one-liner is the failure mode, not thoroughness.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refine
|
|
3
|
+
description: Keep intent.md true — attack the plan for holes (append as Open questions) and refine or repair the Frame, Decisions, Constraints, and Steps to match reality. Usable at any point; you propose, the human approves.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
model: opus
|
|
6
|
+
allowed-tools: Read, Edit, Bash(plumbbob status:*)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Plumbbob — refine the plan
|
|
10
|
+
|
|
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
|
+
|
|
13
|
+
`/plumbbob:refine` keeps `intent.md` honest. Use it at **any point** — right after `/plumbbob:plan`
|
|
14
|
+
to stress-test a fresh frame, or mid-build to repair a plan that drifted from what the
|
|
15
|
+
code is actually doing. It is the document-level complement to `/plumbbob:step` (which only
|
|
16
|
+
sharpens the *next* step): `/plumbbob:refine` works the *whole* plan.
|
|
17
|
+
|
|
18
|
+
## No-session refusal
|
|
19
|
+
|
|
20
|
+
This skill refines an existing plan, so it needs one. Read the state injected above: if
|
|
21
|
+
it is `NO ACTIVE SESSION`, **refuse** in one line and tell the human to run
|
|
22
|
+
`plumbbob start "<title>"` (or `/plumbbob:plan`) first, and edit nothing. Every active state
|
|
23
|
+
is fine — refining is always available.
|
|
24
|
+
|
|
25
|
+
## Two modes
|
|
26
|
+
|
|
27
|
+
- **Attack (diverge in the problem space).** Hand the **Frame**, **Decisions**, and
|
|
28
|
+
**Architecture sketch** a cold, adversarial read and surface holes — ambiguities,
|
|
29
|
+
unhandled edge cases, hidden assumptions, collisions with the existing code. Append
|
|
30
|
+
each as a one-line question under `## Open questions`, **never** as a Decision:
|
|
31
|
+
resolving a hole is the human's convergence, not yours. This mode surfaces; it does
|
|
32
|
+
not decide.
|
|
33
|
+
- **Repair (re-sync to reality).** When the plan has drifted — a Decision was overtaken
|
|
34
|
+
by what you built, a Constraint changed, a Step no longer matches the seam — propose
|
|
35
|
+
the edits that bring `intent.md` back in line with the truth. Show the before/after for
|
|
36
|
+
each, and **write only what the human approves**.
|
|
37
|
+
|
|
38
|
+
## The hard contracts
|
|
39
|
+
|
|
40
|
+
- **You propose; the human converges.** Surface holes and draft repairs, but the human
|
|
41
|
+
approves every change to `intent.md`. Never guess a hole into a Decision.
|
|
42
|
+
- **Open questions for holes, edits for drift.** New uncertainty goes to
|
|
43
|
+
`## Open questions`; settled drift gets repaired in place once the human OKs it.
|
|
44
|
+
- **Refine the plan, not the code.** `/plumbbob:refine` touches `intent.md` only — turning a
|
|
45
|
+
decision into a diff is `/plumbbob:build` or your own hands, never this skill.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: revert
|
|
3
3
|
description: Human-triggered driver for `plumbbob revert` — git reset --hard to a checkpoint SHA (discarding the half-done step) and return to DESIGN.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
model: haiku
|
|
6
|
-
allowed-tools: Bash(
|
|
6
|
+
allowed-tools: Bash(plumbbob status:*), Bash(plumbbob revert:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Plumbbob — revert to a checkpoint (driver)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`
|
|
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 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
|
|
|
15
15
|
## What it does
|
|
16
16
|
|
|
17
|
-
1. Read an optional target step from the way you were invoked (e.g. `/
|
|
18
|
-
2. Run `
|
|
17
|
+
1. Read an optional target step from the way you were invoked (e.g. `/plumbbob:revert --to 2` → step `2`). With no target, revert goes to the last done-checkpoint.
|
|
18
|
+
2. Run `plumbbob revert` (or `plumbbob revert --to <n>`) via Bash. This is a `git reset --hard` — it discards the current in-progress step. Run it exactly as the human asked; do not add or drop the `--to` on your own.
|
|
19
19
|
3. Report the verb's output verbatim — which checkpoint it reset to, or any refusal.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: spike
|
|
3
3
|
description: Human-triggered driver for `plumbbob spike` — open a throwaway worktree experiment for a genuine fork, or tear it down with `spike done`.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
model: haiku
|
|
6
|
-
allowed-tools: Bash(
|
|
6
|
+
allowed-tools: Bash(plumbbob status:*), Bash(plumbbob spike:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Plumbbob — spike an experiment (driver)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`
|
|
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 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
|
|
|
15
15
|
## What it does
|
|
16
16
|
|
|
17
|
-
1. Read the spike target from the way you were invoked: a slug to open one (e.g. `/
|
|
18
|
-
2. Run `
|
|
17
|
+
1. Read the spike target from the way you were invoked: a slug to open one (e.g. `/plumbbob:spike redis-cache`), or the literal `done` to tear the current spike down (`/plumbbob:spike done`). If neither is present, ask which and run nothing.
|
|
18
|
+
2. Run `plumbbob spike "<slug>"` or `plumbbob spike done` via Bash.
|
|
19
19
|
3. Report the verb's output verbatim — the worktree it created or removed, or any refusal.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: status
|
|
3
3
|
description: Show the orientation dashboard — where you are, what's done, what's parked, and the next move. A thin trigger for `plumbbob status`.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
model: haiku
|
|
6
|
-
allowed-tools: Bash(
|
|
6
|
+
allowed-tools: Bash(plumbbob status:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Plumbbob — orient (the where-am-I move)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`
|
|
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
|
-
This is a **thin driver** for `
|
|
13
|
+
This is a **thin driver** for `plumbbob status`. The dashboard above is your
|
|
14
14
|
orientation — the intent, the step list, the parked/open-question counts, and the
|
|
15
15
|
inferred next move. Report it verbatim and point the human at that next move. This
|
|
16
16
|
skill carries **no Edit and no Write tool**: the CLI is the source of truth, so
|
|
@@ -19,4 +19,4 @@ skill carries **no Edit and no Write tool**: the CLI is the source of truth, so
|
|
|
19
19
|
## What it does
|
|
20
20
|
|
|
21
21
|
1. Surface the injected `status` output — the dashboard and its suggested next move.
|
|
22
|
-
2. If it reads `NO ACTIVE SESSION`, tell the human to `/
|
|
22
|
+
2. If it reads `NO ACTIVE SESSION`, tell the human to `/plumbbob:plan` to frame a goal.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: step
|
|
3
|
+
description: Revise the next increment just-in-time — sharpen the next undone step against what's now true, or (with input) re-cut, split, or add a step. Empty input runs an automatic sharpen. One at a time; the human approves.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
model: opus
|
|
6
|
+
allowed-tools: Read, Edit, Bash(plumbbob status:*)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Plumbbob — revise the next step (the single-increment move)
|
|
10
|
+
|
|
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
|
+
|
|
13
|
+
`/plumbbob:plan` authors the **whole** step list up front, so `/plumbbob:step` is the
|
|
14
|
+
**just-in-time revision** move: it keeps the *next* undone step honest against what the
|
|
15
|
+
build has actually taught you, right before you `/plumbbob:build` it. (Framing the whole goal
|
|
16
|
+
is the separate `/plumbbob:plan` move.) It can also add or re-cut a step when scope genuinely
|
|
17
|
+
grew — but its everyday job is to sharpen, not to invent.
|
|
18
|
+
|
|
19
|
+
## Two ways to fire it
|
|
20
|
+
|
|
21
|
+
- **`/plumbbob:step` (no input) → automatic sharpen.** Re-examine the next undone step
|
|
22
|
+
against the completed code, the Decisions, the Constraints, and the build-log, then
|
|
23
|
+
make the obvious revisions to its **done-when** and **seam** so it matches reality —
|
|
24
|
+
e.g. a file moved, a decision narrowed the scope, an earlier step already did part of
|
|
25
|
+
it. This is the zero-effort "keep my next step in sync" move: if the human does
|
|
26
|
+
nothing else, the next step stays current.
|
|
27
|
+
- **`/plumbbob:step <what changed>` → directed revision.** Take the human's input and propose
|
|
28
|
+
the matching change: tighten the done-when, adjust the seam, split the step in two, or
|
|
29
|
+
add a new increment the plan was missing.
|
|
30
|
+
|
|
31
|
+
## What this skill does
|
|
32
|
+
|
|
33
|
+
1. **Read the plan and the reality.** Read `intent.md`'s Frame, Decisions, Constraints,
|
|
34
|
+
and the steps already done, plus the next undone step, to see what it *should* now be.
|
|
35
|
+
2. **Propose the revision** (or the new/split step): a one-line **title**, a **done-when**
|
|
36
|
+
`/plumbbob:verify` can validate, and a **seam** (exact paths, or a `dir/` grant). Keep it
|
|
37
|
+
small enough to verify in one review pass. Show the before/after so the human can see
|
|
38
|
+
what you changed and why.
|
|
39
|
+
3. **Get the human's OK**, then write it into `## Steps` in the standard format —
|
|
40
|
+
`N. [ ] <title> — **done when:** <criterion>` with a `- seam:` sub-line. Revise the
|
|
41
|
+
existing step in place; only append when you are genuinely adding an increment.
|
|
42
|
+
|
|
43
|
+
## The hard contracts
|
|
44
|
+
|
|
45
|
+
- **One verifiable increment.** Each step carries a done-when `/plumbbob:verify` can check
|
|
46
|
+
and a seam small enough to review in one pass.
|
|
47
|
+
- **Edit `## Steps` only**, in the standard format `status` and `build` parse — never
|
|
48
|
+
the Roadmap, never loose prose. A done step (`[x]`) is history; do not rewrite it.
|
|
49
|
+
- **The human approves the revision** before it lands. You propose; they decide.
|