plumbbob 0.9.5 → 0.10.1
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 +89 -87
- package/dist/cli-core.js +18 -8
- package/dist/cli.js +1 -1
- package/dist/lib/agents.js +26 -26
- package/dist/lib/buildlog.js +21 -11
- package/dist/lib/buildlogsync.js +8 -8
- package/dist/lib/check.js +14 -14
- package/dist/lib/commitbody.js +72 -0
- package/dist/lib/commitmsg.js +7 -7
- package/dist/lib/git.js +44 -15
- package/dist/lib/intent.js +20 -19
- package/dist/lib/latch.js +16 -16
- package/dist/lib/orient.js +36 -17
- package/dist/lib/reentry.js +3 -3
- package/dist/lib/settings.js +16 -16
- package/dist/lib/sidecar.js +70 -54
- package/dist/lib/templates.js +1 -1
- package/dist/verbs/abandon.js +122 -0
- package/dist/verbs/agent.js +25 -25
- package/dist/verbs/build.js +9 -9
- package/dist/verbs/check.js +6 -6
- package/dist/verbs/checkpoint.js +48 -47
- package/dist/verbs/doctor.js +35 -29
- package/dist/verbs/finish.js +27 -40
- package/dist/verbs/handoff.js +9 -8
- package/dist/verbs/init.js +9 -9
- package/dist/verbs/park.js +3 -3
- package/dist/verbs/recover.js +12 -12
- package/dist/verbs/revert.js +12 -12
- package/dist/verbs/spike.js +21 -21
- package/dist/verbs/start.js +49 -19
- package/dist/verbs/status.js +8 -8
- package/dist/verbs/turn.js +16 -16
- package/dist/verbs/use.js +4 -4
- package/node_modules/checkride/.claude-plugin/plugin.json +1 -1
- package/node_modules/checkride/CHANGELOG.md +401 -0
- package/node_modules/checkride/README.md +27 -9
- package/node_modules/checkride/dist/adapters.d.ts +12 -4
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +21 -1
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +9 -1
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/cursor.js +59 -1
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.js +10 -1
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +23 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.js +387 -82
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +2 -2
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +14 -5
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +58 -5
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
- package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/history.js +232 -0
- package/node_modules/checkride/dist/baseline/history.js.map +1 -0
- package/node_modules/checkride/dist/baseline/index.d.ts +4 -2
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +2 -1
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +30 -7
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +50 -16
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +5 -1
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +98 -25
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +33 -4
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +21 -6
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.js +1 -1
- package/node_modules/checkride/dist/doctor.d.ts +2 -0
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +39 -1
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +49 -4
- package/node_modules/checkride/dist/gate.d.ts.map +1 -1
- package/node_modules/checkride/dist/gate.js +143 -16
- package/node_modules/checkride/dist/gate.js.map +1 -1
- package/node_modules/checkride/dist/init.d.ts +8 -1
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +68 -7
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +1 -1
- package/node_modules/checkride/dist/node-pin.js +1 -1
- package/node_modules/checkride/dist/orchestrator.d.ts +13 -0
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +118 -13
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +1 -0
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +23 -2
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/launch.js +16 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +1 -1
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/qa/mutation.d.ts +4 -4
- package/node_modules/checkride/dist/qa/mutation.js +4 -4
- package/node_modules/checkride/dist/recover.d.ts +48 -0
- package/node_modules/checkride/dist/recover.d.ts.map +1 -0
- package/node_modules/checkride/dist/recover.js +220 -0
- package/node_modules/checkride/dist/recover.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +1 -1
- package/node_modules/checkride/dist/smoke.js +1 -1
- package/node_modules/checkride/dist/snippets.d.ts +1 -1
- package/node_modules/checkride/package.json +3 -2
- package/node_modules/checkride/schema/checkride.config.schema.json +9 -1
- package/node_modules/checkride/skills/check/SKILL.md +9 -1
- package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
- package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
- package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
- package/node_modules/checkride/templates/shared/vale.ini +34 -0
- package/package.json +13 -11
- package/skills/abandon/SKILL.md +20 -0
- package/skills/build/SKILL.md +69 -64
- package/skills/doctor/SKILL.md +11 -11
- package/skills/finish/SKILL.md +20 -19
- package/skills/harvest/SKILL.md +13 -12
- package/skills/park/SKILL.md +10 -10
- package/skills/plan/SKILL.md +63 -54
- package/skills/recover/SKILL.md +7 -7
- package/skills/refine/SKILL.md +18 -16
- package/skills/revert/SKILL.md +6 -6
- package/skills/spike/SKILL.md +9 -9
- package/skills/status/SKILL.md +5 -5
- package/skills/step/SKILL.md +17 -12
- package/skills/verify/SKILL.md +45 -40
- package/templates/build-log.md +7 -7
- package/templates/intent.md +49 -39
- package/templates/spike-report.md +10 -10
package/dist/verbs/agent.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// `plumbbob agent <subcommand
|
|
1
|
+
// `plumbbob agent <subcommand>`: the doorway to user-authored agents: anything
|
|
2
2
|
// executable that speaks the JSON envelope contract (JSON in on stdin, JSON out
|
|
3
|
-
// on stdout, prose streamed on stderr). `agent list` walks the two agent tiers
|
|
4
|
-
// the repo's tracked `.plumbbob/agents/<name>/`, then the personal
|
|
5
|
-
// `~/.plumbbob/agents/<name>/`
|
|
3
|
+
// on stdout, prose streamed on stderr). `agent list` walks the two agent tiers
|
|
4
|
+
// (the repo's tracked `.plumbbob/agents/<name>/`, then the personal
|
|
5
|
+
// `~/.plumbbob/agents/<name>/`) and prints each resolvable agent. `agent run
|
|
6
6
|
// <name> [--step N] [--mode before|build|after]` composes the StepContext,
|
|
7
7
|
// spawns the manifest command, streams its stderr live, captures and validates
|
|
8
8
|
// the child's envelope, re-emits it on this verb's own stdout (machine) with the
|
|
9
9
|
// human summary on stderr, lands `parked[]` through the build-log, and appends
|
|
10
|
-
// the envelope to the handoff ledger (`builds/<slug>/handoff.json
|
|
10
|
+
// the envelope to the handoff ledger (`builds/<slug>/handoff.json`: untracked,
|
|
11
11
|
// step-scoped, cleared at checkpoint) so later runs can thread it back in.
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// construction, not by policy. A thin read-write shell: resolution,
|
|
12
|
+
// No code path here can checkpoint, flip a step, or chain agents, and that
|
|
13
|
+
// absence is deliberate; the subprocess boundary keeps the human as the clock
|
|
14
|
+
// by construction, not by policy. A thin read-write shell: resolution,
|
|
15
15
|
// composition, and spawn mechanics live in lib/agents.ts.
|
|
16
16
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
@@ -50,10 +50,10 @@ function list(cwd, _args) {
|
|
|
50
50
|
* Run one named agent, or a slot's harness-bound agents, against the step in flight.
|
|
51
51
|
*
|
|
52
52
|
* With a name (or `--agent` flag) it runs exactly that agent, failing loud on a
|
|
53
|
-
* miss
|
|
53
|
+
* miss; the user who typed the name asked for it specifically. With no name it
|
|
54
54
|
* runs whatever the build's harness.json binds to the requested slot. Either
|
|
55
|
-
* way it composes the StepContext, spawns, and applies side effects
|
|
56
|
-
* advancing the loop.
|
|
55
|
+
* way it composes the StepContext, spawns, and applies side effects (never
|
|
56
|
+
* advancing the loop).
|
|
57
57
|
*/
|
|
58
58
|
async function run(cwd, args) {
|
|
59
59
|
const root = findRepoRoot(cwd);
|
|
@@ -72,7 +72,7 @@ async function run(cwd, args) {
|
|
|
72
72
|
process.stderr.write('plumbbob: no step to run against — pass --step N, or `plumbbob build N` first.\n');
|
|
73
73
|
return 1;
|
|
74
74
|
}
|
|
75
|
-
// A `--agent <path>` flag still needs a name
|
|
75
|
+
// A `--agent <path>` flag still needs a name; it labels an explicit run (the
|
|
76
76
|
// handoff ledger and the human summary key on the name), it does not name one.
|
|
77
77
|
if (parsed.name === undefined && parsed.flagPath !== undefined) {
|
|
78
78
|
process.stderr.write('plumbbob: --agent needs an agent name too, for the run label. Try: plumbbob agent run reviewer --agent ./path --step 3.\n');
|
|
@@ -94,10 +94,10 @@ async function run(cwd, args) {
|
|
|
94
94
|
* One agent's full run: resolve it, pick its slot, compose the StepContext, spawn, report.
|
|
95
95
|
*
|
|
96
96
|
* `ambient` marks a harness-bound run whose resolution or slot mismatch
|
|
97
|
-
* degrades to a warning so a batch keeps going
|
|
97
|
+
* degrades to a warning so a batch keeps going; a binding is ambient
|
|
98
98
|
* configuration the loop must survive without; an explicit ask
|
|
99
99
|
* (`ambient: false`) fails loud on the same miss. A run that actually starts
|
|
100
|
-
* and fails (non-zero exit, timeout, …) is a hard failure either way
|
|
100
|
+
* and fails (non-zero exit, timeout, …) is a hard failure either way: the
|
|
101
101
|
* softening covers a *missing* agent, never a broken one.
|
|
102
102
|
*/
|
|
103
103
|
async function runOne(root, slug, step, spec) {
|
|
@@ -126,17 +126,17 @@ async function runOne(root, slug, step, spec) {
|
|
|
126
126
|
mode,
|
|
127
127
|
settings: {
|
|
128
128
|
// The envelope reports the `auto` setting to agents as information only.
|
|
129
|
-
// The checkpoint latch never reads it
|
|
129
|
+
// The checkpoint latch never reads it; a model can write a settings
|
|
130
130
|
// file, so a standing `auto` cannot be a self-approval grant; approval
|
|
131
131
|
// comes only from what the human literally typed. A `true` here informs
|
|
132
132
|
// an agent, it does not self-approve.
|
|
133
133
|
auto: resolveBoolean(root, 'auto', false),
|
|
134
134
|
agentTimeout: resolveNumber(root, 'agentTimeout', 0),
|
|
135
135
|
// Hand this agent its own config block over the envelope's existing
|
|
136
|
-
// `settings` field
|
|
136
|
+
// `settings` field: no new envelope field, no new verb; the config just
|
|
137
137
|
// rides here. Resolution: settings.json's agentConfig[name], with the
|
|
138
138
|
// untracked personal overlay (settings.local.json) replacing the project
|
|
139
|
-
// entry whole
|
|
139
|
+
// entry whole (no deep merge) and {} when neither defines it.
|
|
140
140
|
agent: resolveRecord(root, 'agentConfig')[spec.name] ?? {},
|
|
141
141
|
},
|
|
142
142
|
});
|
|
@@ -159,7 +159,7 @@ async function runOne(root, slug, step, spec) {
|
|
|
159
159
|
/**
|
|
160
160
|
* No name given: run the agents the build's harness.json binds to the requested slot.
|
|
161
161
|
*
|
|
162
|
-
* Bindings merge as a ladder
|
|
162
|
+
* Bindings merge as a ladder: the per-step harness entry beats the harness
|
|
163
163
|
* `defaults`, which beat the settings-level `agents` key; the first level that
|
|
164
164
|
* names the slot wins, replace not append. A missing bound agent degrades to a
|
|
165
165
|
* warning; an absent harness, or one that binds nothing to this slot, is a
|
|
@@ -216,7 +216,7 @@ function degrade(ambient, hard, soft) {
|
|
|
216
216
|
*
|
|
217
217
|
* A value flag missing its value, or `--step` given a non-number, is a loud
|
|
218
218
|
* error (returned as a string) rather than a silent default. An undeclared
|
|
219
|
-
* `--flag` never arrives here
|
|
219
|
+
* `--flag` never arrives here; `run` screens argv against the verb spec in
|
|
220
220
|
* cli-core.ts and refuses before dispatch.
|
|
221
221
|
*/
|
|
222
222
|
function parseRunArgs(args) {
|
|
@@ -254,7 +254,7 @@ function parseRunArgs(args) {
|
|
|
254
254
|
/**
|
|
255
255
|
* Resolve the slot the agent runs in.
|
|
256
256
|
*
|
|
257
|
-
* An explicit `--mode` must name a real slot AND one the manifest declares
|
|
257
|
+
* An explicit `--mode` must name a real slot AND one the manifest declares;
|
|
258
258
|
* an undeclared slot is refused loud, because the user asked for that exact
|
|
259
259
|
* run. With no `--mode`, a single-slot agent uses its only slot; a multi-slot
|
|
260
260
|
* agent must be told which one (harness bindings pick the slot from the
|
|
@@ -282,11 +282,11 @@ function resolveMode(flag, manifest) {
|
|
|
282
282
|
/**
|
|
283
283
|
* Map the run outcome to reporting and side effects.
|
|
284
284
|
*
|
|
285
|
-
* A failed run
|
|
286
|
-
* shell that never started
|
|
285
|
+
* A failed run (non-zero exit, out-of-contract stdout, timeout, interrupt, or a
|
|
286
|
+
* shell that never started) reports on stderr and stops with NO side effects:
|
|
287
287
|
* the envelope of a failed child is not authoritative. A clean run lands
|
|
288
288
|
* `parked[]`, records the envelope in the handoff ledger, prints the human
|
|
289
|
-
* summary on stderr, and re-emits the machine envelope on stdout
|
|
289
|
+
* summary on stderr, and re-emits the machine envelope on stdout, nothing else,
|
|
290
290
|
* ever (the stream discipline: stdout carries the envelope alone).
|
|
291
291
|
*/
|
|
292
292
|
function report(root, slug, name, mode, step, result) {
|
|
@@ -321,7 +321,7 @@ function failureLine(name, result) {
|
|
|
321
321
|
/**
|
|
322
322
|
* Land each parked concern through the build-log's Park list.
|
|
323
323
|
*
|
|
324
|
-
* The agent never writes .plumbbob/ itself
|
|
324
|
+
* The agent never writes .plumbbob/ itself; parked lines only reach the
|
|
325
325
|
* build-log through this verb. A build-log with no "## Park list" section, or no
|
|
326
326
|
* build-log at all, warns once rather than losing the lines silently.
|
|
327
327
|
*/
|
|
@@ -351,7 +351,7 @@ function applyParked(root, slug, parked) {
|
|
|
351
351
|
/**
|
|
352
352
|
* The human-facing summary printed on stderr.
|
|
353
353
|
*
|
|
354
|
-
* A headline plus, for a halt, the route out
|
|
354
|
+
* A headline plus, for a halt, the route out: `blocked` unblocks and re-runs,
|
|
355
355
|
* `drift` sends the plan to /plumbbob:refine. The machine envelope on stdout carries
|
|
356
356
|
* the same status for the calling skill; this stderr copy is the terminal read.
|
|
357
357
|
*/
|
package/dist/verbs/build.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// `plumbbob build <n
|
|
1
|
+
// `plumbbob build <n>`: enter a step: read step n's seam from intent.md and
|
|
2
2
|
// write the normalized SEAM + STEP control files (flat, untracked, per-build).
|
|
3
|
-
// The seam is the step's edit grant
|
|
3
|
+
// The seam is the step's edit grant: the exact paths and `dir/` prefixes the
|
|
4
4
|
// step expects to touch, for orientation, never a lock. The STEP file is the
|
|
5
5
|
// in-flight signal (the dashboard derives the BUILD phase from it); this verb
|
|
6
|
-
// never checkpoints
|
|
6
|
+
// never checkpoints; only `checkpoint` commits.
|
|
7
7
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { findRepoRoot } from "../lib/git.js";
|
|
9
9
|
import { hasSession, intentPath, resolveBuild, seamPath, stampStepStat, stampTick, stepPath } from "../lib/sidecar.js";
|
|
@@ -11,7 +11,7 @@ import { parseStepSeam } from "../lib/intent.js";
|
|
|
11
11
|
import { parseSteps } from "../lib/orient.js";
|
|
12
12
|
import { stepLabel, syncBuildLogState } from "../lib/buildlogsync.js";
|
|
13
13
|
/**
|
|
14
|
-
* Enter step n
|
|
14
|
+
* Enter step n (or the next undone step) and write its SEAM/STEP markers.
|
|
15
15
|
*
|
|
16
16
|
* Refuses a missing session, a malformed step argument, an `N-M` range (a
|
|
17
17
|
* `/plumbbob:build` skill feature, not a CLI one), and a seam that fails to parse.
|
|
@@ -27,13 +27,13 @@ export function build(cwd, args) {
|
|
|
27
27
|
const { build: slug, rest } = resolveBuild(root, args);
|
|
28
28
|
const raw = rest.find((a) => !a.startsWith('--'));
|
|
29
29
|
// A step range like `1-3` is a `/plumbbob:build` skill affordance (auto-approve
|
|
30
|
-
// through the range, then pause), not a CLI capability
|
|
30
|
+
// through the range, then pause), not a CLI capability: the CLI records one
|
|
31
31
|
// in-flight step at a time. Name it rather than bounce off the generic usage.
|
|
32
32
|
if (raw !== undefined && /^\d+-\d*$/.test(raw)) {
|
|
33
33
|
process.stderr.write(`plumbbob: build takes one step number; \`${raw}\` step ranges are a \`/plumbbob:build\` feature (auto-approve through the range, then pause). Try \`plumbbob build ${raw.split('-')[0]}\`.\n`);
|
|
34
34
|
return 1;
|
|
35
35
|
}
|
|
36
|
-
// An explicit arg that isn't a positive integer is a usage error
|
|
36
|
+
// An explicit arg that isn't a positive integer is a usage error: caught before
|
|
37
37
|
// reading intent.md so the message doesn't depend on the plan being present.
|
|
38
38
|
if (raw !== undefined && (!/^\d+$/.test(raw) || Number(raw) < 1)) {
|
|
39
39
|
process.stderr.write('plumbbob: build needs a step number. Try: plumbbob build 2.\n');
|
|
@@ -65,14 +65,14 @@ export function build(cwd, args) {
|
|
|
65
65
|
writeFileSync(stepPath(root, slug), `${step}\n`);
|
|
66
66
|
// Stamp the turn ledger on entry (TICK = TURN): the checkpoint latch demands
|
|
67
67
|
// a human turn after this point before the step may land. Skipped when the
|
|
68
|
-
// ledger is dormant
|
|
68
|
+
// ledger is dormant: a host with no hooks grows no TURN/TICK files.
|
|
69
69
|
stampTick(root, slug);
|
|
70
70
|
// The wall-clock receipt starts here: checkpoint stamps landedAt, and the
|
|
71
71
|
// pair becomes the step's duration in the finish report.
|
|
72
72
|
stampStepStat(root, slug, step, 'startedAt', new Date().toISOString());
|
|
73
73
|
// The build-log's top half is CLI-owned so it never lies: show this step in
|
|
74
|
-
// flight
|
|
75
|
-
// Best-effort
|
|
74
|
+
// flight: Current step plus the ☐/☑ mirror, re-rendered from intent.md.
|
|
75
|
+
// Best-effort: a missing or hand-edited build-log never blocks the build.
|
|
76
76
|
const title = parseSteps(intent).find((s) => s.n === step)?.title ?? null;
|
|
77
77
|
syncBuildLogState(root, slug, stepLabel(step, title));
|
|
78
78
|
const picked = raw === undefined ? ' (next undone)' : '';
|
package/dist/verbs/check.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// `plumbbob check
|
|
1
|
+
// `plumbbob check`: run the heavy gate and report, with NO state change. This
|
|
2
2
|
// is the read-only half of the verify tick: `/plumbbob:verify` runs it before the
|
|
3
3
|
// pause so the human approves on a known-green check. The gate itself lives in
|
|
4
4
|
// lib/check.ts: checkride (our sibling check-runner package) by default, or a
|
|
5
|
-
// spawn command named by the `check` setting
|
|
6
|
-
// ladder (flag → local overlay → tracked settings.json → default)
|
|
5
|
+
// spawn command named by the `check` setting (resolved through the settings
|
|
6
|
+
// ladder (flag → local overlay → tracked settings.json → default)) for repos
|
|
7
7
|
// that gate through something else. Exits with the check's own code (0 = green,
|
|
8
8
|
// 1 = red, 2 = the gate itself broke).
|
|
9
9
|
//
|
|
10
10
|
// Narrowing flags for the iteration loop (`check --bail --only types,lint`) map
|
|
11
|
-
// straight onto checkride's run flags. Only this verb takes them
|
|
11
|
+
// straight onto checkride's run flags. Only this verb takes them: the
|
|
12
12
|
// checkpoint gate is always full-fat.
|
|
13
13
|
import { findRepoRoot } from "../lib/git.js";
|
|
14
14
|
import { hasSession } from "../lib/sidecar.js";
|
|
@@ -35,7 +35,7 @@ export async function check(cwd, args = []) {
|
|
|
35
35
|
* Only the declared flags are read here; an undeclared one never reaches this
|
|
36
36
|
* function, because `run` screens argv against the verb spec in cli-core.ts and
|
|
37
37
|
* refuses before dispatch. This used to tolerate unknown args on the grounds
|
|
38
|
-
* that running the gate mattered more than flag hygiene
|
|
38
|
+
* that running the gate mattered more than flag hygiene, but the same tolerance
|
|
39
39
|
* on the mutating verbs let `checkpoint --help` commit, so the screen is now
|
|
40
40
|
* central and uniform.
|
|
41
41
|
*/
|
|
@@ -69,7 +69,7 @@ function slotList(args, flag) {
|
|
|
69
69
|
/**
|
|
70
70
|
* The one-line human verdict for a check exit code.
|
|
71
71
|
*
|
|
72
|
-
* A broken gate (exit 2) reports distinctly from red
|
|
72
|
+
* A broken gate (exit 2) reports distinctly from red: a misconfigured harness
|
|
73
73
|
* must never read as broken code, though both block a checkpoint.
|
|
74
74
|
*/
|
|
75
75
|
function verdictLine(code) {
|
package/dist/verbs/checkpoint.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
// `plumbbob checkpoint [<n>] [-m <msg>]
|
|
1
|
+
// `plumbbob checkpoint [<n>] [-m <msg>]`: the commit tick that lands a step.
|
|
2
2
|
// Executor-agnostic and author-blind: it reads the diff, not who wrote it, so a
|
|
3
3
|
// `/plumbbob:build` run, your own hands, a vibe session, or another harness all
|
|
4
4
|
// checkpoint identically. It does NOT require a STEP marker (the flat control
|
|
5
5
|
// file recording the step in flight): the step is whatever you pass, else the
|
|
6
6
|
// in-flight STEP, else the next undone step in intent.md. It evaluates the
|
|
7
7
|
// approval latch, gates on a green check, commits any pending work (or records
|
|
8
|
-
// the existing HEAD when the tree is already clean
|
|
8
|
+
// the existing HEAD when the tree is already clean: the human's commit skill
|
|
9
9
|
// may have committed first), records the SHA, flips the intent checkbox to
|
|
10
|
-
// `[x]`, and clears the STEP/SEAM markers
|
|
10
|
+
// `[x]`, and clears the STEP/SEAM markers: returning the dashboard to the
|
|
11
11
|
// DESIGN boundary.
|
|
12
12
|
import { appendFileSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { commit, findRepoRoot, headSha, isDirty, stageAll, stagePath, stagedPaths, stagedStat } from "../lib/git.js";
|
|
14
14
|
import { activeBuild, buildFolder, buildLogPath, buildScope, bumpStepStat, checkpointsPath, clearHandoff, clearTick, hasSession, intentPath, readStats, refreshExcludes, seamPath, stampStepStat, stepPath, } from "../lib/sidecar.js";
|
|
15
15
|
import { runCheck } from "../lib/check.js";
|
|
16
|
+
import { readCommitBody } from "../lib/commitbody.js";
|
|
16
17
|
import { checkLatch } from "../lib/latch.js";
|
|
17
18
|
import { markStepDone, parseSteps } from "../lib/orient.js";
|
|
18
19
|
import { parseBuildScope, parseStepSeam, scopeDrift } from "../lib/intent.js";
|
|
@@ -22,7 +23,7 @@ import { AT_BOUNDARY, syncBuildLogState } from "../lib/buildlogsync.js";
|
|
|
22
23
|
/**
|
|
23
24
|
* Land a step: latch, check gate, commit, record, return to the boundary.
|
|
24
25
|
*
|
|
25
|
-
* Exit 1 on any refusal
|
|
26
|
+
* Exit 1 on any refusal: no session, no resolvable step, a latched tick, or a
|
|
26
27
|
* red/broken check; each refusal says what unblocks it.
|
|
27
28
|
*/
|
|
28
29
|
export async function checkpoint(cwd, args) {
|
|
@@ -31,7 +32,7 @@ export async function checkpoint(cwd, args) {
|
|
|
31
32
|
process.stderr.write('plumbbob: no active session. Run `plumbbob start "<title>"` first.\n');
|
|
32
33
|
return 1;
|
|
33
34
|
}
|
|
34
|
-
// Before either staging path
|
|
35
|
+
// Before either staging path: `-A` below, and the plan commit's folder-scoped
|
|
35
36
|
// add, which also leans on the excludes to skip the in-flight markers inside
|
|
36
37
|
// the build folder.
|
|
37
38
|
refreshExcludes(root);
|
|
@@ -43,10 +44,18 @@ export async function checkpoint(cwd, args) {
|
|
|
43
44
|
process.stderr.write('plumbbob: no step to checkpoint — pass a number, or plan a step in intent.md first.\n');
|
|
44
45
|
return 1;
|
|
45
46
|
}
|
|
46
|
-
//
|
|
47
|
+
// Checked before the latch and the check gate: a socket stdin can never
|
|
48
|
+
// deliver the requested body, so there is no point latching or running a
|
|
49
|
+
// ~55s check only to discover that afterward.
|
|
50
|
+
const bodyResult = readCommitBody(args);
|
|
51
|
+
if (!bodyResult.ok) {
|
|
52
|
+
process.stderr.write(bodyResult.message);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
// The approval latch runs before the check gate: cheap first. The tick may
|
|
47
56
|
// not land without a human turn since the step was entered, a one-turn grant
|
|
48
57
|
// the human typed, or a dormant ledger. A refusal is not an error: the
|
|
49
|
-
// message is the pause affordance
|
|
58
|
+
// message is the pause affordance: exit 1 and hand the turn back.
|
|
50
59
|
const latch = checkLatch(root, step);
|
|
51
60
|
if (!latch.allow) {
|
|
52
61
|
process.stderr.write(latch.message);
|
|
@@ -69,7 +78,7 @@ export async function checkpoint(cwd, args) {
|
|
|
69
78
|
if (isDirty(root)) {
|
|
70
79
|
stageAll(root);
|
|
71
80
|
warnScopeDrift(root, step);
|
|
72
|
-
const body = withMarker(`plumbbob step ${step}`,
|
|
81
|
+
const body = withMarker(`plumbbob step ${step}`, bodyResult.body ?? fallbackBody(root, step));
|
|
73
82
|
sha = commit(root, messageArg(args) ?? subjectForStep(root, step), body);
|
|
74
83
|
}
|
|
75
84
|
else {
|
|
@@ -86,8 +95,8 @@ export async function checkpoint(cwd, args) {
|
|
|
86
95
|
syncBuildLogState(root, activeBuild(root), AT_BOUNDARY);
|
|
87
96
|
rmSync(seamPath(root), { force: true });
|
|
88
97
|
rmSync(stepPath(root), { force: true });
|
|
89
|
-
clearHandoff(root); // the agent-run handoff ledger is step-scoped
|
|
90
|
-
clearTick(root); // the step's entry stamp is spent
|
|
98
|
+
clearHandoff(root); // the agent-run handoff ledger is step-scoped; clear it with the markers.
|
|
99
|
+
clearTick(root); // the step's entry stamp is spent; the next `build <n>` re-stamps.
|
|
91
100
|
process.stdout.write(`plumbbob: step ${step} checkpointed — ${sha.slice(0, 9)}. Back at the boundary.\n`);
|
|
92
101
|
return 0;
|
|
93
102
|
}
|
|
@@ -97,33 +106,46 @@ export async function checkpoint(cwd, args) {
|
|
|
97
106
|
*
|
|
98
107
|
* Giving the plan its own commit keeps the first step's diff from absorbing the
|
|
99
108
|
* scaffold, so `git log` reads baseline → plan → steps. No check gate (there is
|
|
100
|
-
* no code work to verify yet), no intent flip, no step markers
|
|
109
|
+
* no code work to verify yet), no intent flip, no step markers: the plan lives
|
|
101
110
|
* entirely in DESIGN. An optional `--body` (stdin heredoc) rides after the
|
|
102
111
|
* `plumbbob plan` marker line; the folder is whitelisted artifact plane, so
|
|
103
112
|
* there is no scope drift to warn about.
|
|
104
113
|
*/
|
|
105
114
|
function checkpointPlan(root, args) {
|
|
106
115
|
// The latch covers the plan commit too, keyed on the TICK that `start`
|
|
107
|
-
// stamped: the plan pause is a real pause. No step number
|
|
116
|
+
// stamped: the plan pause is a real pause. No step number: a step-range
|
|
108
117
|
// grant does not speak to a plan.
|
|
109
118
|
const latch = checkLatch(root, null);
|
|
110
119
|
if (!latch.allow) {
|
|
111
120
|
process.stderr.write(latch.message);
|
|
112
121
|
return 1;
|
|
113
122
|
}
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
const bodyResult = readCommitBody(args);
|
|
124
|
+
if (!bodyResult.ok) {
|
|
125
|
+
process.stderr.write(bodyResult.message);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
// A repo that gitignores the sidecar (its `.gitignore`, not plumbbob's own
|
|
129
|
+
// info/exclude) makes this folder-scoped add impossible; stagePath skips it
|
|
130
|
+
// and reports false, and the plan still lands as a valid record-only commit:
|
|
131
|
+
// its body carries the plan, the files stay untracked, and `--allow-empty`
|
|
132
|
+
// (in commit) makes the empty diff legal. History still reads baseline → plan
|
|
133
|
+
// → steps.
|
|
134
|
+
const staged = stagePath(root, buildFolder(root));
|
|
135
|
+
const sha = commit(root, planSubject(root), withMarker('plumbbob plan', bodyResult.body ?? undefined));
|
|
116
136
|
appendFileSync(checkpointsPath(root), `plan ${sha}\n`);
|
|
117
137
|
// Landing the plan consumes `start`'s entry stamp: a later hand-built diff
|
|
118
138
|
// (no `build <n>`) must find no stale TICK and stay guidance-governed.
|
|
119
139
|
clearTick(root);
|
|
120
|
-
process.stdout.write(
|
|
140
|
+
process.stdout.write(staged
|
|
141
|
+
? `plumbbob: plan committed — ${sha.slice(0, 9)}. Baseline → plan → steps.\n`
|
|
142
|
+
: `plumbbob: plan committed (record-only — sidecar is gitignored; the plan rides the commit message, the files stay untracked) — ${sha.slice(0, 9)}. Baseline → plan → steps.\n`);
|
|
121
143
|
return 0;
|
|
122
144
|
}
|
|
123
145
|
/**
|
|
124
146
|
* Compose the plan commit's CLI-owned subject: `chore(<scope>): plan`.
|
|
125
147
|
*
|
|
126
|
-
* The scope resolves through the build-default fallback chain
|
|
148
|
+
* The scope resolves through the build-default fallback chain: a bare
|
|
127
149
|
* `chore: plan` when none resolves. The `plumbbob plan` identifier rides the
|
|
128
150
|
* body marker line, never the subject.
|
|
129
151
|
*/
|
|
@@ -134,7 +156,7 @@ function planSubject(root) {
|
|
|
134
156
|
* Resolve the step being checkpointed, or null when none can be determined.
|
|
135
157
|
*
|
|
136
158
|
* Explicit arg > in-flight STEP file > first undone step in intent.md. A `-m`
|
|
137
|
-
* value is a message, never a step
|
|
159
|
+
* value is a message, never a step: `checkpoint -m "2"` must not read as
|
|
138
160
|
* step 2.
|
|
139
161
|
*/
|
|
140
162
|
function resolveStep(root, args) {
|
|
@@ -154,10 +176,10 @@ function resolveStep(root, args) {
|
|
|
154
176
|
}
|
|
155
177
|
}
|
|
156
178
|
/**
|
|
157
|
-
* Warn
|
|
179
|
+
* Warn (never refuse) when the staged tree reaches beyond the step's seam.
|
|
158
180
|
*
|
|
159
181
|
* Guidance, not a gate: the checkpoint captures the drift and says so. The seam
|
|
160
|
-
* (the step's edit grant
|
|
182
|
+
* (the step's edit grant: exact paths or `dir/` prefixes) comes from the
|
|
161
183
|
* in-flight SEAM file when a build is live, else the step's declared seam in
|
|
162
184
|
* intent.md. Plumbbob's own artifact plane is whitelisted inside `scopeDrift`,
|
|
163
185
|
* so the `[x]` flip and build-log line this very checkpoint stages never read
|
|
@@ -177,7 +199,7 @@ function warnScopeDrift(root, step) {
|
|
|
177
199
|
*
|
|
178
200
|
* The normalized SEAM file `build` wrote is authoritative while a build is
|
|
179
201
|
* live; fall back to the step's declared seam parsed from intent.md. Empty when
|
|
180
|
-
* neither resolves
|
|
202
|
+
* neither resolves: the caller then skips the drift warning rather than
|
|
181
203
|
* flagging the whole tree.
|
|
182
204
|
*/
|
|
183
205
|
function seamTokens(root, step) {
|
|
@@ -191,14 +213,14 @@ function seamTokens(root, step) {
|
|
|
191
213
|
}
|
|
192
214
|
}
|
|
193
215
|
catch {
|
|
194
|
-
// no SEAM file
|
|
216
|
+
// no SEAM file: fall through to the declared seam.
|
|
195
217
|
}
|
|
196
218
|
return seamForStep(root, step);
|
|
197
219
|
}
|
|
198
220
|
/**
|
|
199
221
|
* Flip the step's checkbox in intent.md to `[x]`.
|
|
200
222
|
*
|
|
201
|
-
* Best-effort bookkeeping
|
|
223
|
+
* Best-effort bookkeeping (the checkpoint SHA is the source of truth) but the
|
|
202
224
|
* dashboard reads intent.md, so a swallowed failure here would make orientation
|
|
203
225
|
* lie; the catch warns and asks for a hand flip instead.
|
|
204
226
|
*/
|
|
@@ -216,7 +238,7 @@ function flipIntent(root, step) {
|
|
|
216
238
|
*
|
|
217
239
|
* The build's history accrues at each checkpoint instead of being reconstructed
|
|
218
240
|
* at finish; the step's title is lifted from intent.md when still present.
|
|
219
|
-
* Best-effort: a missing or odd build-log never blocks a checkpoint
|
|
241
|
+
* Best-effort: a missing or odd build-log never blocks a checkpoint; the
|
|
220
242
|
* `checkpoints` SHA is the source of truth.
|
|
221
243
|
*/
|
|
222
244
|
function logCheckpoint(root, step, sha) {
|
|
@@ -236,7 +258,7 @@ function logCheckpoint(root, step, sha) {
|
|
|
236
258
|
/**
|
|
237
259
|
* The compact stats receipt riding the Log line, or null when nothing accrued.
|
|
238
260
|
*
|
|
239
|
-
* Only what happened is shown
|
|
261
|
+
* Only what happened is shown: a clean first-try step gets no suffix at all.
|
|
240
262
|
* Wall-clock needs both stamps: a hand-built step never ran `build <n>`, so it
|
|
241
263
|
* has no startedAt.
|
|
242
264
|
*/
|
|
@@ -280,7 +302,7 @@ function subjectForStep(root, step) {
|
|
|
280
302
|
* The build-default Conventional scope, or null for a bare subject.
|
|
281
303
|
*
|
|
282
304
|
* The `**Scope:**` header field in intent.md wins when authored and filled;
|
|
283
|
-
* else the build slug with its date prefix stripped; else null
|
|
305
|
+
* else the build slug with its date prefix stripped; else null: a build with
|
|
284
306
|
* neither field keeps producing bare subjects.
|
|
285
307
|
*/
|
|
286
308
|
function buildDefaultScope(root) {
|
|
@@ -291,7 +313,7 @@ function buildDefaultScope(root) {
|
|
|
291
313
|
}
|
|
292
314
|
}
|
|
293
315
|
catch {
|
|
294
|
-
// no intent.md yet
|
|
316
|
+
// no intent.md yet: fall through to the slug rung.
|
|
295
317
|
}
|
|
296
318
|
return buildScope(activeBuild(root));
|
|
297
319
|
}
|
|
@@ -313,27 +335,6 @@ function messageArg(args) {
|
|
|
313
335
|
const i = args.indexOf('-m');
|
|
314
336
|
return i !== -1 && i + 1 < args.length ? (args[i + 1] ?? null) : null;
|
|
315
337
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Read the `--body` commit body from stdin, or null when absent or empty.
|
|
318
|
-
*
|
|
319
|
-
* The stdin heredoc lets a skill compose proportional prose the CLI never
|
|
320
|
-
* could; null either way hands over to the deterministic fallback body. Reading
|
|
321
|
-
* fd 0 blocks until EOF, which the heredoc supplies; a read error (no stdin
|
|
322
|
-
* attached) degrades to the fallback, and an interactive TTY — which would
|
|
323
|
-
* never send EOF — skips the read instead of hanging.
|
|
324
|
-
*/
|
|
325
|
-
function bodyArg(args) {
|
|
326
|
-
if (!args.includes('--body') || process.stdin.isTTY === true) {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
const raw = readFileSync(0, 'utf8').trimEnd();
|
|
331
|
-
return raw.length > 0 ? raw : null;
|
|
332
|
-
}
|
|
333
|
-
catch {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
338
|
/**
|
|
338
339
|
* The deterministic fallback commit body: the step's done-when, its seam, and
|
|
339
340
|
* the staged diffstat.
|