plumbbob 0.9.5 → 0.10.0

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.
Files changed (142) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +87 -86
  3. package/dist/cli-core.js +8 -8
  4. package/dist/cli.js +1 -1
  5. package/dist/lib/agents.js +26 -26
  6. package/dist/lib/buildlog.js +11 -11
  7. package/dist/lib/buildlogsync.js +8 -8
  8. package/dist/lib/check.js +14 -14
  9. package/dist/lib/commitbody.js +72 -0
  10. package/dist/lib/commitmsg.js +7 -7
  11. package/dist/lib/git.js +44 -15
  12. package/dist/lib/intent.js +20 -19
  13. package/dist/lib/latch.js +16 -16
  14. package/dist/lib/orient.js +36 -17
  15. package/dist/lib/reentry.js +3 -3
  16. package/dist/lib/settings.js +16 -16
  17. package/dist/lib/sidecar.js +54 -54
  18. package/dist/lib/templates.js +1 -1
  19. package/dist/verbs/agent.js +25 -25
  20. package/dist/verbs/build.js +9 -9
  21. package/dist/verbs/check.js +6 -6
  22. package/dist/verbs/checkpoint.js +48 -47
  23. package/dist/verbs/doctor.js +27 -26
  24. package/dist/verbs/finish.js +27 -40
  25. package/dist/verbs/handoff.js +9 -8
  26. package/dist/verbs/init.js +9 -9
  27. package/dist/verbs/park.js +3 -3
  28. package/dist/verbs/recover.js +11 -11
  29. package/dist/verbs/revert.js +12 -12
  30. package/dist/verbs/spike.js +17 -17
  31. package/dist/verbs/start.js +49 -19
  32. package/dist/verbs/status.js +8 -8
  33. package/dist/verbs/turn.js +16 -16
  34. package/dist/verbs/use.js +4 -4
  35. package/node_modules/checkride/.claude-plugin/plugin.json +1 -1
  36. package/node_modules/checkride/CHANGELOG.md +401 -0
  37. package/node_modules/checkride/README.md +27 -9
  38. package/node_modules/checkride/dist/adapters.d.ts +12 -4
  39. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  40. package/node_modules/checkride/dist/adapters.js +21 -1
  41. package/node_modules/checkride/dist/adapters.js.map +1 -1
  42. package/node_modules/checkride/dist/agent-setup/cursor.d.ts +9 -1
  43. package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -1
  44. package/node_modules/checkride/dist/agent-setup/cursor.js +59 -1
  45. package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -1
  46. package/node_modules/checkride/dist/agent-setup/harness.d.ts +1 -1
  47. package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -1
  48. package/node_modules/checkride/dist/agent-setup/harness.js +10 -1
  49. package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -1
  50. package/node_modules/checkride/dist/agent-setup/scripts.d.ts +23 -0
  51. package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -1
  52. package/node_modules/checkride/dist/agent-setup/scripts.js +387 -82
  53. package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -1
  54. package/node_modules/checkride/dist/baseline/fallow.js +2 -2
  55. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +14 -5
  56. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  57. package/node_modules/checkride/dist/baseline/fingerprint.js +58 -5
  58. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  59. package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
  60. package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
  61. package/node_modules/checkride/dist/baseline/history.js +232 -0
  62. package/node_modules/checkride/dist/baseline/history.js.map +1 -0
  63. package/node_modules/checkride/dist/baseline/index.d.ts +4 -2
  64. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  65. package/node_modules/checkride/dist/baseline/index.js +2 -1
  66. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  67. package/node_modules/checkride/dist/baseline/store.d.ts +30 -7
  68. package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
  69. package/node_modules/checkride/dist/baseline/store.js +50 -16
  70. package/node_modules/checkride/dist/baseline/store.js.map +1 -1
  71. package/node_modules/checkride/dist/cli.d.ts +5 -1
  72. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  73. package/node_modules/checkride/dist/cli.js +98 -25
  74. package/node_modules/checkride/dist/cli.js.map +1 -1
  75. package/node_modules/checkride/dist/config.d.ts +33 -4
  76. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  77. package/node_modules/checkride/dist/config.js +21 -6
  78. package/node_modules/checkride/dist/config.js.map +1 -1
  79. package/node_modules/checkride/dist/digest/digest.js +1 -1
  80. package/node_modules/checkride/dist/doctor.d.ts +2 -0
  81. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  82. package/node_modules/checkride/dist/doctor.js +39 -1
  83. package/node_modules/checkride/dist/doctor.js.map +1 -1
  84. package/node_modules/checkride/dist/gate.d.ts +49 -4
  85. package/node_modules/checkride/dist/gate.d.ts.map +1 -1
  86. package/node_modules/checkride/dist/gate.js +143 -16
  87. package/node_modules/checkride/dist/gate.js.map +1 -1
  88. package/node_modules/checkride/dist/init.d.ts +8 -1
  89. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  90. package/node_modules/checkride/dist/init.js +68 -7
  91. package/node_modules/checkride/dist/init.js.map +1 -1
  92. package/node_modules/checkride/dist/node-pin.d.ts +1 -1
  93. package/node_modules/checkride/dist/node-pin.js +1 -1
  94. package/node_modules/checkride/dist/orchestrator.d.ts +13 -0
  95. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  96. package/node_modules/checkride/dist/orchestrator.js +118 -13
  97. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  98. package/node_modules/checkride/dist/pm/index.d.ts +1 -0
  99. package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
  100. package/node_modules/checkride/dist/pm/index.js.map +1 -1
  101. package/node_modules/checkride/dist/pm/launch.d.ts +23 -2
  102. package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -1
  103. package/node_modules/checkride/dist/pm/launch.js +16 -0
  104. package/node_modules/checkride/dist/pm/launch.js.map +1 -1
  105. package/node_modules/checkride/dist/pm/translate.js +1 -1
  106. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  107. package/node_modules/checkride/dist/qa/mutation.d.ts +4 -4
  108. package/node_modules/checkride/dist/qa/mutation.js +4 -4
  109. package/node_modules/checkride/dist/recover.d.ts +48 -0
  110. package/node_modules/checkride/dist/recover.d.ts.map +1 -0
  111. package/node_modules/checkride/dist/recover.js +220 -0
  112. package/node_modules/checkride/dist/recover.js.map +1 -0
  113. package/node_modules/checkride/dist/smoke.d.ts +1 -1
  114. package/node_modules/checkride/dist/smoke.js +1 -1
  115. package/node_modules/checkride/dist/snippets.d.ts +1 -1
  116. package/node_modules/checkride/package.json +3 -2
  117. package/node_modules/checkride/schema/checkride.config.schema.json +9 -1
  118. package/node_modules/checkride/skills/check/SKILL.md +9 -1
  119. package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
  120. package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
  121. package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
  122. package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
  123. package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
  124. package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
  125. package/node_modules/checkride/templates/shared/vale.ini +34 -0
  126. package/package.json +10 -9
  127. package/skills/build/SKILL.md +69 -64
  128. package/skills/doctor/SKILL.md +11 -11
  129. package/skills/finish/SKILL.md +20 -19
  130. package/skills/harvest/SKILL.md +13 -12
  131. package/skills/park/SKILL.md +10 -10
  132. package/skills/plan/SKILL.md +63 -54
  133. package/skills/recover/SKILL.md +6 -6
  134. package/skills/refine/SKILL.md +18 -16
  135. package/skills/revert/SKILL.md +6 -6
  136. package/skills/spike/SKILL.md +9 -9
  137. package/skills/status/SKILL.md +5 -5
  138. package/skills/step/SKILL.md +17 -12
  139. package/skills/verify/SKILL.md +45 -40
  140. package/templates/build-log.md +7 -7
  141. package/templates/intent.md +49 -39
  142. package/templates/spike-report.md +10 -10
