plumbbob 0.9.3 → 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 (278) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +15 -13
  3. package/dist/cli-core.js +19 -2
  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 +2 -2
  9. package/dist/verbs/doctor.js +4 -4
  10. package/dist/verbs/finish.js +1 -1
  11. package/dist/verbs/recover.js +297 -0
  12. package/dist/verbs/revert.js +2 -2
  13. package/dist/verbs/spike.js +8 -2
  14. package/hooks/post-edit.sh +4 -4
  15. package/hooks/pre-bash-commit.sh +3 -3
  16. package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
  17. package/node_modules/checkride/CHANGELOG.md +1003 -0
  18. package/node_modules/checkride/LICENSE +201 -21
  19. package/node_modules/checkride/README.md +171 -30
  20. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
  21. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
  22. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
  23. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
  24. package/node_modules/checkride/dist/adapters.d.ts +18 -6
  25. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  26. package/node_modules/checkride/dist/adapters.js +42 -23
  27. package/node_modules/checkride/dist/adapters.js.map +1 -1
  28. package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
  29. package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
  30. package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
  31. package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
  32. package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
  33. package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
  34. package/node_modules/checkride/dist/agent-setup/command.js +77 -0
  35. package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
  36. package/node_modules/checkride/dist/agent-setup/cursor.d.ts +61 -0
  37. package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
  38. package/node_modules/checkride/dist/agent-setup/cursor.js +189 -0
  39. package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
  40. package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
  41. package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
  42. package/node_modules/checkride/dist/agent-setup/files.js +80 -0
  43. package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
  44. package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
  45. package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
  46. package/node_modules/checkride/dist/agent-setup/harness.js +84 -0
  47. package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
  48. package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
  49. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  50. package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
  51. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  52. package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
  53. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  54. package/node_modules/checkride/dist/agent-setup/index.js +9 -4
  55. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  56. package/node_modules/checkride/dist/agent-setup/scripts.d.ts +58 -0
  57. package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
  58. package/node_modules/checkride/dist/agent-setup/scripts.js +299 -0
  59. package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
  60. package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
  61. package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
  62. package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
  63. package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
  64. package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
  65. package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
  66. package/node_modules/checkride/dist/artifacts/configured.js +45 -0
  67. package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
  68. package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
  69. package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
  70. package/node_modules/checkride/dist/artifacts/format.js +56 -0
  71. package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
  72. package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
  73. package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
  74. package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
  75. package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
  76. package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
  77. package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
  78. package/node_modules/checkride/dist/artifacts/index.js +19 -0
  79. package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
  80. package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
  81. package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
  82. package/node_modules/checkride/dist/artifacts/json.js +47 -0
  83. package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
  84. package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
  85. package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
  86. package/node_modules/checkride/dist/artifacts/raw.js +91 -0
  87. package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
  88. package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
  89. package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
  90. package/node_modules/checkride/dist/artifacts/summary.js +108 -0
  91. package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
  92. package/node_modules/checkride/dist/atomic.d.ts +16 -1
  93. package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
  94. package/node_modules/checkride/dist/atomic.js +25 -3
  95. package/node_modules/checkride/dist/atomic.js.map +1 -1
  96. package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
  97. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
  98. package/node_modules/checkride/dist/baseline/fallow.js +161 -58
  99. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
  100. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +7 -7
  101. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  102. package/node_modules/checkride/dist/baseline/fingerprint.js +16 -19
  103. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  104. package/node_modules/checkride/dist/baseline/index.d.ts +4 -4
  105. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  106. package/node_modules/checkride/dist/baseline/index.js +4 -4
  107. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  108. package/node_modules/checkride/dist/baseline/store.d.ts +12 -4
  109. package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
  110. package/node_modules/checkride/dist/baseline/store.js +16 -9
  111. package/node_modules/checkride/dist/baseline/store.js.map +1 -1
  112. package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
  113. package/node_modules/checkride/dist/baseline-command.js +7 -7
  114. package/node_modules/checkride/dist/baseline-command.js.map +1 -1
  115. package/node_modules/checkride/dist/cli.d.ts +17 -4
  116. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  117. package/node_modules/checkride/dist/cli.js +259 -18
  118. package/node_modules/checkride/dist/cli.js.map +1 -1
  119. package/node_modules/checkride/dist/config.d.ts +84 -8
  120. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  121. package/node_modules/checkride/dist/config.js +122 -18
  122. package/node_modules/checkride/dist/config.js.map +1 -1
  123. package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
  124. package/node_modules/checkride/dist/digest/digest.js +5 -5
  125. package/node_modules/checkride/dist/digest/index.d.ts +2 -2
  126. package/node_modules/checkride/dist/digest/index.js +2 -2
  127. package/node_modules/checkride/dist/doctor.d.ts +25 -6
  128. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  129. package/node_modules/checkride/dist/doctor.js +193 -27
  130. package/node_modules/checkride/dist/doctor.js.map +1 -1
  131. package/node_modules/checkride/dist/gate.d.ts +135 -0
  132. package/node_modules/checkride/dist/gate.d.ts.map +1 -0
  133. package/node_modules/checkride/dist/gate.js +490 -0
  134. package/node_modules/checkride/dist/gate.js.map +1 -0
  135. package/node_modules/checkride/dist/init.d.ts +72 -12
  136. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  137. package/node_modules/checkride/dist/init.js +324 -81
  138. package/node_modules/checkride/dist/init.js.map +1 -1
  139. package/node_modules/checkride/dist/json.d.ts +21 -0
  140. package/node_modules/checkride/dist/json.d.ts.map +1 -0
  141. package/node_modules/checkride/dist/json.js +21 -0
  142. package/node_modules/checkride/dist/json.js.map +1 -0
  143. package/node_modules/checkride/dist/links.d.ts +31 -5
  144. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  145. package/node_modules/checkride/dist/links.js +141 -32
  146. package/node_modules/checkride/dist/links.js.map +1 -1
  147. package/node_modules/checkride/dist/node-pin.d.ts +129 -0
  148. package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
  149. package/node_modules/checkride/dist/node-pin.js +211 -0
  150. package/node_modules/checkride/dist/node-pin.js.map +1 -0
  151. package/node_modules/checkride/dist/orchestrator.d.ts +40 -10
  152. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  153. package/node_modules/checkride/dist/orchestrator.js +196 -102
  154. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  155. package/node_modules/checkride/dist/pack.d.ts +8 -9
  156. package/node_modules/checkride/dist/pack.d.ts.map +1 -1
  157. package/node_modules/checkride/dist/pack.js +53 -17
  158. package/node_modules/checkride/dist/pack.js.map +1 -1
  159. package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
  160. package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
  161. package/node_modules/checkride/dist/pm/detect.js +6 -1
  162. package/node_modules/checkride/dist/pm/detect.js.map +1 -1
  163. package/node_modules/checkride/dist/pm/index.d.ts +6 -2
  164. package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
  165. package/node_modules/checkride/dist/pm/index.js +6 -2
  166. package/node_modules/checkride/dist/pm/index.js.map +1 -1
  167. package/node_modules/checkride/dist/pm/launch.d.ts +51 -0
  168. package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
  169. package/node_modules/checkride/dist/pm/launch.js +92 -0
  170. package/node_modules/checkride/dist/pm/launch.js.map +1 -0
  171. package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
  172. package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
  173. package/node_modules/checkride/dist/pm/tools.js +124 -0
  174. package/node_modules/checkride/dist/pm/tools.js.map +1 -0
  175. package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
  176. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  177. package/node_modules/checkride/dist/pm/translate.js +78 -18
  178. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  179. package/node_modules/checkride/dist/proc.d.ts +30 -0
  180. package/node_modules/checkride/dist/proc.d.ts.map +1 -0
  181. package/node_modules/checkride/dist/proc.js +42 -0
  182. package/node_modules/checkride/dist/proc.js.map +1 -0
  183. package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
  184. package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
  185. package/node_modules/checkride/dist/qa/cli.js +24 -0
  186. package/node_modules/checkride/dist/qa/cli.js.map +1 -0
  187. package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
  188. package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
  189. package/node_modules/checkride/dist/qa/dead.js +88 -0
  190. package/node_modules/checkride/dist/qa/dead.js.map +1 -0
  191. package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
  192. package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
  193. package/node_modules/checkride/dist/qa/dupes.js +62 -0
  194. package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
  195. package/node_modules/checkride/dist/qa/health.d.ts +85 -0
  196. package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
  197. package/node_modules/checkride/dist/qa/health.js +109 -0
  198. package/node_modules/checkride/dist/qa/health.js.map +1 -0
  199. package/node_modules/checkride/dist/qa/index.d.ts +23 -0
  200. package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
  201. package/node_modules/checkride/dist/qa/index.js +22 -0
  202. package/node_modules/checkride/dist/qa/index.js.map +1 -0
  203. package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
  204. package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
  205. package/node_modules/checkride/dist/qa/mutation.js +195 -0
  206. package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
  207. package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
  208. package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
  209. package/node_modules/checkride/dist/qa/qa.js +158 -0
  210. package/node_modules/checkride/dist/qa/qa.js.map +1 -0
  211. package/node_modules/checkride/dist/qa/read.d.ts +40 -0
  212. package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
  213. package/node_modules/checkride/dist/qa/read.js +47 -0
  214. package/node_modules/checkride/dist/qa/read.js.map +1 -0
  215. package/node_modules/checkride/dist/qa/render.d.ts +26 -0
  216. package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
  217. package/node_modules/checkride/dist/qa/render.js +310 -0
  218. package/node_modules/checkride/dist/qa/render.js.map +1 -0
  219. package/node_modules/checkride/dist/security.d.ts +45 -0
  220. package/node_modules/checkride/dist/security.d.ts.map +1 -0
  221. package/node_modules/checkride/dist/security.js +86 -0
  222. package/node_modules/checkride/dist/security.js.map +1 -0
  223. package/node_modules/checkride/dist/smoke.d.ts +5 -5
  224. package/node_modules/checkride/dist/smoke.js +5 -5
  225. package/node_modules/checkride/dist/snippets.d.ts +26 -29
  226. package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
  227. package/node_modules/checkride/dist/snippets.js +26 -30
  228. package/node_modules/checkride/dist/snippets.js.map +1 -1
  229. package/node_modules/checkride/dist/tool-json.d.ts +33 -0
  230. package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
  231. package/node_modules/checkride/dist/tool-json.js +58 -0
  232. package/node_modules/checkride/dist/tool-json.js.map +1 -0
  233. package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
  234. package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
  235. package/node_modules/checkride/dist/triage/cli.js +26 -0
  236. package/node_modules/checkride/dist/triage/cli.js.map +1 -0
  237. package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
  238. package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
  239. package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
  240. package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
  241. package/node_modules/checkride/dist/triage/env.d.ts +54 -0
  242. package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
  243. package/node_modules/checkride/dist/triage/env.js +81 -0
  244. package/node_modules/checkride/dist/triage/env.js.map +1 -0
  245. package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
  246. package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
  247. package/node_modules/checkride/dist/triage/gate.js +121 -0
  248. package/node_modules/checkride/dist/triage/gate.js.map +1 -0
  249. package/node_modules/checkride/dist/triage/index.d.ts +22 -0
  250. package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
  251. package/node_modules/checkride/dist/triage/index.js +20 -0
  252. package/node_modules/checkride/dist/triage/index.js.map +1 -0
  253. package/node_modules/checkride/dist/triage/render.d.ts +19 -0
  254. package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
  255. package/node_modules/checkride/dist/triage/render.js +419 -0
  256. package/node_modules/checkride/dist/triage/render.js.map +1 -0
  257. package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
  258. package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
  259. package/node_modules/checkride/dist/triage/triage.js +131 -0
  260. package/node_modules/checkride/dist/triage/triage.js.map +1 -0
  261. package/node_modules/checkride/package.json +17 -15
  262. package/node_modules/checkride/schema/checkride.config.schema.json +62 -5
  263. package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
  264. package/node_modules/checkride/skills/check/SKILL.md +257 -0
  265. package/node_modules/checkride/skills/qa/SKILL.md +231 -0
  266. package/node_modules/checkride/templates/shared/cspell.json +1 -0
  267. package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
  268. package/package.json +2 -2
  269. package/skills/build/SKILL.md +23 -19
  270. package/skills/finish/SKILL.md +3 -1
  271. package/skills/harvest/SKILL.md +2 -1
  272. package/skills/plan/SKILL.md +8 -7
  273. package/skills/recover/SKILL.md +28 -0
  274. package/skills/refine/SKILL.md +5 -4
  275. package/skills/spike/SKILL.md +1 -1
  276. package/skills/step/SKILL.md +4 -4
  277. package/skills/verify/SKILL.md +19 -14
  278. package/templates/intent.md +8 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plumbbob",
