plumbbob 0.13.3 → 0.13.5
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 +24 -24
- package/dist/cli-core.js +6 -5
- package/dist/lib/notice.js +42 -7
- package/dist/lib/orient.js +379 -11
- package/dist/lib/sidecar.js +53 -31
- package/dist/verbs/checkpoint.js +12 -12
- package/dist/verbs/finish.js +7 -7
- package/dist/verbs/handoff.js +368 -302
- package/dist/verbs/harvest.js +20 -21
- package/dist/verbs/order.js +65 -21
- package/dist/verbs/relay.js +48 -6
- package/dist/verbs/turn.js +111 -31
- package/package.json +1 -1
- package/skills/build/SKILL.md +55 -51
- package/skills/finish/SKILL.md +10 -4
- package/skills/harvest/SKILL.md +43 -25
- package/skills/order/SKILL.md +19 -16
- package/skills/plan/SKILL.md +10 -9
- package/skills/refine/SKILL.md +17 -10
- package/skills/run/SKILL.md +34 -23
- package/skills/step/SKILL.md +28 -23
- package/skills/verify/SKILL.md +44 -39
package/dist/lib/sidecar.js
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
// human-owned (check/auto): the tool only ever reads it, never rewrites it. The
|
|
19
19
|
// phase the dashboard shows (DESIGN/BUILD/SPIKE) is derived, not stored: BUILD ⇔
|
|
20
20
|
// a STEP is in flight, SPIKE ⇔ the SPIKE marker is present, otherwise DESIGN.
|
|
21
|
-
import { createHash } from 'node:crypto';
|
|
22
21
|
import { existsSync, readdirSync, readFileSync, writeFileSync, appendFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
23
22
|
import { dirname, join } from 'node:path';
|
|
24
23
|
import { gitPath } from "./git.js";
|
|
@@ -215,56 +214,85 @@ export function clearStep(root, slug) {
|
|
|
215
214
|
rmSync(stepPath(root, slug), { force: true });
|
|
216
215
|
}
|
|
217
216
|
/**
|
|
218
|
-
* PAUSE: the
|
|
219
|
-
*
|
|
220
|
-
*
|
|
217
|
+
* PAUSE: which card the human has already read, so a later `handoff` prints
|
|
218
|
+
* the one line that points back at it instead of the card. It holds a step
|
|
219
|
+
* number once that step's pause card has shown (`2`), or `landed 2` once the
|
|
220
|
+
* boundary step 2 landed on has shown.
|
|
221
|
+
*
|
|
222
|
+
* It records that a card showed, never what the card said: a later turn that
|
|
223
|
+
* changes the work is the model's reply, and the card comes back only on
|
|
224
|
+
* request (D110 (reply-after-the-card)).
|
|
221
225
|
*/
|
|
222
226
|
export function pausePath(root, slug) {
|
|
223
227
|
return join(artifactDir(root, slug), 'PAUSE');
|
|
224
228
|
}
|
|
225
229
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
230
|
+
* The card PAUSE says has shown, or null when the file is absent or does not
|
|
231
|
+
* parse. A stamp written before D110 (reply-after-the-card) holds a step
|
|
232
|
+
* number and a hash, and reads as that step's pause.
|
|
233
|
+
*/
|
|
234
|
+
export function readPause(root, slug) {
|
|
235
|
+
try {
|
|
236
|
+
const m = /^(landed )?(\d+)(?:\s|$)/.exec(readFileSync(pausePath(root, slug), 'utf8').trim());
|
|
237
|
+
return m === null ? null : { kind: m[1] === undefined ? 'pause' : 'landed', step: Number(m[2]) };
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Record that a card has shown. Best-effort: a failed write costs one more
|
|
245
|
+
* full card on a later turn, never this turn's output or its exit code.
|
|
246
|
+
*/
|
|
247
|
+
export function writePause(root, slug, stamp) {
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(pausePath(root, slug), `${stamp.kind === 'landed' ? 'landed ' : ''}${stamp.step}\n`);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// A read-only tree or a vanished build folder: the card still renders.
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Remove the PAUSE stamp: a revert or an abandon ends the pause it recorded,
|
|
257
|
+
* so the next pause renders whole. Absent is a no-op.
|
|
228
258
|
*/
|
|
229
259
|
export function clearPause(root, slug) {
|
|
230
260
|
rmSync(pausePath(root, slug), { force: true });
|
|
231
261
|
}
|
|
232
262
|
/**
|
|
233
263
|
* STOP: the stamp of the stop card `handoff --stop` last rendered whole, the
|
|
234
|
-
* range it reviews
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* and only once a human turn has read it.
|
|
264
|
+
* range it reviews and the TURN it printed in. While a later render names the
|
|
265
|
+
* same range, handoff prints the one-line reminder, whatever changed since;
|
|
266
|
+
* `plumbbob harvest` records the stop only once a card for the range has shown
|
|
267
|
+
* and a human turn has read it.
|
|
239
268
|
*/
|
|
240
269
|
export function stopPath(root, slug) {
|
|
241
270
|
return join(artifactDir(root, slug), 'STOP');
|
|
242
271
|
}
|
|
243
272
|
/**
|
|
244
273
|
* The stamp of the stop card last rendered, or null when STOP is absent or
|
|
245
|
-
* does not parse.
|
|
274
|
+
* does not parse. A stamp written before D110 (reply-after-the-card) carries
|
|
275
|
+
* a hash and a digest after the turn, and both are ignored.
|
|
246
276
|
*/
|
|
247
277
|
export function readStop(root, slug) {
|
|
248
278
|
try {
|
|
249
|
-
const m = /^(\d+) (\d+|-)
|
|
279
|
+
const m = /^(\d+) (\d+|-)(?:\s|$)/.exec(readFileSync(stopPath(root, slug), 'utf8').trim());
|
|
250
280
|
if (m === null)
|
|
251
281
|
return null;
|
|
252
|
-
return { range: Number(m[1]), turn: m[2] === '-' ? null : Number(m[2])
|
|
282
|
+
return { range: Number(m[1]), turn: m[2] === '-' ? null : Number(m[2]) };
|
|
253
283
|
}
|
|
254
284
|
catch {
|
|
255
285
|
return null;
|
|
256
286
|
}
|
|
257
287
|
}
|
|
258
288
|
/**
|
|
259
|
-
* Record the stamp of the stop card just printed
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* this turn's output or its exit code.
|
|
289
|
+
* Record the stamp of the stop card just printed. Best-effort: a failed write
|
|
290
|
+
* costs one more full card on a later turn, never this turn's output or its
|
|
291
|
+
* exit code.
|
|
263
292
|
*/
|
|
264
293
|
export function writeStop(root, slug, stamp) {
|
|
265
294
|
try {
|
|
266
|
-
|
|
267
|
-
writeFileSync(stopPath(root, slug), `${stamp.range} ${stamp.turn ?? '-'} ${stamp.hash} ${detail}\n`);
|
|
295
|
+
writeFileSync(stopPath(root, slug), `${stamp.range} ${stamp.turn ?? '-'}\n`);
|
|
268
296
|
}
|
|
269
297
|
catch {
|
|
270
298
|
// A read-only tree or a vanished build folder: the card still renders.
|
|
@@ -277,13 +305,6 @@ export function writeStop(root, slug, stamp) {
|
|
|
277
305
|
export function clearStop(root, slug) {
|
|
278
306
|
rmSync(stopPath(root, slug), { force: true });
|
|
279
307
|
}
|
|
280
|
-
/**
|
|
281
|
-
* The digest a STOP stamp keeps of a detail file, so the record can tell the
|
|
282
|
-
* detail a card showed from one written after it.
|
|
283
|
-
*/
|
|
284
|
-
export function detailDigest(detail) {
|
|
285
|
-
return createHash('sha256').update(detail).digest('hex');
|
|
286
|
-
}
|
|
287
308
|
/**
|
|
288
309
|
* The build's `checkpoints` ledger: the recorded SHAs of landed plan and step
|
|
289
310
|
* commits, one line each.
|
|
@@ -689,7 +710,7 @@ export function readTurn(root) {
|
|
|
689
710
|
// ending: a decision ending closes the turn, an orientation ending appears in
|
|
690
711
|
// it, and a driver ending is recorded only so that the last ending wins.
|
|
691
712
|
const RELAY_TIERS = ['decision', 'orientation', 'driver'];
|
|
692
|
-
// Which ending printed: handoff's
|
|
713
|
+
// Which ending printed: handoff's nine forms first, then one per transition.
|
|
693
714
|
// It is what the check's reason names when the ending went missing.
|
|
694
715
|
const RELAY_RENDERS = [
|
|
695
716
|
'card',
|
|
@@ -700,6 +721,7 @@ const RELAY_RENDERS = [
|
|
|
700
721
|
'boundary',
|
|
701
722
|
'pointer',
|
|
702
723
|
'driver',
|
|
724
|
+
'harvest-card',
|
|
703
725
|
'checkpoint',
|
|
704
726
|
'plan-commit',
|
|
705
727
|
'park',
|
|
@@ -911,10 +933,10 @@ export function excludeControl(root) {
|
|
|
911
933
|
// The entry stamp is in-flight control like STEP/SEAM: checkpoint's
|
|
912
934
|
// stageAll must never sweep it into a step commit.
|
|
913
935
|
`${DIRNAME}/builds/*/TICK`,
|
|
914
|
-
// The pause stamp is in-flight control too:
|
|
915
|
-
//
|
|
936
|
+
// The pause stamp is in-flight control too: which card has shown, so a
|
|
937
|
+
// later handoff prints its one line. It must never ride a step commit.
|
|
916
938
|
`${DIRNAME}/builds/*/PAUSE`,
|
|
917
|
-
// The stop stamp likewise:
|
|
939
|
+
// The stop stamp likewise: the range whose card has shown, and the turn it
|
|
918
940
|
// printed in, for the reminder and the record's latch.
|
|
919
941
|
`${DIRNAME}/builds/*/STOP`,
|
|
920
942
|
// The agent-run handoff ledger is step-scoped in-flight state, not a
|
package/dist/verbs/checkpoint.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { appendFileSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { relative } from 'node:path';
|
|
14
14
|
import { commit, findRepoRoot, headSha, isDirty, stageAll, stagePath, stagedPaths, stagedStat } from "../lib/git.js";
|
|
15
|
-
import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, checkpointsPath, clearDetail, clearHandoff,
|
|
15
|
+
import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, checkpointsPath, clearDetail, clearHandoff, clearTick, detailPath, hasSession, intentPath, readRun, readStats, refreshExcludes, seamPath, stampStepStat, stepPath, writePause, } from "../lib/sidecar.js";
|
|
16
16
|
import { runCheck } from "../lib/check.js";
|
|
17
17
|
import { readCommitBody } from "../lib/commitbody.js";
|
|
18
18
|
import { checkLatch } from "../lib/latch.js";
|
|
@@ -22,8 +22,7 @@ import { conventionalSubject, subjectFromTitle, withMarker } from "../lib/commit
|
|
|
22
22
|
import { appendToSection, checkpointLogLine, logEntry, planLogLine } from "../lib/buildlog.js";
|
|
23
23
|
import { AT_BOUNDARY, syncBuildLogState } from "../lib/buildlogsync.js";
|
|
24
24
|
import { advisory, ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
25
|
-
import { boundaryEnding, detailsLink, parseDetailRange, planRecord, stepRecord } from "./handoff.js";
|
|
26
|
-
import { isHarvestDetail } from "./harvest.js";
|
|
25
|
+
import { boundaryEnding, detailsLink, isHarvestDetail, parseDetailRange, planRecord, stepRecord } from "./handoff.js";
|
|
27
26
|
/**
|
|
28
27
|
* Land a step: latch, check gate, commit, record, return to the boundary.
|
|
29
28
|
*
|
|
@@ -122,11 +121,14 @@ export async function checkpoint(cwd, args) {
|
|
|
122
121
|
rmSync(stepPath(root), { force: true });
|
|
123
122
|
clearHandoff(root); // the agent-run handoff ledger is step-scoped; clear it with the markers.
|
|
124
123
|
clearTick(root); // the step's entry stamp is spent; the next `build <n>` re-stamps.
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
//
|
|
124
|
+
// The pause it stamped is over, and the boundary this ending prints is the
|
|
125
|
+
// card the stamp now names, so a later handoff here ends on its pointer
|
|
126
|
+
// (D110 (reply-after-the-card)); the next step's pause renders whole.
|
|
127
|
+
writePause(root, undefined, { kind: 'landed', step });
|
|
128
|
+
// The whole ending, one block: the lead line, the advisories this checkpoint
|
|
129
|
+
// alone can state, and the state table headed by the Verdict folded over
|
|
130
|
+
// what just landed. Read after the flip and the ledger append, so the fold
|
|
131
|
+
// and the pointer both see the boundary the human is standing at.
|
|
130
132
|
const landed = boundaryEnding(root, activeBuild(root), step);
|
|
131
133
|
// A landing that completes the range a typed run marked owes the stop card:
|
|
132
134
|
// the record says so, and the relay check holds a turn that ends here.
|
|
@@ -137,10 +139,8 @@ export async function checkpoint(cwd, args) {
|
|
|
137
139
|
...(owes === null ? {} : { owes }),
|
|
138
140
|
text: ending({
|
|
139
141
|
lead: transition({ label: 'Checkpoint', fact: `Step ${step} complete`, detail: leadDetail(sha, pointer) }),
|
|
140
|
-
verdict: landed.verdict,
|
|
141
142
|
advisories: [drift, flipNotice],
|
|
142
|
-
|
|
143
|
-
pointer: landed.pointer,
|
|
143
|
+
state: landed.state,
|
|
144
144
|
}),
|
|
145
145
|
});
|
|
146
146
|
return 0;
|
|
@@ -202,7 +202,7 @@ function checkpointPlan(root, args) {
|
|
|
202
202
|
remedy: 'unignore .plumbbob/builds/ to keep the record in the tree',
|
|
203
203
|
}),
|
|
204
204
|
],
|
|
205
|
-
|
|
205
|
+
state: boundaryEnding(root, activeBuild(root), null).state,
|
|
206
206
|
}),
|
|
207
207
|
});
|
|
208
208
|
return 0;
|
package/dist/verbs/finish.js
CHANGED
|
@@ -18,10 +18,10 @@ import { buildScope, checkpointsPath, clearTick, hasSession, intentPath, readSta
|
|
|
18
18
|
import { conventionalSubject, withMarker } from "../lib/commitmsg.js";
|
|
19
19
|
import { parseBuildScope } from "../lib/intent.js";
|
|
20
20
|
import { advisory, ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
21
|
-
// The pointer finish leaves the turn on
|
|
22
|
-
// finished session has no state left to render
|
|
23
|
-
// the only move is framing the next goal.
|
|
24
|
-
const NEXT_UP = '
|
|
21
|
+
// The pointer finish leaves the turn on, as its state table's one row. Every
|
|
22
|
+
// other one is `handoff`'s, but a finished session has no state left to render
|
|
23
|
+
// from, and past a closed build the only move is framing the next goal.
|
|
24
|
+
const NEXT_UP = 'Nothing planned - /plumbbob:plan';
|
|
25
25
|
/**
|
|
26
26
|
* Close out the active build: report tail, final commit, control-state cleanup.
|
|
27
27
|
*
|
|
@@ -80,8 +80,8 @@ export function finish(cwd, args = []) {
|
|
|
80
80
|
rmSync(join(sidecarDir(root), 'STATE'), { force: true });
|
|
81
81
|
// The whole ending in the fixed order: the lead line, the advisory that
|
|
82
82
|
// qualifies it (finishing without a report is guidance, never a gate; the
|
|
83
|
-
// session is already closed by the time it prints), and the
|
|
84
|
-
// the turn's last text. finish renders that pointer itself: it just cleared
|
|
83
|
+
// session is already closed by the time it prints), and the state table's
|
|
84
|
+
// pointer, which is the turn's last text. finish renders that pointer itself: it just cleared
|
|
85
85
|
// the session `handoff` would read one from.
|
|
86
86
|
const where = slug === null ? '.plumbbob/' : `.plumbbob/builds/${slug}/`;
|
|
87
87
|
printEnding(root, {
|
|
@@ -102,7 +102,7 @@ export function finish(cwd, args = []) {
|
|
|
102
102
|
remedy: '/plumbbob:finish normally writes the report first',
|
|
103
103
|
}),
|
|
104
104
|
],
|
|
105
|
-
|
|
105
|
+
state: [{ label: 'Next Up', value: NEXT_UP }],
|
|
106
106
|
}),
|
|
107
107
|
});
|
|
108
108
|
return 0;
|