plumbbob 0.13.6 → 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 +6 -6
- package/dist/cli-core.js +15 -1
- package/dist/lib/links.js +97 -0
- package/dist/lib/notice.js +1 -1
- package/dist/lib/orient.js +152 -11
- package/dist/lib/sidecar.js +43 -5
- package/dist/verbs/build.js +20 -12
- package/dist/verbs/checkpoint.js +8 -4
- package/dist/verbs/defer.js +96 -0
- package/dist/verbs/handoff.js +275 -97
- package/dist/verbs/harvest.js +15 -3
- package/dist/verbs/park.js +14 -4
- 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 +37 -19
- package/skills/harvest/SKILL.md +12 -3
- package/skills/park/SKILL.md +26 -9
- package/skills/plan/SKILL.md +43 -16
- package/skills/refine/SKILL.md +19 -5
- package/skills/run/SKILL.md +24 -10
- package/skills/status/SKILL.md +7 -0
- package/skills/verify/SKILL.md +12 -7
- package/templates/intent.md +19 -1
|
@@ -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
|
+
}
|