3
3
  "displayName": "PlumbBob",
4
- "version": "0.9.3",
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,6 +24,7 @@ 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
30
  /** Shared across every verb that resolves through the active-build cursor. */
@@ -162,6 +163,13 @@ const VERBS = [
162
163
  flags: [{ name: '--migrate', gloss: 'move a legacy flat sidecar into builds/ (staged, not committed)' }],
163
164
  notes: 'Exits 0 when everything passes, 1 when a check fails or an un-migrated legacy sidecar is present.',
164
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
+ },
165
173
  {
166
174
  name: 'agent',
167
175
  description: 'list or spawn a user-authored agent',
@@ -333,6 +341,8 @@ async function dispatch(verb, cwd, rest) {
333
341
  return init(rest);
334
342
  case 'doctor':
335
343
  return doctor(cwd, rest);
344
+ case 'recover':
345
+ return recover(cwd, rest);
336
346
  case 'agent':
337
347
  return agent(cwd, rest);
338
348
  case 'turn':
@@ -352,8 +362,15 @@ async function dispatch(verb, cwd, rest) {
352
362
  * used to commit a real checkpoint. Any thrown error becomes a one-line stderr
353
363
  * report and exit 1, so the bin entry only ever exits with the code returned
354
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.
355
372
  */
356
- export async function run(argv) {
373
+ export async function run(argv, cwd = process.cwd()) {
357
374
  const verb = argv[0] ?? 'help';
358
375
  const rest = argv.slice(1);
359
376
  if (verb === 'help' || verb === '--help' || verb === '-h') {
@@ -389,7 +406,7 @@ export async function run(argv) {
389
406
  }
390
407
  }
391
408
  try {
392
- return await dispatch(verb, process.cwd(), rest);
409
+ return await dispatch(verb, cwd, rest);
393
410
  }
394
411
  catch (err) {
395
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.
@@ -0,0 +1,70 @@
1
+ // The gate's re-entrancy marker: which repos have a check run in flight,
2
+ // recorded on the environment so every process in the tree can see it.
3
+ //
4
+ // The failure it exists to stop is not slowness, it is unbounded recursion. A
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
8
+ // pipeline, whose suite spawns a third. Each generation forks wider than the
9
+ // last, and nothing in the loop is aware there is a loop. The only thing that
10
+ // ended the first observed occurrence was checkride's 600s per-check cap
11
+ // killing one generation from the outside.
12
+ //
13
+ // It is scoped BY ROOT, and that is the whole design. A flat "a gate is
14
+ // running" flag is wrong in the one case that matters most: plumbbob's own
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
17
+ // refuses every one of them and turns a green suite red. Recursion is a repo
18
+ // re-entering ITS OWN gate; a different root is ordinary nested work and must
19
+ // pass through untouched.
20
+ //
21
+ // The marker rides the environment rather than a module-level variable because
22
+ // the recursion crosses process boundaries (spawned shells, checkride's tool
23
+ // subprocesses, vitest's worker forks), and a variable in one heap is invisible
24
+ // to all of them. Node builtins only.
25
+ import { resolve } from 'node:path';
26
+ /** The environment key. Namespaced so nothing else can set it by accident. */
27
+ const MARKER = 'PLUMBBOB_GATE_ROOTS';
28
+ /** The separator. A newline cannot appear in a path, unlike `:` on a weird one. */
29
+ const SEP = '\n';
30
+ /**
31
+ * The roots with a gate in flight, as absolute paths.
32
+ */
33
+ function activeRoots(env) {
34
+ const raw = env[MARKER] ?? '';
35
+ return raw.split(SEP).filter((r) => r.length > 0);
36
+ }
37
+ /**
38
+ * True when `root` already has a check gate running in this process tree.
39
+ *
40
+ * Compared as resolved absolute paths, so the same repo reached by a relative
41
+ * path, a symlink-free alias, or a trailing slash still counts as itself.
42
+ * `env` is injectable so the guard is testable without mutating the real one.
43
+ */
44
+ export function gateIsRunningFor(root, env = process.env) {
45
+ return activeRoots(env).includes(resolve(root));
46
+ }
47
+ /**
48
+ * Run `body` with `root` recorded as gated, restoring the prior value after.
49
+ *
50
+ * The marker is set on `process.env` itself, not on a copy handed to one child:
51
+ * the in-process checkride path spawns its own tool subprocesses, and they
52
+ * inherit the parent's environment at spawn time. `finally` restores rather
53
+ * than deletes, so nesting a second root inside the first cannot un-mark the
54
+ * first when the inner call returns.
55
+ */
56
+ export async function withGateMarker(root, body) {
57
+ const prior = process.env[MARKER];
58
+ process.env[MARKER] = [...activeRoots(process.env), resolve(root)].join(SEP);
59
+ try {
60
+ return await body();
61
+ }
62
+ finally {
63
+ if (prior === undefined) {
64
+ delete process.env[MARKER];
65
+ }
66
+ else {
67
+ process.env[MARKER] = prior;
68
+ }
69
+ }
70
+ }
@@ -103,12 +103,12 @@ async function run(cwd, args) {
103
103
  async function runOne(root, slug, step, spec) {
104
104
  const resolution = resolveAgent(root, spec.name, spec.flagPath !== undefined ? { flagPath: spec.flagPath } : {});
105
105
  if (!resolution.ok) {
106
- return degrade(spec.ambient, `plumbbob: ${resolution.error}`, `plumbbob: bound agent "${spec.name}" did not resolve — ${resolution.error} Skipping (D54 — the loop works without it).`);
106
+ return degrade(spec.ambient, `plumbbob: ${resolution.error}`, `plumbbob: bound agent "${spec.name}" did not resolve — ${resolution.error} Skipping — D54 (bindings-degrade-soft): the loop works without it.`);
107
107
  }
108
108
  const { manifest, dir } = resolution.agent;
109
109
  const resolved = resolveMode(spec.mode, manifest);
110
110
  if (!resolved.ok) {
111
- return degrade(spec.ambient, resolved.error, `plumbbob: bound agent "${spec.name}" — ${resolved.error.replace(/^plumbbob:\s*/, '')} Skipping (D54).`);
111
+ return degrade(spec.ambient, resolved.error, `plumbbob: bound agent "${spec.name}" — ${resolved.error.replace(/^plumbbob:\s*/, '')} Skipping — D54 (bindings-degrade-soft).`);
112
112
  }
113
113
  const mode = resolved.mode;
114
114
  let intent;
@@ -423,7 +423,7 @@ function agentReport(cwd) {
423
423
  const listings = listAgents(root);
424
424
  if (listings.length === 0)
425
425
  return { lines: [], failed: 0 };
426
- const lines = ['', 'plumbbob doctor — agents (D48)'];
426
+ const lines = ['', 'plumbbob doctor — agents — D48 (doctor-validates-agents)'];
427
427
  let failed = 0;
428
428
  for (const listing of listings) {
429
429
  const problem = agentProblem(listing, root);
@@ -451,7 +451,7 @@ async function gateReport(cwd, env) {
451
451
  const root = findRepoRoot(cwd);
452
452
  if (root === null)
453
453
  return { lines: [], failed: 0 };
454
- const lines = ['', 'plumbbob doctor — check gate (D32)'];
454
+ const lines = ['', 'plumbbob doctor — check gate — D32 (checkride-gate)'];
455
455
  const command = resolveString(root, 'check', '');
456
456
  if (command.length > 0) {
457
457
  lines.push(` ✓ gate: '${command}' — the "check" setting overrides checkride`);
@@ -523,13 +523,13 @@ function latchReport(cwd) {
523
523
  const line = turn === null
524
524
  ? ' ○ latch: dormant — guidance only (no turn ledger yet; it ticks on your first prompt when the UserPromptSubmit hook is wired — re-run after one to confirm)'
525
525
  : ` ✓ latch: live (turn ${turn})`;
526
- const lines = ['', 'plumbbob doctor — approval latch (D64)', line];
526
+ const lines = ['', 'plumbbob doctor — approval latch — D64 (approval-latch)', line];
527
527
  // The latch ignores a settings `auto`: a model can write that file, and a grant
528
528
  // the model can forge is no grant — self-approval comes only from the human's
529
529
  // typed `/build --auto`. Surface a set value so a human relying on it isn't
530
530
  // silently changed — informational, never a problem.
531
531
  if (resolveBoolean(root, 'auto', false)) {
532
- lines.push(' ○ auto: set in settings but not a grant since D67 — self-approve per run with `/plumbbob:build --auto`');
532
+ lines.push(' ○ auto: set in settings but not a grant since D67 (auto-not-a-grant) — self-approve per run with `/plumbbob:build --auto`');
533
533
  }
534
534
  return { lines, failed: 0 };
535
535
  }
@@ -35,7 +35,7 @@ export function finish(cwd, args = []) {
35
35
  }
36
36
  else {
37
37
  process.stderr.write('plumbbob: note — no report.md found; finishing without one ' +
38
- '(/plumbbob:finish normally writes the report first). No gate (D9).\n');
38
+ '(/plumbbob:finish normally writes the report first). No gate — D9 (finish-no-gate).\n');
39
39
  }
40
40
  // The final commit: stage the report just written plus the build folder's tail —
41
41
  // a step's commit sweeps its own bookkeeping along with the work, so the last