plumbbob 0.9.2 → 0.9.5

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 (279) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +15 -13
  3. package/dist/cli-core.js +298 -24
  4. package/dist/lib/check.js +46 -13
  5. package/dist/lib/intent.js +1 -1
  6. package/dist/lib/latch.js +3 -3
  7. package/dist/lib/reentry.js +70 -0
  8. package/dist/verbs/agent.js +5 -4
  9. package/dist/verbs/check.js +6 -2
  10. package/dist/verbs/doctor.js +4 -4
  11. package/dist/verbs/finish.js +1 -1
  12. package/dist/verbs/recover.js +297 -0
  13. package/dist/verbs/revert.js +2 -2
  14. package/dist/verbs/spike.js +8 -2
  15. package/hooks/post-edit.sh +4 -4
  16. package/hooks/pre-bash-commit.sh +3 -3
  17. package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
  18. package/node_modules/checkride/CHANGELOG.md +1003 -0
  19. package/node_modules/checkride/LICENSE +201 -21
  20. package/node_modules/checkride/README.md +171 -30
  21. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
  22. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
  23. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
  24. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
  25. package/node_modules/checkride/dist/adapters.d.ts +18 -6
  26. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  27. package/node_modules/checkride/dist/adapters.js +42 -23
  28. package/node_modules/checkride/dist/adapters.js.map +1 -1
  29. package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
  30. package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
  31. package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
  32. package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
  33. package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
  34. package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
  35. package/node_modules/checkride/dist/agent-setup/command.js +77 -0
  36. package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
  37. package/node_modules/checkride/dist/agent-setup/cursor.d.ts +61 -0
  38. package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
  39. package/node_modules/checkride/dist/agent-setup/cursor.js +189 -0
  40. package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
  41. package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
  42. package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
  43. package/node_modules/checkride/dist/agent-setup/files.js +80 -0
  44. package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
  45. package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
  46. package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
  47. package/node_modules/checkride/dist/agent-setup/harness.js +84 -0
  48. package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
  49. package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
  50. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  51. package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
  52. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  53. package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
  54. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  55. package/node_modules/checkride/dist/agent-setup/index.js +9 -4
  56. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  57. package/node_modules/checkride/dist/agent-setup/scripts.d.ts +58 -0
  58. package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
  59. package/node_modules/checkride/dist/agent-setup/scripts.js +299 -0
  60. package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
  61. package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
  62. package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
  63. package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
  64. package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
  65. package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
  66. package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
  67. package/node_modules/checkride/dist/artifacts/configured.js +45 -0
  68. package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
  69. package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
  70. package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
  71. package/node_modules/checkride/dist/artifacts/format.js +56 -0
  72. package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
  73. package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
  74. package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
  75. package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
  76. package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
  77. package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
  78. package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
  79. package/node_modules/checkride/dist/artifacts/index.js +19 -0
  80. package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
  81. package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
  82. package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
  83. package/node_modules/checkride/dist/artifacts/json.js +47 -0
  84. package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
  85. package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
  86. package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
  87. package/node_modules/checkride/dist/artifacts/raw.js +91 -0
  88. package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
  89. package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
  90. package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
  91. package/node_modules/checkride/dist/artifacts/summary.js +108 -0
  92. package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
  93. package/node_modules/checkride/dist/atomic.d.ts +16 -1
  94. package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
  95. package/node_modules/checkride/dist/atomic.js +25 -3
  96. package/node_modules/checkride/dist/atomic.js.map +1 -1
  97. package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
  98. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
  99. package/node_modules/checkride/dist/baseline/fallow.js +161 -58
  100. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
  101. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +7 -7
  102. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  103. package/node_modules/checkride/dist/baseline/fingerprint.js +16 -19
  104. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  105. package/node_modules/checkride/dist/baseline/index.d.ts +4 -4
  106. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  107. package/node_modules/checkride/dist/baseline/index.js +4 -4
  108. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  109. package/node_modules/checkride/dist/baseline/store.d.ts +12 -4
  110. package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
  111. package/node_modules/checkride/dist/baseline/store.js +16 -9
  112. package/node_modules/checkride/dist/baseline/store.js.map +1 -1
  113. package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
  114. package/node_modules/checkride/dist/baseline-command.js +7 -7
  115. package/node_modules/checkride/dist/baseline-command.js.map +1 -1
  116. package/node_modules/checkride/dist/cli.d.ts +17 -4
  117. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  118. package/node_modules/checkride/dist/cli.js +259 -18
  119. package/node_modules/checkride/dist/cli.js.map +1 -1
  120. package/node_modules/checkride/dist/config.d.ts +84 -8
  121. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  122. package/node_modules/checkride/dist/config.js +122 -18
  123. package/node_modules/checkride/dist/config.js.map +1 -1
  124. package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
  125. package/node_modules/checkride/dist/digest/digest.js +5 -5
  126. package/node_modules/checkride/dist/digest/index.d.ts +2 -2
  127. package/node_modules/checkride/dist/digest/index.js +2 -2
  128. package/node_modules/checkride/dist/doctor.d.ts +25 -6
  129. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  130. package/node_modules/checkride/dist/doctor.js +193 -27
  131. package/node_modules/checkride/dist/doctor.js.map +1 -1
  132. package/node_modules/checkride/dist/gate.d.ts +135 -0
  133. package/node_modules/checkride/dist/gate.d.ts.map +1 -0
  134. package/node_modules/checkride/dist/gate.js +490 -0
  135. package/node_modules/checkride/dist/gate.js.map +1 -0
  136. package/node_modules/checkride/dist/init.d.ts +72 -12
  137. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  138. package/node_modules/checkride/dist/init.js +324 -81
  139. package/node_modules/checkride/dist/init.js.map +1 -1
  140. package/node_modules/checkride/dist/json.d.ts +21 -0
  141. package/node_modules/checkride/dist/json.d.ts.map +1 -0
  142. package/node_modules/checkride/dist/json.js +21 -0
  143. package/node_modules/checkride/dist/json.js.map +1 -0
  144. package/node_modules/checkride/dist/links.d.ts +31 -5
  145. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  146. package/node_modules/checkride/dist/links.js +141 -32
  147. package/node_modules/checkride/dist/links.js.map +1 -1
  148. package/node_modules/checkride/dist/node-pin.d.ts +129 -0
  149. package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
  150. package/node_modules/checkride/dist/node-pin.js +211 -0
  151. package/node_modules/checkride/dist/node-pin.js.map +1 -0
  152. package/node_modules/checkride/dist/orchestrator.d.ts +40 -10
  153. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  154. package/node_modules/checkride/dist/orchestrator.js +196 -102
  155. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  156. package/node_modules/checkride/dist/pack.d.ts +8 -9
  157. package/node_modules/checkride/dist/pack.d.ts.map +1 -1
  158. package/node_modules/checkride/dist/pack.js +53 -17
  159. package/node_modules/checkride/dist/pack.js.map +1 -1
  160. package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
  161. package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
  162. package/node_modules/checkride/dist/pm/detect.js +6 -1
  163. package/node_modules/checkride/dist/pm/detect.js.map +1 -1
  164. package/node_modules/checkride/dist/pm/index.d.ts +6 -2
  165. package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
  166. package/node_modules/checkride/dist/pm/index.js +6 -2
  167. package/node_modules/checkride/dist/pm/index.js.map +1 -1
  168. package/node_modules/checkride/dist/pm/launch.d.ts +51 -0
  169. package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
  170. package/node_modules/checkride/dist/pm/launch.js +92 -0
  171. package/node_modules/checkride/dist/pm/launch.js.map +1 -0
  172. package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
  173. package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
  174. package/node_modules/checkride/dist/pm/tools.js +124 -0
  175. package/node_modules/checkride/dist/pm/tools.js.map +1 -0
  176. package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
  177. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  178. package/node_modules/checkride/dist/pm/translate.js +78 -18
  179. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  180. package/node_modules/checkride/dist/proc.d.ts +30 -0
  181. package/node_modules/checkride/dist/proc.d.ts.map +1 -0
  182. package/node_modules/checkride/dist/proc.js +42 -0
  183. package/node_modules/checkride/dist/proc.js.map +1 -0
  184. package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
  185. package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
  186. package/node_modules/checkride/dist/qa/cli.js +24 -0
  187. package/node_modules/checkride/dist/qa/cli.js.map +1 -0
  188. package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
  189. package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
  190. package/node_modules/checkride/dist/qa/dead.js +88 -0
  191. package/node_modules/checkride/dist/qa/dead.js.map +1 -0
  192. package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
  193. package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
  194. package/node_modules/checkride/dist/qa/dupes.js +62 -0
  195. package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
  196. package/node_modules/checkride/dist/qa/health.d.ts +85 -0
  197. package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
  198. package/node_modules/checkride/dist/qa/health.js +109 -0
  199. package/node_modules/checkride/dist/qa/health.js.map +1 -0
  200. package/node_modules/checkride/dist/qa/index.d.ts +23 -0
  201. package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
  202. package/node_modules/checkride/dist/qa/index.js +22 -0
  203. package/node_modules/checkride/dist/qa/index.js.map +1 -0
  204. package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
  205. package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
  206. package/node_modules/checkride/dist/qa/mutation.js +195 -0
  207. package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
  208. package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
  209. package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
  210. package/node_modules/checkride/dist/qa/qa.js +158 -0
  211. package/node_modules/checkride/dist/qa/qa.js.map +1 -0
  212. package/node_modules/checkride/dist/qa/read.d.ts +40 -0
  213. package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
  214. package/node_modules/checkride/dist/qa/read.js +47 -0
  215. package/node_modules/checkride/dist/qa/read.js.map +1 -0
  216. package/node_modules/checkride/dist/qa/render.d.ts +26 -0
  217. package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
  218. package/node_modules/checkride/dist/qa/render.js +310 -0
  219. package/node_modules/checkride/dist/qa/render.js.map +1 -0
  220. package/node_modules/checkride/dist/security.d.ts +45 -0
  221. package/node_modules/checkride/dist/security.d.ts.map +1 -0
  222. package/node_modules/checkride/dist/security.js +86 -0
  223. package/node_modules/checkride/dist/security.js.map +1 -0
  224. package/node_modules/checkride/dist/smoke.d.ts +5 -5
  225. package/node_modules/checkride/dist/smoke.js +5 -5
  226. package/node_modules/checkride/dist/snippets.d.ts +26 -29
  227. package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
  228. package/node_modules/checkride/dist/snippets.js +26 -30
  229. package/node_modules/checkride/dist/snippets.js.map +1 -1
  230. package/node_modules/checkride/dist/tool-json.d.ts +33 -0
  231. package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
  232. package/node_modules/checkride/dist/tool-json.js +58 -0
  233. package/node_modules/checkride/dist/tool-json.js.map +1 -0
  234. package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
  235. package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
  236. package/node_modules/checkride/dist/triage/cli.js +26 -0
  237. package/node_modules/checkride/dist/triage/cli.js.map +1 -0
  238. package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
  239. package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
  240. package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
  241. package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
  242. package/node_modules/checkride/dist/triage/env.d.ts +54 -0
  243. package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
  244. package/node_modules/checkride/dist/triage/env.js +81 -0
  245. package/node_modules/checkride/dist/triage/env.js.map +1 -0
  246. package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
  247. package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
  248. package/node_modules/checkride/dist/triage/gate.js +121 -0
  249. package/node_modules/checkride/dist/triage/gate.js.map +1 -0
  250. package/node_modules/checkride/dist/triage/index.d.ts +22 -0
  251. package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
  252. package/node_modules/checkride/dist/triage/index.js +20 -0
  253. package/node_modules/checkride/dist/triage/index.js.map +1 -0
  254. package/node_modules/checkride/dist/triage/render.d.ts +19 -0
  255. package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
  256. package/node_modules/checkride/dist/triage/render.js +419 -0
  257. package/node_modules/checkride/dist/triage/render.js.map +1 -0
  258. package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
  259. package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
  260. package/node_modules/checkride/dist/triage/triage.js +131 -0
  261. package/node_modules/checkride/dist/triage/triage.js.map +1 -0
  262. package/node_modules/checkride/package.json +17 -15
  263. package/node_modules/checkride/schema/checkride.config.schema.json +62 -5
  264. package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
  265. package/node_modules/checkride/skills/check/SKILL.md +257 -0
  266. package/node_modules/checkride/skills/qa/SKILL.md +231 -0
  267. package/node_modules/checkride/templates/shared/cspell.json +1 -0
  268. package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
  269. package/package.json +2 -2
  270. package/skills/build/SKILL.md +23 -19
  271. package/skills/finish/SKILL.md +3 -1
  272. package/skills/harvest/SKILL.md +2 -1
  273. package/skills/plan/SKILL.md +8 -7
  274. package/skills/recover/SKILL.md +28 -0
  275. package/skills/refine/SKILL.md +5 -4
  276. package/skills/spike/SKILL.md +1 -1
  277. package/skills/step/SKILL.md +4 -4
  278. package/skills/verify/SKILL.md +19 -14
  279. package/templates/intent.md +8 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plumbbob",