@@ -1,22 +1,22 @@
1
- // The .plumbbob/ sidecar — plumbbob's control directory beside the code. Control
1
+ // The .plumbbob/ sidecar: plumbbob's control directory beside the code. Control
2
2
  // state lives in flat files (bare numbers, plain path lists, one-line markers) so
3
3
  // shell hooks can read it with a grep and no markdown parsing. Functional and
4
4
  // procedural, node builtins only.
5
5
  //
6
6
  // Two planes share the directory: the tracked ARTIFACT plane (settings.json and
7
- // each build's `builds/<slug>/` folder — intent, build-log, checkpoints, report),
7
+ // each build's `builds/<slug>/` folder: intent, build-log, checkpoints, report),
8
8
  // which rides its branch into the PR, and the git-excluded CONTROL plane (STATE,
9
9
  // the personal settings overlay, the turn ledger, and each build's in-flight
10
10
  // markers), which stays per-worktree and never commits.
11
11
  //
12
12
  // STATE is the session sentinel AND the active-build cursor: its EXISTENCE means
13
- // "a session is active", and its CONTENT names the build that session is on — the
13
+ // "a session is active", and its CONTENT names the build that session is on: the
14
14
  // two reinforce each other (a session is always on some build; the cursor is
15
15
  // meaningless without a session). Content is empty under --local / no build.
