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
package/dist/lib/latch.js CHANGED
@@ -1,25 +1,25 @@
1
- // The approval latch — the ledger-plane predicate `checkpoint` evaluates before
1
+ // The approval latch: the ledger-plane predicate `checkpoint` evaluates before
2
2
  // its check gate (the latch is cheap, so it runs first; the heavy gate already
3
- // ran in the verify tick). The work plane stays guidance — no edit is ever
4
- // blocked — but the commit tick is latched: a step may not land unless a human
3
+ // ran in the verify tick). The work plane stays guidance (no edit is ever
4
+ // blocked) but the commit tick is latched: a step may not land unless a human
5
5
  // turn intervened since the step was entered, the human granted a one-turn
6
6
  // self-approval from their literal prompt, or the turn ledger is dormant (a
7
7
  // host with no hooks grows no ledger, and the latch stays out of the way rather
8
8
  // than wedging). The ledger is a pair of flat sidecar control files: TURN
9
9
  // counts human prompts (ticked by the `plumbbob turn` hook), TICK stamps that
10
- // count when a step is entered — TURN > TICK means a human turn intervened.
10
+ // count when a step is entered; TURN > TICK means a human turn intervened.
11
11
  // Five rows, evaluated in order, first hit wins; the refusal message *is* the
12
12
  // pause affordance. A standing `auto` in a settings file is never a grant: a
13
- // model can write that file, and a grant it can forge is no grant — the latch
13
+ // model can write that file, and a grant it can forge is no grant; the latch
14
14
  // reads it only to say so at the pause, never to allow. It reads only
15
- // TURN/TICK/GRANT and isTTY — it never sniffs the host session. Named exports,
15
+ // TURN/TICK/GRANT and isTTY; it never sniffs the host session. Named exports,
16
16
  // node builtins.
17
17
  import { readFileSync } from 'node:fs';
18
18
  import { grantPath, tickPath, turnPath } from "./sidecar.js";
19
19
  import { resolveBoolean } from "./settings.js";
20
20
  const ALLOW = { allow: true };
21
21
  /**
22
- * The refusal message is the affordance — it tells the model what the pause is,
22
+ * The refusal message is the affordance; it tells the model what the pause is,
23
23
  * how it ends (approval on the next turn lands it; `/plumbbob:build` only starts the
24
24
  * next step), and names the only legitimate grants.
25
25
  */
@@ -31,7 +31,7 @@ boundary; \`/plumbbob:build\` only starts the next step, it never lands this one
31
31
  `;
32
32
  /**
33
33
  * The plan commit gets its own wording: there is no step, no diff, and no
34
- * self-review at plan time — what the human approves is the plan itself.
34
+ * self-review at plan time; what the human approves is the plan itself.
35
35
  */
36
36
  const NO_TURN_PLAN_MESSAGE = `plumbbob: checkpoint refused — no human turn since \`start\` stamped this plan. This
37
37
  is the plan pause: present the plan, then end the turn; the human's approving
@@ -39,7 +39,7 @@ message is the tick that lets it land on the next run. (An explicit \`/plumbbob:
39
39
  step range in the human's own prompt is the only self-approval.)
40
40
  `;
41
41
  /**
42
- * A settings `auto` is never a grant — a model can write that file, and a grant
42
+ * A settings `auto` is never a grant; a model can write that file, and a grant
43
43
  * it can forge is no grant. When one is set, the refusal names it rather than
44
44
  * silently ignoring it, and points at the only self-approval that works.
45
45
  */
