plumbbob 0.13.7 → 0.13.8
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 +3 -3
- package/dist/cli-core.js +13 -0
- package/dist/lib/orient.js +38 -0
- package/dist/lib/sidecar.js +1 -0
- package/dist/verbs/defer.js +96 -0
- package/dist/verbs/handoff.js +37 -16
- package/dist/verbs/harvest.js +14 -2
- package/dist/verbs/park.js +14 -4
- package/package.json +1 -1
- package/skills/build/SKILL.md +12 -6
- package/skills/harvest/SKILL.md +12 -3
- package/skills/park/SKILL.md +17 -3
- package/skills/plan/SKILL.md +9 -4
- package/skills/refine/SKILL.md +9 -4
- package/skills/run/SKILL.md +5 -4
- package/templates/intent.md +7 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
3
|
"displayName": "PlumbBob",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.8",
|
|
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
|
@@ -177,7 +177,7 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
177
177
|
| --- | --- |
|
|
178
178
|
| Now | **Step 1** on Opus - feat(limiter): add a token-bucket limiter |
|
|
179
179
|
| Next Up | **Step 2** on Sonnet - feat(login): wire the limiter into POST /login (details: [intent.md:28](.plumbbob/builds/2026-07-03-rate-limit-the-login-endpoint/intent.md#L28)) |
|
|
180
|
-
| Range
|
|
180
|
+
| | **Range 2** on Sonnet: /plumbbob:run once step 1 lands |
|
|
181
181
|
|
|
182
182
|
**Recommendation**: Approve it, refilling on read. The gate is green and the seam held, and a bucket that refills when it is read needs no timer for the route to clean up.
|
|
183
183
|
|
|
@@ -192,7 +192,7 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
192
192
|
`.plumbbob/settings.json`, for example `"check": "npm test"`; in a repo where
|
|
193
193
|
checkride finds no tools at all, `start` says so up front and names that key.)
|
|
194
194
|
3. **Reply.** Say `looks good` and it commits the step as a checkpoint, marks it done,
|
|
195
|
-
and returns to the boundary, where the
|
|
195
|
+
and returns to the boundary, where the range line beneath Next Up names the run to type
|
|
196
196
|
next and the steps it will land:
|
|
197
197
|
|
|
198
198
|
```text
|
|
@@ -201,7 +201,7 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
201
201
|
| **Verdict** | ● Plumb |
|
|
202
202
|
| --- | --- |
|
|
203
203
|
| Next Up | **Step 2** on Sonnet - feat(login): wire the limiter into POST /login (details: [intent.md:28](.plumbbob/builds/2026-07-03-rate-limit-the-login-endpoint/intent.md#L28)) |
|
|
204
|
-
| Range
|
|
204
|
+
| | **Range 2** on Sonnet: /plumbbob:run (steps 2-3) |
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|

|
package/dist/cli-core.js
CHANGED
|
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
14
14
|
import { start } from "./verbs/start.js";
|
|
15
15
|
import { status } from "./verbs/status.js";
|
|
16
16
|
import { park } from "./verbs/park.js";
|
|
17
|
+
import { defer } from "./verbs/defer.js";
|
|
17
18
|
import { harvest } from "./verbs/harvest.js";
|
|
18
19
|
import { order } from "./verbs/order.js";
|
|
19
20
|
import { build } from "./verbs/build.js";
|
|
@@ -152,6 +153,16 @@ const VERBS = [
|
|
|
152
153
|
tolerateUnknownFlags: true,
|
|
153
154
|
notes: 'Refuses (exit 1) with no session, empty text, or no `## Park list` section.',
|
|
154
155
|
},
|
|
156
|
+
{
|
|
157
|
+
name: 'defer',
|
|
158
|
+
description: 'leave an open question to the work, until what will settle it',
|
|
159
|
+
synopsis: ['defer <Qn> "<what settles it>"'],
|
|
160
|
+
args: [
|
|
161
|
+
{ name: '<Qn>', gloss: 'the open question, by its handle (Q3)' },
|
|
162
|
+
{ name: '<what settles it>', gloss: 'the work the question waits on (free text; quote it)' },
|
|
163
|
+
],
|
|
164
|
+
notes: "Writes `*deferred:* <date>, until <what settles it>` on the question's opener in intent.md, in place of its `*resolve by:*` tag or an earlier deferral, so every count reads it apart from the questions to answer. Refuses (exit 1) with no session, no handle, no until-clause, a handle the open questions lack, or a question already resolved.",
|
|
165
|
+
},
|
|
155
166
|
{
|
|
156
167
|
name: 'order',
|
|
157
168
|
description: 'set the build order: the undone steps in the sequence to build them',
|
|
@@ -378,6 +389,8 @@ async function dispatch(verb, cwd, rest) {
|
|
|
378
389
|
return status(cwd, rest);
|
|
379
390
|
case 'park':
|
|
380
391
|
return park(cwd, rest);
|
|
392
|
+
case 'defer':
|
|
393
|
+
return defer(cwd, rest);
|
|
381
394
|
case 'order':
|
|
382
395
|
return order(cwd, rest);
|
|
383
396
|
case 'harvest':
|
package/dist/lib/orient.js
CHANGED
|
@@ -1221,11 +1221,49 @@ function deferredText(body) {
|
|
|
1221
1221
|
.replace(/^\d{4}-\d{2}-\d{2},?\s*/, '')
|
|
1222
1222
|
.trim();
|
|
1223
1223
|
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Defer an open question to the work: on its opener line, `*deferred:* <date>,
|
|
1226
|
+
* until <what settles it>` takes the place of its `*resolve by:*` tag, or of
|
|
1227
|
+
* an earlier `*deferred:*` marker, or joins the end of a line that has
|
|
1228
|
+
* neither. The until-clause is folded onto one line, since the parser reads
|
|
1229
|
+
* the opener line alone (D120 (deferred-questions)).
|
|
1230
|
+
*
|
|
1231
|
+
* `resolved` names an opener the plan closed, `missing` one the plan lacks or
|
|
1232
|
+
* has not filled in, with the handles still open beside it.
|
|
1233
|
+
*/
|
|
1234
|
+
export function deferQuestion(intent, id, date, until) {
|
|
1235
|
+
const rows = parseOpenQuestionRows(intent);
|
|
1236
|
+
const row = rows.find((q) => q.id === id);
|
|
1237
|
+
if (row === undefined) {
|
|
1238
|
+
const closed = sectionLines(intent, '## Open questions').lines.some((raw) => {
|
|
1239
|
+
const opener = QUESTION_OPENER.exec(raw.trim());
|
|
1240
|
+
return opener?.[1] === id && resolvedOpener(raw);
|
|
1241
|
+
});
|
|
1242
|
+
return closed ? { kind: 'resolved' } : { kind: 'missing', open: rows.map((q) => q.id) };
|
|
1243
|
+
}
|
|
1244
|
+
const lines = intent.split('\n');
|
|
1245
|
+
const raw = lines[row.line - 1] ?? '';
|
|
1246
|
+
const marker = `*deferred:* ${date}, until ${until.replace(/\s*\n\s*/g, ' ').trim()}`;
|
|
1247
|
+
const tag = raw.search(/\*(?:resolve by|deferred):\*/i);
|
|
1248
|
+
lines[row.line - 1] = tag === -1 ? `${raw.trimEnd()}, ${marker}` : `${raw.slice(0, tag).trimEnd()} ${marker}`;
|
|
1249
|
+
const updated = lines.join('\n');
|
|
1250
|
+
return { kind: 'deferred', intent: updated, question: parseOpenQuestionRows(updated).find((q) => q.id === id) ?? row };
|
|
1251
|
+
}
|
|
1224
1252
|
/** Split open question rows by who acts on them (D120 (deferred-questions)). */
|
|
1225
1253
|
export function questionTotals(rows) {
|
|
1226
1254
|
const deferred = rows.filter((q) => q.deferred !== null).length;
|
|
1227
1255
|
return { toAnswer: rows.length - deferred, deferred };
|
|
1228
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* The open questions still waiting on the human that a line of text names by
|
|
1259
|
+
* handle (`Q3`), in the order the plan lists them. A park or a tangent naming
|
|
1260
|
+
* one has set it aside without deferring it, so the count still reads it as a
|
|
1261
|
+
* call owed.
|
|
1262
|
+
*/
|
|
1263
|
+
export function questionsToAnswerNamed(text, rows) {
|
|
1264
|
+
const named = new Set(text.match(/\bQ\d+\b/g) ?? []);
|
|
1265
|
+
return rows.filter((q) => q.deferred === null && named.has(q.id));
|
|
1266
|
+
}
|
|
1229
1267
|
/**
|
|
1230
1268
|
* The dashboard's question count, split the way the card's Questions row
|
|
1231
1269
|
* splits it: `open questions 1 to answer, 2 deferred`, either half alone when
|
package/dist/lib/sidecar.js
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// `plumbbob defer <Qn> "<what settles it>"`: leave an open question to the
|
|
2
|
+
// work. The marker it writes on the question's opener in intent.md,
|
|
3
|
+
// `*deferred:* <date>, until <what settles it>`, is the one thing every count
|
|
4
|
+
// reads to tell a question that waits on the human from one that waits on
|
|
5
|
+
// the work (D120 (deferred-questions)). Until this verb the marker was a hand
|
|
6
|
+
// edit made only at the plan card, so a question set aside anywhere else (parked
|
|
7
|
+
// mid-step, or settled as a tangent at a harvest) kept counting as a call
|
|
8
|
+
// owed. The skill decides and the CLI records (D123 (defer-records)): deferring
|
|
9
|
+
// stays the human's call, and the verb only writes it, dated, in the one form
|
|
10
|
+
// the parser reads. Like park it changes no state the loop runs on, so it runs
|
|
11
|
+
// in any context.
|
|
12
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { findRepoRoot } from "../lib/git.js";
|
|
14
|
+
import { activeBuild, hasSession, intentPath } from "../lib/sidecar.js";
|
|
15
|
+
import { deferQuestion, questionsToAnswerNamed } from "../lib/orient.js";
|
|
16
|
+
import { advisory, ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
17
|
+
import { driverPointer } from "./handoff.js";
|
|
18
|
+
// What every refusal about the arguments points at.
|
|
19
|
+
const USAGE = 'plumbbob defer Q3 "<what settles it>"';
|
|
20
|
+
/**
|
|
21
|
+
* Mark the named open question deferred until what the rest of the arguments
|
|
22
|
+
* say will settle it, and print the ending: the deferral and the pointer.
|
|
23
|
+
*
|
|
24
|
+
* A leading "until" in the text is dropped, since the marker writes its own.
|
|
25
|
+
* Refuses (exit 1) with no session, no question handle, no until-clause, a
|
|
26
|
+
* handle the open questions lack, or a question already resolved.
|
|
27
|
+
*/
|
|
28
|
+
export function defer(cwd, args) {
|
|
29
|
+
const root = findRepoRoot(cwd);
|
|
30
|
+
if (root === null || !hasSession(root)) {
|
|
31
|
+
process.stderr.write(notice({ fact: 'no active session', detail: ['no open questions to defer'], remedy: 'plumbbob start "<title>"' }));
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const [handle = '', ...rest] = args.filter((a) => !a.startsWith('--'));
|
|
35
|
+
const id = /^Q(\d+)$/i.exec(handle.trim());
|
|
36
|
+
if (id === null) {
|
|
37
|
+
process.stderr.write(notice({ fact: 'defer needs a question', detail: ['its handle first, Q3'], remedy: USAGE }));
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
const question = `Q${id[1] ?? ''}`;
|
|
41
|
+
const until = rest
|
|
42
|
+
.join(' ')
|
|
43
|
+
.trim()
|
|
44
|
+
.replace(/^until\s+/i, '');
|
|
45
|
+
if (until.length === 0) {
|
|
46
|
+
process.stderr.write(notice({
|
|
47
|
+
fact: 'defer needs what settles it',
|
|
48
|
+
detail: ['the work the question waits on'],
|
|
49
|
+
remedy: `plumbbob defer ${question} "<what settles it>"`,
|
|
50
|
+
}));
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
const path = intentPath(root);
|
|
54
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
55
|
+
const deferral = deferQuestion(readFileSync(path, 'utf8'), question, date, until);
|
|
56
|
+
if (deferral.kind === 'resolved') {
|
|
57
|
+
process.stderr.write(notice({ fact: `${question} is already resolved`, detail: ['its opener says so'] }));
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (deferral.kind === 'missing') {
|
|
61
|
+
process.stderr.write(notice({
|
|
62
|
+
fact: `no open ${question} in the plan`,
|
|
63
|
+
detail: [deferral.open.length === 0 ? 'no open questions' : `open: ${deferral.open.join(', ')}`],
|
|
64
|
+
remedy: 'fix the handle',
|
|
65
|
+
}));
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
writeFileSync(path, deferral.intent);
|
|
69
|
+
printEnding(root, {
|
|
70
|
+
tier: 'driver',
|
|
71
|
+
render: 'defer',
|
|
72
|
+
text: ending({
|
|
73
|
+
// The parser hands back the marker's until-clause as the opener now
|
|
74
|
+
// reads it, `until` and all, so the lead says what the file says.
|
|
75
|
+
lead: transition({ label: 'Deferred', fact: `${plainHandle(deferral.question)} ${deferral.question.deferred ?? `until ${until}`}` }),
|
|
76
|
+
pointer: driverPointer(root, activeBuild(root)),
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One advisory for each open question a park or a tangent names that still
|
|
83
|
+
* counts as a call owed, with the deferral beneath it: setting a question
|
|
84
|
+
* aside does not defer it, and the card would go on counting it as to answer.
|
|
85
|
+
* Empty when the text names none.
|
|
86
|
+
*/
|
|
87
|
+
export function setAsideAdvisories(text, rows) {
|
|
88
|
+
return questionsToAnswerNamed(text, rows).map((q) => advisory({
|
|
89
|
+
fact: `${plainHandle(q)} still counts as a question to answer`,
|
|
90
|
+
remedy: `plumbbob defer ${q.id} "<what settles it>", if the work will settle it`,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
/** A question's handle as a line of prose names it: the id, then its slug. */
|
|
94
|
+
function plainHandle(q) {
|
|
95
|
+
return q.slug === null ? q.id : `${q.id} (${q.slug})`;
|
|
96
|
+
}
|
package/dist/verbs/handoff.js
CHANGED
|
@@ -51,7 +51,7 @@ import { hasStep } from "../lib/steplist.js";
|
|
|
51
51
|
import { isArtifactPath, parseStepSeam } from "../lib/intent.js";
|
|
52
52
|
import { rerootLinks } from "../lib/links.js";
|
|
53
53
|
import { buildFolder, buildFolderIgnored, buildLogPath, checkpointsPath, detailPath, hasSession, inSpike, intentPath, planCommitted, readPause, readStats, readStop, readTurn, resolveBuild, seamPath, sidecarDir, stepPath, tickPath, writePause, writeStop, } from "../lib/sidecar.js";
|
|
54
|
-
import { buildOrderPhrase, countDiff, diffFileItems, diffRowValue, foldVerdict, lastLedgerSha, measureRange, normalizeDetailHeadings, parseBuildOrder, parseBuildRanges, parseConstraintCount, parseLastCheckpoint, parseOpenQuestionRows, parseOrderedSteps, parseParkTotals, parseRecap, parseRecommendation, parseSections, parseSteps, parseSummary, questionTotals, rangeLanded, rangeSpan, rangeStanding, recapLines, remainingOrder, resolveRun, runBracket, seamRowFromDiff, spentRowValue, stopFollowUp, summaryCheckRow, } from "../lib/orient.js";
|
|
54
|
+
import { buildOrderPhrase, countDiff, diffFileItems, diffRowValue, foldVerdict, lastLedgerSha, markStepDone, measureRange, normalizeDetailHeadings, parseBuildOrder, parseBuildRanges, parseConstraintCount, parseLastCheckpoint, parseOpenQuestionRows, parseOrderedSteps, parseParkTotals, parseRecap, parseRecommendation, parseSections, parseSteps, parseSummary, questionTotals, rangeLanded, rangeSpan, rangeStanding, recapLines, remainingOrder, resolveRun, runBracket, seamRowFromDiff, spentRowValue, stopFollowUp, summaryCheckRow, } from "../lib/orient.js";
|
|
55
55
|
// The inline-diff threshold (docs/presentation.md): at 20 changed lines or
|
|
56
56
|
// fewer the patch rides the turn; at 21 it stays in the working tree behind
|
|
57
57
|
// the counted diff row.
|
|
@@ -1281,9 +1281,11 @@ const ORDER_CAP = 5;
|
|
|
1281
1281
|
* becomes the header, which is the Verdict wherever one was measured
|
|
1282
1282
|
* (D111 (state-table)).
|
|
1283
1283
|
*
|
|
1284
|
-
* The
|
|
1285
|
-
*
|
|
1286
|
-
* the
|
|
1284
|
+
* The Order and Parked rows are segments the note beneath Next Up used to
|
|
1285
|
+
* carry on one indented line; as rows each one has a label of its own, the way
|
|
1286
|
+
* the human scans for it. The range is Next Up's second line, a row with a
|
|
1287
|
+
* blank label, so it reads as the next range beside the next step rather than
|
|
1288
|
+
* as the range the build is in (D122 (next-range-under-next-up)).
|
|
1287
1289
|
*/
|
|
1288
1290
|
function stateRows(input) {
|
|
1289
1291
|
const steps = parseOrderedSteps(input.intent);
|
|
@@ -1296,7 +1298,7 @@ function stateRows(input) {
|
|
|
1296
1298
|
input.now === null ? null : { label: 'Now', value: input.now },
|
|
1297
1299
|
input.stop === null ? null : { label: 'Stop', value: input.stop },
|
|
1298
1300
|
{ label: 'Next Up', value: pointerText(input.nextUp, input.where) },
|
|
1299
|
-
range.length === 0 ? null : { label: '
|
|
1301
|
+
range.length === 0 ? null : { label: '', value: range },
|
|
1300
1302
|
order.length === 0 ? null : { label: 'Order', value: order },
|
|
1301
1303
|
parks.total === 0 ? null : { label: 'Parked', value: parkedValue(parks, input.now !== null) },
|
|
1302
1304
|
questions.toAnswer + questions.deferred === 0 || input.questions === false ? null : { label: 'Questions', value: questionsValue(questions) },
|
|
@@ -1336,32 +1338,51 @@ function pointerLine(nextUp, where) {
|
|
|
1336
1338
|
return nextUpLine(nextUp, where);
|
|
1337
1339
|
}
|
|
1338
1340
|
/**
|
|
1339
|
-
*
|
|
1340
|
-
* `**3** on Opus: /plumbbob:run (steps 6-9)`,
|
|
1341
|
+
* Next Up's range line: the range the next run walks,
|
|
1342
|
+
* `**Range 3** on Opus: /plumbbob:run (steps 6-9)`, its name bold and the
|
|
1341
1343
|
* model its `- model:` line names beside it, so the model reads as the
|
|
1342
1344
|
* range's and not the step's (D115 (card-weights)); the re-cut words for a
|
|
1343
1345
|
* flagged range; the words for a step no range names; or '' for a plan
|
|
1344
|
-
* without ranges. The row
|
|
1345
|
-
* range's
|
|
1346
|
+
* without ranges. The row has no label of its own, so the fact opens on the
|
|
1347
|
+
* range's name (D122 (next-range-under-next-up)).
|
|
1346
1348
|
*
|
|
1347
1349
|
* The bracket is the grant the hook would mint. At a step pause that is the
|
|
1348
1350
|
* range holding the step in flight, its bracket opening on that step, since a
|
|
1349
|
-
* run typed there lands it first
|
|
1350
|
-
* a run ends on
|
|
1351
|
-
*
|
|
1351
|
+
* run typed there lands it first. At the pause on a range's last step, or a
|
|
1352
|
+
* bare range's, a run lands only that step and ends on its range's stop, so
|
|
1353
|
+
* the line names the range the step after it opens instead
|
|
1354
|
+
* (`**Range 4** on Fable: /plumbbob:run once step 9 lands`).
|
|
1352
1355
|
*/
|
|
1353
1356
|
function rangeSegment(intent, steps, exclude) {
|
|
1354
1357
|
const paused = steps.some((s) => s.n === exclude && !s.done) ? exclude : null;
|
|
1355
1358
|
const run = cardRun(intent, paused);
|
|
1359
|
+
if (run.ok && paused !== null && run.left.length === 1) {
|
|
1360
|
+
return afterLanding(intent, paused);
|
|
1361
|
+
}
|
|
1362
|
+
return runSegment(run, (r) => `(${runBracket(r)})`);
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* The range line at the pause that finishes a range: the run the boundary will
|
|
1366
|
+
* name once the step in flight lands, read from the plan with that step
|
|
1367
|
+
* ticked, and opening once it does. '' when no step is left after it.
|
|
1368
|
+
*/
|
|
1369
|
+
function afterLanding(intent, paused) {
|
|
1370
|
+
const next = cardRun(markStepDone(intent, paused), null);
|
|
1371
|
+
return runSegment(next, () => `once step ${paused} lands`);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* A run as the range line names it: the range's name, its model, and the run
|
|
1375
|
+
* with `when` closing it; or, where the run would refuse, the words for a step
|
|
1376
|
+
* no range names or a flagged range's re-cut, and '' for any other refusal.
|
|
1377
|
+
*/
|
|
1378
|
+
function runSegment(run, when) {
|
|
1356
1379
|
if (run.ok) {
|
|
1357
|
-
|
|
1358
|
-
const lands = paused !== null && run.left.length === 1;
|
|
1359
|
-
return lands ? `${name} lands with step ${paused}` : `${name}${onModel(run.range.model)}: /plumbbob:run (${runBracket(run)})`;
|
|
1380
|
+
return `**Range ${run.range.item}**${onModel(run.range.model)}: /plumbbob:run ${when(run)}`;
|
|
1360
1381
|
}
|
|
1361
1382
|
if (run.kind === 'uncovered') {
|
|
1362
1383
|
return run.words;
|
|
1363
1384
|
}
|
|
1364
|
-
return run.kind === 'flagged' ?
|
|
1385
|
+
return run.kind === 'flagged' ? `**Range ${run.item}** needs a re-cut (${run.words})` : '';
|
|
1365
1386
|
}
|
|
1366
1387
|
/**
|
|
1367
1388
|
* The run `/plumbbob:run` typed now would walk, as the card names it: the
|
package/dist/verbs/harvest.js
CHANGED
|
@@ -25,10 +25,11 @@ import { findRepoRoot } from "../lib/git.js";
|
|
|
25
25
|
import { ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
26
26
|
import { appendHarvest, appendToSection, flipParks, harvestLines, logEntry, stopLogLine } from "../lib/buildlog.js";
|
|
27
27
|
import { checkStopLatch } from "../lib/latch.js";
|
|
28
|
-
import { parseBuildRanges, parseDetailStep, parseLastCheckpoint, parseOrderedSteps, stepShas } from "../lib/orient.js";
|
|
28
|
+
import { parseBuildRanges, parseDetailStep, parseLastCheckpoint, parseOpenQuestionRows, parseOrderedSteps, stepShas } from "../lib/orient.js";
|
|
29
29
|
import { activeBuild, buildLogPath, checkpointsPath, clearDetail, clearStop, detailPath, hasSession, intentPath, readStop, } from "../lib/sidecar.js";
|
|
30
30
|
import { hasStep } from "../lib/steplist.js";
|
|
31
31
|
import { boundaryEnding, harvestProposals, isHarvestDetail, parkList, parseDetailRange, stopRecord, } from "./handoff.js";
|
|
32
|
+
import { setAsideAdvisories } from "./defer.js";
|
|
32
33
|
// The three classes a park settles as, in the order the ending counts them.
|
|
33
34
|
const CLASSES = ['blocker', 'tangent', 'pivot'];
|
|
34
35
|
// Where a refusal about the proposals sends the model: the file it wrote them in.
|
|
@@ -100,10 +101,21 @@ export function harvest(cwd, _args) {
|
|
|
100
101
|
const lead = stop === null
|
|
101
102
|
? transition({ label: 'Recorded', fact: `${settled.length} parked idea${settled.length === 1 ? '' : 's'}`, detail: [counted] })
|
|
102
103
|
: transition({ label: 'Stop', fact: `**Range ${stop.item}** recorded`, detail: [counted] });
|
|
104
|
+
// A tangent naming a question that still waits on the human has set it
|
|
105
|
+
// aside without deferring it, and the card would go on counting it as a
|
|
106
|
+
// call owed (D123 (defer-records)).
|
|
107
|
+
const tangents = settled
|
|
108
|
+
.filter((p) => p.kind === 'tangent')
|
|
109
|
+
.map((p) => `${p.text} ${p.reason}`)
|
|
110
|
+
.join(' ');
|
|
103
111
|
printEnding(root, {
|
|
104
112
|
tier: 'orientation',
|
|
105
113
|
render: 'harvest',
|
|
106
|
-
text: ending({
|
|
114
|
+
text: ending({
|
|
115
|
+
lead,
|
|
116
|
+
advisories: setAsideAdvisories(tangents, parseOpenQuestionRows(intent)),
|
|
117
|
+
state: boundaryEnding(root, slug, null).state,
|
|
118
|
+
}),
|
|
107
119
|
});
|
|
108
120
|
return 0;
|
|
109
121
|
}
|
package/dist/verbs/park.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
// step it interrupted, so the relay is one command.
|
|
8
8
|
// Capture is not a state transition, so it runs in any context (terminal or
|
|
9
9
|
// in-session).
|
|
10
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { findRepoRoot } from "../lib/git.js";
|
|
12
|
-
import { activeBuild, hasSession, buildLogPath } from "../lib/sidecar.js";
|
|
12
|
+
import { activeBuild, hasSession, buildLogPath, intentPath } from "../lib/sidecar.js";
|
|
13
13
|
import { appendToSection } from "../lib/buildlog.js";
|
|
14
14
|
import { ending, notice, printEnding, transition } from "../lib/notice.js";
|
|
15
|
+
import { parseOpenQuestionRows } from "../lib/orient.js";
|
|
15
16
|
import { driverPointer } from "./handoff.js";
|
|
17
|
+
import { setAsideAdvisories } from "./defer.js";
|
|
16
18
|
/**
|
|
17
19
|
* Append the given text as one unchecked `- [ ]` line to the build-log's Park list.
|
|
18
20
|
*
|
|
@@ -41,12 +43,20 @@ export function park(cwd, args) {
|
|
|
41
43
|
return 1;
|
|
42
44
|
}
|
|
43
45
|
writeFileSync(path, updated);
|
|
46
|
+
const intent = intentPath(root);
|
|
47
|
+
const questions = parseOpenQuestionRows(existsSync(intent) ? readFileSync(intent, 'utf8') : '');
|
|
44
48
|
// A park interrupts a step without ending it, so the ending is the capture
|
|
45
|
-
// and the pointer back at what was interrupted.
|
|
49
|
+
// and the pointer back at what was interrupted. A park naming a question
|
|
50
|
+
// that still waits on the human says so, since the park list never moves
|
|
51
|
+
// the card's count (D123 (defer-records)).
|
|
46
52
|
printEnding(root, {
|
|
47
53
|
tier: 'driver',
|
|
48
54
|
render: 'park',
|
|
49
|
-
text: ending({
|
|
55
|
+
text: ending({
|
|
56
|
+
lead: transition({ label: 'Parked', fact: text }),
|
|
57
|
+
advisories: setAsideAdvisories(text, questions),
|
|
58
|
+
pointer: driverPointer(root, activeBuild(root)),
|
|
59
|
+
}),
|
|
50
60
|
});
|
|
51
61
|
return 0;
|
|
52
62
|
}
|
package/package.json
CHANGED
package/skills/build/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: build
|
|
|
3
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
4
|
argument-hint: "[step-number | step-range | step-list] [--auto]"
|
|
5
5
|
disable-model-invocation: true
|
|
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:*)
|
|
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 defer:*), Bash(plumbbob agent:*), Bash(plumbbob spike:*), Bash(git diff:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# PlumbBob: build a step (the default engine)
|
|
@@ -66,7 +66,13 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
66
66
|
capture it by running `plumbbob park "<one tidy line>"`: **saying "let's defer
|
|
67
67
|
that" writes nothing; only the park line in the build log is a capture.** When the
|
|
68
68
|
*human* hands you the tangent mid-build, their message **is** the approval to park
|
|
69
|
-
it; capture it right away, say what you parked, and stay on the step.
|
|
69
|
+
it; capture it right away, say what you parked, and stay on the step. An open question
|
|
70
|
+
the human sets aside to wait on other work is deferred, not parked: run
|
|
71
|
+
`plumbbob defer Q3 "<what settles it>"`, because the card counts questions by the
|
|
72
|
+
marker on each opener and never by the park list
|
|
73
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
74
|
+
When `plumbbob park` warns that a question it names still counts as one to answer,
|
|
75
|
+
defer it if the human said the work will settle it, and leave it to them otherwise. If you
|
|
70
76
|
genuinely cannot finish without touching more than the seam, that is scope drift:
|
|
71
77
|
surface it to the human rather than sprawling.
|
|
72
78
|
- **Bound agents can shape the build.** If `plumbbob status` shows a
|
|
@@ -202,8 +208,8 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
|
|
|
202
208
|
Readout fence (its measured `check`, `seam`, `diff`, and `spent` rows folded with your
|
|
203
209
|
three, the changed files listed beneath `diff`), a `diff` fence when the change is 20
|
|
204
210
|
lines or fewer, Your Call, the state table (the Verdict folded worst-of from the same
|
|
205
|
-
rows, the Now row naming the step in flight, Next Up
|
|
206
|
-
rows when they apply), your recommendation, and the `more details` link last. Run it
|
|
211
|
+
rows, the Now row naming the step in flight, Next Up with the next range on its second
|
|
212
|
+
line, and the Order and Parked rows when they apply), your recommendation, and the `more details` link last. Run it
|
|
207
213
|
once: a second run at the same pause prints the one-line reminder instead of the card,
|
|
208
214
|
so if that line comes back where the card belongs (you ran handoff earlier in this
|
|
209
215
|
turn), run `plumbbob handoff --full` and paste that. The gate verdict's one home is that `check`
|
|
@@ -399,8 +405,8 @@ out, or the next step in build order is one the range does not name),
|
|
|
399
405
|
`Parked` row, `2 of 7 open`), the move is `/plumbbob:harvest`, then the run the card names.
|
|
400
406
|
- **After an early stop, settle the step, then rerun what is left.** A halt on trouble
|
|
401
407
|
inside a range ends at that step's pause, and the grant ends with the turn. Once the
|
|
402
|
-
human approves and the step lands, the
|
|
403
|
-
what is left of the range (`| Range
|
|
408
|
+
human approves and the step lands, the range line beneath Next Up names the run that lands
|
|
409
|
+
what is left of the range (`| | **Range 3** on Opus: /plumbbob:run (steps 8-9) |`), and that is
|
|
404
410
|
what the human types to carry on; `/plumbbob:build 8-9` lands the same steps by hand.
|
|
405
411
|
|
|
406
412
|
## The hard contracts
|
package/skills/harvest/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harvest
|
|
3
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, Write, Bash(plumbbob status:*), Bash(plumbbob order:*), Bash(plumbbob handoff:*), Bash(plumbbob harvest:*)
|
|
5
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob order:*), Bash(plumbbob defer:*), Bash(plumbbob handoff:*), Bash(plumbbob harvest:*)
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# PlumbBob: harvest the park list
|
|
@@ -107,7 +107,16 @@ the call on each one theirs. The detail file you write first is the proposal; re
|
|
|
107
107
|
The verb's ending names each range it re-cut, and an advisory names any it left for
|
|
108
108
|
you; re-cut that one in the same confirmed edit, and `plumbbob status` afterward carries
|
|
109
109
|
a `⚠` row for any range still broken.
|
|
110
|
-
-
|
|
111
|
-
|
|
110
|
+
- A confirmed **tangent** that sets an open question aside (its park names `Q3`, and the
|
|
111
|
+
question waits on work still to come) defers that question as well: run
|
|
112
|
+
`plumbbob defer Q3 "<what settles it>"`. Say so in the proposal's reason
|
|
113
|
+
(`tangent: Q3 waits on step 9's numbers`), so the human's confirmation covers the
|
|
114
|
+
deferral. The park list never moves the Questions count, so a tangent alone leaves the
|
|
115
|
+
card reading a call still owed, and the record's ending warns of any question a tangent
|
|
116
|
+
names that still counts as one to answer
|
|
117
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
118
|
+
- Fold a confirmed blocker into `intent.md`, and defer each question a confirmed tangent
|
|
119
|
+
sets aside, before you run `plumbbob harvest`, so the record and the plan agree when it
|
|
120
|
+
lands.
|
|
112
121
|
- Never reclassify or resolve an item the human hasn't confirmed, and default every
|
|
113
122
|
uncertain item to **tangent**, never to blocker.
|
package/skills/park/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Compose one tidy tagged park line, get the human's OK in-turn, the
|
|
|
4
4
|
argument-hint: "[idea]"
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
model: haiku
|
|
7
|
-
allowed-tools: Bash(plumbbob status:*), Bash(plumbbob park:*)
|
|
7
|
+
allowed-tools: Bash(plumbbob status:*), Bash(plumbbob park:*), Bash(plumbbob defer:*)
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# PlumbBob: park an idea (capture, don't chase)
|
|
@@ -58,11 +58,25 @@ Then:
|
|
|
58
58
|
writing it above the capture, or stacking a second ending under the block are the ways
|
|
59
59
|
this goes wrong.
|
|
60
60
|
|
|
61
|
+
## A question set aside is deferred, not parked
|
|
62
|
+
|
|
63
|
+
When the idea is an open question from the plan that the human wants picked up later,
|
|
64
|
+
once other work lands (`leave Q3 until the latency numbers are in`), run
|
|
65
|
+
`plumbbob defer Q3 "<what settles it>"` in place of the park. The question's own opener
|
|
66
|
+
is where it waits, and the card counts it apart from the questions to answer; a park line
|
|
67
|
+
beside it would leave the card counting it as a call still owed
|
|
68
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
69
|
+
`plumbbob defer` prints the same kind of block, `**Deferred**: Q3 (<slug>) until <what
|
|
70
|
+
settles it>` and the pointer, and you relay it the same way. When `plumbbob park` warns that
|
|
71
|
+
a question it names still counts as one to answer, relay the block as printed; whether to
|
|
72
|
+
defer it is the human's call.
|
|
73
|
+
|
|
61
74
|
## The one hard contract
|
|
62
75
|
|
|
63
76
|
The capture itself is the **dumb CLI**, never an edit. This skill carries **no Edit and
|
|
64
|
-
no Write tool** on purpose: you may not append to `build-log.md` yourself
|
|
65
|
-
approval, then shell `plumbbob park
|
|
77
|
+
no Write tool** on purpose: you may not append to `build-log.md` yourself, nor mark a
|
|
78
|
+
question in `intent.md`. Compose, get approval, then shell `plumbbob park`, or
|
|
79
|
+
`plumbbob defer` for a question set aside; those are the only write paths. If approval
|
|
66
80
|
never comes, capture nothing. (When the human's **own message** states the idea, a
|
|
67
81
|
tangent they raise mid-build, that message **is** the approval: capture it directly
|
|
68
82
|
with `plumbbob park`, don't ask again. Deferring in words without the CLI line captures
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: plan
|
|
|
3
3
|
description: "Frame a fresh goal and author the whole plan (Frame, Decisions, Constraints, all Steps, and the build ranges) before any code. Three input modes: no arg interviews you; a file path (or @-mention) absorbs a spec; any other text expands your inline intent."
|
|
4
4
|
argument-hint: "[spec-path | intent]"
|
|
5
5
|
disable-model-invocation: true
|
|
6
|
-
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob handoff:*), Bash(plumbbob start:*), Bash(plumbbob checkpoint:*), Bash(plumbbob agent list:*)
|
|
6
|
+
allowed-tools: Read, Edit, Write, Bash(plumbbob status:*), Bash(plumbbob handoff:*), Bash(plumbbob start:*), Bash(plumbbob checkpoint:*), Bash(plumbbob defer:*), Bash(plumbbob agent list:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# PlumbBob: plan a goal (the whole-goal move)
|
|
@@ -85,13 +85,18 @@ The argument only seeds how you get there.
|
|
|
85
85
|
in one reply: on each opener line swap `*resolve by:*` for `*resolved:* <date>, <the
|
|
86
86
|
answer>`, add the Decision the answer settles, and run `plumbbob handoff --plan` again.
|
|
87
87
|
A question the human would rather let the work answer is deferred, not resolved: on
|
|
88
|
-
`defer Q3 until <what settles it>`,
|
|
88
|
+
`defer Q3 until <what settles it>`, run `plumbbob defer Q3 "<what settles it>"`, which
|
|
89
|
+
keeps the question and swaps its `*resolve by:*` for
|
|
89
90
|
`*deferred:* <date>, until <what settles it>` on the opener line, then run
|
|
90
91
|
`plumbbob handoff --plan` again. It stays open, the card lists it on a `Deferred` line
|
|
91
92
|
beneath the table, out of reach of `yes to all leans`, and the Questions row counts it
|
|
92
93
|
apart (`**1** to answer · 2 deferred`); once the work answers it, swap the marker for
|
|
93
94
|
`*resolved:*` as above
|
|
94
95
|
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
96
|
+
A question the human keeps open on purpose, as the running log of something the build
|
|
97
|
+
tunes, is deferred the same way, until the work it logs; left on `*resolve by:*`, the
|
|
98
|
+
card counts it as a call still owed
|
|
99
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
95
100
|
Deferring is the human's call, never yours: a lean you are unsure of is still a lean.
|
|
96
101
|
4. **Author the Steps.** Write the **full build plan** under `## Steps` (each step a
|
|
97
102
|
small, verifiable increment) in the exact format the parser reads:
|
|
@@ -180,7 +185,7 @@ The argument only seeds how you get there.
|
|
|
180
185
|
|
|
181
186
|
**The card names the run, not the item.** The item's command is the range's
|
|
182
187
|
definition: the form the parser reads, and the kind of stop it makes. The card names
|
|
183
|
-
`/plumbbob:run`
|
|
188
|
+
`/plumbbob:run` on the range line beneath Next Up for the range holding the next step,
|
|
184
189
|
with the steps it will land beside it, and `/plumbbob:run 3` runs range 3 by its item number
|
|
185
190
|
([D95 (run-verb)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d95)).
|
|
186
191
|
A range grants nothing until the human types the run that names it, and then the turn
|
|
@@ -244,7 +249,7 @@ The argument only seeds how you get there.
|
|
|
244
249
|
refusal.)
|
|
245
250
|
- **The plan commit prints its own close.** `plumbbob checkpoint --plan` emits the
|
|
246
251
|
whole boundary ending: `**Plan**: committed (<sha>)`, any advisory, and the state
|
|
247
|
-
table headed by the pointer at the step the build starts on,
|
|
252
|
+
table headed by the pointer at the step the build starts on, the range line beneath it naming the
|
|
248
253
|
run that lands that step's range when the plan declares ranges. Relay that block whole and run no second command; the
|
|
249
254
|
plan-pause card above is the one place `handoff` is still called, because no verb has
|
|
250
255
|
run yet there.
|
package/skills/refine/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: refine
|
|
|
3
3
|
description: "Keep intent.md true: attack the plan for holes (append as Open questions) and refine or repair the Frame, Decisions, Constraints, Steps, and build ranges to match reality (`ranges` re-cuts the whole section). Usable at any point; you propose, the human approves."
|
|
4
4
|
argument-hint: "[focus | ranges]"
|
|
5
5
|
disable-model-invocation: true
|
|
6
|
-
allowed-tools: Read, Edit, Bash(plumbbob status:*), Bash(plumbbob order:*)
|
|
6
|
+
allowed-tools: Read, Edit, Bash(plumbbob status:*), Bash(plumbbob order:*), Bash(plumbbob defer:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# PlumbBob: refine the plan
|
|
@@ -45,10 +45,15 @@ is fine; refining is always available.
|
|
|
45
45
|
message (`yes to all leans`, or `yes to all leans except Q3: <their answer>`), no
|
|
46
46
|
doc-flipping, no extra round-trip per hole. A resolved question gets
|
|
47
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>`):
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
can also leave one to the work (`defer Q3 until <what settles it>`): run
|
|
49
|
+
`plumbbob defer Q3 "<what settles it>"`, which keeps the question and swaps its
|
|
50
|
+
`*resolve by:*` for `*deferred:* <date>, until <what settles it>` on the opener line,
|
|
51
|
+
and the table moves it to a `Deferred` line beneath, still open
|
|
51
52
|
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
53
|
+
A question the table still shows that the human has already set aside (parked, settled
|
|
54
|
+
as a tangent, or kept open as a log) is drift: propose deferring it, until what the
|
|
55
|
+
build says it waits on, rather than asking it again
|
|
56
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
52
57
|
Never retype the table as a list of your own
|
|
53
58
|
([D118 (questions-table)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d118)).
|
|
54
59
|
- **Repair (re-sync to reality).** When the plan has drifted (a Decision was overtaken
|
package/skills/run/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: run
|
|
|
3
3
|
description: "Run what is left of the range holding the next step, or of range N: the plan's `## Build ranges` names the steps, the turn hook mints their grant from the plan, and the run walks them the way a typed range is walked, then ends on the stop card, the range's review with every open park proposed, which one reply settles. A bare range builds its one step to its pause. Reads the build skill beside it for the per-step loop."
|
|
4
4
|
argument-hint: "[range-number]"
|
|
5
5
|
disable-model-invocation: true
|
|
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:*), Bash(plumbbob harvest:*), Bash(plumbbob order:*), Bash(git log:*)
|
|
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 defer:*), Bash(plumbbob agent:*), Bash(plumbbob spike:*), Bash(git diff:*), Bash(plumbbob harvest:*), Bash(plumbbob order:*), Bash(git log:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# PlumbBob: run a range
|
|
@@ -223,8 +223,9 @@ The card's proposals are settled in one reply
|
|
|
223
223
|
and the Your Call block names the replies. Read the human's message as one of them:
|
|
224
224
|
|
|
225
225
|
- **`looks good`** confirms the review and locks in every lean as proposed, as the line
|
|
226
|
-
beneath the card's Your Call label says. Fold each confirmed blocker into `intent.md
|
|
227
|
-
|
|
226
|
+
beneath the card's Your Call label says. Fold each confirmed blocker into `intent.md`, and
|
|
227
|
+
defer each open question a confirmed tangent sets aside, by the harvest skill's rules
|
|
228
|
+
first, then run `plumbbob harvest`, which flips the parks,
|
|
228
229
|
writes the Harvest lines, and writes the range's review beneath a dated line in the Log.
|
|
229
230
|
A blocker that becomes a step goes into a range still to come, or a bare range of its
|
|
230
231
|
own, never into the range this card reviews: that range has landed, its call is the one
|
|
@@ -250,7 +251,7 @@ and the Your Call block names the replies. Read the human's message as one of th
|
|
|
250
251
|
`plumbbob status --run "<the same argument>"` again. When its grant line says
|
|
251
252
|
`unlike the run`, the record re-cut the range the run would walk (a blocker added or moved a
|
|
252
253
|
step in it), and the grant the hook minted before the record no longer covers it: relay
|
|
253
|
-
the record's ending whole and end the turn there, where its
|
|
254
|
+
the record's ending whole and end the turn there, where the range line beneath its Next Up names the re-cut run
|
|
254
255
|
for the human to type
|
|
255
256
|
([D102 (settle-in-one-reply)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d102)).
|
|
256
257
|
Otherwise relay the record's ending and walk the run from section 4.
|
package/templates/intent.md
CHANGED
|
@@ -149,8 +149,9 @@ build reports zero open questions
|
|
|
149
149
|
fill it in and it counts.
|
|
150
150
|
|
|
151
151
|
When the human would rather let the work answer a question, it is deferred, not
|
|
152
|
-
resolved:
|
|
153
|
-
<what settles it> ON THE OPENER LINE, so
|
|
152
|
+
resolved: `plumbbob defer Q4 "<what settles it>"` keeps the question and swaps
|
|
153
|
+
*resolve by:* for *deferred:* <date>, until <what settles it> ON THE OPENER LINE, so
|
|
154
|
+
the opener reads
|
|
154
155
|
|
|
155
156
|
**Q4 (latency-budget)**: What latency budget do we hold?, *deferred:* 2026-10-31, until step 4's latency numbers
|
|
156
157
|
|
|
@@ -158,6 +159,10 @@ It stays open and still counts, but the card lists it on a line beneath the tabl
|
|
|
158
159
|
rather than in it, so `yes to all leans` leaves it alone. Once the work answers it,
|
|
159
160
|
swap the marker for *resolved:* as above
|
|
160
161
|
([D120 (deferred-questions)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d120)).
|
|
162
|
+
A question parked, settled as a tangent, or kept open as the running log of something
|
|
163
|
+
the build tunes waits on the work too, so it is deferred the same way; left on
|
|
164
|
+
*resolve by:*, the card counts it as a call still owed
|
|
165
|
+
([D123 (defer-records)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d123)).
|
|
161
166
|
|
|
162
167
|
Size to the work: a tiny build's single, obvious question may stay one bare line;
|
|
163
168
|
the *plain:*/*lean:* pair earns its keep only when a human decision genuinely waits
|