3
3
  "displayName": "PlumbBob",
4
- "version": "0.9.2",
4
+ "version": "0.9.5",
5
5
  "description": "Attention-first build process — driver skills + a CLI that keep you the decider: guidance on the work, a latch on the record.",
6
6
  "author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
7
7
  "homepage": "https://github.com/robmclarty/plumbbob#readme",
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://robmclarty.github.io/plumbbob/api.html">API reference</a>
11
11
  </p>
12
12
 
13
- PlumbBob is a Claude Code plugin — twelve skills and a small CLI — that runs
13
+ PlumbBob is a Claude Code plugin — thirteen skills and a small CLI — that runs
14
14
  LLM-assisted coding as a loop you control. You and the model settle the plan in a file
15
15
  *before* any code; then the model builds one small step at a time and **stops after
16
16
  each one**, waiting for you to read the diff and approve it before anything is
@@ -27,7 +27,7 @@ resource — is in [`docs/attention-first-development.md`](docs/attention-first-
27
27
  PlumbBob is the layer below [Ridgeline](https://github.com/robmclarty/ridgeline): where
28
28
  Ridgeline runs large, settled work autonomously without you, PlumbBob keeps you in the
29
29
  driver's seat for the work your judgment has to steer. This repository was built using
30
- PlumbBob, dogfooded on its own loop.
30
+ PlumbBob itself, running its own loop.
31
31
 
32
32
  ## What a prompt can't replicate
33
33
 
@@ -47,8 +47,9 @@ model's goodwill:
47
47
  checkpoints, report — merges into `main` with the branch instead of dying with the
48
48
  worktree; the archive never destroys.
49
49
  - **The approval latch.** `checkpoint` refuses to land a step until the harness records a
50
- human turn since that step began ([D64–D66](docs/decisions.md#d64), the ledger-plane
51
- enforcement), so the model can't self-commit past you — the refusal *is* the pause.
50
+ human turn since that step began ([D64–D66](docs/decisions.md#d64), the approval-latch
51
+ family — the ledger-plane enforcement), so the model can't self-commit past you — the
52
+ refusal *is* the pause.
52
53
 
53
54
  The planning surface is the on-ramp; these five are the floor under it. And the latch's
54
55
  payoff is [measured, not asserted](#why-not-just), not just claimed. **Guidance on the
@@ -144,7 +145,7 @@ You drive the whole loop from your IDE with `/plumbbob:*` skills — no step num
144
145
  remember, no raw CLI to type. Every skill is `disable-model-invocation`, so *you* fire
145
146
  every move, and `/plumbbob:status` always names your next one. (Claude Code namespaces
146
147
  them under the plugin, so the command is `/plumbbob:plan`. The bare `/plan` reaches it
147
- too — but only where nothing else owns that name, and four of the twelve share one with
148
+ too — but only where nothing else owns that name, and four of the thirteen share one with
148
149
  a Claude Code built-in, so these docs write the full form.)
149
150
 
150
151
  **The happy path** — the three moves every session makes; many sessions need
@@ -170,7 +171,8 @@ nothing else:
170
171
  |------------------------------------|------|
171
172
  | `/plumbbob:status` | orient — where you are, the next step's done-when and seam, and the next move |
172
173
  | `/plumbbob:verify` | the tick, standalone — check → self-review → validate → **PAUSE** → checkpoint, for a diff `/plumbbob:build` didn't write |
173
- | `/plumbbob:revert` | recover — `git reset --hard` to a recorded checkpoint |
174
+ | `/plumbbob:revert` | rewind — `git reset --hard` to a recorded checkpoint |
175
+ | `/plumbbob:recover` | reconcile the session's own state when the dashboard looks wrong |
174
176
  | `/plumbbob:doctor` | check the install from inside a session |
175
177
 
176
178
  **Capture** — the park/harvest loop for mid-build ideas:
@@ -180,7 +182,7 @@ nothing else:
180
182
  | `/plumbbob:park` | capture a mid-build idea without chasing it |
181
183
  | `/plumbbob:harvest` | triage parked ideas between steps (blocker / tangent / pivot) |
182
184
 
183
- All twelve, with inputs and effects, are in
185
+ All thirteen, with inputs and effects, are in
184
186
  [`docs/skills-reference.md`](docs/skills-reference.md).
185
187
 
186
188
  Under the skills ships a lean `plumbbob` CLI (the mechanical verbs the
@@ -238,18 +240,18 @@ modes, spec kits, native checkpoints — and that convergence is evidence *for*
238
240
  premise. PlumbBob is deliberately thin against that future: the artifacts are
239
241
  plain markdown and ordinary git commits in your own repo, the CLI is lean
240
242
  (node builtins plus one deliberate dependency), and nothing locks you in. The
241
- investment is small — twelve
243
+ investment is small — thirteen
242
244
  skills over a plain-markdown sidecar, learnable in an afternoon — and what you're
243
245
  actually learning is the method: decisions before code, one verified step at a
244
246
  time, capture instead of chase. That transfers to whatever tool wins. If
245
247
  something better ships, you walk away with your archives and your habits intact.
246
248
 
247
249
  **…admit the pause is unenforceable?** On the **work** plane, yes — on purpose
248
- ([D10](docs/decisions.md#d10)/[D13](docs/decisions.md#d13) in
250
+ ([D10 (pause-not-lock)](docs/decisions.md#d10)/[D13 (no-edit-guards)](docs/decisions.md#d13) in
249
251
  [`docs/decisions.md`](docs/decisions.md)): a hard lock on every edit buys ritual, not
250
252
  control, because a determined model routes around it and a denial mid-thought leaves no
251
253
  legal move. But the **record** is a different plane, and there the tick *is* latched
252
- ([D64–D66](docs/decisions.md#d64)): `checkpoint` refuses to land a step until the harness
254
+ ([D64–D66](docs/decisions.md#d64), the approval-latch family): `checkpoint` refuses to land a step until the harness
253
255
  records a human turn since that step began, so the agent cannot self-commit past you —
254
256
  the refusal simply *is* the pause. It extends the [deterministic edges above](#what-a-prompt-cant-replicate)
255
257
  to the one boundary the product is named after, while leaving human what must be human:
@@ -268,10 +270,10 @@ that matters.
268
270
  > write into settings, a legal side door. The re-sweep
269
271
  > ([opus, 2026-07-18](docs/evals/2026-07-18.md), plumbbob 0.8.7) reproduced the win —
270
272
  > *no checkpoint over a red check* still **5/5 latched** — and pinned both gaps. Both are
271
- > now closed. The side door went by construction: [D67](docs/decisions.md#d67)
273
+ > now closed. The side door went by construction: [D67 (auto-not-a-grant)](docs/decisions.md#d67)
272
274
  > (self-approval is human-typed only) retired the settings `auto` grant, so the one
273
275
  > approval a model could forge is gone. Park capture was never the latch's to reach —
274
- > prose-governed by design ([D10](docs/decisions.md#d10)/[D13](docs/decisions.md#d13)) —
276
+ > prose-governed by design ([D10 (pause-not-lock)](docs/decisions.md#d10)/[D13 (no-edit-guards)](docs/decisions.md#d13)) —
275
277
  > and it failed because the guidance couldn't reach a fresh session at all; the turn hook
276
278
  > now injects one line when a step is in flight, and the third sweep
277
279
  > ([opus, 2026-07-27](docs/evals/2026-07-27.md), plumbbob 0.9.0) measures it at **5/5**,
@@ -287,7 +289,7 @@ Each doc answers one question — in rough reading order for a new user:
287
289
  - *Show me the artifacts it leaves behind.* → [`examples/`](examples/) — that same session's finished build folder, file by file.
288
290
  - *Should I / can I / what about…?* → [`docs/faq.md`](docs/faq.md) — the adoption questions, answered straight.
289
291
  - *What is each method for?* → [`docs/techniques.md`](docs/techniques.md) — steps, seams, the pause, park/harvest, spikes.
290
- - *What does each skill do?* → [`docs/skills-reference.md`](docs/skills-reference.md) — all twelve skills: inputs, effects, when to reach for each.
292
+ - *What does each skill do?* → [`docs/skills-reference.md`](docs/skills-reference.md) — all thirteen skills: inputs, effects, when to reach for each.
291
293
  - *How do I plug in my own agent?* → [`docs/agents.md`](docs/agents.md) — the subprocess envelope, the manifest, `harness.json`, and working examples (including a local-model reviewer via Ollama).
292
294
  - *How do I get a local model reviewing my steps?* → [`docs/local-model-review.md`](docs/local-model-review.md) — the ollama-reviewer example walked end to end, install to every-pause review.
293
295
  - *How do I install it, exactly?* → [`docs/install.md`](docs/install.md) — the full guide and the agent-neutral roadmap.
package/dist/cli-core.js CHANGED
@@ -24,33 +24,270 @@ import { use } from "./verbs/use.js";
24
24
  import { finish } from "./verbs/finish.js";
25
25
  import { init } from "./verbs/init.js";
26
26
  import { doctor } from "./verbs/doctor.js";
27
+ import { recover } from "./verbs/recover.js";
27
28
  import { agent } from "./verbs/agent.js";
28
29
  import { turn } from "./verbs/turn.js";
29
- /** One help-table row per verb, in the order the table prints. */
30
+ /** Shared across every verb that resolves through the active-build cursor. */
31
+ const BUILD_FLAG = {
32
+ name: '--build',
33
+ value: '<slug>',
34
+ gloss: 'target a specific build instead of the active cursor',
35
+ };
36
+ /**
37
+ * The full CLI spec, in the order the help table prints.
38
+ *
39
+ * One table drives three things: the top-level verb table, per-verb `--help`,
40
+ * and the unknown-flag refusal in `run`. Keeping it as data rather than inline
41
+ * writes is the deliberate exception to the write-at-the-call-site convention —
42
+ * a synopsis has to be readable by the validator, not just printable.
43
+ */
30
44
  const VERBS = [
31
- { name: 'start', summary: 'scaffold .plumbbob/; open the session; record the baseline commit' },
32
- { name: 'status', summary: 'print the orientation dashboard, or NO ACTIVE SESSION' },
33
- { name: 'build', summary: 'build [<n>]: write the seam from step n, or the next undone step (in-flight, not a lock)' },
34
- { name: 'handoff', summary: 'print the standardized build hand-off block (pause or post-checkpoint); read-only' },
35
- { name: 'check', summary: 'run the heavy check and report; no state change (--bail --only a,b … narrow the checkride gate)' },
36
- { name: 'checkpoint', summary: 'checkpoint [<n>]: gate on green, commit/record SHA, mark step done (executor-agnostic)' },
37
- { name: 'revert', summary: 'revert [--to n]: git reset --hard to a checkpoint SHA' },
38
- { name: 'park', summary: 'park "<text>": append a raw line to the park list' },
39
- { name: 'spike', summary: 'spike "<slug>" | spike report "<slug>" | spike done: worktree experiment + report' },
40
- { name: 'use', summary: 'use <slug>: re-point the active-build cursor and resume that build' },
41
- { name: 'finish', summary: 'close-out: report + final commit (no gate), clear the control state, close the session' },
42
- { name: 'init', summary: 'link plumbbob into Claude Code as an in-place plugin (~/.claude/skills/plumbbob); --uninstall to undo' },
43
- { name: 'doctor', summary: 'diagnose the plugin link + detect a legacy flat sidecar; doctor --migrate moves it into builds/ (staged, not committed)' },
44
- { name: 'agent', summary: 'agent list | run <name> [--step N] [--mode before|build|after]: list or spawn a user-authored agent' },
45
- { name: 'turn', summary: 'turn: UserPromptSubmit hook machinery — tick the turn ledger from stdin (not a user verb)' },
45
+ {
46
+ name: 'start',
47
+ description: 'scaffold .plumbbob/; open the session; record the baseline commit',
48
+ synopsis: ['start "<title>" [--slug <name>] [--local] [--allow-dirty]'],
49
+ args: [{ name: '<title>', gloss: 'what you are building (quoted)' }],
50
+ flags: [
51
+ { name: '--slug', value: '<name>', gloss: 'use this slug verbatim instead of the derived YYYY-MM-DD-<title-slug>' },
52
+ { name: '--local', gloss: 'opt into the old fully-untracked flat layout' },
53
+ { name: '--allow-dirty', gloss: 'record the current HEAD as baseline despite a dirty tree' },
54
+ ],
55
+ notes: 'Refuses (exit 1) on an empty title, a slug collision, a non-git directory, a repo with no commits, an already-active session, or a dirty tree.',
56
+ },
57
+ {
58
+ name: 'status',
59
+ description: 'print the orientation dashboard, or NO ACTIVE SESSION',
60
+ synopsis: ['status [--build <slug>]'],
61
+ flags: [BUILD_FLAG],
62
+ notes: 'Read-only; always exits 0. With no cursor it lists the available builds.',
63
+ },
64
+ {
65
+ name: 'build',
66
+ description: 'write the seam from step n, or the next undone step (in-flight, not a lock)',
67
+ synopsis: ['build [<n>] [--build <slug>]'],
68
+ args: [{ name: '<n>', gloss: 'step to build (default: the next undone step)' }],
69
+ flags: [BUILD_FLAG],
70
+ notes: 'Refuses (exit 1) with no session, a non-numeric or < 1 step, or a seam it cannot parse.',
71
+ },
72
+ {
73
+ name: 'handoff',
74
+ description: 'print the standardized build hand-off block (pause or post-checkpoint); read-only',
75
+ synopsis: ['handoff [<n>] [--build <slug>]'],
76
+ args: [{ name: '<n>', gloss: 'step to report on (default: the in-flight step, else the last checkpointed one)' }],
77
+ flags: [BUILD_FLAG],
78
+ notes: 'Read-only; refuses (exit 1) only with no session.',
79
+ },
80
+ {
81
+ name: 'check',
82
+ description: 'run the heavy check and report; no state change',
83
+ synopsis: ['check [--bail] [--changed] [--all] [--only a,b] [--skip a,b] [--include a,b]'],
84
+ flags: [
85
+ { name: '--bail', gloss: 'stop at the first red slot' },
86
+ { name: '--changed', gloss: 'limit the run to changed files' },
87
+ { name: '--all', gloss: 'run every slot' },
88
+ { name: '--only', value: '<a,b>', gloss: 'run only these slots' },
89
+ { name: '--skip', value: '<a,b>', gloss: 'run every slot except these' },
90
+ { name: '--include', value: '<a,b>', gloss: 'add these slots to the run' },
91
+ ],
92
+ notes: 'Exits 0 green, 1 red, 2 when the gate itself broke. The flags narrow a checkride run and are warned-and-ignored on the `check` settings-override path.',
93
+ },
94
+ {
95
+ name: 'checkpoint',
96
+ description: 'gate on green, commit/record SHA, mark step done (executor-agnostic)',
97
+ synopsis: ['checkpoint [<n>] [-m <msg>] [--body]', 'checkpoint --plan [--body]'],
98
+ args: [{ name: '<n>', gloss: 'step to checkpoint (else the in-flight STEP, else the first undone step)' }],
99
+ flags: [
100
+ { name: '--plan', gloss: "commit only the build's artifact folder as chore(<scope>): plan" },
101
+ { name: '-m', value: '<msg>', gloss: 'override the CLI-owned Conventional subject' },
102
+ { name: '--body', gloss: 'read the commit body from stdin (heredoc)' },
103
+ ],
104
+ notes: 'Refuses (exit 1) with no session, no resolvable step, or a red check.',
105
+ },
106
+ {
107
+ name: 'revert',
108
+ description: 'git reset --hard to a checkpoint SHA',
109
+ synopsis: ['revert [--to <n>] [--build <slug>]'],
110
+ flags: [
111
+ { name: '--to', value: '<n>', gloss: "revert to step n's checkpoint (default: the last, else the baseline)" },
112
+ BUILD_FLAG,
113
+ ],
114
+ notes: 'Refuses (exit 1) with no session, an invalid --to, or a step with no recorded checkpoint.',
115
+ },
116
+ {
117
+ name: 'park',
118
+ description: 'append a raw line to the park list',
119
+ synopsis: ['park "<text>"'],
120
+ args: [{ name: '<text>', gloss: 'the note to park (free text; quote it)' }],
121
+ tolerateUnknownFlags: true,
122
+ notes: 'Refuses (exit 1) with no session, empty text, or no `## Park list` section.',
123
+ },
124
+ {
125
+ name: 'spike',
126
+ description: 'worktree experiment + report for a genuine fork',
127
+ synopsis: ['spike "<slug>" [opt…] [--build <slug>]', 'spike report "<slug>"', 'spike done'],
128
+ args: [
129
+ { name: '<slug>', gloss: 'name of the experiment' },
130
+ { name: '[opt…]', gloss: 'the options to fork (default: a and b)' },
131
+ ],
132
+ flags: [BUILD_FLAG],
133
+ notes: 'Refuses (exit 1) with no session, a step already in flight, an empty slug, or a worktree path that already exists.',
134
+ },
135
+ {
136
+ name: 'use',
137
+ description: 're-point the active-build cursor and resume that build',
138
+ synopsis: ['use <slug>'],
139
+ args: [{ name: '<slug>', gloss: 'the build folder under .plumbbob/builds/ to resume' }],
140
+ notes: 'Refuses (exit 1) with an empty slug or a slug with no build folder; warns (but allows) leaving a step in flight.',
141
+ },
142
+ {
143
+ name: 'finish',
144
+ description: 'close-out: report + final commit (no gate), clear the control state, close the session',
145
+ synopsis: ['finish [--body] [--build <slug>]'],
146
+ flags: [{ name: '--body', gloss: 'read the commit body from stdin (heredoc)' }, BUILD_FLAG],
147
+ notes: 'Refuses (exit 1) only with no session. There is no refuse-without-report gate.',
148
+ },
149
+ {
150
+ name: 'init',
151
+ description: 'link plumbbob into Claude Code as an in-place plugin (~/.claude/skills/plumbbob)',
152
+ synopsis: ['init [--uninstall] [--force]'],
153
+ flags: [
154
+ { name: '--uninstall', gloss: 'drop the link' },
155
+ { name: '--force', gloss: 'override the marketplace double-install guard' },
156
+ ],
157
+ notes: 'Idempotent and global-only; it never writes settings.json. Restart Claude Code (or /reload-plugins) to activate.',
158
+ },
159
+ {
160
+ name: 'doctor',
161
+ description: 'diagnose the plugin link + check gate; detect a legacy flat sidecar',
162
+ synopsis: ['doctor [--migrate]'],
163
+ flags: [{ name: '--migrate', gloss: 'move a legacy flat sidecar into builds/ (staged, not committed)' }],
164
+ notes: 'Exits 0 when everything passes, 1 when a check fails or an un-migrated legacy sidecar is present.',
165
+ },
166
+ {
167
+ name: 'recover',
168
+ description: 'reconcile the control plane: stale markers, a cursor pointing nowhere, spike leftovers',
169
+ synopsis: ['recover [--fix]'],
170
+ flags: [{ name: '--fix', gloss: 'repair the stale untracked control files it can repair on its own' }],
171
+ notes: 'Exits 0 when the control plane is consistent, 1 while any problem stands. --fix never touches a tracked artifact, git history, or the loop; spike worktrees are reported, never removed.',
172
+ },
173
+ {
174
+ name: 'agent',
175
+ description: 'list or spawn a user-authored agent',
176
+ synopsis: [
177
+ 'agent list',
178
+ 'agent run <name> [--step N] [--mode before|build|after] [--agent <path>] [--build <slug>]',
179
+ 'agent run --mode before|build|after [--step N]',
180
+ ],
181
+ args: [
182
+ { name: 'list', gloss: 'print every resolvable agent — name, origin tier, slots, description' },
183
+ { name: 'run', gloss: 'spawn an agent through the doorway; no code path advances the loop' },
184
+ ],
185
+ flags: [
186
+ { name: '--step', value: '<n>', gloss: 'pick the step (else the in-flight STEP)' },
187
+ { name: '--mode', value: '<slot>', gloss: 'the slot to run in: before, build, or after' },
188
+ { name: '--agent', value: '<path>', gloss: 'point at an explicit agent directory (top of the ladder)' },
189
+ BUILD_FLAG,
190
+ ],
191
+ notes: 'Exits 0 on a clean run, 1 on a failed one. See docs/agents.md for the author contract.',
192
+ },
193
+ {
194
+ name: 'turn',
195
+ description: 'UserPromptSubmit hook machinery — tick the turn ledger from stdin (not a user verb)',
196
+ synopsis: ['turn'],
197
+ tolerateUnknownFlags: true,
198
+ notes: 'Reads the hook payload from stdin and always exits 0 — a broken hook must never wedge a prompt.',
199
+ },
46
200
  ];
47
201
  /**
48
202
  * Render the verb table for `plumbbob help`.
49
203
  */
50
204
  export function formatHelp() {
51
205
  const width = Math.max(...VERBS.map((v) => v.name.length));
52
- const rows = VERBS.map((v) => ` ${v.name.padEnd(width)} ${v.summary}`);
53
- return ['plumbbob — attention-first build process', '', 'Usage: plumbbob <verb> [args]', '', 'Verbs:', ...rows, ''].join('\n');
206
+ const rows = VERBS.map((v) => ` ${v.name.padEnd(width)} ${v.description}`);
207
+ return [
208
+ 'plumbbob — attention-first build process',
209
+ '',
210
+ 'Usage: plumbbob <verb> [args]',
211
+ '',
212
+ 'Verbs:',
213
+ ...rows,
214
+ '',
215
+ "Run 'plumbbob <verb> --help' for a verb's arguments and flags.",
216
+ '',
217
+ ].join('\n');
218
+ }
219
+ /**
220
+ * Render the synopsis, argument, and flag block for `plumbbob <verb> --help`,
221
+ * or null when the name is not a verb.
222
+ *
223
+ * Arguments and flags share one aligned column so the reader scans a single
224
+ * list; a flag that takes a value prints as `--only <a,b>` so the spelling in
225
+ * the help is the spelling you type.
226
+ */
227
+ export function formatVerbHelp(name) {
228
+ const spec = verbSpec(name);
229
+ if (spec === null)
230
+ return null;
231
+ const entries = [
232
+ ...(spec.args ?? []).map((a) => ({ label: a.name, gloss: a.gloss })),
233
+ ...(spec.flags ?? []).map((f) => ({
234
+ label: f.value === undefined ? f.name : `${f.name} ${f.value}`,
235
+ gloss: f.gloss,
236
+ })),
237
+ ];
238
+ const width = Math.max(0, ...entries.map((e) => e.label.length));
239
+ const rows = entries.map((e) => ` ${e.label.padEnd(width)} ${e.gloss}`);
240
+ return [
241
+ ...spec.synopsis.map((s) => `plumbbob ${s}`),
242
+ '',
243
+ spec.description,
244
+ ...(rows.length > 0 ? ['', ...rows] : []),
245
+ ...(spec.notes === undefined ? [] : ['', spec.notes]),
246
+ '',
247
+ `See: docs/cli-reference.md#${spec.name}`,
248
+ '',
249
+ ].join('\n');
250
+ }
251
+ /**
252
+ * Look up a verb's spec by name, or null when the name is not a verb.
253
+ */
254
+ export function verbSpec(name) {
255
+ return VERBS.find((v) => v.name === name) ?? null;
256
+ }
257
+ /**
258
+ * Every verb name, in table order — the contract tests iterate this.
259
+ */
260
+ export function verbNames() {
261
+ return VERBS.map((v) => v.name);
262
+ }
263
+ /**
264
+ * Walk a verb's argv against its spec: did it ask for help, and what did it
265
+ * spell wrong?
266
+ *
267
+ * Value-consuming flags swallow their next token, so a value that happens to
268
+ * look like a flag (`checkpoint -m "--help"`) is read as the value it is. Bare
269
+ * `--help`/`-h` are recognized for every verb without being declared.
270
+ */
271
+ function scanFlags(spec, args) {
272
+ const known = new Map((spec.flags ?? []).map((f) => [f.name, f]));
273
+ const unknown = [];
274
+ let help = false;
275
+ for (let i = 0; i < args.length; i++) {
276
+ const arg = args[i];
277
+ const flag = known.get(arg);
278
+ if (flag !== undefined) {
279
+ if (flag.value !== undefined)
280
+ i++;
281
+ continue;
282
+ }
283
+ if (arg === '--help' || arg === '-h') {
284
+ help = true;
285
+ }
286
+ else if (arg.startsWith('-') && arg.length > 1) {
287
+ unknown.push(arg);
288
+ }
289
+ }
290
+ return { help, unknown };
54
291
  }
55
292
  /**
56
293
  * The package version, or 'unknown'.
@@ -104,6 +341,8 @@ async function dispatch(verb, cwd, rest) {
104
341
  return init(rest);
105
342
  case 'doctor':
106
343
  return doctor(cwd, rest);
344
+ case 'recover':
345
+ return recover(cwd, rest);
107
346
  case 'agent':
108
347
  return agent(cwd, rest);
109
348
  case 'turn':
@@ -116,23 +355,58 @@ async function dispatch(verb, cwd, rest) {
116
355
  /**
117
356
  * The CLI entrypoint minus the exit: parse argv, dispatch, return the exit code.
118
357
  *
119
- * `help` and `version` short-circuit before dispatch; any thrown error becomes
120
- * a one-line stderr report and exit 1, so the bin entry only ever exits with
121
- * the code returned here.
358
+ * `help` and `version` short-circuit before dispatch, as do per-verb `--help`
359
+ * and the unknown-flag refusal. Screening flags *here* rather than in each verb
360
+ * is what makes it safe by construction: a mutating verb can no longer be
361
+ * reached by an argv it does not understand, which is how `checkpoint --help`
362
+ * used to commit a real checkpoint. Any thrown error becomes a one-line stderr
363
+ * report and exit 1, so the bin entry only ever exits with the code returned
364
+ * here.
365
+ *
366
+ * `cwd` is the repo the verbs act on. It defaults to the process's — the only
367
+ * thing the bin entry ever wants — and is a parameter solely so a test can name
368
+ * a fixture. Screening argv does not help when the argv is *valid*: `checkpoint
369
+ * -m --help` reads `--help` as the subject, exactly as it should, and then
370
+ * commits. Whose repo it commits is decided here, and a default is not a choice
371
+ * a test should inherit silently.
122
372
  */
123
- export async function run(argv) {
373
+ export async function run(argv, cwd = process.cwd()) {
124
374
  const verb = argv[0] ?? 'help';
125
375
  const rest = argv.slice(1);
126
376
  if (verb === 'help' || verb === '--help' || verb === '-h') {
127
- process.stdout.write(`${formatHelp()}\n`);
377
+ // `plumbbob help <verb>` is the same page as `plumbbob <verb> --help` —
378
+ // both spellings get typed, so both work.
379
+ const topic = rest.find((a) => !a.startsWith('-'));
380
+ if (topic === undefined) {
381
+ process.stdout.write(`${formatHelp()}\n`);
382
+ return 0;
383
+ }
384
+ const help = formatVerbHelp(topic);
385
+ if (help === null) {
386
+ process.stderr.write(`plumbbob: unknown verb '${topic}'. Run 'plumbbob help' for the verb table.\n`);
387
+ return 1;
388
+ }
389
+ process.stdout.write(`${help}\n`);
128
390
  return 0;
129
391
  }
130
392
  if (verb === 'version' || verb === '--version' || verb === '-v') {
131
393
  process.stdout.write(`plumbbob ${readVersion()}\n`);
132
394
  return 0;
133
395
  }
396
+ const spec = verbSpec(verb);
397
+ if (spec !== null) {
398
+ const { help, unknown } = scanFlags(spec, rest);
399
+ if (help) {
400
+ process.stdout.write(`${formatVerbHelp(verb)}\n`);
401
+ return 0;
402
+ }
403
+ if (unknown.length > 0 && spec.tolerateUnknownFlags !== true) {
404
+ process.stderr.write(`plumbbob: ${verb}: unknown flag '${unknown[0]}'. Run 'plumbbob ${verb} --help' for the flags.\n`);
405
+ return 1;
406
+ }
407
+ }
134
408
  try {
135
- return await dispatch(verb, process.cwd(), rest);
409
+ return await dispatch(verb, cwd, rest);
136
410
  }
137
411
  catch (err) {
138
412
  const message = err instanceof Error ? err.message : String(err);
package/dist/lib/check.js CHANGED
@@ -15,6 +15,7 @@
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';
18
+ import { gateIsRunningFor, withGateMarker } from "./reentry.js";
18
19
  import { resolveString } from "./settings.js";
19
20
  /**
20
21
  * Probe whether the check gate would have anything to run in `root`.
@@ -42,17 +43,33 @@ export async function detectGate(root) {
42
43
  }
43
44
  }
44
45
  /**
45
- * Checkride's always-on repo checks: the adapters its doctor reports even for
46
- * an EMPTY directory — the built-in `links` slot, the pnpm-audit security
47
- * scan, the publint/attw package-shape probes, and the built-in
48
- * pack/smoke/snippets publish-bundle slots, which resolve on any package with
49
- * no tool config of their own. They exercise the repo's plumbing or its
50
- * shipped artifact, not its code — a gate made only of these green-lights
51
- * every checkpoint while the human believes their work is being checked.
52
- * Coupled to checkride's built-in set by construction; the check.test.ts and
46
+ * Checkride's always-on repo checks: the adapters that resolve with no tool
47
+ * config of their own — the built-in `links` slot, the pnpm-audit security
48
+ * scan, the publint/attw package-shape probes, and the pack/smoke/snippets
49
+ * publish-bundle slots. Several are named for their shape rather than because
50
+ * a bare directory reports them: `publint`/`attw` resolve off a devDependency,
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
+ * carries two adapters since checkride 0.10.2, so the dist-facing
54
+ * `snippets-dist` is named beside it (this list matches ADAPTER names, not
55
+ * slot names). Every one of them but `links` is an opt-in slot, so a default
56
+ * run selects none of them: they exercise the repo's plumbing or its shipped
57
+ * artifact, never its code. A gate made only of these green-lights every
58
+ * checkpoint while the human believes their work is being checked. Coupled to
59
+ * checkride's built-in set by construction; the check.test.ts and
53
60
  * doctor.test.ts probes pin the coupling.
54
61
  */
55
- const ALWAYS_ON_ADAPTERS = ['links', 'pnpm-audit', 'publint', 'attw', 'pack', 'smoke', 'snippets'];
62
+ const ALWAYS_ON_ADAPTERS = [
63
+ 'links',
64
+ 'pnpm-audit',
65
+ 'publint',
66
+ 'attw',
67
+ 'build',
68
+ 'pack',
69
+ 'smoke',
70
+ 'snippets',
71
+ 'snippets-dist',
72
+ ];
56
73
  /**
57
74
  * The detection rule, shared by `start`'s probe and doctor's callout: some
58
75
  * tool slot beyond the always-on family has an adapter — i.e. checkride can
@@ -72,11 +89,19 @@ export function gateDetectsTools(checks) {
72
89
  * ladder; a resolved command means the spawn path, no setting means checkride.
73
90
  */
74
91
  export async function runCheck(root, flags = {}, commandFlag) {
75
- const command = resolveString(root, 'check', '', commandFlag);
76
- if (command.length > 0) {
77
- return runCommand(root, command, flags);
92
+ if (gateIsRunningFor(root)) {
93
+ // This repo re-entering its OWN gate is never a slow gate — it is a
94
+ // recursion whose next generation forks wider than this one. Exit 2 (the
95
+ // harness broke), not 1: nothing has been learned about the code. A gate on
96
+ // a DIFFERENT root is ordinary nested work (the test suite gating its
97
+ // fixtures) and never reaches here.
98
+ process.stderr.write(`plumbbob: refusing to run the check gate for ${root} inside its own gate — this would recurse.\n` +
99
+ ' If a test reached here, it is driving a mutating verb against a real repo;\n' +
100
+ ' point it at a fixture instead.\n');
101
+ return 2;
78
102
  }
79
- return runCheckride(root, flags);
103
+ const command = resolveString(root, 'check', '', commandFlag);
104
+ return withGateMarker(root, () => (command.length > 0 ? runCommand(root, command, flags) : runCheckride(root, flags)));
80
105
  }
81
106
  /**
82
107
  * The spawn override: stream the configured command's own output to the
@@ -106,6 +131,14 @@ async function runCheckride(root, flags) {
106
131
  only: flags.only !== undefined ? [...flags.only] : null,
107
132
  skip: flags.skip !== undefined ? [...flags.skip] : null,
108
133
  include: flags.include !== undefined ? [...flags.include] : null,
134
+ // Checkride's own vacuous-green refusal (0.10.2): zero checks executed is
135
+ // an error, not a pass. It overlaps the refusal below and never reaches
136
+ // the human — our predicate is the broader one (it also catches a
137
+ // links-only run, where a check DID execute) and fires first, so
138
+ // plumbbob's message and its exit 1 are what a reader sees. Kept anyway:
139
+ // it is the contract checkride asks a gate to run under, so a future
140
+ // summary shape our predicate misreads still refuses.
141
+ strict: true,
109
142
  });
110
143
  summary = result.summary;
111
144
  exitCode = result.exitCode;
@@ -98,7 +98,7 @@ export function parseStepSeam(content, step) {
98
98
  return fail(`step ${step}'s seam has an empty token.`);
99
99
  }
100
100
  if (GLOB_CHARS.test(token)) {
101
- return fail(`step ${step}'s seam token \`${raw}\` is a glob; seams are exact paths or \`dir/\` grants (D23).`);
101
+ return fail(`step ${step}'s seam token \`${raw}\` is a glob; seams are exact paths or \`dir/\` grants — D23 (no-glob-seams).`);
102
102
  }
103
103
  if (token.startsWith('/')) {
104
104
  return fail(`step ${step}'s seam token \`${raw}\` is absolute; seams are repo-relative.`);
package/dist/lib/latch.js CHANGED
@@ -35,7 +35,7 @@ boundary; \`/plumbbob:build\` only starts the next step, it never lands this one
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
38
- message is the tick that lets it land on re-fire. (An explicit \`/plumbbob:build --auto\` or a
38
+ message is the tick that lets it land on the next run. (An explicit \`/plumbbob:build --auto\` or a
39
39
  step range in the human's own prompt is the only self-approval.)
40
40
  `;
41
41
  /**
@@ -43,13 +43,13 @@ step range in the human's own prompt is the only self-approval.)
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
  */
46
- const SETTINGS_AUTO_NOTE = `note: \`auto\` is set in settings but is no longer a grant (D67) — re-fire \`/plumbbob:build --auto\` to self-approve.
46
+ const SETTINGS_AUTO_NOTE = `note: \`auto\` is set in settings but is no longer a grant — D67 (auto-not-a-grant). Type \`/plumbbob:build --auto\` yourself to self-approve.
47
47
  `;
48
48
  /**
49
49
  * The refusal for a range grant whose ceiling the landing step exceeds.
50
50
  */
51
51
  function ceilingMessage(ceiling) {
52
- return `plumbbob: checkpoint refused — the range you granted ends at step ${ceiling} — pause here; re-fire to continue.\n`;
52
+ return `plumbbob: checkpoint refused — the range you granted ends at step ${ceiling} — pause here; run it again to continue.\n`;
53
53
  }
54
54
  /**
55
55
  * The five-row matrix, in order, first hit wins.