plumbbob 0.13.0 → 0.13.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 +1 -1
- package/README.md +75 -54
- package/dist/cli-core.js +29 -9
- package/dist/lib/buildlog.js +77 -0
- package/dist/lib/git.js +53 -0
- package/dist/lib/latch.js +46 -5
- package/dist/lib/notice.js +3 -2
- package/dist/lib/orient.js +878 -61
- package/dist/lib/sidecar.js +164 -3
- package/dist/lib/steplist.js +128 -0
- package/dist/verbs/build.js +108 -14
- package/dist/verbs/checkpoint.js +41 -13
- package/dist/verbs/handoff.js +508 -42
- package/dist/verbs/harvest.js +224 -0
- package/dist/verbs/order.js +15 -11
- package/dist/verbs/recover.js +31 -1
- package/dist/verbs/relay.js +15 -2
- package/dist/verbs/status.js +64 -17
- package/dist/verbs/turn.js +170 -58
- package/package.json +1 -1
- package/skills/build/SKILL.md +85 -34
- package/skills/finish/SKILL.md +18 -4
- package/skills/harvest/SKILL.md +54 -11
- package/skills/order/SKILL.md +20 -10
- package/skills/plan/SKILL.md +77 -17
- package/skills/refine/SKILL.md +24 -3
- package/skills/run/SKILL.md +256 -0
- package/skills/status/SKILL.md +10 -3
- package/skills/step/SKILL.md +25 -3
- package/skills/verify/SKILL.md +6 -2
- package/templates/intent.md +30 -8
package/dist/verbs/turn.js
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
// `plumbbob turn`: the turn-ledger hook. Wired to the harness's
|
|
2
|
-
// UserPromptSubmit event, it runs once per
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// self-approval only when the human typed `/build --auto
|
|
8
|
-
//
|
|
9
|
-
// keystroke, because `build`
|
|
10
|
-
// could forge would be no grant).
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
2
|
+
// UserPromptSubmit event, it runs once per prompt and maintains the sidecar's
|
|
3
|
+
// git-excluded turn ledger (flat files under `.plumbbob/`): it ticks `TURN`,
|
|
4
|
+
// the monotonic count of human turns the checkpoint latch compares against a
|
|
5
|
+
// step's entry stamp to know a human turn intervened since the step began, and
|
|
6
|
+
// it rewrites `GRANT` from the literal prompt, minting a one-turn
|
|
7
|
+
// self-approval only when the human typed `/build --auto`, a step range (`N-M`,
|
|
8
|
+
// or a list such as `20,5-9`), or `/plumbbob:run`, clearing it otherwise (those
|
|
9
|
+
// strings can only come from a human keystroke, because `build` and `run` are
|
|
10
|
+
// disable-model-invocation: a grant the model could forge would be no grant).
|
|
11
|
+
// A run's grant is the steps left of the range the plan names, read from the
|
|
12
|
+
// active build's `intent.md` at the keystroke, and `RUN` beside it marks the
|
|
13
|
+
// range the run is landing. The harness also sends a background job's
|
|
14
|
+
// completion notice through this event, as a prompt the human never typed, so
|
|
15
|
+
// a notice ticks nothing, keeps the grant and the mark, and clears only the
|
|
16
|
+
// ending `RELAY` holds from an earlier turn. When a step is in flight a typed
|
|
17
|
+
// prompt also emits ONE `UserPromptSubmit` additionalContext line so a fresh
|
|
18
|
+
// session (post-compaction, or a scripted `-p` turn where no skill prose is in
|
|
19
|
+
// context) still knows the beat: a tangent is parked, not chased, and a
|
|
20
|
+
// message that also asks something is answered before the park's block.
|
|
21
|
+
// Guidance only: it blocks nothing. It ALWAYS exits 0 and any failure degrades
|
|
22
|
+
// to no output, so neither the tick nor the emit can ever wedge prompting.
|
|
17
23
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
-
import {
|
|
24
|
+
import { parseBuildRanges, readRunArgs, resolveRun } from "../lib/orient.js";
|
|
25
|
+
import { clearRelay, findSessionRoot, inSpike, intentPath, notePrompt, readPrompt, readRun, setGrant, setRun, stepPath, turnPath, } from "../lib/sidecar.js";
|
|
26
|
+
import { hasStep, normalizeStepList, readBuildArgs } from "../lib/steplist.js";
|
|
19
27
|
/**
|
|
20
|
-
* The hook entry point: apply the
|
|
28
|
+
* The hook entry point: apply the prompt, then emit the in-flight-step nudge
|
|
29
|
+
* when it counted as a human turn.
|
|
21
30
|
*
|
|
22
31
|
* Always returns 0: a broken turn must never wedge a prompt, so any failure is
|
|
23
32
|
* swallowed and degrades to no output.
|
|
24
33
|
*/
|
|
25
34
|
export function turn(cwd, _args) {
|
|
26
35
|
try {
|
|
27
|
-
applyTurn(cwd, readStdin())
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
if (applyTurn(cwd, readStdin())) {
|
|
37
|
+
const context = stepInFlightContext(cwd);
|
|
38
|
+
if (context !== null)
|
|
39
|
+
process.stdout.write(context);
|
|
40
|
+
}
|
|
31
41
|
}
|
|
32
42
|
catch {
|
|
33
43
|
// A broken turn must never wedge a prompt: swallow and exit 0.
|
|
@@ -78,7 +88,17 @@ function inFlightStep(root) {
|
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
/**
|
|
81
|
-
* The testable core: apply one
|
|
91
|
+
* The testable core: apply one prompt given the raw hook stdin, and say
|
|
92
|
+
* whether it counted as a human turn.
|
|
93
|
+
*
|
|
94
|
+
* A typed prompt ticks TURN and rewrites GRANT and the RUN mark. A notice
|
|
95
|
+
* touches none of them, because the latch reads TURN passing a step's entry
|
|
96
|
+
* stamp as its one sign that a human answered, and a grant the human typed has
|
|
97
|
+
* to outlive a job that reports back partway through the range. A notice that starts a turn of
|
|
98
|
+
* its own clears RELAY, whose ending was judged when its own turn ended; one
|
|
99
|
+
* the harness folds into the running turn arrives under that turn's prompt id
|
|
100
|
+
* and leaves RELAY alone, since the ending the turn just printed has yet to be
|
|
101
|
+
* judged.
|
|
82
102
|
*
|
|
83
103
|
* Split from the fd-0 read so the unit tests can drive it directly: an
|
|
84
104
|
* in-process test cannot feed fd 0 (the same constraint checkpoint's `--body`
|
|
@@ -87,20 +107,56 @@ function inFlightStep(root) {
|
|
|
87
107
|
export function applyTurn(cwd, raw) {
|
|
88
108
|
const root = findSessionRoot(cwd);
|
|
89
109
|
if (root === null)
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
return false; // no active session above cwd: silent no-op.
|
|
111
|
+
const { prompt, promptId } = readPayload(raw);
|
|
112
|
+
const notice = isNotice(prompt);
|
|
113
|
+
if (notice) {
|
|
114
|
+
if (promptId === null || promptId !== readPrompt(root))
|
|
115
|
+
clearRelay(root);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
writeFileSync(turnPath(root), `${readTurn(root) + 1}\n`);
|
|
119
|
+
const minted = mintSafely(root, prompt);
|
|
120
|
+
setGrant(root, minted.grant);
|
|
121
|
+
setRun(root, minted.run);
|
|
122
|
+
}
|
|
123
|
+
// Last, so a record that cannot be written never costs the tick or the grant.
|
|
124
|
+
notePrompt(root, promptId);
|
|
125
|
+
return !notice;
|
|
94
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a prompt is the harness reporting on background work rather than
|
|
129
|
+
* the human typing.
|
|
130
|
+
*
|
|
131
|
+
* Nothing in the payload marks one, but every notice opens on a frame of the
|
|
132
|
+
* harness's own at its very first character: `<task-notification>` for a
|
|
133
|
+
* finished shell job, a Monitor event, or a subagent's task, and
|
|
134
|
+
* `<agent-message ` for a subagent's or another session's report. A misread
|
|
135
|
+
* errs safe: a typed prompt taken for a notice costs one tick and one grant,
|
|
136
|
+
* so the latch only grows stricter, while a notice taken for a typed prompt
|
|
137
|
+
* is a turn no human took.
|
|
138
|
+
*/
|
|
139
|
+
export function isNotice(prompt) {
|
|
140
|
+
return prompt.startsWith('<task-notification>') || prompt.startsWith('<agent-message ');
|
|
141
|
+
}
|
|
142
|
+
// A build invocation, bare or namespaced, starting a token.
|
|
143
|
+
const BUILD_INVOCATION = /(?<![\w/.~-])\/(?:plumbbob:)?build\b/;
|
|
144
|
+
// A run invocation: only the namespaced verb, since a bare `/run` reaches
|
|
145
|
+
// Claude Code's own skill, starting a token and ending at a space, a line's
|
|
146
|
+
// end, or sentence punctuation.
|
|
147
|
+
const RUN_INVOCATION = /(?<![\w/.~-])\/plumbbob:run(?=[\s.;:!?,]|$)/;
|
|
95
148
|
/**
|
|
96
149
|
* Derive the GRANT content from the human's prompt, or null to clear it.
|
|
97
150
|
*
|
|
98
151
|
* A grant is minted only from a literal `build` invocation the human typed:
|
|
99
152
|
* `build` is disable-model-invocation, so this string can only come from a
|
|
100
153
|
* human keystroke, never model-authored pressure: a grant the model can forge
|
|
101
|
-
* is no grant. A
|
|
102
|
-
*
|
|
103
|
-
*
|
|
154
|
+
* is no grant. A range mints `steps <list>`, exactly the steps it names in
|
|
155
|
+
* their normal form (`20, 5-9` mints `steps 5-9,20`), and beats `--auto`
|
|
156
|
+
* (bounded wins), so a range that does not parse mints nothing rather than
|
|
157
|
+
* falling back to `auto`. A bare `/build`, a bare step number, or any
|
|
158
|
+
* non-invocation prompt mints nothing. The namespaced `/plumbbob:build` form is
|
|
159
|
+
* honored alongside the bare one.
|
|
104
160
|
*
|
|
105
161
|
* The invocation must *start* a token: a slash preceded by a word character,
|
|
106
162
|
* `/`, `.`, `-`, or `~` is a path segment (`src/build`, `./build`), not a
|
|
@@ -116,47 +172,103 @@ export function applyTurn(cwd, raw) {
|
|
|
116
172
|
* argument the human typed.
|
|
117
173
|
*/
|
|
118
174
|
export function grantFromPrompt(prompt) {
|
|
119
|
-
const invocation =
|
|
175
|
+
const invocation = BUILD_INVOCATION.exec(prompt);
|
|
120
176
|
if (invocation === null)
|
|
121
177
|
return null;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
178
|
+
const args = readBuildArgs(argumentLine(prompt, invocation));
|
|
179
|
+
if (args.range)
|
|
180
|
+
return args.steps === null ? null : `steps ${normalizeStepList(args.steps)}`;
|
|
181
|
+
return args.auto ? 'auto' : null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The text an invocation's arguments are read from: the rest of its line.
|
|
185
|
+
*/
|
|
186
|
+
function argumentLine(prompt, invocation) {
|
|
187
|
+
return prompt.slice(invocation.index + invocation[0].length).split('\n', 1)[0] ?? '';
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The GRANT one typed prompt mints and the RUN mark it leaves, given the
|
|
191
|
+
* active build as the hook read it (null when it could not) and the range the
|
|
192
|
+
* RUN mark names now.
|
|
193
|
+
*
|
|
194
|
+
* Whichever invocation comes first in the prompt is the one the human fired.
|
|
195
|
+
* A run mints `steps <what is left>` of the range the plan names at the
|
|
196
|
+
* keystroke, and marks that range, so a later edit to the ranges or the build
|
|
197
|
+
* order leaves both as they were. A bare range's run mints nothing and marks
|
|
198
|
+
* nothing, since it builds its step to the pause, and so does a run that names
|
|
199
|
+
* nothing it can walk, a span or a list of ranges, and a plan the hook could
|
|
200
|
+
* not read. Any other prompt mints what `grantFromPrompt` reads, and keeps the
|
|
201
|
+
* mark only while a step of the marked range waits at its pause, so a halt
|
|
202
|
+
* inside the run approved with `looks good` still lands under it.
|
|
203
|
+
*/
|
|
204
|
+
export function mintFromPrompt(prompt, build, marked) {
|
|
205
|
+
const run = RUN_INVOCATION.exec(prompt);
|
|
206
|
+
const first = BUILD_INVOCATION.exec(prompt);
|
|
207
|
+
if (run === null || (first !== null && first.index < run.index)) {
|
|
208
|
+
return { grant: grantFromPrompt(prompt), run: heldMark(build, marked) };
|
|
209
|
+
}
|
|
210
|
+
const args = readRunArgs(argumentLine(prompt, run));
|
|
211
|
+
const resolved = args === null || build === null ? null : resolveRun({ ...build, item: args.item });
|
|
212
|
+
if (resolved?.ok !== true || resolved.grant === null)
|
|
213
|
+
return { grant: null, run: null };
|
|
214
|
+
return { grant: resolved.grant, run: resolved.range.item };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The RUN mark a prompt that is not a run leaves: the marked range while a
|
|
218
|
+
* step it names is in flight, so waiting at its pause, else none.
|
|
219
|
+
*/
|
|
220
|
+
function heldMark(build, marked) {
|
|
221
|
+
if (build === null || marked === null || build.inFlight === null)
|
|
222
|
+
return null;
|
|
223
|
+
const step = build.inFlight;
|
|
224
|
+
const range = parseBuildRanges(build.intent).ranges.find((r) => r.item === marked);
|
|
225
|
+
return range !== undefined && hasStep(range.steps, step) ? marked : null;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* What a typed prompt mints, read against the active build, degrading to no
|
|
229
|
+
* grant and no mark on any failure, so a broken read can never leave the last
|
|
230
|
+
* turn's grant standing.
|
|
231
|
+
*/
|
|
232
|
+
function mintSafely(root, prompt) {
|
|
233
|
+
try {
|
|
234
|
+
return mintFromPrompt(prompt, readBuild(root), readRun(root));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return { grant: null, run: null };
|
|
141
238
|
}
|
|
142
|
-
if (ceiling !== undefined)
|
|
143
|
-
return `range ${ceiling}`;
|
|
144
|
-
return auto ? 'auto' : null;
|
|
145
239
|
}
|
|
146
240
|
/**
|
|
147
|
-
*
|
|
241
|
+
* The active build as a run reads it: the plan's text, the step in flight,
|
|
242
|
+
* and whether a spike is open; null when the plan cannot be read, which mints
|
|
243
|
+
* no run.
|
|
244
|
+
*/
|
|
245
|
+
function readBuild(root) {
|
|
246
|
+
try {
|
|
247
|
+
return { intent: readFileSync(intentPath(root), 'utf8'), inFlight: inFlightStep(root), spiking: inSpike(root) };
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The `prompt` and `prompt_id` fields of the UserPromptSubmit JSON, as '' and
|
|
255
|
+
* null when either is absent.
|
|
148
256
|
*
|
|
149
257
|
* Absent, unparseable, or non-string input contributes nothing rather than
|
|
150
|
-
* wedging the tool: the tick still lands (a human turn did occur)
|
|
151
|
-
* prompt clears any GRANT; the verb never
|
|
258
|
+
* wedging the tool: the tick still lands (a human turn did occur), the empty
|
|
259
|
+
* prompt clears any GRANT, and a missing id records none; the verb never
|
|
260
|
+
* throws over bad stdin.
|
|
152
261
|
*/
|
|
153
|
-
function
|
|
262
|
+
function readPayload(raw) {
|
|
154
263
|
try {
|
|
155
264
|
const parsed = JSON.parse(raw);
|
|
156
|
-
return
|
|
265
|
+
return {
|
|
266
|
+
prompt: typeof parsed.prompt === 'string' ? parsed.prompt : '',
|
|
267
|
+
promptId: typeof parsed.prompt_id === 'string' && parsed.prompt_id !== '' ? parsed.prompt_id : null,
|
|
268
|
+
};
|
|
157
269
|
}
|
|
158
270
|
catch {
|
|
159
|
-
return '';
|
|
271
|
+
return { prompt: '', promptId: null };
|
|
160
272
|
}
|
|
161
273
|
}
|
|
162
274
|
/**
|
package/package.json
CHANGED
package/skills/build/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: build
|
|
3
|
-
description: "The default engine: read the next planned step from intent, implement it (its done-when, seam, Decisions, Constraints), then verify it through to the approval pause. Swappable: build by hand/vibed/another harness and run /plumbbob:verify instead. `--auto` self-approves and chains to done; a step range like `1-3` self-approves
|
|
4
|
-
argument-hint: "[step-number | step-range] [--auto]"
|
|
3
|
+
description: "The default engine: read the next planned step from intent, implement it (its done-when, seam, Decisions, Constraints), then verify it through to the approval pause. Swappable: build by hand/vibed/another harness and run /plumbbob:verify instead. `--auto` self-approves and chains to done; a step range like `1-3`, or a list like `20,5-9`, self-approves exactly the steps it names, in build order, then stops."
|
|
4
|
+
argument-hint: "[step-number | step-range | step-list] [--auto]"
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob build:*), Bash(plumbbob handoff:*), Bash(plumbbob check:*), Bash(plumbbob checkpoint:*), Bash(plumbbob park:*), Bash(plumbbob agent:*), Bash(plumbbob spike:*), Bash(git diff:*)
|
|
7
7
|
---
|
|
@@ -35,15 +35,18 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
35
35
|
## What this skill does, in order
|
|
36
36
|
|
|
37
37
|
1. **Pick the step.** Use the number you were invoked with (for example `/plumbbob:build 4`), or,
|
|
38
|
-
if you were given a range like `/plumbbob:build 1-3
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if you were given a range like `/plumbbob:build 1-3` or a list like `/plumbbob:build 20,5-9`,
|
|
39
|
+
start at its first undone step in build order, the one the dashboard above marks
|
|
40
|
+
`← requested`, and land only the steps it names (see the range note under `--auto`).
|
|
41
|
+
With no argument, don't resolve the next step yourself; the CLI does: bare `plumbbob build`
|
|
41
42
|
(step 2) enters the next undone step in build order (the `## Build order` line when the
|
|
42
43
|
plan has one, else the numbering) and refuses with a `/plumbbob:step` nudge when every
|
|
43
44
|
step is checkpointed.
|
|
44
45
|
2. **Enter the step.** Run `plumbbob build <n>`, or bare `plumbbob build` to enter the
|
|
45
46
|
next undone step (records the in-flight STEP + SEAM so `/plumbbob:status` shows the step in
|
|
46
|
-
flight; the seam is awareness, not a lock).
|
|
47
|
+
flight; the seam is awareness, not a lock). When it refuses with `no step entered` because
|
|
48
|
+
a stop card is waiting for its reply, relay the refusal whole and end the turn: the card's
|
|
49
|
+
reply is the human's, and no step is built until it records.
|
|
47
50
|
- **Spike-as-step.** If the step's title opens with `spike:` / `Spike:`, the increment
|
|
48
51
|
*is* the experiment: resolving a fork the plan couldn't settle on paper. After
|
|
49
52
|
entering it, run `plumbbob spike report "<slug>"` (the CLI scaffolds a
|
|
@@ -181,6 +184,12 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
181
184
|
line in the build-log's `## Log` and then truncates it, so the detail lands in the
|
|
182
185
|
tracked ledger rather than piling up on disk.
|
|
183
186
|
|
|
187
|
+
**A bare range's pause is its stop.** When the injected dashboard's ranges block lists
|
|
188
|
+
this step as a bare range (`/plumbbob:build 5`, its row marked `▸`), the plan reserved a
|
|
189
|
+
call for this pause, and the `stop:` line beneath the row names it. The recommendation
|
|
190
|
+
speaks to that call: the move you would take on the plan's question, and why, rather
|
|
191
|
+
than a verdict on the diff alone.
|
|
192
|
+
|
|
184
193
|
*Paste the turn.* Run `plumbbob handoff` and paste its output whole, verbatim, at top
|
|
185
194
|
level, trailing blank line included, then end the turn. Never nest it inside a fence of
|
|
186
195
|
your own; it carries fences of its own and they cannot nest. It prints the Now line
|
|
@@ -223,8 +232,11 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
223
232
|
|
|
224
233
|
**Then hand off with the next model.** `plumbbob checkpoint` prints the whole boundary
|
|
225
234
|
ending itself: `**Checkpoint**: Step N complete (<sha>)`, the Verdict the step earned,
|
|
226
|
-
any advisory (a staged path outside the seam is the common one),
|
|
227
|
-
|
|
235
|
+
any advisory (a staged path outside the seam is the common one), the Stop part when
|
|
236
|
+
this landing completes a range (`**Stop**: Range 3 of 5 landed - <its stop>`, the reason
|
|
237
|
+
the build stops here), and Next Up. Relay that block whole, the Stop part with the rest
|
|
238
|
+
(the relay check holds a boundary that dropped it), run no second command, and write
|
|
239
|
+
nothing around it. It is the
|
|
228
240
|
orientation tier (no Your Call block, no recommendation), and it points at the next
|
|
229
241
|
undone step carrying that step's `- model:` recommendation (the plan's
|
|
230
242
|
smallest-model-that-fits call) so the human knows which `/model` to select before
|
|
@@ -287,7 +299,18 @@ and approves in the human's place**, and it **chains**:
|
|
|
287
299
|
advance the loop), or a new decision is needed. The step is still in flight and the
|
|
288
300
|
human has a call to make about it, so end on the same turn the default pause does:
|
|
289
301
|
write the detail file, run `plumbbob handoff`, and paste its block.
|
|
290
|
-
- **A
|
|
302
|
+
- **A call the plan reserves halts at its pause.** A step the plan's `## Build ranges`
|
|
303
|
+
names as a bare range (`/plumbbob:build 10`) is a decision the plan left for the human,
|
|
304
|
+
and `plumbbob build` says so as it enters the step (`step 10 ends at its pause, where
|
|
305
|
+
the plan reserves the call for the human`). Build it, verify it, and end on the same
|
|
306
|
+
turn a halt on trouble does: write the detail file, run `plumbbob handoff`, and paste
|
|
307
|
+
its block. The latch would let it land, since `--auto` grants every step, so keeping
|
|
308
|
+
the step open is yours to do
|
|
309
|
+
([D94 (auto-and-stops)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d94)).
|
|
310
|
+
A range that lands its steps runs through, since its stop is a review that can wait
|
|
311
|
+
for the end of the run, and a typed range stays exactly as typed.
|
|
312
|
+
- **A clean halt lands its step first.** The top of a requested range is reached (the
|
|
313
|
+
next step in build order is one the range does not name, or none is left), or no
|
|
291
314
|
planned steps remain, and `plumbbob build` names that step as it enters it (for
|
|
292
315
|
example `step 3 is the last undone step in the plan`). That step checkpoints like
|
|
293
316
|
every step before it, and its
|
|
@@ -302,36 +325,64 @@ only because the human asked for it by name. A range hands the clock back once i
|
|
|
302
325
|
step has landed: the step after it waits for the human's next run. The default (no flag,
|
|
303
326
|
no range) always ends at the pause.
|
|
304
327
|
|
|
305
|
-
### A step range (`N-M`) is a bounded `--auto`
|
|
328
|
+
### A step range (`N-M`, or a list like `20,5-9`) is a bounded `--auto`
|
|
306
329
|
|
|
307
330
|
`/plumbbob:build 1-3` self-approves steps 1 through 3 exactly as `--auto` does, then **stops
|
|
308
|
-
once step 3 has landed** instead of chaining to done.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
331
|
+
once step 3 has landed** instead of chaining to done. A range may also list its steps, numbers
|
|
332
|
+
and spans joined by commas (`/plumbbob:build 20,5-9`, typed with or without a space after a
|
|
333
|
+
comma), and it grants exactly the steps it names: the checkpoint refuses any other
|
|
334
|
+
([D91 (step-list-grants)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d91)).
|
|
335
|
+
The range *is* the opt-in; you do not also pass `--auto`.
|
|
336
|
+
|
|
337
|
+
**A run hands you a range the plan names, walked the same way.** `/plumbbob:run` (the run
|
|
338
|
+
skill) reads this section for its walk: its grant is the `steps` list the turn hook minted
|
|
339
|
+
from the plan's `## Build ranges` when the human typed the run, in the normal form a typed
|
|
340
|
+
list mints, so the latch, `plumbbob build`'s notice at the top step, and every halt below
|
|
341
|
+
read it exactly as they read a typed range
|
|
342
|
+
([D96 (plan-names-the-grant)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d96)).
|
|
343
|
+
|
|
344
|
+
Run it as the `--auto` loop, walked in build order. **Start at the range's first undone step
|
|
345
|
+
in build order, however it is written** (`5-9,20` under an order that puts 20 first starts at
|
|
346
|
+
20), and after each landing take the next undone step in build order. Build it if the range
|
|
347
|
+
names it; **stop before building any step the range does not name**, or once none is left. It
|
|
348
|
+
adds no machinery: just the one more entry already in the halt list above, and it is a clean
|
|
349
|
+
halt, so the top step is never held back for approval. The human approved it by typing the
|
|
350
|
+
range, and `plumbbob build` says so as it enters that step:
|
|
351
|
+
`step 3 is the top of the range you granted`, or, when the walk ends early (the plan runs
|
|
352
|
+
out, or the next step in build order is one the range does not name),
|
|
315
353
|
`the last undone step in the range you granted`.
|
|
316
354
|
|
|
317
|
-
-
|
|
318
|
-
|
|
319
|
-
and lands the single step `N`. Only a
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
check. Do not skip past
|
|
328
|
-
|
|
329
|
-
|
|
355
|
+
- **A range that parses**: build each step it names in build order, self-approving and
|
|
356
|
+
checkpointing each, the last included, then relay the last one's checkpoint block and end
|
|
357
|
+
the turn. `N-N` is a range like any other: it builds and lands the single step `N`. Only a
|
|
358
|
+
bare `/plumbbob:build N` ends at the pause.
|
|
359
|
+
- **A range that does not parse** (for example `3-1`, or `1--3`): that is not a range you can
|
|
360
|
+
walk, and it grants nothing; report it and stop rather than guess the intent.
|
|
361
|
+
- **A number past the last planned step** (for example `1-9` with three steps): build through
|
|
362
|
+
the last planned step the range names and stop; that is the existing "no planned steps
|
|
363
|
+
remain" halt, not an error. A number the plan lacks is never reached.
|
|
364
|
+
- **A step inside the range won't build** (its seam won't parse): `plumbbob build <n>` fails
|
|
365
|
+
exactly as it always does; surface it and stop, the same as a red check. Do not skip past
|
|
366
|
+
the gap.
|
|
367
|
+
- **The range starts above the next undone step**: you are jumping over earlier planned work;
|
|
368
|
+
note that you are skipping the steps before it, then proceed (the same latitude as a
|
|
330
369
|
single-number jump like `/plumbbob:build 4`).
|
|
331
|
-
- **The build order puts a
|
|
332
|
-
`1, 4, 2, 3` under `/plumbbob:build 1-3`):
|
|
333
|
-
|
|
334
|
-
|
|
370
|
+
- **The build order puts a step the range does not name inside it** (the order reads
|
|
371
|
+
`1, 4, 2, 3` under `/plumbbob:build 1-3`): the halt above fires before step 4, and
|
|
372
|
+
`plumbbob build` names step 1 as the last undone step in the range. The range that walks
|
|
373
|
+
the whole stretch lists step 4 too (`/plumbbob:build 1-4`, or `/plumbbob:build 1,4,2,3`).
|
|
374
|
+
- **At a range's stop, harvest before the next range.** No harvest runs between the steps
|
|
375
|
+
inside a range, so the parks pile up, and a blocker among them would otherwise ride into
|
|
376
|
+
the next one. A run's range ends on the stop card, which lists every open park with a
|
|
377
|
+
proposed class, and the reply that settles the card records them (the run skill's
|
|
378
|
+
section on the reply at a stop). A range landed here, by a typed range, ends on the plain
|
|
379
|
+
boundary instead; when the human asks what comes next there with parks open (the note's
|
|
380
|
+
`parked 2 of 7 open`), the move is `/plumbbob:harvest`, then the run the card names.
|
|
381
|
+
- **After an early stop, settle the step, then rerun what is left.** A halt on trouble
|
|
382
|
+
inside a range ends at that step's pause, and the grant ends with the turn. Once the
|
|
383
|
+
human approves and the step lands, the note beneath Next Up names the run that lands
|
|
384
|
+
what is left of the range (`· range 3 of 5: /plumbbob:run (steps 8-9)`), and that is what
|
|
385
|
+
the human types to carry on; `/plumbbob:build 8-9` lands the same steps by hand.
|
|
335
386
|
|
|
336
387
|
## The hard contracts
|
|
337
388
|
|
package/skills/finish/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: finish
|
|
3
3
|
description: "Finish the build: write the report (what shipped, decisions, parked/harvested items, deferred tangents), then make the final commit that closes the session. The build folder rides the branch into the PR: no separate archive. Report by default, no gate."
|
|
4
4
|
disable-model-invocation: true
|
|
5
|
-
allowed-tools: Read, Write, Bash(plumbbob status:*), Bash(plumbbob finish:*)
|
|
5
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob harvest:*), Bash(plumbbob order:*), Bash(plumbbob finish:*)
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# PlumbBob: finish the build (the close-out)
|
|
@@ -17,7 +17,21 @@ record.
|
|
|
17
17
|
|
|
18
18
|
## What this skill does, in order
|
|
19
19
|
|
|
20
|
-
1. **
|
|
20
|
+
1. **Settle a stop card still waiting.** Read `.plumbbob/detail.md`. When it opens on a
|
|
21
|
+
`# Detail · Range <N>` header, the last range's stop card is waiting for its reply, and
|
|
22
|
+
`/plumbbob:finish` typed at it is that reply, the move its Your Call block names
|
|
23
|
+
([D102 (settle-in-one-reply)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d102)).
|
|
24
|
+
Record it the way `looks good` would before anything else: fold each blocker its
|
|
25
|
+
`## Harvest` section proposes into `intent.md` by the harvest skill's rules (read
|
|
26
|
+
`../harvest/SKILL.md` from this skill's base directory), then run `plumbbob harvest`,
|
|
27
|
+
which writes the range's review into the Log and flips the parks the card settled. A
|
|
28
|
+
finish that skipped it would leave the last range's review out of the Log and its parks
|
|
29
|
+
open in the report. When `plumbbob harvest` refuses with `step 13 joined it after it
|
|
30
|
+
landed`, a blocker went into the range the card reviews: move that step to a range
|
|
31
|
+
still to come, or a bare range of its own, leave the detail file as it is, fold nothing
|
|
32
|
+
again, and run `plumbbob harvest` once more. On any other refusal, report what it names
|
|
33
|
+
and stop before the report. Any other detail file, or none, has nothing waiting.
|
|
34
|
+
2. **Write the report** to the active build's `report.md`
|
|
21
35
|
(`.plumbbob/builds/<slug>/report.md`), from `intent.md` + `build-log.md`.
|
|
22
36
|
The build-log's `## Log` is already the chronological history; `plumbbob checkpoint`
|
|
23
37
|
wrote a dated line for every step as it landed. **Read it as the spine; do not
|
|
@@ -34,7 +48,7 @@ record.
|
|
|
34
48
|
`## Stats` (the per-step receipts) itself, so do **not** hand-write either; a report
|
|
35
49
|
that spells them out collides with the CLI's own tail. This is the "yeah, I did that"
|
|
36
50
|
artifact. Write it by default; the human may edit it.
|
|
37
|
-
|
|
51
|
+
3. **Finish.** Run `plumbbob finish`, which appends the checkpoint SHAs to the
|
|
38
52
|
report and makes the final commit (subject `chore(<scope>): finish`, its body
|
|
39
53
|
led by a `plumbbob finish` marker plus an optional proportional `--body`), then
|
|
40
54
|
clears the control state (the in-flight markers, then `STATE` last; one delete
|
|
@@ -58,7 +72,7 @@ record.
|
|
|
58
72
|
cannot analyze the extra redirect, so it blocks the command or stops to ask instead of
|
|
59
73
|
letting this skill's allowance cover it. Omit `--body` entirely and the commit carries
|
|
60
74
|
the `plumbbob finish` marker subject only.
|
|
61
|
-
|
|
75
|
+
4. **Point at the next goal.** Relay `plumbbob finish`'s output verbatim, blank lines
|
|
62
76
|
included: its lead line, which names the branch the tracked folder now rides into the
|
|
63
77
|
PR, any advisory beneath it, and `**Next Up**: Nothing planned - /plumbbob:plan`. That
|
|
64
78
|
is the orientation tier's close (the
|
package/skills/harvest/SKILL.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harvest
|
|
3
|
-
description: "Triage the park list at a step boundary: propose one class (blocker/tangent/pivot)
|
|
3
|
+
description: "Triage the park list at a step boundary: propose one class (blocker/tangent/pivot) for every open parked item at once, and once the human settles them in one reply, fold a confirmed blocker into intent and record the rest with plumbbob harvest. A run's stop card carries its own harvest."
|
|
4
4
|
disable-model-invocation: true
|
|
5
|
-
allowed-tools: Read, Edit, Bash(plumbbob status:*), Bash(plumbbob order:*)
|
|
5
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob order:*), Bash(plumbbob harvest:*)
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# PlumbBob: harvest the park list
|
|
@@ -23,10 +23,21 @@ DESIGN boundary, not mid-step. Read the dashboard injected above:
|
|
|
23
23
|
Chasing parked items mid-step is the disease parking prevents.
|
|
24
24
|
- At the boundary (the dashboard reads `[DESIGN]`): go ahead.
|
|
25
25
|
|
|
26
|
+
A range's stop is the boundary that matters most. No harvest runs between the steps
|
|
27
|
+
inside a range, so the parks it piled up wait for the stop where it lands, and a
|
|
28
|
+
blocker among them is caught there before the next range builds on it. A run's stop card
|
|
29
|
+
carries its harvest
|
|
30
|
+
([D100 (stop-card)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d100)):
|
|
31
|
+
every open park is on the card with a proposed lean, and the reply that settles the card
|
|
32
|
+
records them, so there is nothing left for this skill to do there. If
|
|
33
|
+
`.plumbbob/detail.md` opens on a `# Detail · Range <N>` header, that card is still waiting:
|
|
34
|
+
**refuse**, since writing this skill's detail file would overwrite the range's review, and
|
|
35
|
+
point the human at the card's own reply (`looks good`, or a change to a lean).
|
|
36
|
+
|
|
26
37
|
## What this skill does
|
|
27
38
|
|
|
28
|
-
Walk the **Park list** in `build-log.md`
|
|
29
|
-
|
|
39
|
+
Walk the **Park list** in `build-log.md` and, for every open item, **propose exactly one
|
|
40
|
+
class**, all of them at once:
|
|
30
41
|
|
|
31
42
|
- **blocker**: the plan was wrong or incomplete; can't proceed. Folds into `intent.md`
|
|
32
43
|
and is handled now.
|
|
@@ -35,18 +46,50 @@ one class**:
|
|
|
35
46
|
|
|
36
47
|
## The one hard contract
|
|
37
48
|
|
|
38
|
-
You **propose**; the **human calls it**.
|
|
39
|
-
one line of reasoning,
|
|
40
|
-
|
|
49
|
+
You **propose**; the **human calls it**. Propose a class for every open item at once,
|
|
50
|
+
with one line of reasoning each, and the human settles them all in one reply
|
|
51
|
+
([D102 (settle-in-one-reply)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d102)):
|
|
52
|
+
one reply per item is what made a harvest a chore, and seeing every proposal at once keeps
|
|
53
|
+
the call on each one theirs. The detail file you write first is the proposal; record
|
|
54
|
+
**only after** that reply confirms them:
|
|
41
55
|
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
- Write the proposals into `.plumbbob/detail.md` first, under a `# Detail · Harvest`
|
|
57
|
+
header, one line per open item in a `## Harvest` section:
|
|
58
|
+
`- P6 → tangent: <the reason, one clause>`, the class written `blocker`, `tangent`,
|
|
59
|
+
or `pivot` (for a pivot signal). A park's handle is `P` and its place in the
|
|
60
|
+
Park list, counting the harvested lines too (the count the note sizes as
|
|
61
|
+
`parked 3 of 8 open`), and it is the one name the detail file, the turn, and the
|
|
62
|
+
human's reply all use
|
|
63
|
+
([D105 (park-handles)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d105)).
|
|
64
|
+
Then list the same proposals in the turn, each with its handle and the park's text, and
|
|
65
|
+
end the turn there.
|
|
66
|
+
- A reply that changes a proposal (`make P7 a tangent`) is the human's call: rewrite
|
|
67
|
+
that line, list the proposals again, and end the turn. A reply that confirms them
|
|
68
|
+
(`looks good`) is what lets you write.
|
|
69
|
+
- On that confirmation, run `plumbbob harvest`
|
|
70
|
+
([D104 (harvest-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d104)).
|
|
71
|
+
It flips each settled item from `- [ ]` to `- [x]` in the Park list, so
|
|
72
|
+
`/plumbbob:status` stops counting it as open, and records each confirmed class under
|
|
73
|
+
`build-log.md`'s `## Harvest` section. Relay its ending whole, the `**Recorded**:` line
|
|
74
|
+
and the Next Up beneath it. It refuses a proposal naming a handle the list lacks (`P9`
|
|
75
|
+
with eight parked) or a class other than blocker, tangent, or pivot; fix the line it
|
|
76
|
+
names and run it again.
|
|
45
77
|
- A confirmed **blocker** also folds its decision into `intent.md`, slugged at birth
|
|
46
78
|
like any Decision, anchored the same way
|
|
47
79
|
(`- <a id="d5"></a>**D5 (retry-cap)**: <the call>, *because* <why>`), never a bare `D5`.
|
|
48
80
|
A blocker that becomes a new step takes the next free number under `## Steps` and,
|
|
49
81
|
when it has to land before an existing undone step, its place in the sequence through
|
|
50
|
-
`plumbbob order 7 8 5` (the `## Build order` line), never by renumbering.
|
|
82
|
+
`plumbbob order 7 8 5` (the `## Build order` line), never by renumbering. When the plan
|
|
83
|
+
carries `## Build ranges`, the new step goes into a range too: a range still to come
|
|
84
|
+
(the one it builds with), or a bare range of its own when it brings a call the human
|
|
85
|
+
has to make before it lands, and never a range that has landed, since that range's call
|
|
86
|
+
was made and the step would reopen it. A new range takes the next free item number, and
|
|
87
|
+
no item is renumbered, because the stop card's stamp, the detail file's header, and
|
|
88
|
+
`plumbbob harvest` find a landed range by its number. A range the step breaks is re-cut
|
|
89
|
+
in the same confirmed edit
|
|
90
|
+
([D92 (build-ranges)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d92));
|
|
91
|
+
`plumbbob status` afterward carries a `⚠` row for any range still broken.
|
|
92
|
+
- Fold a confirmed blocker into `intent.md` before you run `plumbbob harvest`, so the
|
|
93
|
+
record and the plan agree when it lands.
|
|
51
94
|
- Never reclassify or resolve an item the human hasn't confirmed, and default every
|
|
52
95
|
uncertain item to **tangent**, never to blocker.
|