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/lib/agents.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// The agent contract. A user-authored agent is anything executable that speaks a
|
|
2
|
-
// versioned JSON envelope
|
|
3
|
-
// child's stdin, JSON on its stdout, prose on its stderr
|
|
2
|
+
// versioned JSON envelope (a JSON manifest on disk (`agent.json`), JSON on the
|
|
3
|
+
// child's stdin, JSON on its stdout, prose on its stderr) so the doorway is
|
|
4
4
|
// runtime-agnostic: any language, any framework, one subprocess boundary. This
|
|
5
5
|
// module is the contract's validator, resolver, and runner: it type-narrows the
|
|
6
6
|
// on-disk manifest and the output envelope a child returns (refusing a contract
|
|
7
7
|
// major-version mismatch with an upgrade hint rather than guessing at a shape
|
|
8
|
-
// that is only stable within a major
|
|
8
|
+
// that is only stable within a major: additions are a minor bump, removals or
|
|
9
9
|
// renames a major), it walks the agent tiers to resolve an agent by name, it
|
|
10
10
|
// composes the step context a child reads on stdin, and it spawns the child.
|
|
11
11
|
// The validators are pure; the resolver reads `agent.json` files off disk with
|
|
@@ -28,7 +28,7 @@ export const CONTRACT_VERSION = 1;
|
|
|
28
28
|
* The three lifecycle slots an agent may bind to: `before` (context in),
|
|
29
29
|
* `build` (the diff), `after` (advisory review).
|
|
30
30
|
*
|
|
31
|
-
* Exactly these, never a fourth
|
|
31
|
+
* Exactly these, never a fourth; "a salient point in the middle" is judgment
|
|
32
32
|
* the host model handles in prose, not a declarative slot.
|
|
33
33
|
*/
|
|
34
34
|
export const SLOTS = ['before', 'build', 'after'];
|
|
@@ -122,7 +122,7 @@ export function parseEnvelope(raw) {
|
|
|
122
122
|
* True when a string names one of the three slots.
|
|
123
123
|
*
|
|
124
124
|
* Exported for the `agent run` verb's `--mode` refusal: a mode the manifest
|
|
125
|
-
* doesn't declare is refused loud
|
|
125
|
+
* doesn't declare is refused loud; the user who typed it asked for that slot
|
|
126
126
|
* specifically.
|
|
127
127
|
*/
|
|
128
128
|
export function isSlot(value) {
|
|
@@ -169,7 +169,7 @@ function parseSlots(value) {
|
|
|
169
169
|
/**
|
|
170
170
|
* Narrow the envelope's optional `parked[]` (absent = none).
|
|
171
171
|
*
|
|
172
|
-
* Present, it must be an array of non-blank strings
|
|
172
|
+
* Present, it must be an array of non-blank strings: each becomes a park line
|
|
173
173
|
* the CLI lands through the park verb. A malformed list is refused rather than
|
|
174
174
|
* silently dropped: losing a parked concern is a quiet data loss. Returns the
|
|
175
175
|
* trimmed lines or an error string.
|
|
@@ -219,13 +219,13 @@ function envelopeFail(error) {
|
|
|
219
219
|
return { ok: false, error };
|
|
220
220
|
}
|
|
221
221
|
/**
|
|
222
|
-
* The project agents tier
|
|
222
|
+
* The project agents tier: `.plumbbob/agents/` under the repo root.
|
|
223
223
|
*/
|
|
224
224
|
export function projectAgentsDir(root) {
|
|
225
225
|
return join(root, '.plumbbob', 'agents');
|
|
226
226
|
}
|
|
227
227
|
/**
|
|
228
|
-
* The personal agents tier
|
|
228
|
+
* The personal agents tier: `~/.plumbbob/agents/`, the user's own library.
|
|
229
229
|
*/
|
|
230
230
|
export function personalAgentsDir(home) {
|
|
231
231
|
return join(home, '.plumbbob', 'agents');
|
|
@@ -246,7 +246,7 @@ function agentsHome(home) {
|
|
|
246
246
|
* A `flagPath` (from `--agent <path>`) points straight at an agent directory
|
|
247
247
|
* and takes top priority. A tier "hits" when it holds an `agent.json`: a
|
|
248
248
|
* malformed one there is surfaced as an error (project still shadows
|
|
249
|
-
* personal), never silently skipped in favor of a different agent
|
|
249
|
+
* personal), never silently skipped in favor of a different agent; an
|
|
250
250
|
* explicit ask fails loud. Returns the not-found error only when no tier holds
|
|
251
251
|
* a manifest at all.
|
|
252
252
|
*/
|
|
@@ -273,7 +273,7 @@ export function resolveAgent(root, name, opts = {}) {
|
|
|
273
273
|
/**
|
|
274
274
|
* Every resolvable agent across both tiers, sorted by name.
|
|
275
275
|
*
|
|
276
|
-
* Project shadows personal: a name defined in both resolves
|
|
276
|
+
* Project shadows personal: a name defined in both resolves (and lists) from
|
|
277
277
|
* project. Each entry carries its origin and resolution; a malformed manifest
|
|
278
278
|
* lands as an errored resolution rather than crashing the walk.
|
|
279
279
|
*/
|
|
@@ -325,7 +325,7 @@ export function formatAgentList(listings) {
|
|
|
325
325
|
* Returns null when the directory holds no manifest (a tier miss, so
|
|
326
326
|
* resolution falls through), an errored resolution when the manifest is
|
|
327
327
|
* present but unreadable/malformed, else the resolved agent. A missing file
|
|
328
|
-
* (ENOENT throw) is the miss signal
|
|
328
|
+
* (ENOENT throw) is the miss signal: one read, no TOCTOU race.
|
|
329
329
|
*/
|
|
330
330
|
function loadAgentDir(dir, origin) {
|
|
331
331
|
const file = join(dir, 'agent.json');
|
|
@@ -367,10 +367,10 @@ function agentDirNames(dir) {
|
|
|
367
367
|
/**
|
|
368
368
|
* Compose the StepContext for one step.
|
|
369
369
|
*
|
|
370
|
-
* The strict seam parse is the only refusal path
|
|
370
|
+
* The strict seam parse is the only refusal path: a missing step or
|
|
371
371
|
* unparseable seam gates git behavior, so it fails loud; everything else is
|
|
372
372
|
* best-effort and lands whatever it can, returning `warnings` (skipped-bullet
|
|
373
|
-
* lines) for the caller to print on stderr rather than writing there itself
|
|
373
|
+
* lines) for the caller to print on stderr rather than writing there itself;
|
|
374
374
|
* this module stays pure, the verb does the IO.
|
|
375
375
|
*/
|
|
376
376
|
export function composeStepContext(params) {
|
|
@@ -410,7 +410,7 @@ function skipWarnings(heading, skipped) {
|
|
|
410
410
|
* Validate a parsed harness.json.
|
|
411
411
|
*
|
|
412
412
|
* Contract first (a major mismatch gets the same upgrade hint as the manifest
|
|
413
|
-
* and envelope). Structure is strict
|
|
413
|
+
* and envelope). Structure is strict: a `steps` that is not an object, a
|
|
414
414
|
* non-numeric step key, or a step entry that is not an object is the author's
|
|
415
415
|
* error and is refused loud. Slot *contents* stay lenient: a slot value may be
|
|
416
416
|
* one name or a list, and blanks/non-strings drop rather than refuse, because
|
|
@@ -445,9 +445,9 @@ function parseHarness(raw) {
|
|
|
445
445
|
/**
|
|
446
446
|
* Read and validate a harness.json at `path`.
|
|
447
447
|
*
|
|
448
|
-
* The caller builds the path from the build folder
|
|
448
|
+
* The caller builds the path from the build folder; this module stays
|
|
449
449
|
* ignorant of the sidecar layout, taking the full path. Returns null when the
|
|
450
|
-
* file is absent (a build with no bound agents
|
|
450
|
+
* file is absent (a build with no bound agents: a clean no-op); an errored
|
|
451
451
|
* parse when present-but-broken (invalid JSON or a malformed structure the
|
|
452
452
|
* author must fix); else the validated bindings. Shared so every reader
|
|
453
453
|
* (`agent run`, `status`) parses the harness the same way.
|
|
@@ -474,7 +474,7 @@ export function readHarnessFile(path) {
|
|
|
474
474
|
*
|
|
475
475
|
* Accepts a harness `defaults` block, a per-step entry, or the settings-level
|
|
476
476
|
* defaults. A slot value may be a single agent name or a list; blanks and
|
|
477
|
-
* non-strings drop. Only the three real slots are read
|
|
477
|
+
* non-strings drop. Only the three real slots are read; `note` and any
|
|
478
478
|
* stranger key are ignored here. A slot present but naming no valid agent
|
|
479
479
|
* still counts as bound-to-nothing (an explicit override to none), so callers
|
|
480
480
|
* get clean replace semantics up the ladder.
|
|
@@ -493,7 +493,7 @@ export function parseSlotBindings(raw) {
|
|
|
493
493
|
* The agents bound to one slot for one step, merged down the bindings ladder.
|
|
494
494
|
*
|
|
495
495
|
* A per-step slot entry overrides the harness `defaults`, which override the
|
|
496
|
-
* settings-level defaults
|
|
496
|
+
* settings-level defaults: the first level that names the slot wins, replace
|
|
497
497
|
* not append, the same most-specific-wins shape as the settings ladder. The
|
|
498
498
|
* `--agent` flag and an explicit name sit above all of this, but the verb
|
|
499
499
|
* takes the single-agent path for those and never reaches here. Returns []
|
|
@@ -511,7 +511,7 @@ export function resolveSlotAgents(params) {
|
|
|
511
511
|
return [];
|
|
512
512
|
}
|
|
513
513
|
/**
|
|
514
|
-
* Coerce a slot value
|
|
514
|
+
* Coerce a slot value (one agent name or a list) to a trimmed name list,
|
|
515
515
|
* dropping blanks and non-strings.
|
|
516
516
|
*/
|
|
517
517
|
function asNameList(value) {
|
|
@@ -519,7 +519,7 @@ function asNameList(value) {
|
|
|
519
519
|
return items.filter((v) => typeof v === 'string' && v.trim().length > 0).map((v) => v.trim());
|
|
520
520
|
}
|
|
521
521
|
/**
|
|
522
|
-
* True when a value is a plain JSON object
|
|
522
|
+
* True when a value is a plain JSON object: not null, not an array.
|
|
523
523
|
*/
|
|
524
524
|
function isObject(value) {
|
|
525
525
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -532,13 +532,13 @@ function isObject(value) {
|
|
|
532
532
|
* directory rides in `PLUMBBOB_AGENT_DIR` so a root-cwd agent can still reach
|
|
533
533
|
* its files. The composed StepContext is delivered as JSON on stdin. Stdout is
|
|
534
534
|
* piped and captured (the envelope); stderr is inherited so the child's prose
|
|
535
|
-
* streams live to the terminal
|
|
535
|
+
* streams live to the terminal: production (narrating) never collides with
|
|
536
536
|
* consumption (one structured result at the pause). A SIGINT while the child
|
|
537
|
-
* runs kills it and reports `interrupted` rather than orphaning it
|
|
537
|
+
* runs kills it and reports `interrupted` rather than orphaning it; the human
|
|
538
538
|
* is present, Ctrl-C works. A positive `timeoutSeconds` arms a kill timer
|
|
539
539
|
* (0 = off; a timeout is the user's explicit opt-in). Async `spawn`, not
|
|
540
540
|
* `spawnSync`, so the parent stays live to interrupt gracefully. Never
|
|
541
|
-
* rejects
|
|
541
|
+
* rejects: every failure mode is a resolved `AgentRunResult`.
|
|
542
542
|
*/
|
|
543
543
|
export function runAgent(params) {
|
|
544
544
|
return new Promise((resolve) => {
|
|
@@ -576,7 +576,7 @@ export function runAgent(params) {
|
|
|
576
576
|
child.stdout.on('data', (chunk) => {
|
|
577
577
|
stdout += chunk.toString();
|
|
578
578
|
});
|
|
579
|
-
// A shell that never launched (
|
|
579
|
+
// A shell that never launched (for example `sh` itself missing): distinct from a
|
|
580
580
|
// command that ran and exited non-zero, which arrives on `close`.
|
|
581
581
|
child.on('error', (err) => {
|
|
582
582
|
finish({ ok: false, reason: 'spawn', error: err.message });
|
|
@@ -599,9 +599,9 @@ export function runAgent(params) {
|
|
|
599
599
|
/**
|
|
600
600
|
* Parse the child's captured stdout into a validated envelope.
|
|
601
601
|
*
|
|
602
|
-
* JSON first (a non-JSON stdout is out of contract
|
|
602
|
+
* JSON first (a non-JSON stdout is out of contract: the envelope must be one
|
|
603
603
|
* JSON object), then the envelope validator, which carries the
|
|
604
|
-
* contract-mismatch hint. Kept private
|
|
604
|
+
* contract-mismatch hint. Kept private; `runAgent` is the only caller and the
|
|
605
605
|
* verb reads the union, not this.
|
|
606
606
|
*/
|
|
607
607
|
function parseChildEnvelope(stdout) {
|
package/dist/lib/buildlog.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// Mutations to the active build-log.md
|
|
1
|
+
// Mutations to the active build-log.md: the human-facing ledger inside the
|
|
2
2
|
// build's tracked `builds/<slug>/` folder. `park` appends to its Park list;
|
|
3
3
|
// `checkpoint` appends to its Log so the build's history accrues line-by-line
|
|
4
4
|
// *as it happens* rather than being reconstructed at finish. The `## Steps`
|
|
5
5
|
// mirror and `**Current step:**` line are CLI-owned: `build`, `checkpoint`,
|
|
6
6
|
// and `revert` re-render them from intent.md so the ledger's top half tracks
|
|
7
|
-
// reality without a model turn. Pure string transforms
|
|
7
|
+
// reality without a model turn. Pure string transforms: no fs, no classes;
|
|
8
8
|
// callers own the IO so these stay trivially testable.
|
|
9
9
|
/**
|
|
10
|
-
* Append `line` after the last non-blank line of the `## <heading>` section
|
|
11
|
-
*
|
|
10
|
+
* Append `line` after the last non-blank line of the `## <heading>` section:
|
|
11
|
+
* that is, just before the next `## ` heading or EOF.
|
|
12
12
|
*
|
|
13
13
|
* Returns null when the section is absent so the caller can report rather than
|
|
14
14
|
* silently corrupt the doc.
|
|
@@ -33,9 +33,9 @@ export function appendToSection(content, heading, line) {
|
|
|
33
33
|
return lines.join('\n');
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Replace the `**Current step:**` line with `**Current step:** <label
|
|
37
|
-
* `build` sets it to
|
|
38
|
-
* reset it to `none (at the boundary)`.
|
|
36
|
+
* Replace the `**Current step:**` line with `**Current step:** <label>`:
|
|
37
|
+
* `build` sets it to the step's number and title joined by an em dash when a
|
|
38
|
+
* step opens; `checkpoint`/`revert` reset it to `none (at the boundary)`.
|
|
39
39
|
*
|
|
40
40
|
* Returns null when the line is absent so the caller skips rather than
|
|
41
41
|
* corrupting the doc.
|
|
@@ -50,14 +50,14 @@ export function setCurrentStep(content, label) {
|
|
|
50
50
|
return lines.join('\n');
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Regenerate the `## Steps` mirror from intent.md's parsed steps
|
|
53
|
+
* Regenerate the `## Steps` mirror from intent.md's parsed steps: one
|
|
54
54
|
* `- ☑/☐ N. <title>` line each, in order.
|
|
55
55
|
*
|
|
56
56
|
* Only the mirror's own `- ☑/☐` list lines are owned; the italic instructions
|
|
57
57
|
* paragraph (and any other prose) is preserved, and exactly one blank line
|
|
58
58
|
* separates it from the list. Returns null when the section is absent so the
|
|
59
59
|
* caller skips rather than corrupting the doc. With no steps the mirror
|
|
60
|
-
* renders empty
|
|
60
|
+
* renders empty (the plan is not authored yet) rather than inventing a
|
|
61
61
|
* placeholder.
|
|
62
62
|
*/
|
|
63
63
|
export function syncStepsSection(content, steps) {
|
|
@@ -92,9 +92,9 @@ function isMirrorLine(line) {
|
|
|
92
92
|
* The Log line `checkpoint` writes for a landed step: dated, names the step
|
|
93
93
|
* (its title when intent.md still carries it), and carries the short SHA.
|
|
94
94
|
*
|
|
95
|
-
* One line of the build's history
|
|
95
|
+
* One line of the build's history: `/plumbbob:finish` reads these instead of
|
|
96
96
|
* re-narrating the build. `stats` is the optional compact receipt of what the
|
|
97
|
-
* step cost (
|
|
97
|
+
* step cost (for example `2 red, 34m`): null when nothing accrued, so a clean
|
|
98
98
|
* first-try step reads as a bare line.
|
|
99
99
|
*/
|
|
100
100
|
export function checkpointLogLine(date, step, sha, title, stats = null) {
|
|
@@ -102,3 +102,13 @@ export function checkpointLogLine(date, step, sha, title, stats = null) {
|
|
|
102
102
|
const titled = title !== null && title.length > 0 ? `${head} — ${title}` : head;
|
|
103
103
|
return stats !== null && stats.length > 0 ? `${titled} (${stats})` : titled;
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The Log line `abandon` writes for a dropped-but-kept step: the first
|
|
107
|
+
* non-checkpoint event the log records. Dated, names the step (its title when
|
|
108
|
+
* intent.md still carries it), and states plainly that the work stayed in the
|
|
109
|
+
* tree, so the narrative never has a step go quiet mid-flight.
|
|
110
|
+
*/
|
|
111
|
+
export function abandonLogLine(date, step, title) {
|
|
112
|
+
const head = `- ${date} — step ${step} abandoned · work kept in tree`;
|
|
113
|
+
return title !== null && title.length > 0 ? `${head} — ${title}` : head;
|
|
114
|
+
}
|
package/dist/lib/buildlogsync.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// Keep the build-log's top half
|
|
2
|
-
// mirror
|
|
1
|
+
// Keep the build-log's top half (the `**Current step:**` line and the `## Steps`
|
|
2
|
+
// mirror) in sync with intent.md, the build's plan document. The build-log is the
|
|
3
3
|
// human-facing ledger in the build's tracked `builds/<slug>/` folder; its top half
|
|
4
4
|
// is CLI-owned so it never lies: `build` sets the current-step label when a step
|
|
5
5
|
// opens, `checkpoint` and `revert` reset it to the boundary, and all three
|
|
6
6
|
// re-render the mirror from intent.md's parsed steps so it tracks reality without
|
|
7
7
|
// a model turn. The write is best-effort (a missing or hand-edited build-log never
|
|
8
|
-
// fails a verb
|
|
8
|
+
// fails a verb: the checkpoints ledger and intent.md stay the source of truth),
|
|
9
9
|
// matching how `checkpoint`'s Log append behaves.
|
|
10
10
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { buildLogPath, intentPath } from "./sidecar.js";
|
|
12
12
|
import { parseSteps } from "./orient.js";
|
|
13
13
|
import { setCurrentStep, syncStepsSection } from "./buildlog.js";
|
|
14
|
-
/** The Current-step label for no step in flight
|
|
14
|
+
/** The Current-step label for no step in flight: the loop is at the step boundary. */
|
|
15
15
|
export const AT_BOUNDARY = 'none (at the boundary)';
|
|
16
16
|
/**
|
|
17
|
-
* The current-step label for an open step:
|
|
18
|
-
* when the step carries no title. `build` passes
|
|
19
|
-
* AT_BOUNDARY.
|
|
17
|
+
* The current-step label for an open step: the number and title joined by an
|
|
18
|
+
* em dash, or a bare `<n>` when the step carries no title. `build` passes
|
|
19
|
+
* this; the boundary verbs pass AT_BOUNDARY.
|
|
20
20
|
*/
|
|
21
21
|
export function stepLabel(n, title) {
|
|
22
22
|
return title !== null && title.trim().length > 0 ? `${n} — ${title.trim()}` : String(n);
|
|
@@ -26,7 +26,7 @@ export function stepLabel(n, title) {
|
|
|
26
26
|
* `label` and re-render the `## Steps` mirror from intent.md.
|
|
27
27
|
*
|
|
28
28
|
* Each transform is skipped when its target is absent (null), and the whole
|
|
29
|
-
* thing swallows any IO error
|
|
29
|
+
* thing swallows any IO error: the ledger must never block the verb.
|
|
30
30
|
*/
|
|
31
31
|
export function syncBuildLogState(root, slug, label) {
|
|
32
32
|
try {
|
package/dist/lib/check.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// pointers, and an all-slots-skipped run REFUSES rather than green-lighting
|
|
12
12
|
// a repo checkride can't see (a vacuous pass is not a pass).
|
|
13
13
|
// Checkride's stream discipline holds: human progress goes to stderr. Exit 0 is
|
|
14
|
-
// green, 1 is red, 2 means the harness itself broke
|
|
14
|
+
// green, 1 is red, 2 means the harness itself broke: reported distinctly,
|
|
15
15
|
// because a misconfigured gate must not read as broken code (both still block).
|
|
16
16
|
import { spawnSync } from 'node:child_process';
|
|
17
17
|
import { runChecks, runDoctor } from 'checkride';
|
|
@@ -21,7 +21,7 @@ import { resolveString } from "./settings.js";
|
|
|
21
21
|
* Probe whether the check gate would have anything to run in `root`.
|
|
22
22
|
*
|
|
23
23
|
* A configured `check` command answers yes on its own; otherwise checkride's
|
|
24
|
-
* doctor detection runs in-process
|
|
24
|
+
* doctor detection runs in-process: the same pass `plumbbob doctor` reports
|
|
25
25
|
* as a table. `start` reads this to surface "nothing to check" at plan time,
|
|
26
26
|
* while the human is still deciding, instead of at the first refused
|
|
27
27
|
* checkpoint.
|
|
@@ -37,19 +37,19 @@ export async function detectGate(root) {
|
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
39
|
// A broken checkride harness is a different problem with its own reporters
|
|
40
|
-
// (doctor's ✗ row, runCheck's exit 2)
|
|
40
|
+
// (doctor's ✗ row, runCheck's exit 2); the plan-time probe stays quiet
|
|
41
41
|
// rather than mislabeling it "nothing to check".
|
|
42
42
|
return { configured: null, detected: true };
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Checkride's always-on repo checks: the adapters that resolve with no tool
|
|
47
|
-
* config of their own
|
|
47
|
+
* config of their own: the built-in `links` slot, the pnpm-audit security
|
|
48
48
|
* scan, the publint/attw package-shape probes, and the pack/smoke/snippets
|
|
49
49
|
* publish-bundle slots. Several are named for their shape rather than because
|
|
50
50
|
* a bare directory reports them: `publint`/`attw` resolve off a devDependency,
|
|
51
|
-
* `build` off a `scripts.build` entry
|
|
52
|
-
* not that checkride can see the code that goes into it
|
|
51
|
+
* `build` off a `scripts.build` entry (which says the package can be built,
|
|
52
|
+
* not that checkride can see the code that goes into it), and `snippets`
|
|
53
53
|
* carries two adapters since checkride 0.10.2, so the dist-facing
|
|
54
54
|
* `snippets-dist` is named beside it (this list matches ADAPTER names, not
|
|
55
55
|
* slot names). Every one of them but `links` is an opt-in slot, so a default
|
|
@@ -72,7 +72,7 @@ const ALWAYS_ON_ADAPTERS = [
|
|
|
72
72
|
];
|
|
73
73
|
/**
|
|
74
74
|
* The detection rule, shared by `start`'s probe and doctor's callout: some
|
|
75
|
-
* tool slot beyond the always-on family has an adapter
|
|
75
|
+
* tool slot beyond the always-on family has an adapter: that is, checkride can
|
|
76
76
|
* see the CODE (a tsconfig, a test runner, a linter…), not just the repo.
|
|
77
77
|
*/
|
|
78
78
|
export function gateDetectsTools(checks) {
|
|
@@ -90,7 +90,7 @@ export function gateDetectsTools(checks) {
|
|
|
90
90
|
*/
|
|
91
91
|
export async function runCheck(root, flags = {}, commandFlag) {
|
|
92
92
|
if (gateIsRunningFor(root)) {
|
|
93
|
-
// This repo re-entering its OWN gate is never a slow gate
|
|
93
|
+
// This repo re-entering its OWN gate is never a slow gate: it is a
|
|
94
94
|
// recursion whose next generation forks wider than this one. Exit 2 (the
|
|
95
95
|
// harness broke), not 1: nothing has been learned about the code. A gate on
|
|
96
96
|
// a DIFFERENT root is ordinary nested work (the test suite gating its
|
|
@@ -105,7 +105,7 @@ export async function runCheck(root, flags = {}, commandFlag) {
|
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* The spawn override: stream the configured command's own output to the
|
|
108
|
-
* terminal and return its exit code
|
|
108
|
+
* terminal and return its exit code: the command is trusted verbatim, no
|
|
109
109
|
* interpretation.
|
|
110
110
|
*/
|
|
111
111
|
function runCommand(root, command, flags) {
|
|
@@ -133,7 +133,7 @@ async function runCheckride(root, flags) {
|
|
|
133
133
|
include: flags.include !== undefined ? [...flags.include] : null,
|
|
134
134
|
// Checkride's own vacuous-green refusal (0.10.2): zero checks executed is
|
|
135
135
|
// an error, not a pass. It overlaps the refusal below and never reaches
|
|
136
|
-
// the human
|
|
136
|
+
// the human: our predicate is the broader one (it also catches a
|
|
137
137
|
// links-only run, where a check DID execute) and fires first, so
|
|
138
138
|
// plumbbob's message and its exit 1 are what a reader sees. Kept anyway:
|
|
139
139
|
// it is the contract checkride asks a gate to run under, so a future
|
|
@@ -145,7 +145,7 @@ async function runCheckride(root, flags) {
|
|
|
145
145
|
}
|
|
146
146
|
catch (err) {
|
|
147
147
|
// The harness broke, not the code: a malformed checkride.config.json or
|
|
148
|
-
// the like. Checkride's own CLI maps this to exit 2; so do we
|
|
148
|
+
// the like. Checkride's own CLI maps this to exit 2; so do we: a
|
|
149
149
|
// misconfigured gate must report distinctly from red.
|
|
150
150
|
const message = err instanceof Error ? err.message : String(err);
|
|
151
151
|
process.stderr.write(`plumbbob: the check gate itself failed — ${message}\n` +
|
|
@@ -156,7 +156,7 @@ async function runCheckride(root, flags) {
|
|
|
156
156
|
// Zero-config checkride skips slots with no detected tool; a repo it can't
|
|
157
157
|
// see must refuse, not vacuously green-light the checkpoint. The built-in
|
|
158
158
|
// `links` slot is always-on (empty detect list), so it alone proves
|
|
159
|
-
// nothing about the code
|
|
159
|
+
// nothing about the code: a links-only run is still vacuous.
|
|
160
160
|
process.stderr.write('plumbbob: checkride found nothing to check in this repo — refusing to call that green.\n' +
|
|
161
161
|
' Add tool configs (tsconfig, vitest, …) or a checkride.config.json custom check,\n' +
|
|
162
162
|
' or set the "check" key in .plumbbob/settings.json to gate through another command.\n');
|
|
@@ -171,7 +171,7 @@ async function runCheckride(root, flags) {
|
|
|
171
171
|
* Name the failing slots and where their raw diagnostics landed, so the agent
|
|
172
172
|
* (verify) reads the tool's own JSON instead of scraping scrollback.
|
|
173
173
|
*
|
|
174
|
-
* The summary is the canonical pointer
|
|
174
|
+
* The summary is the canonical pointer: `output_file` is the adapter's
|
|
175
175
|
* preferred name, but non-JSON output falls back to `.check/<slot>.stdout.txt`.
|
|
176
176
|
*/
|
|
177
177
|
function reportFailingSlots(summary) {
|
|
@@ -184,7 +184,7 @@ function reportFailingSlots(summary) {
|
|
|
184
184
|
process.stderr.write(`plumbbob: failing slots:\n${lines.join('\n')}\n Full report: .check/summary.json\n`);
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
|
-
* True when any narrowing flag was passed
|
|
187
|
+
* True when any narrowing flag was passed: the trigger for the spawn path's
|
|
188
188
|
* flags-are-ignored warning.
|
|
189
189
|
*/
|
|
190
190
|
function hasFlags(flags) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The `--body` stdin reader shared by `checkpoint` and `finish`: both verbs
|
|
2
|
+
// take a commit body on fd 0 via a single-quoted heredoc (`--body <<'BODY'`),
|
|
3
|
+
// never as an argument value. `evaluateCommitBody` is the pure decision over
|
|
4
|
+
// an already-known fd-0 shape, so every shape is a direct unit test with no
|
|
5
|
+
// fd tricks; `readCommitBody` gathers the real one: the mirror of latch.ts's
|
|
6
|
+
// evaluateLatch/checkLatch split.
|
|
7
|
+
//
|
|
8
|
+
// Four fd-0 shapes reach this in practice: a heredoc is a regular file, a
|
|
9
|
+
// pipe is a FIFO, an interactive terminal is a TTY, and an agent harness
|
|
10
|
+
// hands the CLI a socket. The first three already degrade safely: a TTY
|
|
11
|
+
// skips the read (it would never send EOF), a regular file/FIFO/`/dev/null`
|
|
12
|
+
// read to EOF and fall back to the caller's own default body when empty. A
|
|
13
|
+
// socket also never sends EOF on its own, so the same blind `readFileSync(0)`
|
|
14
|
+
// blocks forever and, worse, drops the requested body silently. Refuse
|
|
15
|
+
// instead: a silently dropped body reads as success.
|
|
16
|
+
import { fstatSync, readFileSync } from 'node:fs';
|
|
17
|
+
/**
|
|
18
|
+
* The refusal shown when `--body` is asked to read a socket: names the form
|
|
19
|
+
* that works so the retry succeeds instead of guessing again.
|
|
20
|
+
*/
|
|
21
|
+
export const SOCKET_REFUSAL = "plumbbob: --body refuses — stdin is a socket, and a socket never sends EOF the way a heredoc does, so the read would block forever and the body would be lost. Redirect it in with the `--body <<'BODY'` … `BODY` heredoc form instead.\n";
|
|
22
|
+
/**
|
|
23
|
+
* Decide the `--body` outcome from an already-known fd-0 shape.
|
|
24
|
+
*
|
|
25
|
+
* Absent flag or a TTY both mean "don't read": the caller's fallback body
|
|
26
|
+
* applies. `other` covers a heredoc (regular file), a pipe (FIFO), and
|
|
27
|
+
* `/dev/null` (character device) alike: each is read to EOF, and an empty or
|
|
28
|
+
* failed read degrades to the same fallback an absent `--body` would use.
|
|
29
|
+
*/
|
|
30
|
+
export function evaluateCommitBody(hasBodyFlag, shape, read) {
|
|
31
|
+
if (!hasBodyFlag || shape === 'tty') {
|
|
32
|
+
return { ok: true, body: null };
|
|
33
|
+
}
|
|
34
|
+
if (shape === 'socket') {
|
|
35
|
+
return { ok: false, message: SOCKET_REFUSAL };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const raw = read().trimEnd();
|
|
39
|
+
return { ok: true, body: raw.length > 0 ? raw : null };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { ok: true, body: null };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read the `--body` commit body from the real fd 0, or refuse when it can
|
|
47
|
+
* never deliver one.
|
|
48
|
+
*
|
|
49
|
+
* `checkpoint` and `finish` both call this where each used to call its own
|
|
50
|
+
* copy of `bodyArg`: one implementation, one refusal string, so the two
|
|
51
|
+
* verbs cannot drift apart on the wording.
|
|
52
|
+
*/
|
|
53
|
+
export function readCommitBody(args) {
|
|
54
|
+
return evaluateCommitBody(args.includes('--body'), stdinShape(), () => readFileSync(0, 'utf8'));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fd-0 shape the guard cares about: a TTY skips the read (never sends
|
|
58
|
+
* EOF), a socket refuses (the same problem, but silent), everything else is
|
|
59
|
+
* read as before. An unreadable fd (no stdin attached at all) is treated the
|
|
60
|
+
* same as any other shape; the read attempt that follows fails safely.
|
|
61
|
+
*/
|
|
62
|
+
function stdinShape() {
|
|
63
|
+
if (process.stdin.isTTY === true) {
|
|
64
|
+
return 'tty';
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return fstatSync(0).isSocket() ? 'socket' : 'other';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return 'other';
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/lib/commitmsg.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Conventional-Commit subject assembly. Plumbbob's commit subjects read as
|
|
2
|
-
// `type(scope): description`
|
|
3
|
-
// scope
|
|
2
|
+
// `type(scope): description` (real feat/fix/chore types and a real parenthesised
|
|
3
|
+
// scope) so `git log` speaks the same grammar as the rest of the branch. The
|
|
4
4
|
// `plumbbob`/`step N` identifiers ride a marker line at the head of the body,
|
|
5
|
-
// which `withMarker` prepends
|
|
5
|
+
// which `withMarker` prepends, so `git log --grep plumbbob` finds every plumbbob
|
|
6
6
|
// commit. Functional and procedural, node builtins only, no gate: these only
|
|
7
7
|
// shape a string the CLI is about to commit.
|
|
8
8
|
/**
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* prefix on a step title.
|
|
11
11
|
*
|
|
12
12
|
* Anchoring on this closed set keeps a prose title like `Note: rewire the
|
|
13
|
-
* cache` from being misread as a `Note` type
|
|
13
|
+
* cache` from being misread as a `Note` type; only these words, immediately
|
|
14
14
|
* followed by an optional `(scope)`, an optional `!`, and a colon, count as an
|
|
15
15
|
* author-supplied prefix.
|
|
16
16
|
*/
|
|
@@ -23,7 +23,7 @@ const PREFIX = new RegExp(`^(${TYPES.join('|')})(?:\\(([^)]+)\\))?(!)?:\\s+(.+)$
|
|
|
23
23
|
*
|
|
24
24
|
* `feat(escape-hatch): add the flag` → { type:'feat', scope:'escape-hatch',
|
|
25
25
|
* breaking:false, description:'add the flag' }. A title with no recognised
|
|
26
|
-
* prefix comes back whole as the description with a null type/scope
|
|
26
|
+
* prefix comes back whole as the description with a null type/scope; the
|
|
27
27
|
* caller then defaults the type and fills the scope from the build.
|
|
28
28
|
*/
|
|
29
29
|
export function parseConventionalTitle(title) {
|
|
@@ -46,11 +46,11 @@ export function conventionalSubject(parts) {
|
|
|
46
46
|
/**
|
|
47
47
|
* A Conventional subject from a step title, filling gaps from the build.
|
|
48
48
|
*
|
|
49
|
-
* An author-written prefix (`fix(parser): …`) is honoured verbatim
|
|
49
|
+
* An author-written prefix (`fix(parser): …`) is honoured verbatim: its type,
|
|
50
50
|
* scope, breaking marker, and exact wording all win. A bare prose title
|
|
51
51
|
* defaults to `defaultType` and the build `scope`, and gets its sentence-case
|
|
52
52
|
* opener de-capitalised (`Add the flag` → `add the flag`) to match
|
|
53
|
-
* Conventional style
|
|
53
|
+
* Conventional style, while an all-caps acronym start (`API`, `OAuth`) is
|
|
54
54
|
* left alone.
|
|
55
55
|
*/
|
|
56
56
|
export function subjectFromTitle(title, defaultType, scope) {
|