plumbbob 0.13.6 → 0.13.7
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 +5 -5
- package/dist/cli-core.js +2 -1
- package/dist/lib/links.js +97 -0
- package/dist/lib/notice.js +1 -1
- package/dist/lib/orient.js +114 -11
- package/dist/lib/sidecar.js +42 -5
- package/dist/verbs/build.js +20 -12
- package/dist/verbs/checkpoint.js +8 -4
- package/dist/verbs/handoff.js +248 -91
- package/dist/verbs/harvest.js +1 -1
- package/dist/verbs/recover.js +2 -2
- package/dist/verbs/relay.js +74 -6
- package/dist/verbs/start.js +40 -14
- package/dist/verbs/status.js +16 -4
- package/dist/verbs/turn.js +29 -12
- package/package.json +1 -1
- package/skills/build/SKILL.md +26 -14
- package/skills/park/SKILL.md +9 -6
- package/skills/plan/SKILL.md +35 -13
- package/skills/refine/SKILL.md +13 -4
- package/skills/run/SKILL.md +19 -6
- package/skills/status/SKILL.md +7 -0
- package/skills/verify/SKILL.md +12 -7
- package/templates/intent.md +14 -1
package/dist/verbs/relay.js
CHANGED
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
// stop card is a miss however whole the boundary was, since the card is the
|
|
30
30
|
// run's ending, and its reason names `plumbbob handoff --stop` as the retry.
|
|
31
31
|
//
|
|
32
|
+
// A reminder can close the turn and still leave the human without the reply.
|
|
33
|
+
// When the reply was written before a tool call and the final message is the
|
|
34
|
+
// reminder alone, a client that folds text written ahead of a tool call shows
|
|
35
|
+
// the line and nothing else (GitHub issue #12). That shape is held once too,
|
|
36
|
+
// by a reason that asks for the reply again and ends on the line to type. It
|
|
37
|
+
// names no command, since a retry that ran one would bury the reply the same
|
|
38
|
+
// way (D121 (reply-after-the-tools)).
|
|
39
|
+
//
|
|
32
40
|
// The final message comes from the payload's `last_assistant_message`, never
|
|
33
41
|
// from the transcript, which does not yet hold that message when Stop fires;
|
|
34
42
|
// the transcript supplies only the turn's earlier messages, which an
|
|
@@ -79,13 +87,53 @@ export function relayCheck(cwd, raw) {
|
|
|
79
87
|
// relayed: the card is the run's ending, and `handoff --stop` overwrites
|
|
80
88
|
// this record once it prints.
|
|
81
89
|
const carried = record.owes !== undefined ? false : carriedEnding(record, payload.last_assistant_message, payload);
|
|
90
|
+
if (carried === false)
|
|
91
|
+
return hold(root, record, reason(record));
|
|
92
|
+
// The reminder closed the turn, but the reply stood above a tool call.
|
|
93
|
+
if (carried === true && buriedReply(record, payload.last_assistant_message, payload))
|
|
94
|
+
return hold(root, record, buriedReason(record));
|
|
82
95
|
// Whole, or not this check's to judge: let the turn end.
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Stamp the once in `RELAY` and emit the hold with its reason, or nothing when
|
|
100
|
+
* the stamp cannot land.
|
|
101
|
+
*/
|
|
102
|
+
function hold(root, record, text) {
|
|
85
103
|
// A hold that cannot be counted could repeat, so it is no hold at all.
|
|
86
104
|
if (!holdRelay(root, record))
|
|
87
105
|
return null;
|
|
88
|
-
return `${JSON.stringify({ hookSpecificOutput: { hookEventName: 'Stop', additionalContext:
|
|
106
|
+
return `${JSON.stringify({ hookSpecificOutput: { hookEventName: 'Stop', additionalContext: text } })}\n`;
|
|
107
|
+
}
|
|
108
|
+
// The renders a reply at a card already shown closes on: the one line that
|
|
109
|
+
// points back at the pause or the stop.
|
|
110
|
+
const REMINDERS = new Set(['reminder', 'stop-reminder']);
|
|
111
|
+
/**
|
|
112
|
+
* Whether a reminder that closed the turn left the reply above a tool call:
|
|
113
|
+
* the final message is the reminder alone, with no words above its label,
|
|
114
|
+
* while the turn's earlier messages carried words. The printed line itself is
|
|
115
|
+
* not counted as words, since the transcript can already hold the final
|
|
116
|
+
* message when Stop fires. A transcript the check cannot read or scope is no
|
|
117
|
+
* evidence, and neither is a turn whose earlier messages said nothing.
|
|
118
|
+
*/
|
|
119
|
+
function buriedReply(record, last, payload) {
|
|
120
|
+
if (!REMINDERS.has(record.render) || leadOf(last).trim().length > 0)
|
|
121
|
+
return false;
|
|
122
|
+
const earlier = turnText(payload);
|
|
123
|
+
if (earlier === null)
|
|
124
|
+
return false;
|
|
125
|
+
return earlier.split(record.text.trim()).join('').trim().length > 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The one-line reason for a reply buried above a tool call: what happened, and
|
|
129
|
+
* the line to end on, with no command to run, since a retry that ran one would
|
|
130
|
+
* leave the reply above a tool call again.
|
|
131
|
+
*/
|
|
132
|
+
function buriedReason(record) {
|
|
133
|
+
return (`plumbbob: the turn ended on the ${DECISION_NAMES[record.render] ?? 'pause reminder'} it printed, ` +
|
|
134
|
+
'but the reply stood above a tool call, where a client can fold it out of sight. ' +
|
|
135
|
+
'Write the reply again, in your own words, with no tool call after it, ' +
|
|
136
|
+
`and end the turn on the same line, typed as it printed: ${record.text.trim()}`);
|
|
89
137
|
}
|
|
90
138
|
/**
|
|
91
139
|
* Whether the turn carried the recorded ending: true when it did, false when
|
|
@@ -197,7 +245,27 @@ function blockCount(part) {
|
|
|
197
245
|
* runs to the next. Text before the first label is not part of any.
|
|
198
246
|
*/
|
|
199
247
|
function partsOf(text) {
|
|
248
|
+
return readEnding(text).parts;
|
|
249
|
+
}
|
|
250
|
+
/** The text above an ending's first labeled part: the words a reply wrote above the line it closes on. */
|
|
251
|
+
function leadOf(text) {
|
|
252
|
+
return readEnding(text).lead.join('\n');
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* One walk over a stretch of text: the lines before its first labeled part,
|
|
256
|
+
* and the parts themselves, each run to the next, with a fence read as one
|
|
257
|
+
* stretch so a label inside it opens nothing.
|
|
258
|
+
*/
|
|
259
|
+
function readEnding(text) {
|
|
260
|
+
const lead = [];
|
|
200
261
|
const parts = [];
|
|
262
|
+
const keep = (line) => {
|
|
263
|
+
const last = parts.at(-1);
|
|
264
|
+
if (last === undefined)
|
|
265
|
+
lead.push(line);
|
|
266
|
+
else
|
|
267
|
+
last.lines.push(line);
|
|
268
|
+
};
|
|
201
269
|
let rail = null;
|
|
202
270
|
for (const line of text.split('\n')) {
|
|
203
271
|
const fence = /^\s*(`{3,})/.exec(line)?.[1];
|
|
@@ -206,7 +274,7 @@ function partsOf(text) {
|
|
|
206
274
|
}
|
|
207
275
|
else if (rail !== null && fence !== undefined && fence.length >= rail.length && line.trim() === fence) {
|
|
208
276
|
rail = null;
|
|
209
|
-
|
|
277
|
+
keep(line);
|
|
210
278
|
continue;
|
|
211
279
|
}
|
|
212
280
|
const label = rail === null ? labelOf(line) : undefined;
|
|
@@ -214,10 +282,10 @@ function partsOf(text) {
|
|
|
214
282
|
parts.push({ label, lines: [line] });
|
|
215
283
|
}
|
|
216
284
|
else {
|
|
217
|
-
|
|
285
|
+
keep(line);
|
|
218
286
|
}
|
|
219
287
|
}
|
|
220
|
-
return parts;
|
|
288
|
+
return { lead, parts };
|
|
221
289
|
}
|
|
222
290
|
/**
|
|
223
291
|
* The label a line opens a part on, or undefined when it opens none: a bold
|
package/dist/verbs/start.js
CHANGED
|
@@ -7,8 +7,8 @@ import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { detectGate } from "../lib/check.js";
|
|
9
9
|
import { readTemplate, stampTemplate } from "../lib/templates.js";
|
|
10
|
-
import { findRepoRoot, hasCommit, headSha, isDirty
|
|
11
|
-
import { sidecarDir, buildDir, listBuilds, slugify, checkpointsPath, intentPath, buildLogPath, beginSession, hasSession, excludeControl, excludeSidecar, setGrant, stampTick, } from "../lib/sidecar.js";
|
|
10
|
+
import { findRepoRoot, hasCommit, headSha, isDirty } from "../lib/git.js";
|
|
11
|
+
import { sidecarDir, buildDir, listBuilds, slugify, checkpointsPath, intentPath, buildLogPath, beginSession, hasSession, excludeControl, excludeSidecar, buildFolderIgnored, planCommitted, setGrant, stampTick, } from "../lib/sidecar.js";
|
|
12
12
|
import { settingsPath } from "../lib/settings.js";
|
|
13
13
|
import { advisory, ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
14
14
|
/**
|
|
@@ -41,6 +41,12 @@ export async function start(cwd, args) {
|
|
|
41
41
|
// Every advisory the session accrues on its way open, collected rather than
|
|
42
42
|
// printed: they belong inside the one ending below, in the order found.
|
|
43
43
|
const advisories = [];
|
|
44
|
+
// `--local` on a session already open is the plan card's way out: keep this
|
|
45
|
+
// build untracked before its plan commit lands. It needs no title.
|
|
46
|
+
const open = local ? findRepoRoot(cwd) : null;
|
|
47
|
+
if (open !== null && hasSession(open)) {
|
|
48
|
+
return keepLocal(open);
|
|
49
|
+
}
|
|
44
50
|
if (title.length === 0) {
|
|
45
51
|
process.stderr.write(notice({ fact: 'start needs a title', remedy: 'plumbbob start "what you are building"' }));
|
|
46
52
|
return 1;
|
|
@@ -154,7 +160,7 @@ export async function start(cwd, args) {
|
|
|
154
160
|
// excludes the whole sidecar by design and says so, so skip the probe there.
|
|
155
161
|
// Guidance only, and the session is already open: a probe hiccup stays silent
|
|
156
162
|
// rather than half-crashing a scaffolded session.
|
|
157
|
-
if (!local &&
|
|
163
|
+
if (!local && buildFolderIgnored(root, slug)) {
|
|
158
164
|
advisories.push(advisory({
|
|
159
165
|
fact: 'this repo gitignores .plumbbob/',
|
|
160
166
|
detail: ['the build folder cannot ride the branch', 'plan and finish commits will be record-only'],
|
|
@@ -210,20 +216,40 @@ function datedSlug(title) {
|
|
|
210
216
|
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${base}`;
|
|
211
217
|
}
|
|
212
218
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
219
|
+
* Keep an open session untracked: `plumbbob start --local` typed while a build
|
|
220
|
+
* is open and its plan not yet committed, the way out the plan card offers
|
|
221
|
+
* (D117 (publish-on-the-card)).
|
|
215
222
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
223
|
+
* It writes the blanket `.plumbbob/` line to this clone's info/exclude, the
|
|
224
|
+
* line a `--local` start writes, so the plan and finish commits land
|
|
225
|
+
* record-only and nothing under `.plumbbob/` rides the branch. The folder
|
|
226
|
+
* keeps its place and its slug; only git's view of it changes. Refuses once the
|
|
227
|
+
* plan has landed, since the folder is in a commit by then and an exclude
|
|
228
|
+
* cannot take it back.
|
|
219
229
|
*/
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
function keepLocal(root) {
|
|
231
|
+
if (planCommitted(root)) {
|
|
232
|
+
process.stderr.write(notice({
|
|
233
|
+
fact: 'this session can no longer be kept local',
|
|
234
|
+
detail: ['its plan is already committed with the build folder'],
|
|
235
|
+
remedy: 'plumbbob finish to close it before starting another',
|
|
236
|
+
}));
|
|
237
|
+
return 1;
|
|
226
238
|
}
|
|
239
|
+
excludeSidecar(root);
|
|
240
|
+
printEnding(root, {
|
|
241
|
+
tier: 'driver',
|
|
242
|
+
render: 'start',
|
|
243
|
+
text: ending({
|
|
244
|
+
lead: transition({
|
|
245
|
+
label: 'Session',
|
|
246
|
+
fact: 'kept local',
|
|
247
|
+
detail: ['.plumbbob/ excluded in this clone', 'the plan and finish commits land record-only'],
|
|
248
|
+
remedy: 'plumbbob handoff --plan to show the plan card again',
|
|
249
|
+
}),
|
|
250
|
+
}),
|
|
251
|
+
});
|
|
252
|
+
return 0;
|
|
227
253
|
}
|
|
228
254
|
/**
|
|
229
255
|
* Read the value that follows a `--flag` in argv (for example `--slug my-build`), or
|
package/dist/verbs/status.js
CHANGED
|
@@ -8,14 +8,15 @@
|
|
|
8
8
|
// `--run`, it prints the run a `/plumbbob:run` invocation names above the
|
|
9
9
|
// dashboard, and names the waiting card again beneath the run's grant.
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
|
-
import { join } from 'node:path';
|
|
11
|
+
import { join, relative } from 'node:path';
|
|
12
12
|
import { PLAN_COMMIT_MARKER, commitsSince, findRepoRoot } from "../lib/git.js";
|
|
13
13
|
import { SLOTS, readHarnessFile, resolveAgent, } from "../lib/agents.js";
|
|
14
14
|
import { buildFolder, buildLogPath, checkpointsPath, hasSession, inSpike, intentPath, listBuilds, resolveBuild, stepPath, } from "../lib/sidecar.js";
|
|
15
15
|
import { readGrant } from "../lib/latch.js";
|
|
16
|
-
import { formatOrientation, formatRunDashboard, lastLedgerSha, orient, parseRequestedStep, resolveRunArgs, } from "../lib/orient.js";
|
|
16
|
+
import { formatOrientation, formatRunDashboard, lastLedgerSha, orient, parseOpenQuestionRows, parseRequestedStep, resolveRunArgs, } from "../lib/orient.js";
|
|
17
17
|
import { normalizeStepList } from "../lib/steplist.js";
|
|
18
|
-
import {
|
|
18
|
+
import { blocks, notice } from "../lib/notice.js";
|
|
19
|
+
import { questionsPart, waitingCard } from "./handoff.js";
|
|
19
20
|
/**
|
|
20
21
|
* Read a file as utf8, or '' when it is missing or unreadable.
|
|
21
22
|
*/
|
|
@@ -67,7 +68,10 @@ function grantText(grant) {
|
|
|
67
68
|
* With `--run "<raw args>"` (how the `/plumbbob:run` skill passes its own),
|
|
68
69
|
* the dashboard prints beneath a header naming the run the text resolves to
|
|
69
70
|
* and the grant the turn hook minted, or carries the refusal in its pointer
|
|
70
|
-
* when the run names nothing it can walk.
|
|
71
|
+
* when the run names nothing it can walk. With `--questions` it prints the
|
|
72
|
+
* plan's open questions as the plan card tables them, and nothing else: the
|
|
73
|
+
* ones that wait on the human in the table, and the ones deferred to the work
|
|
74
|
+
* on a line beneath it.
|
|
71
75
|
*/
|
|
72
76
|
export function status(cwd, args = []) {
|
|
73
77
|
const root = findRepoRoot(cwd);
|
|
@@ -88,6 +92,14 @@ export function status(cwd, args = []) {
|
|
|
88
92
|
return 0;
|
|
89
93
|
}
|
|
90
94
|
}
|
|
95
|
+
if (rest.includes('--questions')) {
|
|
96
|
+
// The open questions alone, as the plan card tables them: the view a skill
|
|
97
|
+
// relays when the human asks what is still open (D118 (questions-table)),
|
|
98
|
+
// with the deferred ones on their own line (D120 (deferred-questions)).
|
|
99
|
+
const part = questionsPart(parseOpenQuestionRows(readOr(intentPath(root, slug))), relative(root, intentPath(root, slug)));
|
|
100
|
+
process.stdout.write(part === null ? notice({ fact: 'no open questions in this plan' }) : blocks([part]));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
91
103
|
const inFlightRaw = readOr(stepPath(root, slug)).trim();
|
|
92
104
|
// Out-of-band commits (ones that landed outside the checkpoints ledger) are
|
|
93
105
|
// surfaced here, never blocked. Counting them needs git (orient is pure), so
|
package/dist/verbs/turn.js
CHANGED
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
// is in context) still knows it: mid-step, a tangent is parked, not chased;
|
|
20
20
|
// at a pause or a stop whose card has shown, the message is the human's reply,
|
|
21
21
|
// answered in the model's own words and closed on the one line `handoff`
|
|
22
|
-
// prints, never on the card again (D110 (reply-after-the-card)).
|
|
22
|
+
// prints, never on the card again (D110 (reply-after-the-card)). Every tool
|
|
23
|
+
// call comes before that answer and none after it, since a client that folds
|
|
24
|
+
// text written ahead of a tool call would show the line and hide the answer
|
|
25
|
+
// (D121 (reply-after-the-tools)). Guidance
|
|
23
26
|
// only: it blocks nothing. It ALWAYS exits 0 and any failure degrades to no
|
|
24
27
|
// output, so neither the tick nor the emit can ever wedge prompting.
|
|
25
28
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -60,8 +63,9 @@ export function turn(cwd, _args) {
|
|
|
60
63
|
* Three beats, read off the control files. A step whose pause card has shown
|
|
61
64
|
* (STEP, and PAUSE naming that step's pause) is waiting for the human's call,
|
|
62
65
|
* so the message is the reply to it: answered or acted on in the model's own
|
|
63
|
-
* words, and closed on the one line `handoff` prints
|
|
64
|
-
* again (D110 (reply-after-the-card)
|
|
66
|
+
* words after every tool call, and closed on the one line `handoff` prints
|
|
67
|
+
* rather than the card again (D110 (reply-after-the-card),
|
|
68
|
+
* D121 (reply-after-the-tools)). A step in flight with no card yet is
|
|
65
69
|
* mid-build, where a tangent is a park and not an edit. A stop card waiting
|
|
66
70
|
* for its reply is the pause's beat at a stop. At a pause or a stop, a
|
|
67
71
|
* `prompt` asking to see the card (`show the pause`, the phrase the card's
|
|
@@ -127,18 +131,23 @@ function parkNudge(step) {
|
|
|
127
131
|
return (`plumbbob: step ${step} is in flight — a new idea or tangent is a park, not an edit: ` +
|
|
128
132
|
`capture it with \`plumbbob park "<one line>"\` and relay the block it prints whole, ` +
|
|
129
133
|
`with nothing written around it, since that block is the whole turn. ` +
|
|
130
|
-
`The one exception is a message that also asks something: answer the question
|
|
131
|
-
`then
|
|
134
|
+
`The one exception is a message that also asks something: run \`plumbbob park\` first, then answer the question, ` +
|
|
135
|
+
`then end the turn on the park's block, typed as it printed, with no tool call after the answer. ` +
|
|
132
136
|
`The block's pointer back at the step closes the turn either way, so run no \`plumbbob handoff\` after it. ` +
|
|
133
137
|
`The step stays open and lands when you run \`plumbbob checkpoint ${step}\` after approval.`);
|
|
134
138
|
}
|
|
135
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* The nudge at a pause whose card has shown: the message is the reply, every
|
|
141
|
+
* tool call comes before it, and the card is not printed again.
|
|
142
|
+
*/
|
|
136
143
|
function pauseNudge(step) {
|
|
137
144
|
return (`plumbbob: step ${step}'s card has shown, and this message is the human's reply to it. ` +
|
|
138
145
|
`An approval lands the step: run \`plumbbob checkpoint ${step}\` and relay its ending. ` +
|
|
139
|
-
`Anything else is answered in your own words
|
|
140
|
-
`
|
|
141
|
-
`
|
|
146
|
+
`Anything else is answered in your own words, after every tool call and with none after it: ` +
|
|
147
|
+
`first read what you need and make the change it asks for, ` +
|
|
148
|
+
`write \`.plumbbob/detail.md\` again when a change moves what the card said, so the record matches what lands, ` +
|
|
149
|
+
`and run \`plumbbob handoff\` last among them, keeping the one line it prints. ` +
|
|
150
|
+
REPLY_LAST +
|
|
142
151
|
`Only when the human asks to see the card again, run \`plumbbob handoff --full\` and paste its output exactly as it printed, as the whole turn. ` +
|
|
143
152
|
`A tangent the human hands you is still a park: \`plumbbob park "<one line>"\`.`);
|
|
144
153
|
}
|
|
@@ -146,11 +155,19 @@ function pauseNudge(step) {
|
|
|
146
155
|
function stopNudge(range, count) {
|
|
147
156
|
return (`plumbbob: range ${range} of ${count}'s stop card has shown, and this message is the human's reply to it. ` +
|
|
148
157
|
`An approval records the stop: fold each confirmed blocker into the plan, then run \`plumbbob harvest\` and relay its ending. ` +
|
|
149
|
-
`Anything else is answered in your own words
|
|
150
|
-
`(a lean is its line under the detail file's \`## Harvest\`)
|
|
151
|
-
`
|
|
158
|
+
`Anything else is answered in your own words, after every tool call and with none after it: ` +
|
|
159
|
+
`first read what you need and make the change it asks for (a lean is its line under the detail file's \`## Harvest\`), ` +
|
|
160
|
+
`and run \`plumbbob handoff --stop\` last among them, keeping the one line it prints. ` +
|
|
161
|
+
REPLY_LAST +
|
|
152
162
|
`Only when the human asks to see the card again, run \`plumbbob handoff --stop --full\` and paste its output exactly as it printed, as the whole turn.`);
|
|
153
163
|
}
|
|
164
|
+
// How a reply at a card already shown ends, the same at a pause and a stop: the
|
|
165
|
+
// answer after the last tool call, then the line, because a client that folds
|
|
166
|
+
// text written before a tool call shows the line and hides the answer
|
|
167
|
+
// (D121 (reply-after-the-tools)).
|
|
168
|
+
const REPLY_LAST = `Then write your answer and end the turn on that line, typed as it printed, with no tool call after your answer: ` +
|
|
169
|
+
`a client that folds text written before a tool call would hide the answer and show the line alone. ` +
|
|
170
|
+
`Print no card for it. `;
|
|
154
171
|
/**
|
|
155
172
|
* The in-flight step number, or null when no STEP marker is present.
|
|
156
173
|
*
|
package/package.json
CHANGED
package/skills/build/SKILL.md
CHANGED
|
@@ -186,7 +186,9 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
186
186
|
of its own, one command to a line, which handoff keeps line for line. A slash command
|
|
187
187
|
or a path stays in backticks in the sentence. `checkpoint` records this file beneath the step's dated
|
|
188
188
|
line in the build-log's `## Log` and then truncates it, so the detail lands in the
|
|
189
|
-
tracked ledger rather than piling up on disk.
|
|
189
|
+
tracked ledger rather than piling up on disk. Write a relative link from `.plumbbob/`, where
|
|
190
|
+
the file lives (`builds/<slug>/intent.md#d5`); the record re-aims it at the build folder.
|
|
191
|
+
A decision cited by its slug in plain text (`D5 (no-link-no-reconnect)`) needs no link at all.
|
|
190
192
|
|
|
191
193
|
**A bare range's pause is its stop.** When the injected dashboard's ranges block lists
|
|
192
194
|
this step as a bare range (`/plumbbob:build 5`, its row marked `▸`), the plan reserved a
|
|
@@ -213,7 +215,7 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
213
215
|
one, the write-versus-relay line of
|
|
214
216
|
[D78 (em-dash-ban)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d78).
|
|
215
217
|
|
|
216
|
-
*Then answer the reply in your own words.* The card shows once
|
|
218
|
+
*Then answer the reply in your own words, after every tool call.* The card shows once
|
|
217
219
|
([D110 (reply-after-the-card)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d110)):
|
|
218
220
|
every later turn at this pause is your reply, closed by one line. A message that
|
|
219
221
|
**asks** ("expand 2", "what does that mean?", "why did the seam row flag that?") is an
|
|
@@ -222,11 +224,16 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
222
224
|
make the change, run the check, and say what moved and what the check found; when the
|
|
223
225
|
change moves what the card said (a judgment row, the Summary, the recommendation), write
|
|
224
226
|
the detail file again so the record matches what lands. Nothing lands until the human
|
|
225
|
-
says `looks good`. Either way,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
says `looks good`. Either way, every tool call comes before your reply and none after
|
|
228
|
+
it ([D121 (reply-after-the-tools)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d121)):
|
|
229
|
+
read, edit, run the check, write the detail file, and run `plumbbob handoff` last among
|
|
230
|
+
them. At a pause whose card has shown it prints one line,
|
|
231
|
+
`**Next Up**: Back to **Step N** on <Model> - <title>`, whatever changed since. Keep
|
|
232
|
+
that line, then write your reply, and end the turn on the line, whole and verbatim. The
|
|
233
|
+
turn is your tool calls, then your reply, then that line, and nothing after it: a
|
|
234
|
+
client that folds text written before a tool call (Zed, through the agent bridge) shows
|
|
235
|
+
the line and hides a reply written above the call. The CLI picks the form, never you:
|
|
236
|
+
paste whichever one it prints, and never retype the card.
|
|
230
237
|
|
|
231
238
|
The card comes back one way: the human asks to see it (`show the pause`, or any wording
|
|
232
239
|
of it). Then run `plumbbob handoff --full` and paste its output whole, with nothing
|
|
@@ -238,12 +245,13 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
238
245
|
**Then hand off with the next model.** `plumbbob checkpoint` prints the whole boundary
|
|
239
246
|
ending itself: `**Checkpoint**: Step N complete (<sha>)`, any advisory (a staged path
|
|
240
247
|
outside the seam is the common one), and the state table: the Verdict the step earned,
|
|
241
|
-
the Stop row when this landing completes a range (`| Stop | Range 3
|
|
248
|
+
the Stop row when this landing completes a range (`| Stop | **Range 3** landed - <its
|
|
242
249
|
stop> |`, the reason the build stops here), Next Up, and the rows beneath it. Relay that
|
|
243
250
|
block whole, the table with the rest (the relay check holds a boundary that dropped a
|
|
244
251
|
row), run no second command, and write nothing around it. A later turn at this boundary
|
|
245
|
-
is your reply
|
|
246
|
-
has shown
|
|
252
|
+
is your reply, in the same order as at a pause: a `plumbbob handoff` there prints Next
|
|
253
|
+
Up's one line, since the boundary has shown, so run it last among your tool calls, then
|
|
254
|
+
write your reply and end on that line, with no tool call after it. It is the
|
|
247
255
|
orientation tier (no Your Call block, no recommendation), and its Next Up points at the
|
|
248
256
|
next undone step carrying that step's `- model:` recommendation (the plan's
|
|
249
257
|
smallest-model-that-fits call) so the human knows which `/model` to select before
|
|
@@ -371,9 +379,13 @@ out, or the next step in build order is one the range does not name),
|
|
|
371
379
|
- **A step inside the range won't build** (its seam won't parse): `plumbbob build <n>` fails
|
|
372
380
|
exactly as it always does; surface it and stop, the same as a red check. Do not skip past
|
|
373
381
|
the gap.
|
|
374
|
-
- **The range starts above the next undone step
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
- **The range starts above the next undone step** (the order reads `6, 4, 5` under
|
|
383
|
+
`/plumbbob:build 4-5`): the first step enters as a single-number jump like `/plumbbob:build 4`
|
|
384
|
+
would, with the skipped step noted. Once it lands, the next undone step in build order is
|
|
385
|
+
the one you jumped, which the range does not name, so the halt above fires there, and
|
|
386
|
+
`plumbbob build` names step 4 as the last undone step in the range, its detail naming step 6
|
|
387
|
+
as the step the walk halts before. The range that walks the whole stretch names the jumped
|
|
388
|
+
step too (`/plumbbob:build 6,4-5`).
|
|
377
389
|
- **The build order puts a step the range does not name inside it** (the order reads
|
|
378
390
|
`1, 4, 2, 3` under `/plumbbob:build 1-3`): the halt above fires before step 4, and
|
|
379
391
|
`plumbbob build` names step 1 as the last undone step in the range. The range that walks
|
|
@@ -388,7 +400,7 @@ out, or the next step in build order is one the range does not name),
|
|
|
388
400
|
- **After an early stop, settle the step, then rerun what is left.** A halt on trouble
|
|
389
401
|
inside a range ends at that step's pause, and the grant ends with the turn. Once the
|
|
390
402
|
human approves and the step lands, the state table's Range row names the run that lands
|
|
391
|
-
what is left of the range (`| Range | 3
|
|
403
|
+
what is left of the range (`| Range | **3** on Opus: /plumbbob:run (steps 8-9) |`), and that is
|
|
392
404
|
what the human types to carry on; `/plumbbob:build 8-9` lands the same steps by hand.
|
|
393
405
|
|
|
394
406
|
## The hard contracts
|
package/skills/park/SKILL.md
CHANGED
|
@@ -47,12 +47,15 @@ Then:
|
|
|
47
47
|
the capture plus that one pointer are the whole turn, and both are the CLI's to render,
|
|
48
48
|
never yours to compose from the dashboard. The capture is a one-beat interruption; the
|
|
49
49
|
step keeps its focus.
|
|
50
|
-
4. **A question beside the tangent is answered
|
|
51
|
-
asks something,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
4. **A question beside the tangent is answered after the capture.** When the human's
|
|
51
|
+
message also asks something, run `plumbbob park` first, then answer the question, then
|
|
52
|
+
end the turn on the park's block, typed as it printed, with no tool call after the
|
|
53
|
+
answer ([D121 (reply-after-the-tools)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d121)):
|
|
54
|
+
a client that folds text written before a tool call would show the block and hide the
|
|
55
|
+
answer. A message that is only a tangent ends on the block alone. Either way the
|
|
56
|
+
block's pointer back at the step closes the turn, so run no `plumbbob handoff` after
|
|
57
|
+
it. The answer is what the human asked for; dropping it to keep the block alone,
|
|
58
|
+
writing it above the capture, or stacking a second ending under the block are the ways
|
|
56
59
|
this goes wrong.
|
|
57
60
|
|
|
58
61
|
## The one hard contract
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -75,7 +75,24 @@ The argument only seeds how you get there.
|
|
|
75
75
|
stake, in plain words, enough to judge it cold), then a `*lean:*` sub-line (your
|
|
76
76
|
proposed resolution: one answer to react to, not a menu), so the human settles it in
|
|
77
77
|
one pass without a round-trip. (A tiny, obvious question may stay one bare line; the
|
|
78
|
-
sub-lines earn their keep only when a human decision genuinely waits on it.)
|
|
78
|
+
sub-lines earn their keep only when a human decision genuinely waits on it.) The plan
|
|
79
|
+
card tables the open questions, one row each, and a cell shows only the first sentence
|
|
80
|
+
of its line, so write each to stand on that sentence: the opener asks one question in
|
|
81
|
+
one sentence, `*plain:*` opens on why it matters, and `*lean:*` opens on the answer
|
|
82
|
+
([D118 (questions-table)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d118)).
|
|
83
|
+
The rest of each line is for the cold reader of `intent.md`. The card's
|
|
84
|
+
`yes to all leans` move (or `yes to all leans except Q3: <their answer>`) settles them
|
|
85
|
+
in one reply: on each opener line swap `*resolve by:*` for `*resolved:* <date>, <the
|
|
86
|
+
answer>`, add the Decision the answer settles, and run `plumbbob handoff --plan` again.
|
|
87
|
+
A question the human would rather let the work answer is deferred, not resolved: on
|
|
88
|
+
`defer Q3 until <what settles it>`, keep the question and swap its `*resolve by:*` for
|
|
89
|
+
`*deferred:* <date>, until <what settles it>` on the opener line, then run
|
|
90
|
+
`plumbbob handoff --plan` again. It stays open, the card lists it on a `Deferred` line
|
|
91
|
+
beneath the table, out of reach of `yes to all leans`, and the Questions row counts it
|
|
92
|
+
apart (`**1** to answer · 2 deferred`); once the work answers it, swap the marker for
|
|
93
|
+
`*resolved:*` as above
|
|
94
|
+
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
95
|
+
Deferring is the human's call, never yours: a lean you are unsure of is still a lean.
|
|
79
96
|
4. **Author the Steps.** Write the **full build plan** under `## Steps` (each step a
|
|
80
97
|
small, verifiable increment) in the exact format the parser reads:
|
|
81
98
|
|
|
@@ -215,7 +232,8 @@ The argument only seeds how you get there.
|
|
|
215
232
|
a message that asks (`expand 2`, or any question) is answered from the detail file or
|
|
216
233
|
`intent.md` and changes nothing; a message that directs is what to sharpen, cheap
|
|
217
234
|
now, before any code. The revert move vanishes there on its own: nothing has landed
|
|
218
|
-
to wind back.
|
|
235
|
+
to wind back. The turn is your framed plan, then that block, and nothing after it:
|
|
236
|
+
anything you would add goes above the `---` rule the block opens on.
|
|
219
237
|
- **The plan commit is latched too.** Once the turn ledger exists, `checkpoint
|
|
220
238
|
--plan` refuses to land in the same turn `start` stamped it: present the plan,
|
|
221
239
|
**end the turn**, and the human's approving message is the tick that lets it
|
|
@@ -230,17 +248,21 @@ The argument only seeds how you get there.
|
|
|
230
248
|
run that lands that step's range when the plan declares ranges. Relay that block whole and run no second command; the
|
|
231
249
|
plan-pause card above is the one place `handoff` is still called, because no verb has
|
|
232
250
|
run yet there.
|
|
233
|
-
- **
|
|
234
|
-
|
|
235
|
-
see the plan and build record in review
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
- **The card says what the commit publishes.** Until the plan commit lands, the plan
|
|
252
|
+
card's `looks good` row names the tracked folder, which rides this branch into the PR
|
|
253
|
+
(teammates will see the plan and build record in review), and a `keep it local` row
|
|
254
|
+
offers the way out. You compose neither note, before the
|
|
255
|
+
card or after it. When the human picks `keep it local`, run `plumbbob start --local`
|
|
256
|
+
on the open session: it excludes `.plumbbob/` in this clone, so the plan and finish
|
|
257
|
+
commits land record-only and the folder never rides the branch. Then run
|
|
258
|
+
`plumbbob handoff --plan` and end on the card, which now says record-only. Once the
|
|
259
|
+
plan has landed, the folder is in a commit and `start --local` refuses.
|
|
260
|
+
- **Record-only variant.** When the repo's own `.gitignore` excludes `.plumbbob/`, or
|
|
261
|
+
the session was kept local, the folder can't ride the branch; `start` flags a
|
|
262
|
+
gitignored repo up front, the card's `looks good` row says record-only, and the plan
|
|
263
|
+
commit lands as an empty commit whose message carries the plan, the files staying
|
|
264
|
+
untracked. The rationale is published in `git log`, not the folder, so pass a fuller
|
|
265
|
+
`--body` than you otherwise would, since the message is the whole record.
|
|
244
266
|
8. **Offer to stress-test it.** Suggest `/plumbbob:refine` to attack the frame for holes (or
|
|
245
267
|
to repair the plan as it drifts). Optional, the human's call.
|
|
246
268
|
|
package/skills/refine/SKILL.md
CHANGED
|
@@ -38,10 +38,19 @@ is fine; refining is always available.
|
|
|
38
38
|
may stay one bare line; the sub-lines earn their keep only when a human decision
|
|
39
39
|
genuinely waits on it.)
|
|
40
40
|
|
|
41
|
-
Then
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
Then show the human what is open: run `plumbbob status --questions` and paste its
|
|
42
|
+
table whole, one row per open question with why it matters and your lean, each cell
|
|
43
|
+
the first sentence of its line, so write each line to stand on that sentence. Then
|
|
44
|
+
invite per-question answers; the human can settle any of them right there in one
|
|
45
|
+
message (`yes to all leans`, or `yes to all leans except Q3: <their answer>`), no
|
|
46
|
+
doc-flipping, no extra round-trip per hole. A resolved question gets
|
|
47
|
+
`*resolved:* <date>, <the answer>` in place of its `*resolve by:*` on the opener line. The human
|
|
48
|
+
can also leave one to the work (`defer Q3 until <what settles it>`): keep the question
|
|
49
|
+
and swap its `*resolve by:*` for `*deferred:* <date>, until <what settles it>` on the
|
|
50
|
+
opener line, and the table moves it to a `Deferred` line beneath, still open
|
|
51
|
+
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
52
|
+
Never retype the table as a list of your own
|
|
53
|
+
([D118 (questions-table)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d118)).
|
|
45
54
|
- **Repair (re-sync to reality).** When the plan has drifted (a Decision was overtaken
|
|
46
55
|
by what you built, a Constraint changed, a Step no longer matches the seam, or a
|
|
47
56
|
**step's title no longer describes what the diff actually does**), propose the edits
|
package/skills/run/SKILL.md
CHANGED
|
@@ -122,7 +122,7 @@ walk before it, since the checkpoint would refuse it. The halt list is the build
|
|
|
122
122
|
red check, a self-review mismatch, a bound agent's `blocked` or `drift`, or a new decision
|
|
123
123
|
ends at that step's pause card. `plumbbob build` names the top step as the top of the range
|
|
124
124
|
you granted when it enters it. Once that step lands, its checkpoint prints the boundary
|
|
125
|
-
with the Stop row (`| Stop | Range 3
|
|
125
|
+
with the Stop row (`| Stop | **Range 3** landed - <its stop> |`); relay none of it, and end
|
|
126
126
|
the run on the stop card, below, in the same turn.
|
|
127
127
|
|
|
128
128
|
**A bare range.** `grant: none, as a bare range builds to its pause`: build the range's one
|
|
@@ -207,6 +207,15 @@ it, and record nothing in this turn: `plumbbob harvest` refuses in the turn the
|
|
|
207
207
|
because the human has not read it yet. If the turn is held with a reason saying the stop card
|
|
208
208
|
is still owed, do exactly what the reason says.
|
|
209
209
|
|
|
210
|
+
*After a halt the human approved, the card comes short.* When the step that completed the
|
|
211
|
+
range halted at its pause and `looks good` landed it, the human read that step's Summary,
|
|
212
|
+
Readout, and recommendation a moment ago, so `plumbbob handoff --stop` prints only the
|
|
213
|
+
Harvested table, the moves, the state table with its Stop row, and the link
|
|
214
|
+
([D119 (short-stop-after-a-halt)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d119)).
|
|
215
|
+
Write the range's detail file all the same, in full: the stop's record reads it, and
|
|
216
|
+
`show the stop` prints the card whole from it. Paste the short card exactly as it printed;
|
|
217
|
+
never add back the parts it left out.
|
|
218
|
+
|
|
210
219
|
## 6. The reply at a stop
|
|
211
220
|
|
|
212
221
|
The card's proposals are settled in one reply
|
|
@@ -224,14 +233,18 @@ and the Your Call block names the replies. Read the human's message as one of th
|
|
|
224
233
|
- **A change** (`make P7 a tangent`, or a change to the review) is direction, and the turn
|
|
225
234
|
that makes it is your reply
|
|
226
235
|
([D110 (reply-after-the-card)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d110)).
|
|
227
|
-
Rewrite the detail file's line or section,
|
|
228
|
-
|
|
236
|
+
Rewrite the detail file's line or section, run `plumbbob handoff --stop` last among your
|
|
237
|
+
tool calls and keep the one-line reminder it prints, then say in your own words what
|
|
238
|
+
moved and end the turn on that line, with no tool call after your reply
|
|
239
|
+
([D121 (reply-after-the-tools)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d121)).
|
|
229
240
|
The card does not come back for a change; nothing is recorded until `looks good`, which
|
|
230
241
|
records every lean as the detail file then reads.
|
|
231
242
|
- **A question**, or `expand 2`, is answered from the detail file, the range's commits, or
|
|
232
|
-
the Log, never from recall
|
|
233
|
-
|
|
234
|
-
`
|
|
243
|
+
the Log, never from recall: read what you need and run `plumbbob handoff --stop` first,
|
|
244
|
+
then write the answer, and end the turn on the one-line reminder it printed, with no tool
|
|
245
|
+
call after the answer. Only `show the stop`, or any wording of it, takes
|
|
246
|
+
`plumbbob handoff --stop --full`, which prints the card whole after a short one too, and
|
|
247
|
+
never to read the card for yourself.
|
|
235
248
|
- **`/plumbbob:run`** typed at the card confirms everything the way `looks good` does, and
|
|
236
249
|
runs the next range. Record first: fold the blockers and run `plumbbob harvest`, then read
|
|
237
250
|
`plumbbob status --run "<the same argument>"` again. When its grant line says
|
package/skills/status/SKILL.md
CHANGED
|
@@ -39,3 +39,10 @@ annotate it.
|
|
|
39
39
|
the `next →` names the run to type, `/plumbbob:run` with the steps it will land beside
|
|
40
40
|
it. You relay the block with the rest; a `⚠` row is `/plumbbob:refine`'s to fix.
|
|
41
41
|
4. If it reads `NO ACTIVE SESSION`, tell the human to `/plumbbob:plan` to frame a goal.
|
|
42
|
+
5. If the human asks what is still open, run `plumbbob status --questions` and relay its
|
|
43
|
+
output whole: a table row per question that waits on the human, linked to its entry in
|
|
44
|
+
`intent.md`, with why it matters and the lean, and a `Deferred` line beneath it naming
|
|
45
|
+
the questions left to the work and what settles each
|
|
46
|
+
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
47
|
+
Never list the questions in your own words
|
|
48
|
+
([D118 (questions-table)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d118)).
|