plumbbob 0.11.3 → 0.12.0
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 +1 -1
- package/dist/lib/latch.js +1 -1
- package/dist/lib/orient.js +10 -0
- package/dist/verbs/build.js +23 -7
- package/dist/verbs/checkpoint.js +14 -7
- package/package.json +1 -1
- package/skills/build/SKILL.md +38 -24
- package/skills/finish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +2 -1
- package/skills/verify/SKILL.md +4 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
3
|
"displayName": "PlumbBob",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
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
|
@@ -221,7 +221,7 @@ nothing else:
|
|
|
221
221
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
222
222
|
| ------------------------------------ | ------ |
|
|
223
223
|
| `/plumbbob:plan` | plan the whole goal: open the session and author intent's Frame, Decisions, Constraints, **and all Steps** |
|
|
224
|
-
| `/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
|
|
224
|
+
| `/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 stops |
|
|
225
225
|
| `/plumbbob:finish` | finish up: write the report, make the final commit, clear for a fresh goal |
|
|
226
226
|
|
|
227
227
|
**Plan-shaping moves**: optional, for when the plan needs work mid-flight:
|
package/dist/lib/latch.js
CHANGED
|
@@ -112,7 +112,7 @@ export function checkLatch(root, step) {
|
|
|
112
112
|
/**
|
|
113
113
|
* The current GRANT, or null when the file is absent or unparseable.
|
|
114
114
|
*/
|
|
115
|
-
function readGrant(root) {
|
|
115
|
+
export function readGrant(root) {
|
|
116
116
|
try {
|
|
117
117
|
return parseGrant(readFileSync(grantPath(root), 'utf8'));
|
|
118
118
|
}
|
package/dist/lib/orient.js
CHANGED
|
@@ -741,6 +741,16 @@ export function parseSections(detail) {
|
|
|
741
741
|
export function parseRecommendation(detail) {
|
|
742
742
|
return sectionProse(detail, /^##\s+recommendation\s*$/i);
|
|
743
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* The step `.plumbbob/detail.md` was written for, read off its
|
|
746
|
+
* `# Detail · Step <n> · <title>` header, or null when the first line is not a
|
|
747
|
+
* step header (a plan pause's detail, or a file the model wrote without one).
|
|
748
|
+
*/
|
|
749
|
+
export function parseDetailStep(detail) {
|
|
750
|
+
const first = detail.trimStart().split('\n', 1)[0] ?? '';
|
|
751
|
+
const m = /^#\s+Detail\b.*?\bStep\s+(\d+)\b/.exec(first);
|
|
752
|
+
return m === null ? null : Number(m[1]);
|
|
753
|
+
}
|
|
744
754
|
/**
|
|
745
755
|
* Render an Orientation as the plain-text dashboard `status` prints.
|
|
746
756
|
*/
|
package/dist/verbs/build.js
CHANGED
|
@@ -10,14 +10,16 @@ import { hasSession, intentPath, resolveBuild, seamPath, stampStepStat, stampTic
|
|
|
10
10
|
import { parseStepSeam } from "../lib/intent.js";
|
|
11
11
|
import { parseSteps } from "../lib/orient.js";
|
|
12
12
|
import { stepLabel, syncBuildLogState } from "../lib/buildlogsync.js";
|
|
13
|
+
import { readGrant } from "../lib/latch.js";
|
|
13
14
|
import { notice } from "../lib/notice.js";
|
|
14
15
|
/**
|
|
15
16
|
* Enter step n (or the next undone step) and write its SEAM/STEP markers.
|
|
16
17
|
*
|
|
17
18
|
* Refuses a missing session, a malformed step argument, an `N-M` range (a
|
|
18
19
|
* `/plumbbob:build` skill feature, not a CLI one), and a seam that fails to parse.
|
|
19
|
-
* On entry it stamps the turn ledger and the step's start time,
|
|
20
|
-
*
|
|
20
|
+
* On entry it stamps the turn ledger and the step's start time, re-renders the
|
|
21
|
+
* build-log's Current step line, and says so when the step is the top of a
|
|
22
|
+
* range the human typed.
|
|
21
23
|
*/
|
|
22
24
|
export function build(cwd, args) {
|
|
23
25
|
const root = findRepoRoot(cwd);
|
|
@@ -28,12 +30,13 @@ export function build(cwd, args) {
|
|
|
28
30
|
const { build: slug, rest } = resolveBuild(root, args);
|
|
29
31
|
const raw = rest.find((a) => !a.startsWith('--'));
|
|
30
32
|
// A step range like `1-3` is a `/plumbbob:build` skill affordance (auto-approve
|
|
31
|
-
// through the range, then
|
|
32
|
-
// in-flight step at a time. Name it rather than bounce off the
|
|
33
|
+
// every step through the range's top, then stop), not a CLI capability: the CLI
|
|
34
|
+
// records one in-flight step at a time. Name it rather than bounce off the
|
|
35
|
+
// generic usage.
|
|
33
36
|
if (raw !== undefined && /^\d+-\d*$/.test(raw)) {
|
|
34
37
|
process.stderr.write(notice({
|
|
35
38
|
fact: 'build takes one step number',
|
|
36
|
-
detail: [`\`${raw}\` is a /plumbbob:build range, which auto-approves through
|
|
39
|
+
detail: [`\`${raw}\` is a /plumbbob:build range, which auto-approves through its top and then stops`],
|
|
37
40
|
remedy: `plumbbob build ${raw.split('-')[0]}`,
|
|
38
41
|
}));
|
|
39
42
|
return 1;
|
|
@@ -94,9 +97,22 @@ export function build(cwd, args) {
|
|
|
94
97
|
: skipped > 0
|
|
95
98
|
? ['explicitly requested', `skips ${skipped} undone step${skipped === 1 ? '' : 's'}`]
|
|
96
99
|
: [];
|
|
97
|
-
//
|
|
98
|
-
//
|
|
100
|
+
// The top step of a typed range lands like every step under it, and the turn
|
|
101
|
+
// ends on its checkpoint. Saying so as the step is entered puts that fact in
|
|
102
|
+
// front of the model before it chooses between landing the step and holding
|
|
103
|
+
// it at a pause, which is the choice a range kept getting wrong.
|
|
104
|
+
const grant = readGrant(root);
|
|
105
|
+
const top = grant?.kind === 'range' && grant.ceiling === step
|
|
106
|
+
? notice({
|
|
107
|
+
fact: `step ${step} is the top of the range you granted`,
|
|
108
|
+
remedy: 'land it on green, then stop at the boundary',
|
|
109
|
+
})
|
|
110
|
+
: '';
|
|
111
|
+
// Two lines (three at a range's top), one colon each, then the seam as a
|
|
112
|
+
// plain readout beneath the notice that frames it: the paths are a list, and
|
|
113
|
+
// a list is not a one-liner.
|
|
99
114
|
process.stdout.write(notice({ fact: `building step ${step}`, detail: picked }) +
|
|
115
|
+
top +
|
|
100
116
|
notice({
|
|
101
117
|
fact: 'the seam is orientation, not a lock',
|
|
102
118
|
detail: [`${parsed.seam.length} path${parsed.seam.length === 1 ? '' : 's'}`],
|
package/dist/verbs/checkpoint.js
CHANGED
|
@@ -16,7 +16,7 @@ import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, check
|
|
|
16
16
|
import { runCheck } from "../lib/check.js";
|
|
17
17
|
import { readCommitBody } from "../lib/commitbody.js";
|
|
18
18
|
import { checkLatch } from "../lib/latch.js";
|
|
19
|
-
import { markStepDone, parseSteps } from "../lib/orient.js";
|
|
19
|
+
import { markStepDone, parseDetailStep, parseSteps } from "../lib/orient.js";
|
|
20
20
|
import { parseBuildScope, parseStepSeam, scopeDrift } from "../lib/intent.js";
|
|
21
21
|
import { conventionalSubject, subjectFromTitle, withMarker } from "../lib/commitmsg.js";
|
|
22
22
|
import { appendToSection, checkpointLogLine, logEntry, planLogLine } from "../lib/buildlog.js";
|
|
@@ -87,7 +87,7 @@ export async function checkpoint(cwd, args) {
|
|
|
87
87
|
// tracked ledger is the archive, and it rides the branch where a commit body
|
|
88
88
|
// would not survive the squash); the commit body keeps its marker and the
|
|
89
89
|
// lead prose only.
|
|
90
|
-
const record = stepRecord(root, activeBuild(root), step, readDetail(root) ?? '');
|
|
90
|
+
const record = stepRecord(root, activeBuild(root), step, readDetail(root, step) ?? '');
|
|
91
91
|
let sha;
|
|
92
92
|
// The drift advisory is read off the index (staged, before the commit clears
|
|
93
93
|
// it) but printed inside the ending below, where the fixed order puts it: the
|
|
@@ -165,7 +165,7 @@ function checkpointPlan(root, args) {
|
|
|
165
165
|
appendFileSync(checkpointsPath(root), `plan ${sha}\n`);
|
|
166
166
|
// The cold read that approved the plan is the Log's first entry, beneath a
|
|
167
167
|
// dated plan line; the detail file is then spent, the same as at a step.
|
|
168
|
-
const pointer = logPlan(root, sha, planRecord(readDetail(root) ?? ''));
|
|
168
|
+
const pointer = logPlan(root, sha, planRecord(readDetail(root, null) ?? ''));
|
|
169
169
|
clearDetail(root);
|
|
170
170
|
// Landing the plan consumes `start`'s entry stamp: a later hand-built diff
|
|
171
171
|
// (no `build <n>`) must find no stale TICK and stay guidance-governed.
|
|
@@ -444,16 +444,23 @@ function fallbackBody(root, step) {
|
|
|
444
444
|
}
|
|
445
445
|
/**
|
|
446
446
|
* The in-flight step's detail from `.plumbbob/detail.md`, or null when the file
|
|
447
|
-
* is absent or
|
|
447
|
+
* is absent, empty, or written for another step.
|
|
448
448
|
*
|
|
449
449
|
* The model overwrites it before every pause; checkpoint reads it once here for
|
|
450
450
|
* the Log's record, and the caller clears it afterward. A missing or blank file
|
|
451
|
-
* records nothing
|
|
451
|
+
* records nothing, and so does one whose header names a different step: that
|
|
452
|
+
* file is a leftover, written after its own step had already landed, and
|
|
453
|
+
* recording it would file one step's story under another. The plan commit
|
|
454
|
+
* passes no step and takes the file as it is.
|
|
452
455
|
*/
|
|
453
|
-
function readDetail(root) {
|
|
456
|
+
function readDetail(root, step) {
|
|
454
457
|
try {
|
|
455
458
|
const raw = readFileSync(detailPath(root), 'utf8').trim();
|
|
456
|
-
|
|
459
|
+
if (raw.length === 0) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
const written = step === null ? null : parseDetailStep(raw);
|
|
463
|
+
return written === null || written === step ? raw : null;
|
|
457
464
|
}
|
|
458
465
|
catch {
|
|
459
466
|
return null;
|
package/package.json
CHANGED
package/skills/build/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 through step 3, then
|
|
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 through step 3, then stops."
|
|
4
4
|
argument-hint: "[step-number | step-range] [--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:*)
|
|
@@ -36,8 +36,8 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
36
36
|
|
|
37
37
|
1. **Pick the step.** Use the number you were invoked with (for example `/plumbbob:build 4`), or,
|
|
38
38
|
if you were given a range like `/plumbbob:build 1-3`, start at the first number and treat
|
|
39
|
-
the second as the
|
|
40
|
-
argument, don't resolve the next step yourself; the CLI does: bare `plumbbob build`
|
|
39
|
+
the second as the last step you approve and land yourself (see the range note under
|
|
40
|
+
`--auto`). With no argument, don't resolve the next step yourself; the CLI does: bare `plumbbob build`
|
|
41
41
|
(step 2) enters the next undone step and refuses with a `/plumbbob:step` nudge when every
|
|
42
42
|
step is checkpointed.
|
|
43
43
|
2. **Enter the step.** Run `plumbbob build <n>`, or bare `plumbbob build` to enter the
|
|
@@ -87,7 +87,9 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
87
87
|
<<'BODY'`). Do **not** pass it as an argument value (`--body "$(cat <<'BODY'…)"`):
|
|
88
88
|
`--body` ignores an argument and always reads stdin. Under an agent harness that
|
|
89
89
|
stdin is a socket, and `--body` now refuses rather than blocking on one; the
|
|
90
|
-
refusal names this exact heredoc form.
|
|
90
|
+
refusal names this exact heredoc form. End that line at `<<'BODY'`, with no `2>&1`
|
|
91
|
+
after it: Claude Code's permission checker cannot analyze the extra redirect, so it
|
|
92
|
+
blocks the command or stops to ask instead of letting this skill's allowance cover it.
|
|
91
93
|
- **A drifted subject is reconciled at the pause, never silently.** The planned
|
|
92
94
|
title *is* the checkpoint subject ([D68 (conventional-subjects)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d68)). If the diff drifted
|
|
93
95
|
from it, **present** a corrected subject at the pause (`planned title → proposed
|
|
@@ -256,30 +258,42 @@ and approves in the human's place**, and it **chains**:
|
|
|
256
258
|
self-review finds no done-when / Decision / Constraint mismatch, checkpoint** and move
|
|
257
259
|
straight on to the next planned step. Repeat. `--auto` adds no new machinery; the
|
|
258
260
|
`after`-agent output simply feeds the *existing* self-review halt condition.
|
|
259
|
-
- **Stop and hand back to the human** the moment
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
- **Stop and hand back to the human** the moment a halt arrives, and end the turn by the
|
|
262
|
+
kind of halt it is
|
|
263
|
+
([D85 (range-top-lands)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d85)):
|
|
264
|
+
- **A halt on trouble leaves a step unlanded.** The check is red, the self-review finds
|
|
265
|
+
a mismatch (surface exactly what, and do not checkpoint it), a bound agent returns
|
|
266
|
+
`blocked` or `drift` (unblock-and-re-run, or `/plumbbob:refine`; an agent cannot
|
|
267
|
+
advance the loop), or a new decision is needed. The step is still in flight and the
|
|
268
|
+
human has a call to make about it, so end on the same turn the default pause does:
|
|
269
|
+
write the detail file, run `plumbbob handoff`, and paste its block.
|
|
270
|
+
- **A clean halt lands its step first.** The top of a requested range is reached, or no
|
|
271
|
+
planned steps remain. That step checkpoints like every step before it, and its
|
|
272
|
+
checkpoint's block is the whole ending: relay it with nothing written around it, then
|
|
273
|
+
end the turn. Write no detail file and run no `handoff`, because nothing is pending.
|
|
274
|
+
The block already carries the Verdict the step earned and a Next Up pointing at what
|
|
275
|
+
comes next (the next undone step and its `- model:` recommendation, or the finish),
|
|
276
|
+
which is what a fresh context window needs to pick the right `/model`.
|
|
268
277
|
|
|
269
278
|
`--auto` and a step range are the only paths that checkpoint without a human pause, and
|
|
270
|
-
only because the human asked for it by name
|
|
271
|
-
|
|
279
|
+
only because the human asked for it by name. A range hands the clock back once its top
|
|
280
|
+
step has landed: the step after it waits for the human's next run. The default (no flag,
|
|
281
|
+
no range) always ends at the pause.
|
|
272
282
|
|
|
273
283
|
### A step range (`N-M`) is a bounded `--auto`
|
|
274
284
|
|
|
275
|
-
`/plumbbob:build 1-3` self-approves steps 1 through 3 exactly as `--auto` does, then **
|
|
276
|
-
|
|
277
|
-
pass `--auto`. Run it as the `--auto` loop with one extra halt: **stop before building
|
|
278
|
-
any next step whose plan number is past the top of the range.** It adds no machinery:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
285
|
+
`/plumbbob:build 1-3` self-approves steps 1 through 3 exactly as `--auto` does, then **stops
|
|
286
|
+
once step 3 has landed** instead of chaining to done. The range *is* the opt-in; you do not
|
|
287
|
+
also pass `--auto`. Run it as the `--auto` loop with one extra halt: **stop before building
|
|
288
|
+
any next step whose plan number is past the top of the range.** It adds no machinery: just
|
|
289
|
+
the one more entry already in the halt list above, and it is a clean halt, so the top step
|
|
290
|
+
is never held back for approval. The human approved it by typing the range, and
|
|
291
|
+
`plumbbob build` says so as it enters that step:
|
|
292
|
+
`step 3 is the top of the range you granted`.
|
|
293
|
+
|
|
294
|
+
- **`N-M` with N ≤ M**: build N…M, self-approving and checkpointing each, M included, then
|
|
295
|
+
relay M's checkpoint block and end the turn. `N-N` is a range like any other: it builds
|
|
296
|
+
and lands the single step `N`. Only a bare `/plumbbob:build N` ends at the pause.
|
|
283
297
|
- **N > M** (for example `3-1`): that is not a range you can walk; report it and stop rather
|
|
284
298
|
than guess the intent.
|
|
285
299
|
- **M past the last planned step** (for example `1-9` with three steps): build through the
|
|
@@ -302,7 +316,7 @@ just the one more entry already in the halt list above.
|
|
|
302
316
|
- **Default ends at the pause.** Implement → verify → wait for approval; never
|
|
303
317
|
checkpoint without it. Only an explicit `--auto` or a step range lets the agent approve
|
|
304
318
|
in your place, and it still halts on a red check or any mismatch; a range also stops
|
|
305
|
-
|
|
319
|
+
once its top step lands.
|
|
306
320
|
- **Agents feed the beat; they never advance it**. `before` loads context,
|
|
307
321
|
`build` writes the diff, `after` is advisory; none can checkpoint, flip a step, or
|
|
308
322
|
chain. `blocked` → unblock and re-run; `drift` → `/plumbbob:refine`. You are still the one
|
package/skills/finish/SKILL.md
CHANGED
|
@@ -53,8 +53,11 @@ record.
|
|
|
53
53
|
Redirect the heredoc *into* the command (`--body <<'BODY'`). Do **not** pass it as
|
|
54
54
|
an argument value (`--body "$(cat <<'BODY'…)"`): `--body` ignores an argument and
|
|
55
55
|
always reads stdin. Under an agent harness that stdin is a socket, and `--body` now
|
|
56
|
-
refuses rather than blocking on one; the refusal names this exact heredoc form.
|
|
57
|
-
|
|
56
|
+
refuses rather than blocking on one; the refusal names this exact heredoc form. End
|
|
57
|
+
that line at `<<'BODY'`, with no `2>&1` after it: Claude Code's permission checker
|
|
58
|
+
cannot analyze the extra redirect, so it blocks the command or stops to ask instead of
|
|
59
|
+
letting this skill's allowance cover it. Omit `--body` entirely and the commit carries
|
|
60
|
+
the `plumbbob finish` marker subject only.
|
|
58
61
|
3. **Point at the next goal.** Relay `plumbbob finish`'s output verbatim, blank lines
|
|
59
62
|
included: its lead line, which names the branch the tracked folder now rides into the
|
|
60
63
|
PR, any advisory beneath it, and `**Next Up**: Nothing planned - /plumbbob:plan`. That
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -142,7 +142,8 @@ an agent can follow with `/plumbbob:build`. The argument only seeds how you get
|
|
|
142
142
|
a `plan <sha>` line in
|
|
143
143
|
`checkpoints`. This keeps the first step's diff clean, so history reads
|
|
144
144
|
baseline → plan → steps. Pass a proportional `--body` (the single-quoted stdin
|
|
145
|
-
heredoc
|
|
145
|
+
heredoc, with nothing after `<<'BODY'` on its line, not even `2>&1`) when the
|
|
146
|
+
rationale is worth carrying; skip it for a small plan. Do this
|
|
146
147
|
only on the human's approval; the plan is their convergence.
|
|
147
148
|
- **The plan pause is a decision turn.** Present the framed plan for the human's call,
|
|
148
149
|
then give it the cold read (**§ The cold read**), write the result into
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -163,7 +163,10 @@ this skill verifies it the same way: **it reads the diff, not the author**
|
|
|
163
163
|
Redirect the heredoc *into* the command (`--body <<'BODY'`). Do **not** pass it as
|
|
164
164
|
an argument value (`--body "$(cat <<'BODY'…)"`): `--body` ignores an argument and
|
|
165
165
|
always reads stdin. Under an agent harness that stdin is a socket, and `--body` now
|
|
166
|
-
refuses rather than blocking on one; the refusal names this exact heredoc form.
|
|
166
|
+
refuses rather than blocking on one; the refusal names this exact heredoc form. End
|
|
167
|
+
that line at `<<'BODY'`, with no `2>&1` after it: Claude Code's permission checker
|
|
168
|
+
cannot analyze the extra redirect, so it blocks the command or stops to ask instead of
|
|
169
|
+
letting this skill's allowance cover it.
|
|
167
170
|
|
|
168
171
|
Do **not** run a TIL scan or reach for a separate commit skill; the body is yours to
|
|
169
172
|
write in one breath. Omit `--body` entirely and the CLI writes a deterministic body
|