16
16
  // hasSession stays existence-only; activeBuild reads the content. Homing the
17
- // cursor here — not in settings.local.json — keeps that overlay purely
17
+ // cursor here (not in settings.local.json) keeps that overlay purely
18
18
  // human-owned (check/auto): the tool only ever reads it, never rewrites it. The
19
- // phase the dashboard shows (DESIGN/BUILD/SPIKE) is derived, not stored — BUILD ⇔
19
+ // phase the dashboard shows (DESIGN/BUILD/SPIKE) is derived, not stored: BUILD ⇔
20
20
  // a STEP is in flight, SPIKE ⇔ the SPIKE marker is present, otherwise DESIGN.
21
21
  import { existsSync, readdirSync, readFileSync, writeFileSync, appendFileSync, rmSync, mkdirSync } from 'node:fs';
22
22
  import { dirname, join } from 'node:path';
@@ -48,7 +48,7 @@ export function buildDir(root, slug) {
48
48
  * Derive a filesystem-safe slug from a build title.
49
49
  *
50
50
  * Lowercased, every run of non-alphanumerics collapsed to a single hyphen,
51
- * trimmed of leading/trailing hyphens. The CLI stays dumb and explicit —
51
+ * trimmed of leading/trailing hyphens. The CLI stays dumb and explicit:
52
52
  * collision handling belongs to the caller (`start` refuses an existing slug
53
53
  * rather than silently suffixing `-2`, and prepends the `YYYY-MM-DD-` date that
54
54
  * keeps `listBuilds`' lexical sort chronological). A title with no alphanumerics
@@ -65,7 +65,7 @@ export function slugify(title) {
65
65
  * date prefix `start` prepends stripped off.
66
66
  *
67
67
  * `2026-07-18-escape-hatch` yields the human-meaningful `escape-hatch`. Null
68
- * when no build resolves (`--local`, or a slug that is nothing but a date) — the
68
+ * when no build resolves (`--local`, or a slug that is nothing but a date); the
69
69
  * caller then omits the `(scope)` segment, which Conventional Commits permit.
70
70
  */
71
71
  export function buildScope(slug) {
@@ -76,7 +76,7 @@ export function buildScope(slug) {
76
76
  return stripped.length > 0 ? stripped : null;
77
77
  }
78
78
  /**
79
- * Existing build slugs — the directory names under `builds/`, sorted.
79
+ * Existing build slugs: the directory names under `builds/`, sorted.
80
80
  *
81
81
  * Empty when `builds/` is absent (a `--local` repo, or before the first tracked
82
82
  * `start`).
@@ -110,7 +110,7 @@ export function activeBuild(root, flag) {
110
110
  return builds.length === 1 ? (builds[0] ?? null) : null;
111
111
  }
112
112
  /**
113
- * STATE's content — the active-build cursor — or null when it names no build.
113
+ * STATE's content (the active-build cursor) or null when it names no build.
114
114
  *
115
115
  * Empty means "no build" (--local, or a session whose cursor is unset), so
116
116
  * activeBuild falls through to the sole-build rule. The literal value `active`
@@ -135,7 +135,7 @@ function readCursor(root) {
135
135
  *
136
136
  * Every verb resolves through this: an explicit `--build <slug>` wins, else the
137
137
  * cursor / sole-build fallback of `activeBuild`. `rest` matters because the slug
138
- * is a bare token — scanning positionals on the raw argv would let it masquerade
138
+ * is a bare token: scanning positionals on the raw argv would let it masquerade
139
139
  * as a step number or a spike slug, so callers scan `rest` instead.
140
140
  */
141
141
  export function resolveBuild(root, args) {
@@ -145,7 +145,7 @@ export function resolveBuild(root, args) {
145
145
  return { build: activeBuild(root, args[i + 1]), rest: [...args.slice(0, i), ...args.slice(i + 2)] };
146
146
  }
147
147
  /**
148
- * The STATE file — session sentinel and active-build cursor in one.
148
+ * The STATE file: session sentinel and active-build cursor in one.
149
149
  */
150
150
  function statePath(root) {
151
151
  return join(root, DIRNAME, 'STATE');
@@ -158,7 +158,7 @@ function statePath(root) {
158
158
  * omit it and the dir resolves from the cursor (the default the
159
159
  * executor-agnostic path reads lean on). Either way a `null` slug falls back to
160
160
  * the flat sidecar root, which covers the `--local` layout and any
161
- * no-cursor/no-build repo — the path reads stay stable even before a tracked
161
+ * no-cursor/no-build repo: the path reads stay stable even before a tracked
162
162
  * build exists or when a "no active session" guard is about to fire.
163
163
  */
164
164
  function artifactDir(root, slug) {
@@ -166,7 +166,7 @@ function artifactDir(root, slug) {
166
166
  return resolved === null ? sidecarDir(root) : buildDir(root, resolved);
167
167
  }
168
168
  /**
169
- * The resolved build's artifact folder — the `builds/<slug>/` dir for the active
169
+ * The resolved build's artifact folder: the `builds/<slug>/` dir for the active
170
170
  * build (or the flat sidecar root under `--local`/no-cursor).
171
171
  *
172
172
  * Public so the plan-approval commit can stage exactly this build's scaffold and
@@ -176,7 +176,7 @@ export function buildFolder(root, slug) {
176
176
  return artifactDir(root, slug);
177
177
  }
178
178
  /**
179
- * The SPIKE marker — a single-purpose presence flag, like SEAM/STEP.
179
+ * The SPIKE marker: a single-purpose presence flag, like SEAM/STEP.
180
180
  *
181
181
  * Written by `spike` on open, removed on `spike done`. Its existence is the one
182
182
  * signal the dashboard and the spike gates read to know "a spike is active".
@@ -185,39 +185,39 @@ export function spikePath(root, slug) {
185
185
  return join(artifactDir(root, slug), 'SPIKE');
186
186
  }
187
187
  /**
188
- * SEAM — the in-flight step's edit grant: a plain list of the paths the step may
188
+ * SEAM: the in-flight step's edit grant: a plain list of the paths the step may
189
189
  * touch, one per line, so the hooks read it with a grep and no markdown parsing.
190
190
  */
191
191
  export function seamPath(root, slug) {
192
192
  return join(artifactDir(root, slug), 'SEAM');
193
193
  }
194
194
  /**
195
- * STEP — the number of the step in flight, a bare number in a flat file.
195
+ * STEP: the number of the step in flight, a bare number in a flat file.
196
196
  */
197
197
  export function stepPath(root, slug) {
198
198
  return join(artifactDir(root, slug), 'STEP');
199
199
  }
200
200
  /**
201
- * The build's `checkpoints` ledger — the recorded SHAs of landed plan and step
201
+ * The build's `checkpoints` ledger: the recorded SHAs of landed plan and step
202
202
  * commits, one line each.
203
203
  */
204
204
  export function checkpointsPath(root, slug) {
205
205
  return join(artifactDir(root, slug), 'checkpoints');
206
206
  }
207
207
  /**
208
- * intent.md — the build's plan: steps, seams, decisions, done-when.
208
+ * intent.md: the build's plan: steps, seams, decisions, done-when.
209
209
  */
210
210
  export function intentPath(root, slug) {
211
211
  return join(artifactDir(root, slug), 'intent.md');
212
212
  }
213
213
  /**
214
- * build-log.md — the human-facing build ledger (the Steps mirror and the Log).
214
+ * build-log.md: the human-facing build ledger (the Steps mirror and the Log).
215
215
  */
216
216
  export function buildLogPath(root, slug) {
217
217
  return join(artifactDir(root, slug), 'build-log.md');
218
218
  }
219
219
  /**
220
- * report.md — the close-out report, beside intent.md / build-log.md.
220
+ * report.md: the close-out report, beside intent.md / build-log.md.
221
221
  *
222
222
  * The finish skill writes it and `finish` commits it with the folder; the
223
223
  * tracked build folder is the archive, so the report rides the branch into the
@@ -227,7 +227,7 @@ export function reportPath(root, slug) {
227
227
  return join(artifactDir(root, slug), 'report.md');
228
228
  }
229
229
  // --- Spike reports: `spike-NN-<slug>.md` files in the build folder, beside
230
- // report.md. One per spike — an explicit `/plumbbob:spike` or a planned `spike:` step —
230
+ // report.md. One per spike (an explicit `/plumbbob:spike` or a planned `spike:` step)
231
231
  // so a build with several forks tells its decision history through the folder
232
232
  // listing. NN is CLI-allocated (the human never numbers them), zero-padded for a
233
233
  // stable sort. ---
@@ -251,7 +251,7 @@ export function listSpikeReports(root, slug) {
251
251
  * sanitized spike slug.
252
252
  *
253
253
  * One past the highest existing NN, so a gap never reallocates a number. Pure
254
- * allocation — the caller writes the stamped template there.
254
+ * allocation: the caller writes the stamped template there.
255
255
  */
256
256
  export function nextSpikeReportPath(root, slug, spikeSlug) {
257
257
  const used = listSpikeReports(root, slug).map((name) => Number(name.slice('spike-'.length, 'spike-'.length + 2)));
@@ -260,10 +260,10 @@ export function nextSpikeReportPath(root, slug, spikeSlug) {
260
260
  return join(artifactDir(root, slug), `spike-${nn}-${spikeSlug}.md`);
261
261
  }
262
262
  /**
263
- * handoff.json — the agent-run ledger.
263
+ * handoff.json: the agent-run ledger.
264
264
  *
265
265
  * Each `agent run` appends its validated envelope here so a later invocation can
266
- * thread earlier results into the next call's `context[]` — the CLI itself is
266
+ * thread earlier results into the next call's `context[]`: the CLI itself is
267
267
  * memoryless between runs, and the file survives context-window compaction where
268
268
  * inline stdout does not. It is in-flight control state like STEP/SEAM:
269
269
  * untracked (excludeControl), scoped to the current step, cleared when the step
@@ -276,7 +276,7 @@ export function handoffPath(root, slug) {
276
276
  * Append one entry to the build's handoff.json.
277
277
  *
278
278
  * Creates the file (as a JSON array) when absent and tolerates a malformed
279
- * existing file by starting fresh — a corrupt ledger must never wedge a run.
279
+ * existing file by starting fresh: a corrupt ledger must never wedge a run.
280
280
  * Pretty-printed so it stays readable.
281
281
  */
282
282
  export function appendHandoff(root, slug, entry) {
@@ -295,7 +295,7 @@ export function appendHandoff(root, slug, entry) {
295
295
  writeFileSync(path, `${JSON.stringify(entries, null, 2)}\n`);
296
296
  }
297
297
  /**
298
- * Remove the build's handoff.json — the step-scoped ledger clears when the step
298
+ * Remove the build's handoff.json: the step-scoped ledger clears when the step
299
299
  * checkpoints, the same beat that clears STEP/SEAM.
300
300
  *
301
301
  * An absent file is a no-op (`force`).
@@ -304,13 +304,13 @@ export function clearHandoff(root, slug) {
304
304
  rmSync(handoffPath(root, slug), { force: true });
305
305
  }
306
306
  /**
307
- * stats.json — the per-build stats receipt, beside checkpoints.
307
+ * stats.json: the per-build stats receipt, beside checkpoints.
308
308
  */
309
309
  export function statsPath(root, slug) {
310
310
  return join(artifactDir(root, slug), 'stats.json');
311
311
  }
312
312
  /**
313
- * The whole stats file, or {} when absent or corrupt — malformed contributes
313
+ * The whole stats file, or {} when absent or corrupt: malformed contributes
314
314
  * nothing.
315
315
  */
316
316
  export function readStats(root, slug) {
@@ -325,14 +325,14 @@ export function readStats(root, slug) {
325
325
  /**
326
326
  * Increment one counter on one step.
327
327
  *
328
- * Best-effort by contract: a failed write is swallowed — the stats are a
328
+ * Best-effort by contract: a failed write is swallowed; the stats are a
329
329
  * receipt, never a gate on the verb that accrues them.
330
330
  */
331
331
  export function bumpStepStat(root, slug, step, key) {
332
332
  patchStepStat(root, slug, step, (current) => ({ [key]: (current[key] ?? 0) + 1 }));
333
333
  }
334
334
  /**
335
- * Stamp one timestamp on one step — startedAt at `build <n>`, landedAt at
335
+ * Stamp one timestamp on one step: startedAt at `build <n>`, landedAt at
336
336
  * checkpoint.
337
337
  *
338
338
  * Same best-effort contract as bumpStepStat.
@@ -362,7 +362,7 @@ function patchStepStat(root, slug, step, patch) {
362
362
  /**
363
363
  * A session exists iff STATE exists.
364
364
  *
365
- * Deleting STATE (at finish) is what flips the repo back to "no session" — the
365
+ * Deleting STATE (at finish) is what flips the repo back to "no session": the
366
366
  * file is the single source of truth for "is there a session". `start` calls
367
367
  * beginSession; `finish` removes the file. Existence is the whole session
368
368
  * signal; STATE's content is a separate axis (the cursor).
@@ -381,7 +381,7 @@ export function beginSession(root, slug = null) {
381
381
  writeCursor(root, slug);
382
382
  }
383
383
  /**
384
- * Re-point the cursor at an existing build (`use`) — a plain content rewrite
384
+ * Re-point the cursor at an existing build (`use`): a plain content rewrite
385
385
  * that leaves the session sentinel (STATE's existence) intact.
386
386
  *
387
387
  * Callers guard hasSession first, so this never resurrects a finished session.
@@ -396,13 +396,13 @@ function writeCursor(root, slug) {
396
396
  writeFileSync(statePath(root), slug === null ? '' : `${slug}\n`);
397
397
  }
398
398
  /**
399
- * True while a spike is open — the SPIKE marker exists (content is irrelevant).
399
+ * True while a spike is open: the SPIKE marker exists (content is irrelevant).
400
400
  */
401
401
  export function inSpike(root, slug) {
402
402
  return existsSync(spikePath(root, slug));
403
403
  }
404
404
  /**
405
- * Drop the SPIKE marker — existence is the whole signal.
405
+ * Drop the SPIKE marker: existence is the whole signal.
406
406
  */
407
407
  export function markSpike(root, slug) {
408
408
  writeFileSync(spikePath(root, slug), 'active\n');
@@ -415,20 +415,20 @@ export function clearSpike(root, slug) {
415
415
  }
416
416
  // --- The turn ledger: the checkpoint latch's inputs. `.plumbbob/TURN` is a count
417
417
  // of human prompts the model never writes, with a one-turn `.plumbbob/GRANT`
418
- // beside it — both flat, per-worktree control (never per-build, never committed).
418
+ // beside it: both flat, per-worktree control (never per-build, never committed).
419
419
  // `plumbbob turn` (the UserPromptSubmit hook) ticks TURN once per human prompt
420
420
  // and rewrites GRANT from the literal prompt; the checkpoint latch reads them to
421
421
  // refuse a land until a human turn has intervened since the step was entered.
422
422
  // Kept in the sidecar so the lone GRANT `rmSync` stays where deletions of control
423
423
  // state are allowed. ---
424
424
  /**
425
- * TURN — the human-turn count the UserPromptSubmit hook maintains.
425
+ * TURN: the human-turn count the UserPromptSubmit hook maintains.
426
426
  */
427
427
  export function turnPath(root) {
428
428
  return join(root, DIRNAME, 'TURN');
429
429
  }
430
430
  /**
431
- * GRANT — the one-turn self-approval minted from the human's literal prompt.
431
+ * GRANT: the one-turn self-approval minted from the human's literal prompt.
432
432
  */
433
433
  export function grantPath(root) {
434
434
  return join(root, DIRNAME, 'GRANT');
@@ -437,8 +437,8 @@ export function grantPath(root) {
437
437
  * Rewrite (or clear) the one-turn GRANT: a non-null value is the minted grant
438
438
  * (`auto` | `range M`), null clears it.
439
439
  *
440
- * The turn hook calls this on every human prompt — minted on a match, cleared
441
- * otherwise — so a grant lives exactly one turn by construction. Pairing the
440
+ * The turn hook calls this on every human prompt (minted on a match, cleared
441
+ * otherwise) so a grant lives exactly one turn by construction. Pairing the
442
442
  * write with its delete here means a grant can never half-persist, and the
443
443
  * delete lives where deletions belong.
444
444
  */
@@ -451,7 +451,7 @@ export function setGrant(root, grant) {
451
451
  }
452
452
  }
453
453
  /**
454
- * TICK — the per-build entry stamp: the TURN value recorded when work was
454
+ * TICK: the per-build entry stamp: the TURN value recorded when work was
455
455
  * entered (`build <n>` for a step, `start` for the plan), cleared when
456
456
  * `checkpoint` lands.
457
457
  *
@@ -474,7 +474,7 @@ export function stampTick(root, slug) {
474
474
  writeFileSync(tickPath(root, slug), `${turn}\n`);
475
475
  }
476
476
  /**
477
- * Consume the entry stamp — `checkpoint` clears it when a step (or the plan)
477
+ * Consume the entry stamp: `checkpoint` clears it when a step (or the plan)
478
478
  * lands, the same beat that clears STEP/SEAM/handoff.
479
479
  *
480
480
  * Absent is a no-op; the rmSync lives here with the sidecar's other deletions.
@@ -486,7 +486,7 @@ export function clearTick(root, slug) {
486
486
  * The TURN count, or null when the ledger is absent or unreadable.
487
487
  *
488
488
  * Absence is the "dormant" signal stampTick keys off and the doctor latch probe
489
- * reports — never an error.
489
+ * reports: never an error.
490
490
  */
491
491
  export function readTurn(root) {
492
492
  try {
@@ -502,7 +502,7 @@ export function readTurn(root) {
502
502
  * `.plumbbob/STATE` file), or null when there is none.
503
503
  *
504
504
  * The turn hook runs on every human prompt in every repo; this is the cheap,
505
- * git-free probe that keeps it a silent no-op outside a live plumbbob session —
505
+ * git-free probe that keeps it a silent no-op outside a live plumbbob session:
506
506
  * filesystem only, never a host sniff.
507
507
  */
508
508
  export function findSessionRoot(cwd) {
@@ -519,8 +519,8 @@ export function findSessionRoot(cwd) {
519
519
  /**
520
520
  * Append `patterns` to the repo's info/exclude, each at most once.
521
521
  *
522
- * Idempotent — a re-`start` after finish must not double-add. `gitPath` resolves
523
- * to the *common* gitdir's exclude — the only one git reads — so this works from
522
+ * Idempotent: a re-`start` after finish must not double-add. `gitPath` resolves
523
+ * to the *common* gitdir's exclude (the only one git reads) so this works from
524
524
  * a linked worktree, whose per-worktree gitdir has no `info/`. info/exclude
525
525
  * keeps the exclusion personal machinery, never something imposed on the repo's
526
526
  * own tracked ignore file.
@@ -546,7 +546,7 @@ function addExcludes(root, patterns) {
546
546
  * Git-exclude the control plane while the artifact plane stays tracked.
547
547
  *
548
548
  * With `builds/<slug>/` tracked, only the per-worktree control files are
549
- * excluded — the session sentinel STATE (whose content is the active-build
549
+ * excluded: the session sentinel STATE (whose content is the active-build
550
550
  * cursor), the personal settings overlay, the turn ledger, and the in-flight
551
551
  * markers inside every build. Everything else under `.plumbbob/` (settings.json,
552
552
  * and each build's intent/build-log/checkpoints/report) rides the branch into
@@ -563,11 +563,11 @@ export function excludeControl(root) {
563
563
  `${DIRNAME}/builds/*/STEP`,
564
564
  `${DIRNAME}/builds/*/SEAM`,
565
565
  `${DIRNAME}/builds/*/SPIKE`,
566
- // The entry stamp is in-flight control like STEP/SEAM — checkpoint's
566
+ // The entry stamp is in-flight control like STEP/SEAM: checkpoint's
567
567
  // stageAll must never sweep it into a step commit.
568
568
  `${DIRNAME}/builds/*/TICK`,
569
569
  // The agent-run handoff ledger is step-scoped in-flight state, not a
570
- // tracked artifact — it must never ride a step commit into the PR.
570
+ // tracked artifact: it must never ride a step commit into the PR.
571
571
  `${DIRNAME}/builds/*/handoff.json`,
572
572
  // The checkride gate writes raw tool output to `.check/`; checkpoint's
573
573
  // stageAll must never sweep it into a step commit.
@@ -575,7 +575,7 @@ export function excludeControl(root) {
575
575
  ]);
576
576
  }
577
577
  /**
578
- * `start --local`: opt out of the tracked layout into a fully-untracked sidecar —
578
+ * `start --local`: opt out of the tracked layout into a fully untracked sidecar:
579
579
  * some team repos won't accept tool folders in-tree.
580
580
  *
581
581
  * Excludes the whole `.plumbbob/` directory.
@@ -586,7 +586,7 @@ export function excludeSidecar(root) {
586
586
  /**
587
587
  * Whether info/exclude already carries `pattern` verbatim.
588
588
  *
589
- * Absent or unreadable reads as "no" — the caller's job is to write it, not to
589
+ * Absent or unreadable reads as "no": the caller's job is to write it, not to
590
590
  * fail on a repo that has never been excluded.
591
591
  */
592
592
  function hasExclude(root, pattern) {
@@ -603,15 +603,15 @@ function hasExclude(root, pattern) {
603
603
  * Re-apply the control-plane excludes on the way into a `git add -A`.
604
604
  *
605
605
  * `start` writes them once, but a plumbbob upgraded mid-build can add a control
606
- * file the running session's exclude never learned — TICK, GRANT and
607
- * handoff.json all arrived that way — and checkpoint/finish stage with `-A`,
606
+ * file the running session's exclude never learned (TICK, GRANT and
607
+ * handoff.json all arrived that way) and checkpoint/finish stage with `-A`,
608
608
  * which would sweep the unexcluded file into the commit and ride it into the PR.
609
609
  * Idempotent, so the common path is one read and no write.
610
610
  *
611
- * A fully-untracked sidecar is already covered by its blanket `.plumbbob/` line;
611
+ * A fully untracked sidecar is already covered by its blanket `.plumbbob/` line;
612
612
  * layering the narrow patterns underneath would only add noise, so skip it. That
613
613
  * check reads info/exclude rather than the cursor because a null cursor means
614
- * "`--local` OR no build named" — and calling excludeSidecar on the tracked
614
+ * "`--local` OR no build named", and calling excludeSidecar on the tracked
615
615
  * layout would untrack the whole artifact plane.
616
616
  */
617
617
  export function refreshExcludes(root) {
@@ -9,7 +9,7 @@ import { readFileSync } from 'node:fs';
9
9
  * Read a template by filename from the package's `templates/` directory.
10
10
  *
11
11
  * Resolved relative to the compiled module URL, so the same relative hop works
12
- * from `src/lib/` and `dist/lib/` alike — the templates ship with the package,
12
+ * from `src/lib/` and `dist/lib/` alike: the templates ship with the package,
13
13
  * never with the consumer's repo.
14
14
  */
15
15
  export function readTemplate(name) {
@@ -1,17 +1,17 @@
1
- // `plumbbob agent <subcommand>` — the doorway to user-authored agents: anything
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>/` — and prints each resolvable agent. `agent run
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` — untracked,
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
- // There is deliberately no code path here to checkpoint, flip a step, or chain
13
- // agents — the subprocess boundary keeps the human as the clock by
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 — the user who typed the name asked for it specifically. With no name it
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 — never
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 — it labels an explicit run (the
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 — a binding is ambient
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 — the
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 — a model can write a settings
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 — no new envelope field, no new verb; the config just
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 — no deep merge — and {} when neither defines it.
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 — the per-step harness entry beats the harness
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 — `run` screens argv against the verb spec in
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 — 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:
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 — nothing else,
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 — parked lines only reach the
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 — `blocked` unblocks and re-runs,
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
  */
@@ -1,9 +1,9 @@
1
- // `plumbbob build <n>` — enter a step: read step n's seam from intent.md and
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 — the exact paths and `dir/` prefixes the
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 — only `checkpoint` commits.
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 — or the next undone step — and write its SEAM/STEP markers.
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 — the CLI records one
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 — caught before
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 — a host with no hooks grows no TURN/TICK files.
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 — Current step plus the ☐/☑ mirror, re-rendered from intent.md.
75
- // Best-effort — a missing or hand-edited build-log never blocks the build.
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)' : '';
@@ -1,14 +1,14 @@
1
- // `plumbbob check` — run the heavy gate and report, with NO state change. This
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 — resolved through the settings
6
- // ladder (flag → local overlay → tracked settings.json → default) — for repos
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 — the
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 — but the same tolerance
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 — a misconfigured harness
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) {