@@ -60,19 +60,19 @@ function ceilingMessage(ceiling) {
60
60
  */
61
61
  export function evaluateLatch(input) {
62
62
  if (input.isTTY)
63
- return ALLOW; // 1 — a human at the keyboard
63
+ return ALLOW; // 1: a human at the keyboard
64
64
  if (input.turn === null || input.tick === null)
65
- return ALLOW; // 2 — dormant ledger / hand-built diff
65
+ return ALLOW; // 2: dormant ledger / hand-built diff
66
66
  if (input.grant?.kind === 'auto')
67
- return ALLOW; // 3 — one-turn grant the human typed
67
+ return ALLOW; // 3: one-turn grant the human typed
68
68
  if (input.grant?.kind === 'range' && input.step !== null) {
69
69
  if (input.step <= input.grant.ceiling)
70
70
  return ALLOW;
71
71
  return { allow: false, reason: 'ceiling', message: ceilingMessage(input.grant.ceiling) };
72
72
  }
73
73
  if (input.turn > input.tick)
74
- return ALLOW; // 4 — a human turn intervened
75
- // 5 — the pause (plan-worded when no step is landing). A settings `auto` never
74
+ return ALLOW; // 4: a human turn intervened
75
+ // 5: the pause (plan-worded when no step is landing). A settings `auto` never
76
76
  // allows; when one is set, the refusal notes it rather than honoring it.
77
77
  const base = input.step === null ? NO_TURN_PLAN_MESSAGE : NO_TURN_MESSAGE;
78
78
  return { allow: false, reason: 'no-turn', message: input.settingsAuto ? base + SETTINGS_AUTO_NOTE : base };
@@ -80,7 +80,7 @@ export function evaluateLatch(input) {
80
80
  /**
81
81
  * Parse a GRANT file's content (`auto` | `range <M>`).
82
82
  *
83
- * Anything else — including a corrupted or hand-mangled file — contributes
83
+ * Anything else (including a corrupted or hand-mangled file) contributes
84
84
  * nothing: no grant, never an error.
85
85
  */
86
86
  export function parseGrant(raw) {
@@ -105,7 +105,7 @@ export function checkLatch(root, step) {
105
105
  tick: readCount(tickPath(root)),
106
106
  grant: readGrant(root),
107
107
  step,
108
- // Read from the settings ladder — but only to decorate the pause, never to allow.
108
+ // Read from the settings ladder, but only to decorate the pause, never to allow.
109
109
  settingsAuto: resolveBoolean(root, 'auto', false),
110
110
  });
111
111
  }
@@ -1,5 +1,5 @@
1
1
  // The orientation model behind `status`: parse a live build session into the
2
- // where-am-I dashboard — title, phase, step list, last checkpoint, counts, and
2
+ // where-am-I dashboard: title, phase, step list, last checkpoint, counts, and
3
3
  // one suggested next move. Pure and best-effort by design: it takes raw file
4
4
  // contents (no fs), and a malformed document degrades to fewer fields rather
5
5
  // than throwing.
@@ -20,7 +20,7 @@ function sectionLines(content, heading) {
20
20
  return lines.slice(start + 1, end);
21
21
  }
22
22
  /**
23
- * The build title — the first `# ` heading in intent.md, or null when absent.
23
+ * The build title: the first `# ` heading in intent.md, or null when absent.
24
24
  */
25
25
  export function parseTitle(intent) {
26
26
  for (const line of intent.split('\n')) {
@@ -32,12 +32,14 @@ export function parseTitle(intent) {
32
32
  return null;
33
33
  }
34
34
  /**
35
- * Parse the steps under `## Steps`: `N. [ |x] Title — **done when:** ...`.
35
+ * Parse the steps under `## Steps`: `N. [ |x] Title, **done when:** ...`
36
+ * (the legacy em-dash separator before the marker parses the same).
36
37
  *
37
- * The title is the text up to the first em dash; `planned` is true when the
38
- * step's block carries a `done when` criterion. Only `## Steps` is machine-read
39
- * as the build plan — narrative roadmap prose lives in its own section and
40
- * never lands here.
38
+ * The title is the text before the `**done when:**` marker on the opener line,
39
+ * trailing separator stripped; an opener without the marker falls back to the
40
+ * text up to the first em dash. `planned` is true when the step's block carries
41
+ * a `done when` criterion. Only `## Steps` is machine-read as the build plan;
42
+ * narrative roadmap prose lives in its own section and never lands here.
41
43
  */
42
44
  export function parseSteps(intent) {
43
45
  const lines = sectionLines(intent, '## Steps');
@@ -46,10 +48,12 @@ export function parseSteps(intent) {
46
48
  const m = /^(\d+)\.\s+\[([ xX])\]\s+(.*)$/.exec(line);
47
49
  if (m) {
48
50
  const rest = m[3] ?? '';
51
+ const marker = /\*\*done when:\*\*/i.exec(rest);
52
+ const head = marker ? rest.slice(0, marker.index) : (rest.split('—')[0] ?? rest);
49
53
  starts.push({
50
54
  n: Number(m[1]),
51
55
  done: (m[2] ?? ' ').toLowerCase() === 'x',
52
- title: (rest.split('—')[0] ?? rest).trim(),
56
+ title: head.replace(/\s*[—,-]+\s*$/, '').trim(),
53
57
  idx,
54
58
  });
55
59
  }
@@ -88,13 +92,28 @@ export function markStepDone(intent, n) {
88
92
  }
89
93
  /**
90
94
  * Count the questions still open: `- Q\d+:` openers under `## Open questions`
91
- * that do not say "resolved".
95
+ * that carry neither a resolution marker nor an unfilled scaffold body.
92
96
  *
93
- * The opener may carry a slug-at-birth gloss — `- Q2 (some-slug): ...` — which
94
- * the count reads through; sub-lines (`*plain:*`/`*lean:*`) never match.
97
+ * The opener may carry a slug-at-birth gloss (`- Q2 (some-slug): ...`) and the
98
+ * anchored form a citable question is born in:
99
+ * `- <a id="q2"></a>**Q2 (some-slug)**: ...`, which is what a `[Q2 (some-slug)](#q2)`
100
+ * reference site elsewhere in the file lands on. The count reads through both, and
101
+ * through any mix of them a hand-edited intent leaves mid-build; sub-lines
102
+ * (`*plain:*`/`*lean:*`) never match.
103
+ *
104
+ * Two rules settle the rest. "Resolved" counts as a marker only as a whole word, so
105
+ * a question whose opener still calls itself "unresolved" keeps counting; a bare
106
+ * substring read that as resolved and dropped a live hole off the dashboard. And an
107
+ * opener whose body has not been filled in yet, still opening on its `<...>`
108
+ * fill-in, is scaffold rather than a question, which is what makes a fresh build
109
+ * report zero rather than one.
95
110
  */
96
111
  export function parseOpenQuestions(intent) {
97
- return sectionLines(intent, '## Open questions').filter((l) => /^- Q\d+(?: \([^)]+\))?:/.test(l.trim()) && !/resolved/i.test(l)).length;
112
+ const opener = /^-\s+(?:<a id="[^"]*"><\/a>\s*)?\*{0,2}Q\d+(?: \([^)]+\))?\*{0,2}:\s*(.*)$/;
113
+ return sectionLines(intent, '## Open questions').filter((l) => {
114
+ const body = opener.exec(l.trim())?.[1];
115
+ return body !== undefined && !/\bresolved\b/i.test(l) && !/^<[^>]*>/.test(body);
116
+ }).length;
98
117
  }
99
118
  /**
100
119
  * Count the open parked items: `- [ ]` lines under `## Park list`.
@@ -122,12 +141,12 @@ export function parseLastCheckpoint(checkpoints) {
122
141
  return last;
123
142
  }
124
143
  /**
125
- * The SHA of the last ledger line of ANY kind — baseline, plan, or step.
144
+ * The SHA of the last ledger line of ANY kind: baseline, plan, or step.
126
145
  *
127
146
  * This is the reconciliation anchor for the out-of-band commit count: a commit
128
147
  * that lands after the plan but before the first step checkpoint is exactly the
129
148
  * window the reconciliation line exists for, so anchoring on step lines alone
130
- * would leave it invisible. `parseLastCheckpoint` stays step-only — it feeds
149
+ * would leave it invisible. `parseLastCheckpoint` stays step-only; it feeds
131
150
  * the "last checkpoint step N" display, where baseline/plan would be noise.
132
151
  */
133
152
  export function lastLedgerSha(checkpoints) {
@@ -162,7 +181,7 @@ function nextMove(spiking, steps, inFlight, parked) {
162
181
  return 'plan the first step — `/plumbbob:step`';
163
182
  }
164
183
  // Batch-default: the steps were planned up front, so finishing them usually
165
- // means "finish up" — but `/plumbbob:step` can still add an increment if reality grew.
184
+ // means "finish up", but `/plumbbob:step` can still add an increment if reality grew.
166
185
  const harvest = parked > 0 ? `harvest ${parked} parked idea${parked === 1 ? '' : 's'} — \`/plumbbob:harvest\`; then ` : '';
167
186
  return `${harvest}finish up — \`/plumbbob:finish\` (or \`/plumbbob:step\` to add another increment)`;
168
187
  }
@@ -207,7 +226,7 @@ export function formatOrientation(o) {
207
226
  return head;
208
227
  }
209
228
  // Surface the next step's detail so the human can review it (and `/plumbbob:step`-
210
- // revise) before building. Only what's present — a rough step shows neither.
229
+ // revise) before building. Only what's present: a rough step shows neither.
211
230
  const detail = [];
212
231
  if (o.nextDoneWhen !== null) {
213
232
  detail.push(` done when: ${o.nextDoneWhen}`);
@@ -225,7 +244,7 @@ export function formatOrientation(o) {
225
244
  const cpLine = cp === null ? 'last checkpoint none yet' : `last checkpoint step ${cp.n} · ${cp.sha.slice(0, 7)}`;
226
245
  // A neutral reconciliation note, only when there is something to reconcile:
227
246
  // commits landed since the last checkpoint that plumbbob's ledger didn't
228
- // record. Informational — the human commits freely, so this never gates.
247
+ // record. Informational: the human commits freely, so this never gates.
229
248
  const receipts = o.outOfBand > 0
230
249
  ? [`${o.outOfBand} commit${o.outOfBand === 1 ? '' : 's'} since the last checkpoint landed outside plumbbob's ledger.`]
231
250
  : [];
@@ -3,8 +3,8 @@
3
3
  //
4
4
  // The failure it exists to stop is not slowness, it is unbounded recursion. A
5
5
  // repo whose `check` setting is its own full pipeline (`pnpm check`) has a gate
6
- // that runs its test suite; anything in that suite which gates THAT SAME REPO —
7
- // a test driving `checkpoint` against the process cwd — spawns a second full
6
+ // that runs its test suite; anything in that suite which gates THAT SAME REPO
7
+ // (a test driving `checkpoint` against the process cwd) spawns a second full
8
8
  // pipeline, whose suite spawns a third. Each generation forks wider than the
9
9
  // last, and nothing in the loop is aware there is a loop. The only thing that
10
10
  // ended the first observed occurrence was checkride's 600s per-check cap
@@ -13,7 +13,7 @@
13
13
  // It is scoped BY ROOT, and that is the whole design. A flat "a gate is
14
14
  // running" flag is wrong in the one case that matters most: plumbbob's own
15
15
  // suite legitimately gates dozens of fixture repos, and when the suite itself
16
- // is running under this repo's gate it inherits the marker — so a flat flag
16
+ // is running under this repo's gate it inherits the marker, so a flat flag
17
17
  // refuses every one of them and turns a green suite red. Recursion is a repo
18
18
  // re-entering ITS OWN gate; a different root is ordinary nested work and must
19
19
  // pass through untouched.
@@ -1,19 +1,19 @@
1
1
  // The settings ladder: a resolved setting comes from, in priority order,
2
- // 1. a CLI flag — passed in by the verb (undefined when absent)
3
- // 2. settings.local.json — untracked personal overlay, per-worktree
4
- // 3. settings.json — tracked project defaults
5
- // 4. a built-in default — supplied by the caller
2
+ // 1. a CLI flag: passed in by the verb (undefined when absent)
3
+ // 2. settings.local.json: untracked personal overlay, per-worktree
4
+ // 3. settings.json: tracked project defaults
5
+ // 4. a built-in default: supplied by the caller
6
6
  // The first defined rung wins. Both files are optional JSON; a missing or
7
7
  // malformed file contributes nothing rather than throwing, so a broken personal
8
8
  // overlay can never wedge the tool. Functional and procedural, node builtins only.
9
9
  //
10
- // Known keys: `check` (string — the command for the heavy verify gate, a project
11
- // default in settings.json) and `auto` (boolean — a personal preference, so it
10
+ // Known keys: `check` (string: the command for the heavy verify gate, a project
11
+ // default in settings.json) and `auto` (boolean: a personal preference, so it
12
12
  // belongs in settings.local.json). `auto` is NOT a checkpoint grant: the approval
13
- // latch never reads it to allow a land — a model can write this file, and a grant
14
- // it can forge is no grant — only to name it at the pause when set; self-approval
13
+ // latch never reads it to allow a land (a model can write this file, and a grant
14
+ // it can forge is no grant) only to name it at the pause when set; self-approval
15
15
  // comes solely from the human's literal `/build --auto` or a typed step range.
16
- // The per-worktree active-build cursor is NOT here — it lives in the STATE file
16
+ // The per-worktree active-build cursor is NOT here: it lives in the STATE file
17
17
  // (see sidecar.ts), which keeps this overlay a human-authored file rather than
18
18
  // tool-managed state.
19
19
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
@@ -34,7 +34,7 @@ export function localSettingsPath(root) {
34
34
  /**
35
35
  * Read one settings file as a plain object.
36
36
  *
37
- * A missing, malformed, or non-object file yields {} — an empty rung that
37
+ * A missing, malformed, or non-object file yields {}: an empty rung that
38
38
  * contributes nothing rather than wedging the tool.
39
39
  */
40
40
  function readSettings(path) {
@@ -61,7 +61,7 @@ function resolveSetting(root, key, flag) {
61
61
  return readSettings(settingsPath(root))[key];
62
62
  }
63
63
  /**
64
- * Resolve a string setting (e.g. `check`).
64
+ * Resolve a string setting (for example `check`).
65
65
  *
66
66
  * A missing rung, or one holding a non-string / blank value, yields the
67
67
  * caller's fallback rather than gating on garbage.
@@ -71,7 +71,7 @@ export function resolveString(root, key, fallback, flag) {
71
71
  return typeof value === 'string' && value.trim().length > 0 ? value : fallback;
72
72
  }
73
73
  /**
74
- * Resolve a boolean setting (e.g. `auto`). A missing or non-boolean rung
74
+ * Resolve a boolean setting (for example `auto`). A missing or non-boolean rung
75
75
  * yields the caller's fallback.
76
76
  */
77
77
  export function resolveBoolean(root, key, fallback, flag) {
@@ -79,10 +79,10 @@ export function resolveBoolean(root, key, fallback, flag) {
79
79
  return typeof value === 'boolean' ? value : fallback;
80
80
  }
81
81
  /**
82
- * Resolve a non-negative integer setting (e.g. `agentTimeout`).
82
+ * Resolve a non-negative integer setting (for example `agentTimeout`).
83
83
  *
84
84
  * A missing rung, or one holding a non-finite / negative / non-integer number,
85
- * yields the caller's fallback rather than a garbage timeout — 0 means "no
85
+ * yields the caller's fallback rather than a garbage timeout: 0 means "no
86
86
  * timeout", so a broken value must never silently become one.
87
87
  */
88
88
  export function resolveNumber(root, key, fallback, flag) {
@@ -90,11 +90,11 @@ export function resolveNumber(root, key, fallback, flag) {
90
90
  return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : fallback;
91
91
  }
92
92
  /**
93
- * Resolve an object-valued setting (e.g. the `agents` slot-binding defaults)
93
+ * Resolve an object-valued setting (for example the `agents` slot-binding defaults)
94
94
  * across the same ladder.
95
95
  *
96
96
  * The first defined, object-typed rung wins; a missing rung, or one holding a
97
- * non-object / array / scalar, yields {} rather than garbage — mirrors the
97
+ * non-object / array / scalar, yields {} rather than garbage: mirrors the
98
98
  * string/boolean/number resolvers above. No flag rung: the caller (`agent
99
99
  * run`) merges these under the per-build harness bindings, not over a flag.
100
100
  */