plumbbob 0.10.1 → 0.10.3
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 +1 -1
- package/README.md +4 -4
- package/dist/cli-core.js +9 -2
- package/dist/lib/buildlog.js +17 -1
- package/dist/lib/orient.js +74 -14
- package/dist/verbs/build.js +12 -3
- package/dist/verbs/finish.js +1 -1
- package/dist/verbs/status.js +24 -3
- package/package.json +2 -1
- package/skills/build/SKILL.md +8 -1
- package/templates/build-log.md +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
3
|
"displayName": "PlumbBob",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.3",
|
|
5
5
|
"description": "Attention-first build process — driver skills + a CLI that keep you the decider: guidance on the work, a latch on the record.",
|
|
6
6
|
"author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
|
|
7
7
|
"homepage": "https://github.com/robmclarty/plumbbob#readme",
|
package/README.md
CHANGED
|
@@ -152,7 +152,7 @@ a Claude Code built-in, so these docs write the full form.)
|
|
|
152
152
|
nothing else:
|
|
153
153
|
|
|
154
154
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
155
|
-
|
|
155
|
+
| ------------------------------------ | ------ |
|
|
156
156
|
| `/plumbbob:plan` | plan the whole goal: open the session and author intent's Frame, Decisions, Constraints, **and all Steps** |
|
|
157
157
|
| `/plumbbob:build` | implement the next planned step, then verify it to the pause: `--auto` self-approves and chains to done; a range like `1-3` self-approves through step 3, then pauses |
|
|
158
158
|
| `/plumbbob:finish` | finish up: write the report, make the final commit, clear for a fresh goal |
|
|
@@ -160,7 +160,7 @@ nothing else:
|
|
|
160
160
|
**Plan-shaping moves**: optional, for when the plan needs work mid-flight:
|
|
161
161
|
|
|
162
162
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
163
|
-
|
|
163
|
+
| ------------------------------------ | ------ |
|
|
164
164
|
| `/plumbbob:step` | revise/sharpen the next step (empty input auto-syncs it to reality) |
|
|
165
165
|
| `/plumbbob:refine` | attack the frame for holes, or repair a drifted plan |
|
|
166
166
|
| `/plumbbob:spike` | throwaway worktree experiment for a fork the plan can't settle |
|
|
@@ -168,7 +168,7 @@ nothing else:
|
|
|
168
168
|
**Helpers**: orient, verify, recover, diagnose:
|
|
169
169
|
|
|
170
170
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
171
|
-
|
|
171
|
+
| ------------------------------------ | ------ |
|
|
172
172
|
| `/plumbbob:status` | orient: where you are, the next step's done-when and seam, and the next move |
|
|
173
173
|
| `/plumbbob:verify` | the tick, standalone: check → self-review → validate → **PAUSE** → checkpoint, for a diff `/plumbbob:build` didn't write |
|
|
174
174
|
| `/plumbbob:revert` | rewind: `git reset --hard` to a recorded checkpoint |
|
|
@@ -179,7 +179,7 @@ nothing else:
|
|
|
179
179
|
**Capture**: the park/harvest loop for mid-build ideas:
|
|
180
180
|
|
|
181
181
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
182
|
-
|
|
182
|
+
| ------------------------------------ | ------ |
|
|
183
183
|
| `/plumbbob:park` | capture a mid-build idea without chasing it |
|
|
184
184
|
| `/plumbbob:harvest` | triage parked ideas between steps (blocker / tangent / pivot) |
|
|
185
185
|
|
package/dist/cli-core.js
CHANGED
|
@@ -58,8 +58,15 @@ const VERBS = [
|
|
|
58
58
|
{
|
|
59
59
|
name: 'status',
|
|
60
60
|
description: 'print the orientation dashboard, or NO ACTIVE SESSION',
|
|
61
|
-
synopsis: ['status [--build <slug>]'],
|
|
62
|
-
flags: [
|
|
61
|
+
synopsis: ['status [--build <slug>] [--invoked "<args>"]'],
|
|
62
|
+
flags: [
|
|
63
|
+
BUILD_FLAG,
|
|
64
|
+
{
|
|
65
|
+
name: '--invoked',
|
|
66
|
+
value: '"<args>"',
|
|
67
|
+
gloss: 'the raw skill invocation; an explicit step number in it repoints the dashboard at that step',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
63
70
|
notes: 'Read-only; always exits 0. With no cursor it lists the available builds.',
|
|
64
71
|
},
|
|
65
72
|
{
|
package/dist/lib/buildlog.js
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* Append `line` after the last non-blank line of the `## <heading>` section:
|
|
11
11
|
* that is, just before the next `## ` heading or EOF.
|
|
12
12
|
*
|
|
13
|
+
* When `line` is a list item and the line above it is prose (the Park list's
|
|
14
|
+
* guidance blockquote, the Log's instructions paragraph), a blank line goes in
|
|
15
|
+
* first. Markdown wants lists separated from prose (MD032), and a consumer
|
|
16
|
+
* repo that lints `.plumbbob/**` turns the missing blank into a red gate at
|
|
17
|
+
* the first park or checkpoint of a build.
|
|
18
|
+
*
|
|
13
19
|
* Returns null when the section is absent so the caller can report rather than
|
|
14
20
|
* silently corrupt the doc.
|
|
15
21
|
*/
|
|
@@ -29,9 +35,19 @@ export function appendToSection(content, heading, line) {
|
|
|
29
35
|
insertAt = i + 1;
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
|
-
lines
|
|
38
|
+
const prev = lines[insertAt - 1] ?? '';
|
|
39
|
+
const needsBlank = isListLine(line) && prev.trim() !== '' && !isListLine(prev);
|
|
40
|
+
lines.splice(insertAt, 0, ...(needsBlank ? ['', line] : [line]));
|
|
33
41
|
return lines.join('\n');
|
|
34
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A markdown list line: `-`, `*`, or `+` after optional indent. Every caller
|
|
45
|
+
* of appendToSection appends one of these, so the blank-line rule above keys
|
|
46
|
+
* on the line it lands after, not on which verb is writing.
|
|
47
|
+
*/
|
|
48
|
+
function isListLine(line) {
|
|
49
|
+
return /^\s*[-*+]\s/.test(line);
|
|
50
|
+
}
|
|
35
51
|
/**
|
|
36
52
|
* Replace the `**Current step:**` line with `**Current step:** <label>`:
|
|
37
53
|
* `build` sets it to the step's number and title joined by an em dash when a
|
package/dist/lib/orient.js
CHANGED
|
@@ -159,21 +159,70 @@ export function lastLedgerSha(checkpoints) {
|
|
|
159
159
|
}
|
|
160
160
|
return last;
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* The step number an explicit skill invocation asks for, out of the raw
|
|
164
|
+
* argument text `status --invoked` receives.
|
|
165
|
+
*
|
|
166
|
+
* The first whitespace-separated token shaped like `22` or `22-24` names the
|
|
167
|
+
* step (a range asks to start at its first number); flags such as `--auto` and
|
|
168
|
+
* anything else parse to null, meaning "no explicit ask", which leaves the
|
|
169
|
+
* dashboard's own suggestion in charge. Null in means null out, so a host that
|
|
170
|
+
* never substitutes the invocation degrades to today's rendering.
|
|
171
|
+
*/
|
|
172
|
+
export function parseRequestedStep(raw) {
|
|
173
|
+
if (raw === null) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
for (const token of raw.trim().split(/\s+/)) {
|
|
177
|
+
const m = /^(\d+)(?:-\d+)?$/.exec(token);
|
|
178
|
+
if (m) {
|
|
179
|
+
const n = Number(m[1]);
|
|
180
|
+
return n >= 1 ? n : null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
162
185
|
/**
|
|
163
186
|
* The single primary next move the dashboard suggests.
|
|
164
187
|
*
|
|
165
188
|
* It suggests; the dashboard prints the full list + counts so the human can
|
|
166
|
-
* always override.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
189
|
+
* always override. An explicitly requested step outranks the derived
|
|
190
|
+
* suggestion: the request IS the human's override, so the move names that step
|
|
191
|
+
* (noting anything it skips or collides with) rather than arguing for the
|
|
192
|
+
* default. Otherwise the phase decides: a spike in progress and an in-flight
|
|
193
|
+
* step each have one obvious next move; else you are at the boundary and the
|
|
194
|
+
* move follows from the steps.
|
|
169
195
|
*/
|
|
170
|
-
function nextMove(spiking, steps, inFlight, parked) {
|
|
196
|
+
function nextMove(spiking, steps, inFlight, parked, requested) {
|
|
197
|
+
const target = requested === null ? undefined : steps.find((s) => s.n === requested);
|
|
198
|
+
if (requested !== null && target === undefined) {
|
|
199
|
+
return `step ${requested} is not in the plan (${steps.length} step${steps.length === 1 ? '' : 's'} planned) — report the mismatch rather than guess`;
|
|
200
|
+
}
|
|
171
201
|
if (spiking) {
|
|
172
|
-
|
|
202
|
+
const tail = target === undefined ? '' : `; then build step ${target.n} (explicitly requested)`;
|
|
203
|
+
return `close the spike — \`plumbbob spike done\`${tail}`;
|
|
173
204
|
}
|
|
174
205
|
if (inFlight !== null) {
|
|
206
|
+
if (target !== undefined && target.n !== inFlight) {
|
|
207
|
+
return `build step ${target.n} — explicitly requested (step ${inFlight} is still in flight: \`/plumbbob:verify\` it or \`/plumbbob:abandon\` it first)`;
|
|
208
|
+
}
|
|
175
209
|
return `finish step ${inFlight} — \`/plumbbob:verify\` (or keep editing, then \`/plumbbob:verify\`)`;
|
|
176
210
|
}
|
|
211
|
+
if (target !== undefined) {
|
|
212
|
+
if (target.done) {
|
|
213
|
+
return `build step ${target.n} — explicitly requested (already checkpointed)`;
|
|
214
|
+
}
|
|
215
|
+
const skipped = steps.filter((s) => !s.done && s.n < target.n).length;
|
|
216
|
+
const notes = [];
|
|
217
|
+
if (skipped > 0) {
|
|
218
|
+
notes.push(`skips ${skipped} undone step${skipped === 1 ? '' : 's'}`);
|
|
219
|
+
}
|
|
220
|
+
if (!target.planned) {
|
|
221
|
+
notes.push('still unplanned: `/plumbbob:step` it first');
|
|
222
|
+
}
|
|
223
|
+
const suffix = notes.length > 0 ? ` (${notes.join('; ')})` : '';
|
|
224
|
+
return `build step ${target.n} — explicitly requested${suffix}`;
|
|
225
|
+
}
|
|
177
226
|
// At the boundary (DESIGN): the move follows from the steps.
|
|
178
227
|
const nextUndone = steps.find((s) => !s.done);
|
|
179
228
|
if (nextUndone === undefined) {
|
|
@@ -195,8 +244,13 @@ function nextMove(spiking, steps, inFlight, parked) {
|
|
|
195
244
|
export function orient(input) {
|
|
196
245
|
const steps = parseSteps(input.intent);
|
|
197
246
|
const parked = parseParked(input.buildLog);
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
// The target the dashboard details: the explicitly requested step when it
|
|
248
|
+
// exists in the plan, else the next undone. A requested number that names no
|
|
249
|
+
// planned step carries nothing here; the next-move line reports the mismatch
|
|
250
|
+
// while the rest of the dashboard renders as usual.
|
|
251
|
+
const requestedStep = input.requested === null ? undefined : steps.find((s) => s.n === input.requested);
|
|
252
|
+
const target = requestedStep ?? steps.find((s) => !s.done);
|
|
253
|
+
const seamParse = target === undefined ? null : parseStepSeam(input.intent, target.n);
|
|
200
254
|
const phase = input.spiking ? 'SPIKE' : input.inFlight !== null ? 'BUILD' : 'DESIGN';
|
|
201
255
|
return {
|
|
202
256
|
title: parseTitle(input.intent),
|
|
@@ -205,10 +259,11 @@ export function orient(input) {
|
|
|
205
259
|
lastCheckpoint: parseLastCheckpoint(input.checkpoints),
|
|
206
260
|
parked,
|
|
207
261
|
openQuestions: parseOpenQuestions(input.intent),
|
|
208
|
-
next: nextMove(input.spiking, steps, input.inFlight, parked),
|
|
209
|
-
|
|
262
|
+
next: nextMove(input.spiking, steps, input.inFlight, parked, input.requested),
|
|
263
|
+
requested: requestedStep?.n ?? null,
|
|
264
|
+
nextDoneWhen: target?.doneWhen ?? null,
|
|
210
265
|
nextSeam: seamParse !== null && seamParse.ok ? seamParse.seam : [],
|
|
211
|
-
nextModel:
|
|
266
|
+
nextModel: target?.model ?? null,
|
|
212
267
|
outOfBand: input.outOfBand,
|
|
213
268
|
};
|
|
214
269
|
}
|
|
@@ -218,14 +273,19 @@ export function orient(input) {
|
|
|
218
273
|
export function formatOrientation(o) {
|
|
219
274
|
const doneCount = o.steps.filter((s) => s.done).length;
|
|
220
275
|
const nextUndone = o.steps.find((s) => !s.done);
|
|
276
|
+
// One arrow, always: an explicitly requested step takes the marker and the
|
|
277
|
+
// detail rows, so the injected state never argues with the invocation. With
|
|
278
|
+
// no (valid) request the next undone step keeps them, as ever.
|
|
279
|
+
const requestedStep = o.requested === null ? undefined : o.steps.find((s) => s.n === o.requested);
|
|
280
|
+
const marked = requestedStep ?? nextUndone;
|
|
221
281
|
const stepLines = o.steps.map((s) => {
|
|
222
|
-
const marker = s.done ? '✓' : s ===
|
|
223
|
-
const tail = s ===
|
|
282
|
+
const marker = s.done ? '✓' : s === marked ? '▸' : ' ';
|
|
283
|
+
const tail = s === marked ? (requestedStep === undefined ? ' ← next' : ' ← requested') : '';
|
|
224
284
|
const head = ` ${marker} ${s.n} ${s.title}${tail}`;
|
|
225
|
-
if (s !==
|
|
285
|
+
if (s !== marked) {
|
|
226
286
|
return head;
|
|
227
287
|
}
|
|
228
|
-
// Surface the
|
|
288
|
+
// Surface the target step's detail so the human can review it (and `/plumbbob:step`-
|
|
229
289
|
// revise) before building. Only what's present: a rough step shows neither.
|
|
230
290
|
const detail = [];
|
|
231
291
|
if (o.nextDoneWhen !== null) {
|
package/dist/verbs/build.js
CHANGED
|
@@ -40,13 +40,14 @@ export function build(cwd, args) {
|
|
|
40
40
|
return 1;
|
|
41
41
|
}
|
|
42
42
|
const intent = readFileSync(intentPath(root, slug), 'utf8');
|
|
43
|
+
const steps = parseSteps(intent);
|
|
43
44
|
// No argument ⇒ enter the next undone step in intent.md (the same idiom
|
|
44
45
|
// `checkpoint` uses), so a bare `plumbbob build` advances the loop without the
|
|
45
46
|
// skill re-deriving the step in prose. Every step checkpointed ⇒ a `/plumbbob:step`
|
|
46
47
|
// nudge, not a silent no-op.
|
|
47
48
|
let step;
|
|
48
49
|
if (raw === undefined) {
|
|
49
|
-
const nextUndone =
|
|
50
|
+
const nextUndone = steps.find((s) => !s.done);
|
|
50
51
|
if (nextUndone === undefined) {
|
|
51
52
|
process.stderr.write('plumbbob: no undone step to build — every planned step is checkpointed. `/plumbbob:step` to add an increment, or `/plumbbob:finish`.\n');
|
|
52
53
|
return 1;
|
|
@@ -73,9 +74,17 @@ export function build(cwd, args) {
|
|
|
73
74
|
// The build-log's top half is CLI-owned so it never lies: show this step in
|
|
74
75
|
// flight: Current step plus the ☐/☑ mirror, re-rendered from intent.md.
|
|
75
76
|
// Best-effort: a missing or hand-edited build-log never blocks the build.
|
|
76
|
-
const title =
|
|
77
|
+
const title = steps.find((s) => s.n === step)?.title ?? null;
|
|
77
78
|
syncBuildLogState(root, slug, stepLabel(step, title));
|
|
78
|
-
|
|
79
|
+
// An explicit jump past undone work says so out loud: the entered step and
|
|
80
|
+
// the count it skips read back to the human (and to the transcript), so a
|
|
81
|
+
// deliberate `build 22` and a confused one look different on the page.
|
|
82
|
+
const skipped = raw === undefined ? 0 : steps.filter((s) => !s.done && s.n < step).length;
|
|
83
|
+
const picked = raw === undefined
|
|
84
|
+
? ' (next undone)'
|
|
85
|
+
: skipped > 0
|
|
86
|
+
? ` (explicitly requested; skips ${skipped} undone step${skipped === 1 ? '' : 's'})`
|
|
87
|
+
: '';
|
|
79
88
|
process.stdout.write(`plumbbob: building step ${step}${picked}. Seam (for orientation; not a lock):\n${parsed.seam.map((p) => ` ${p}`).join('\n')}\n`);
|
|
80
89
|
return 0;
|
|
81
90
|
}
|
package/dist/verbs/finish.js
CHANGED
|
@@ -140,7 +140,7 @@ function appendStats(root, slug) {
|
|
|
140
140
|
'## Stats',
|
|
141
141
|
'',
|
|
142
142
|
'| step | red checks | drift warnings | reverts | wall-clock |',
|
|
143
|
-
'
|
|
143
|
+
'| ---- | ---------- | -------------- | ------- | ---------- |',
|
|
144
144
|
...rows,
|
|
145
145
|
`| **total** | ${totals.redChecks} | ${totals.driftWarnings} | ${totals.reverts} | ${formatWall(totals.wallMs > 0 ? totals.wallMs : null)} |`,
|
|
146
146
|
'',
|
package/dist/verbs/status.js
CHANGED
|
@@ -9,7 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import { commitsSince, findRepoRoot } from "../lib/git.js";
|
|
10
10
|
import { SLOTS, readHarnessFile, resolveAgent, } from "../lib/agents.js";
|
|
11
11
|
import { buildFolder, buildLogPath, checkpointsPath, hasSession, inSpike, intentPath, listBuilds, resolveBuild, stepPath, } from "../lib/sidecar.js";
|
|
12
|
-
import { formatOrientation, lastLedgerSha, orient } from "../lib/orient.js";
|
|
12
|
+
import { formatOrientation, lastLedgerSha, orient, parseRequestedStep } from "../lib/orient.js";
|
|
13
13
|
/**
|
|
14
14
|
* Read a file as utf8, or '' when it is missing or unreadable.
|
|
15
15
|
*/
|
|
@@ -21,12 +21,31 @@ function readOr(path) {
|
|
|
21
21
|
return '';
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Split `--invoked "<raw args>"` out of the argument list, ahead of any other
|
|
26
|
+
* flag parsing.
|
|
27
|
+
*
|
|
28
|
+
* The value is the verbatim text of a skill invocation (the `$ARGUMENTS` the
|
|
29
|
+
* `/plumbbob:build` skill passes through), so it can contain anything,
|
|
30
|
+
* including tokens that look like this CLI's own flags; extracting it first
|
|
31
|
+
* keeps such a value from confusing `resolveBuild`. A missing flag, or one
|
|
32
|
+
* with no value, reads as null: no explicit ask.
|
|
33
|
+
*/
|
|
34
|
+
function takeInvoked(args) {
|
|
35
|
+
const i = args.indexOf('--invoked');
|
|
36
|
+
if (i === -1)
|
|
37
|
+
return { invoked: null, rest: args };
|
|
38
|
+
return { invoked: args[i + 1] ?? null, rest: [...args.slice(0, i), ...args.slice(i + 2)] };
|
|
39
|
+
}
|
|
24
40
|
/**
|
|
25
41
|
* Print the orientation dashboard for the active build, plus its harness bindings.
|
|
26
42
|
*
|
|
27
43
|
* No session prints the exact `NO ACTIVE SESSION` sentinel; a session with
|
|
28
44
|
* builds but no active one lists them and points at `use`. Always exits 0:
|
|
29
|
-
* status is a report, never a gate.
|
|
45
|
+
* status is a report, never a gate. With `--invoked "<raw args>"` (how the
|
|
46
|
+
* `/plumbbob:build` skill passes its own invocation through), an explicit step
|
|
47
|
+
* number in the raw text repoints the dashboard's marker, detail, and next
|
|
48
|
+
* move at that step, so the injected state never contradicts the human's ask.
|
|
30
49
|
*/
|
|
31
50
|
export function status(cwd, args = []) {
|
|
32
51
|
const root = findRepoRoot(cwd);
|
|
@@ -34,10 +53,11 @@ export function status(cwd, args = []) {
|
|
|
34
53
|
process.stdout.write('NO ACTIVE SESSION\n');
|
|
35
54
|
return 0;
|
|
36
55
|
}
|
|
56
|
+
const { invoked, rest } = takeInvoked(args);
|
|
37
57
|
// A session with builds but no resolvable cursor (finish cleared it, or the repo
|
|
38
58
|
// holds several builds and none is active) has no single dashboard to show, so
|
|
39
59
|
// list the builds and point at `use` instead of rendering a broken, empty one.
|
|
40
|
-
const { build: slug } = resolveBuild(root,
|
|
60
|
+
const { build: slug } = resolveBuild(root, rest);
|
|
41
61
|
if (slug === null) {
|
|
42
62
|
const builds = listBuilds(root);
|
|
43
63
|
if (builds.length > 0) {
|
|
@@ -59,6 +79,7 @@ export function status(cwd, args = []) {
|
|
|
59
79
|
checkpoints,
|
|
60
80
|
inFlight: /^\d+$/.test(inFlightRaw) ? Number(inFlightRaw) : null,
|
|
61
81
|
spiking: inSpike(root, slug),
|
|
82
|
+
requested: parseRequestedStep(invoked),
|
|
62
83
|
outOfBand: anchor === null ? 0 : commitsSince(root, anchor),
|
|
63
84
|
});
|
|
64
85
|
const lines = [formatOrientation(orientation), ...harnessSection(root, slug)];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Attention-first build process: 14 skills + a CLI that keep you the decider — guidance on the work, a latch on the record.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@types/node": "26.2.0",
|
|
54
54
|
"@vitest/coverage-v8": "4.1.10",
|
|
55
55
|
"@vvago/vale": "3.17.1",
|
|
56
|
+
"cspell": "10.0.1",
|
|
56
57
|
"fallow": "3.14.0",
|
|
57
58
|
"fascicle": "0.8.16",
|
|
58
59
|
"markdownlint-cli2": "0.23.2",
|
package/skills/build/SKILL.md
CHANGED
|
@@ -8,7 +8,14 @@ allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob build:*
|
|
|
8
8
|
|
|
9
9
|
# PlumbBob: build a step (the default engine)
|
|
10
10
|
|
|
11
|
-
Current session state (injected when this skill runs): !`plumbbob status 2>/dev/null || echo "plumbbob CLI not on PATH in this session. Marketplace install: confirm the plugin is enabled in /plugin, then /reload-plugins. Skills-dir/global install: npm i -g plumbbob && plumbbob init."`
|
|
11
|
+
Current session state (injected when this skill runs): !`plumbbob status --invoked "$ARGUMENTS" 2>/dev/null || echo "plumbbob CLI not on PATH in this session. Marketplace install: confirm the plugin is enabled in /plugin, then /reload-plugins. Skills-dir/global install: npm i -g plumbbob && plumbbob init."`
|
|
12
|
+
|
|
13
|
+
**The invocation outranks the injected state.** The dashboard above is computed from
|
|
14
|
+
the plan; the arguments you were invoked with are the human's explicit ask. When they
|
|
15
|
+
name a step, build that step, whatever any `next →` line suggests. The CLI already
|
|
16
|
+
repoints the dashboard at an explicit step number when it can see one (`--invoked`),
|
|
17
|
+
so a surviving mismatch means the invocation text never reached it, and the typed
|
|
18
|
+
number still wins.
|
|
12
19
|
|
|
13
20
|
This is the **bundled executor**: the default engine, not the only one. It is
|
|
14
21
|
**swappable**: you can implement any step by hand, in a vibe session, or with
|
package/templates/build-log.md
CHANGED
|
@@ -36,11 +36,11 @@ check green + checkpoint taken, via `/plumbbob:verify` or `/plumbbob:build`.)*
|
|
|
36
36
|
Classify each parked item as exactly ONE. Naming it before acting is what keeps you
|
|
37
37
|
from sprawling across branches.
|
|
38
38
|
|
|
39
|
-
| Class | Meaning | Action
|
|
40
|
-
|
|
41
|
-
| **blocker** | Plan was wrong/incomplete; can't proceed | `/plumbbob:revert`, fold into intent
|
|
42
|
-
| **tangent** | A different path, not clearly better | Defer or kill. Default here.
|
|
43
|
-
| **pivot signal** | Evidence the whole approach is wrong | Stop. Replan deliberately.
|
|
39
|
+
| Class | Meaning | Action |
|
|
40
|
+
| ---------------- | ----------------------------------------- | ------------------------------------ |
|
|
41
|
+
| **blocker** | Plan was wrong/incomplete; can't proceed | `/plumbbob:revert`, fold into intent |
|
|
42
|
+
| **tangent** | A different path, not clearly better | Defer or kill. Default here. |
|
|
43
|
+
| **pivot signal** | Evidence the whole approach is wrong | Stop. Replan deliberately. |
|
|
44
44
|
|
|
45
45
|
> Reality check: almost everything that *feels* like a pivot is a tangent. Require a
|
|
46
46
|
> failed assumption, not a shinier idea, before you pivot.
|