plumbbob 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +92 -89
  3. package/dist/cli-core.js +25 -8
  4. package/dist/cli.js +1 -1
  5. package/dist/lib/agents.js +26 -26
  6. package/dist/lib/buildlog.js +11 -11
  7. package/dist/lib/buildlogsync.js +8 -8
  8. package/dist/lib/check.js +55 -22
  9. package/dist/lib/commitbody.js +72 -0
  10. package/dist/lib/commitmsg.js +7 -7
  11. package/dist/lib/git.js +44 -15
  12. package/dist/lib/intent.js +21 -20
  13. package/dist/lib/latch.js +19 -19
  14. package/dist/lib/orient.js +36 -17
  15. package/dist/lib/reentry.js +70 -0
  16. package/dist/lib/settings.js +16 -16
  17. package/dist/lib/sidecar.js +54 -54
  18. package/dist/lib/templates.js +1 -1
  19. package/dist/verbs/agent.js +27 -27
  20. package/dist/verbs/build.js +9 -9
  21. package/dist/verbs/check.js +6 -6
  22. package/dist/verbs/checkpoint.js +48 -47
  23. package/dist/verbs/doctor.js +31 -30
  24. package/dist/verbs/finish.js +28 -41
  25. package/dist/verbs/handoff.js +9 -8
  26. package/dist/verbs/init.js +9 -9
  27. package/dist/verbs/park.js +3 -3
  28. package/dist/verbs/recover.js +297 -0
  29. package/dist/verbs/revert.js +13 -13
  30. package/dist/verbs/spike.js +25 -19
  31. package/dist/verbs/start.js +49 -19
  32. package/dist/verbs/status.js +8 -8
  33. package/dist/verbs/turn.js +16 -16
  34. package/dist/verbs/use.js +4 -4
  35. package/hooks/post-edit.sh +4 -4
  36. package/hooks/pre-bash-commit.sh +3 -3
  37. package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
  38. package/node_modules/checkride/CHANGELOG.md +1404 -0
  39. package/node_modules/checkride/LICENSE +201 -21
  40. package/node_modules/checkride/README.md +197 -38
  41. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
  42. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
  43. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
  44. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
  45. package/node_modules/checkride/dist/adapters.d.ts +30 -10
  46. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  47. package/node_modules/checkride/dist/adapters.js +62 -23
  48. package/node_modules/checkride/dist/adapters.js.map +1 -1
  49. package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
  50. package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
  51. package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
  52. package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
  53. package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
  54. package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
  55. package/node_modules/checkride/dist/agent-setup/command.js +77 -0
  56. package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
  57. package/node_modules/checkride/dist/agent-setup/cursor.d.ts +69 -0
  58. package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
  59. package/node_modules/checkride/dist/agent-setup/cursor.js +247 -0
  60. package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
  61. package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
  62. package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
  63. package/node_modules/checkride/dist/agent-setup/files.js +80 -0
  64. package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
  65. package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
  66. package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
  67. package/node_modules/checkride/dist/agent-setup/harness.js +93 -0
  68. package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
  69. package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
  70. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  71. package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
  72. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  73. package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
  74. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  75. package/node_modules/checkride/dist/agent-setup/index.js +9 -4
  76. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  77. package/node_modules/checkride/dist/agent-setup/scripts.d.ts +81 -0
  78. package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
  79. package/node_modules/checkride/dist/agent-setup/scripts.js +604 -0
  80. package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
  81. package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
  82. package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
  83. package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
  84. package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
  85. package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
  86. package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
  87. package/node_modules/checkride/dist/artifacts/configured.js +45 -0
  88. package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
  89. package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
  90. package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
  91. package/node_modules/checkride/dist/artifacts/format.js +56 -0
  92. package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
  93. package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
  94. package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
  95. package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
  96. package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
  97. package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
  98. package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
  99. package/node_modules/checkride/dist/artifacts/index.js +19 -0
  100. package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
  101. package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
  102. package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
  103. package/node_modules/checkride/dist/artifacts/json.js +47 -0
  104. package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
  105. package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
  106. package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
  107. package/node_modules/checkride/dist/artifacts/raw.js +91 -0
  108. package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
  109. package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
  110. package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
  111. package/node_modules/checkride/dist/artifacts/summary.js +108 -0
  112. package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
  113. package/node_modules/checkride/dist/atomic.d.ts +16 -1
  114. package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
  115. package/node_modules/checkride/dist/atomic.js +25 -3
  116. package/node_modules/checkride/dist/atomic.js.map +1 -1
  117. package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
  118. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
  119. package/node_modules/checkride/dist/baseline/fallow.js +163 -60
  120. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
  121. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +20 -11
  122. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  123. package/node_modules/checkride/dist/baseline/fingerprint.js +73 -23
  124. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  125. package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
  126. package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
  127. package/node_modules/checkride/dist/baseline/history.js +232 -0
  128. package/node_modules/checkride/dist/baseline/history.js.map +1 -0
  129. package/node_modules/checkride/dist/baseline/index.d.ts +7 -5
  130. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  131. package/node_modules/checkride/dist/baseline/index.js +5 -4
  132. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  133. package/node_modules/checkride/dist/baseline/store.d.ts +41 -10
  134. package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
  135. package/node_modules/checkride/dist/baseline/store.js +65 -24
  136. package/node_modules/checkride/dist/baseline/store.js.map +1 -1
  137. package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
  138. package/node_modules/checkride/dist/baseline-command.js +7 -7
  139. package/node_modules/checkride/dist/baseline-command.js.map +1 -1
  140. package/node_modules/checkride/dist/cli.d.ts +21 -4
  141. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  142. package/node_modules/checkride/dist/cli.js +350 -36
  143. package/node_modules/checkride/dist/cli.js.map +1 -1
  144. package/node_modules/checkride/dist/config.d.ts +114 -9
  145. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  146. package/node_modules/checkride/dist/config.js +142 -23
  147. package/node_modules/checkride/dist/config.js.map +1 -1
  148. package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
  149. package/node_modules/checkride/dist/digest/digest.js +6 -6
  150. package/node_modules/checkride/dist/digest/index.d.ts +2 -2
  151. package/node_modules/checkride/dist/digest/index.js +2 -2
  152. package/node_modules/checkride/dist/doctor.d.ts +27 -6
  153. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  154. package/node_modules/checkride/dist/doctor.js +231 -27
  155. package/node_modules/checkride/dist/doctor.js.map +1 -1
  156. package/node_modules/checkride/dist/gate.d.ts +180 -0
  157. package/node_modules/checkride/dist/gate.d.ts.map +1 -0
  158. package/node_modules/checkride/dist/gate.js +617 -0
  159. package/node_modules/checkride/dist/gate.js.map +1 -0
  160. package/node_modules/checkride/dist/init.d.ts +79 -12
  161. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  162. package/node_modules/checkride/dist/init.js +385 -81
  163. package/node_modules/checkride/dist/init.js.map +1 -1
  164. package/node_modules/checkride/dist/json.d.ts +21 -0
  165. package/node_modules/checkride/dist/json.d.ts.map +1 -0
  166. package/node_modules/checkride/dist/json.js +21 -0
  167. package/node_modules/checkride/dist/json.js.map +1 -0
  168. package/node_modules/checkride/dist/links.d.ts +31 -5
  169. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  170. package/node_modules/checkride/dist/links.js +141 -32
  171. package/node_modules/checkride/dist/links.js.map +1 -1
  172. package/node_modules/checkride/dist/node-pin.d.ts +129 -0
  173. package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
  174. package/node_modules/checkride/dist/node-pin.js +211 -0
  175. package/node_modules/checkride/dist/node-pin.js.map +1 -0
  176. package/node_modules/checkride/dist/orchestrator.d.ts +53 -10
  177. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  178. package/node_modules/checkride/dist/orchestrator.js +312 -113
  179. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  180. package/node_modules/checkride/dist/pack.d.ts +8 -9
  181. package/node_modules/checkride/dist/pack.d.ts.map +1 -1
  182. package/node_modules/checkride/dist/pack.js +53 -17
  183. package/node_modules/checkride/dist/pack.js.map +1 -1
  184. package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
  185. package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
  186. package/node_modules/checkride/dist/pm/detect.js +6 -1
  187. package/node_modules/checkride/dist/pm/detect.js.map +1 -1
  188. package/node_modules/checkride/dist/pm/index.d.ts +7 -2
  189. package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
  190. package/node_modules/checkride/dist/pm/index.js +6 -2
  191. package/node_modules/checkride/dist/pm/index.js.map +1 -1
  192. package/node_modules/checkride/dist/pm/launch.d.ts +72 -0
  193. package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
  194. package/node_modules/checkride/dist/pm/launch.js +108 -0
  195. package/node_modules/checkride/dist/pm/launch.js.map +1 -0
  196. package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
  197. package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
  198. package/node_modules/checkride/dist/pm/tools.js +124 -0
  199. package/node_modules/checkride/dist/pm/tools.js.map +1 -0
  200. package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
  201. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  202. package/node_modules/checkride/dist/pm/translate.js +78 -18
  203. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  204. package/node_modules/checkride/dist/proc.d.ts +30 -0
  205. package/node_modules/checkride/dist/proc.d.ts.map +1 -0
  206. package/node_modules/checkride/dist/proc.js +42 -0
  207. package/node_modules/checkride/dist/proc.js.map +1 -0
  208. package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
  209. package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
  210. package/node_modules/checkride/dist/qa/cli.js +24 -0
  211. package/node_modules/checkride/dist/qa/cli.js.map +1 -0
  212. package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
  213. package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
  214. package/node_modules/checkride/dist/qa/dead.js +88 -0
  215. package/node_modules/checkride/dist/qa/dead.js.map +1 -0
  216. package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
  217. package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
  218. package/node_modules/checkride/dist/qa/dupes.js +62 -0
  219. package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
  220. package/node_modules/checkride/dist/qa/health.d.ts +85 -0
  221. package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
  222. package/node_modules/checkride/dist/qa/health.js +109 -0
  223. package/node_modules/checkride/dist/qa/health.js.map +1 -0
  224. package/node_modules/checkride/dist/qa/index.d.ts +23 -0
  225. package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
  226. package/node_modules/checkride/dist/qa/index.js +22 -0
  227. package/node_modules/checkride/dist/qa/index.js.map +1 -0
  228. package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
  229. package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
  230. package/node_modules/checkride/dist/qa/mutation.js +195 -0
  231. package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
  232. package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
  233. package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
  234. package/node_modules/checkride/dist/qa/qa.js +158 -0
  235. package/node_modules/checkride/dist/qa/qa.js.map +1 -0
  236. package/node_modules/checkride/dist/qa/read.d.ts +40 -0
  237. package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
  238. package/node_modules/checkride/dist/qa/read.js +47 -0
  239. package/node_modules/checkride/dist/qa/read.js.map +1 -0
  240. package/node_modules/checkride/dist/qa/render.d.ts +26 -0
  241. package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
  242. package/node_modules/checkride/dist/qa/render.js +310 -0
  243. package/node_modules/checkride/dist/qa/render.js.map +1 -0
  244. package/node_modules/checkride/dist/recover.d.ts +48 -0
  245. package/node_modules/checkride/dist/recover.d.ts.map +1 -0
  246. package/node_modules/checkride/dist/recover.js +220 -0
  247. package/node_modules/checkride/dist/recover.js.map +1 -0
  248. package/node_modules/checkride/dist/security.d.ts +45 -0
  249. package/node_modules/checkride/dist/security.d.ts.map +1 -0
  250. package/node_modules/checkride/dist/security.js +86 -0
  251. package/node_modules/checkride/dist/security.js.map +1 -0
  252. package/node_modules/checkride/dist/smoke.d.ts +6 -6
  253. package/node_modules/checkride/dist/smoke.js +6 -6
  254. package/node_modules/checkride/dist/snippets.d.ts +27 -30
  255. package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
  256. package/node_modules/checkride/dist/snippets.js +26 -30
  257. package/node_modules/checkride/dist/snippets.js.map +1 -1
  258. package/node_modules/checkride/dist/tool-json.d.ts +33 -0
  259. package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
  260. package/node_modules/checkride/dist/tool-json.js +58 -0
  261. package/node_modules/checkride/dist/tool-json.js.map +1 -0
  262. package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
  263. package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
  264. package/node_modules/checkride/dist/triage/cli.js +26 -0
  265. package/node_modules/checkride/dist/triage/cli.js.map +1 -0
  266. package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
  267. package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
  268. package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
  269. package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
  270. package/node_modules/checkride/dist/triage/env.d.ts +54 -0
  271. package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
  272. package/node_modules/checkride/dist/triage/env.js +81 -0
  273. package/node_modules/checkride/dist/triage/env.js.map +1 -0
  274. package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
  275. package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
  276. package/node_modules/checkride/dist/triage/gate.js +121 -0
  277. package/node_modules/checkride/dist/triage/gate.js.map +1 -0
  278. package/node_modules/checkride/dist/triage/index.d.ts +22 -0
  279. package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
  280. package/node_modules/checkride/dist/triage/index.js +20 -0
  281. package/node_modules/checkride/dist/triage/index.js.map +1 -0
  282. package/node_modules/checkride/dist/triage/render.d.ts +19 -0
  283. package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
  284. package/node_modules/checkride/dist/triage/render.js +419 -0
  285. package/node_modules/checkride/dist/triage/render.js.map +1 -0
  286. package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
  287. package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
  288. package/node_modules/checkride/dist/triage/triage.js +131 -0
  289. package/node_modules/checkride/dist/triage/triage.js.map +1 -0
  290. package/node_modules/checkride/package.json +18 -15
  291. package/node_modules/checkride/schema/checkride.config.schema.json +70 -5
  292. package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
  293. package/node_modules/checkride/skills/check/SKILL.md +265 -0
  294. package/node_modules/checkride/skills/qa/SKILL.md +231 -0
  295. package/node_modules/checkride/templates/shared/cspell.json +1 -0
  296. package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
  297. package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
  298. package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
  299. package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
  300. package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
  301. package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
  302. package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
  303. package/node_modules/checkride/templates/shared/vale.ini +34 -0
  304. package/package.json +10 -9
  305. package/skills/build/SKILL.md +85 -76
  306. package/skills/doctor/SKILL.md +11 -11
  307. package/skills/finish/SKILL.md +21 -18
  308. package/skills/harvest/SKILL.md +13 -11
  309. package/skills/park/SKILL.md +10 -10
  310. package/skills/plan/SKILL.md +69 -59
  311. package/skills/recover/SKILL.md +28 -0
  312. package/skills/refine/SKILL.md +21 -18
  313. package/skills/revert/SKILL.md +6 -6
  314. package/skills/spike/SKILL.md +9 -9
  315. package/skills/status/SKILL.md +5 -5
  316. package/skills/step/SKILL.md +20 -15
  317. package/skills/verify/SKILL.md +56 -46
  318. package/templates/build-log.md +7 -7
  319. package/templates/intent.md +52 -41
  320. package/templates/spike-report.md +10 -10
@@ -4,6 +4,1388 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.12.2] - 2026-08-12
8
+
9
+ ### Added
10
+
11
+ - **A `prose-voice` example** under `examples/`: the `prose` slot end to end —
12
+ a legacy doc grandfathered at eight findings, violations that trip
13
+ `Repo.Drift` in fresh markdown and `Repo.ThereIs`/`Repo.Latin` inside a
14
+ TypeScript doc comment, a ratchet scenario pruning the baseline, and the
15
+ `docs/voice` exemplars the AGENTS.md stanza points writing sessions at. The
16
+ e2e harness grows a `proseRules` violation field so an example asserts
17
+ *which* vale rule fired, not merely that `prose` went red.
18
+
19
+ ### Fixed
20
+
21
+ - The `prose` exemplars pre-flight now says `is a file, not a directory` when
22
+ a file sits at the configured path, instead of the false `does not exist`.
23
+ - Re-resolved `nanoid` to 3.3.18 for GHSA-2v37-7h3g-55p8 (high severity;
24
+ dev-only, via vitest → vite → postcss). No override entry — postcss's own
25
+ range already permits the patched line.
26
+
27
+ ### Internal
28
+
29
+ - New tests pin the default runner's exemplars pre-flight wiring, and the rule
30
+ that a run whose vale output is unreadable masks nothing and prunes nothing
31
+ while the slots the run did observe still ratchet.
32
+
33
+ ## [0.12.1] - 2026-08-07
34
+
35
+ ### Added
36
+
37
+ - **A `prose` slot for writing-style linting**, powered by `vale`. Opt-in like
38
+ `format`, it checks markdown and TypeScript doc comments (via a JS format
39
+ mapping, so code and string literals stay untouched) for doubled words,
40
+ Latin abbreviations (`e.g.`/`i.e.`/`etc.`), hyphenated `-ly` adverbs,
41
+ sentence-initial "There is", weasel words (off by default — the one
42
+ subjective rule), and the lexical fingerprints of AI-generated prose:
43
+ overused vocabulary like `delve`/`tapestry`/`meticulous`, stock frames like
44
+ "it's not just X — it's Y", and the minted corporate dialect (`the ask`,
45
+ `learnings`, `leverage`/`utilize` as verbs). `init --add prose` scaffolds a
46
+ hermetic, repo-owned vale config and six house-style rule files under
47
+ `.vale/styles/Repo/` — no upstream package, no `vale sync` needed on the
48
+ default path. Findings ratchet through the shared baseline the same way
49
+ `lint`/`struct`/`spell` already do. `spell` keeps sole ownership of
50
+ spelling; the scaffold disables vale's own dictionary and vocabulary
51
+ checks to avoid a second, conflicting wordlist.
52
+ - **An `exemplars` config key on the `prose` slot**, for anchoring voice
53
+ rather than just policing vocabulary. Point it at a directory of
54
+ hand-written prose and the generated AGENTS.md stanza tells writing
55
+ sessions to read and imitate it before touching markdown or doc comments —
56
+ and never to edit or add to it, since a generated "improvement" would
57
+ replace the human original with a copy of the model's own register. The
58
+ check itself fails if the named directory is missing or empty, so a stale
59
+ config can't stay green; checkride never scores prose against the
60
+ exemplars, since judging voice deliberately stays a human call.
61
+
62
+ ### Fixed
63
+
64
+ - The README's slot table, its opt-in-slots sentence, the cheat sheet's
65
+ pipeline table, and the config schema's `checks` description had all
66
+ drifted out of sync with the slot registry over several releases (missing
67
+ the publish-ready bundle, missing `dupes`/`health`, undercounting opt-in
68
+ slots). Brought current and pinned by a new test that fails whenever a
69
+ slot is added without updating all four surfaces.
70
+
71
+ ### Internal
72
+
73
+ - checkride now dogfoods its own `prose` slot: 112 initial findings fixed by
74
+ hand (11 subject-first rewrites, 28 Latin substitutions, 21 `-ly`-hyphen
75
+ drops), landing at zero findings with no baseline grandfathering needed.
76
+
77
+ ## [0.12.0] - 2026-08-07
78
+
79
+ ### Added
80
+
81
+ - **`checkride recover` — restore a clobbered baseline from git history.** The
82
+ motivating incident: checkride landed in a large monorepo, other open PRs
83
+ merged the trunk back, and `checkride.baseline.json` came out of the merges
84
+ with many entries silently deleted (an agent resolving a conflict by removing
85
+ the "problem" lines, most likely). Missing entries fail closed, so the team
86
+ hit a wall of red with no tooling to diagnose or undo it — the remedy was
87
+ manual git archaeology, and the tempting wrong move, re-running
88
+ `checkride baseline`, would have grandfathered every genuinely-new finding
89
+ introduced since the damage.
90
+
91
+ `checkride recover` does the dig instead: it walks the baseline file's recent
92
+ commits (`--depth`, default 25), dedupes them into at most five distinct
93
+ snapshots plus one synthesized union, and lists them with `+restored/−absent`
94
+ deltas against the current file. `--pick <n|sha|union>` applies one by writing
95
+ the file directly — never `git checkout` — so the restore is an ordinary
96
+ working-tree edit to review and commit. The default write is the union of the
97
+ candidate and the current file: nothing is removed, and a stale resurrected
98
+ key is pruned by the ratchet on the next full green run. `--exact` writes the
99
+ snapshot verbatim (refused while the file has uncommitted changes), `--dry-run`
100
+ previews the per-slot delta, `--json` emits the machine shape. Shallow clones
101
+ list with a truncation note; a repo with no committed baseline history says so
102
+ at exit 0.
103
+
104
+ - **A mangled or plundered baseline now explains itself.** A committed
105
+ `checkride.baseline.json` that no longer parses (a botched merge, usually)
106
+ used to be indistinguishable from having no baseline: every grandfathered
107
+ finding reported red with no hint why. A run now prints one stderr line under
108
+ the summary naming the file as present-but-unparseable and pointing at
109
+ `checkride recover`; `checkride doctor` grows an advisory workspace row with
110
+ the same diagnosis (and the key count when the file is healthy). And when the
111
+ file parses but a merge silently dropped entries, a red run probes git history
112
+ (best-effort, bounded at 750 ms, silent on any failure) and prints one hint —
113
+ `N of M new finding(s) were grandfathered until <sha>` — naming
114
+ `checkride recover` as the remedy, so a wall of red arrives with its diagnosis
115
+ attached.
116
+
117
+ ### Changed
118
+
119
+ - **The baseline file is canonical on disk.** `writeBaseline` now sorts the
120
+ top-level slots (keys within a slot were already sorted), so two branches that
121
+ grandfather the same debt produce byte-identical files instead of
122
+ insertion-order diff churn — the README's "the file is canonical" claim is now
123
+ true on disk, not just in comparisons.
124
+
125
+ - **`DoctorEnv` gained a required `readText` probe** (it feeds doctor's new
126
+ baseline row). The type sits outside the promised programmatic surface, but a
127
+ TypeScript consumer injecting a custom `env` into `runDoctor` adds one field
128
+ on upgrade.
129
+
130
+ ### Contract
131
+
132
+ - New command `checkride recover` with `--pick <n|sha|union>`, `--exact`,
133
+ `--dry-run`, `--depth <n>`, and `--json` (docs/contract.md §CLI). List mode is
134
+ a reader: Markdown on stdout, exit 0. Apply mode writes
135
+ `checkride.baseline.json` atomically and never mutates git state. Exit codes
136
+ are 0 and 2 only — never 1. Additive; every existing command and flag is
137
+ unchanged.
138
+
139
+ ### Internal
140
+
141
+ - The website caught up with the product and can no longer drift: a new
142
+ `agents.html` covers the bundled plugin, the Claude Code / Cursor
143
+ asymmetries, and the "could not run" verdict; the command reference gained
144
+ `recover`, `hooks`, `gate`, `triage`, and `qa`; and `test/site.test.ts` +
145
+ cspell now hold `site/` under `pnpm check` (inline scripts must parse, the
146
+ command and slot tables must track `CLI_COMMANDS` and `SLOTS`, internal links
147
+ must resolve). Every baseline-adjacent doc (README, getting-started, CI,
148
+ cheatsheet, plugin skill) now routes a clobbered baseline to `recover`.
149
+
150
+ ## [0.11.2] - 2026-08-04
151
+
152
+ ### Changed
153
+
154
+ - **A Cursor stand-down now reaches the user, exactly once.** The promise since
155
+ 0.11.0 was that a stand-down is never silent: Claude Code gets a
156
+ `systemMessage`, Cursor gets stderr. Those were never equivalent, and the
157
+ Cursor half does not hold — Cursor documents no channel that shows hook stderr
158
+ to a user, mentioning only a Hooks output panel under **Customize** that
159
+ someone has to go and open. So on the one verdict whose entire purpose is to
160
+ name a fix only a human can apply (`pnpm install`, an `engines` pin), the human
161
+ saw nothing.
162
+
163
+ `followup_message` is the field that reaches the chat, and the reason it was
164
+ avoided is real: it *submits a new turn*, so a stand-down spending it every
165
+ turn rebuilds the unbounded loop 0.11.0 removed. It is therefore rationed to
166
+ one. The generated hook script already reads `loop_count` to bound `block`, and
167
+ that same guard now bounds `stand_down` — one nudge naming the fix, silence
168
+ after. The guard errs toward quiet: Cursor's `loop_count` counts every
169
+ auto-follow-up in a conversation rather than the consecutive ones, so an
170
+ over-count costs a silent stand-down and never a loop.
171
+
172
+ - **`CHECKRIDE_GATE_RETRY`, a new environment variable the generated gate script
173
+ sets.** checkride's own stand-down — a refusal it detects from inside, such as
174
+ an `engines` pin the hook's Node fails — needs the same one-shot bound and
175
+ cannot compute it, because the stop payload reaches the hook script and never
176
+ the child process. The script now exports `0` when the one message is unspent
177
+ and `1` when it is not, and `checkride gate --harness cursor` emits a followup
178
+ only on an explicit `0`.
179
+
180
+ **Absence is not `0`.** A hook script generated before this release exports
181
+ neither value and keeps the older stderr-only behavior. That asymmetry is the
182
+ point: it is what stops a refreshed checkride from looping under an
183
+ unrefreshed script, given the `loop_limit: null` the gate's own hook entry
184
+ asks for. Run `checkride agent-setup` to pick up the new script.
185
+
186
+ The variable is an internal contract between the two halves checkride
187
+ generates, not a knob — setting it by hand only suppresses or unlocks one
188
+ message.
189
+
190
+ ## [0.11.1] - 2026-08-04
191
+
192
+ ### Fixed
193
+
194
+ - **The Cursor stop gate no longer writes package-manager noise to the channel
195
+ Cursor parses as protocol.** The two generated gate scripts were asymmetric,
196
+ and accidentally so: the Claude Code script captured the run, so a failed
197
+ launch wrote into a variable it could then decline to print, while the Cursor
198
+ script ran the launcher uncaptured in order to let checkride's own JSON pass
199
+ straight through. A *failed* launch writes to that same stdout, and nothing
200
+ filtered it — so in a repo whose dependencies were never installed, two lines
201
+ of pnpm resolution error landed on stdout while the correct stand-down
202
+ explanation went to stderr.
203
+
204
+ The turn still ended as intended, because Cursor does not parse that text as a
205
+ hook body. The reason to fix it anyway is what happens if it ever does: a
206
+ bare-text followup would submit a new turn on every stand-down, rebuilding the
207
+ unbounded loop 0.11.0 removed, and doing it on the one cause an agent can do
208
+ nothing about. It also muddies the gate's own diagnostic — a maintainer
209
+ reading a Cursor transcript sees a dependency-resolution error on the protocol
210
+ channel and a different, correct explanation beside it on stderr.
211
+
212
+ Both scripts now capture, and each re-prints that body only on the two exit
213
+ statuses checkride answers with by design. Anything else is the launcher's
214
+ error rather than a verdict, and `block`/`stand_down` own stdout there alone.
215
+
216
+ ### Internal
217
+
218
+ - The marketing site under `site/` picks up Fathom analytics and Open Graph plus
219
+ Twitter Card metadata across all three pages. The published package is
220
+ unchanged.
221
+
222
+ ## [0.11.0] - 2026-08-04
223
+
224
+ ### Added
225
+
226
+ - **`gate.preflight` — a supported seam for wrapping the stop gate.** checkride
227
+ owns the generated hook scripts and overwrites them on every refresh, so a repo
228
+ with something to say before the gate had nowhere to say it: editing the script
229
+ lost the edit, and re-pointing the harness config lost it to the next
230
+ `hooks add`. A `"preflight"` under `gate` in checkride.config.json names a
231
+ repo-relative script the generated gate runs, from the repo root, before
232
+ checkride is started — which is what lets it answer for a repo where checkride
233
+ *cannot* start.
234
+
235
+ Its exit code is read in the gate's own vocabulary rather than a second
236
+ convention: `0` runs the gate, `2` blocks the turn, anything else stands the
237
+ gate down. Both non-zero branches use the script's output as the message and
238
+ never start checkride. A configured path that does not exist **blocks**, so a
239
+ typo cannot quietly disarm the gate.
240
+
241
+ It is a config key rather than a `hooks add --preflight` flag for one reason:
242
+ the path is re-read on every write, so a teammate running the bare command
243
+ restores the wiring instead of silently dropping it. A preflight narrows
244
+ nothing, so it never adds the "NOT the full check" clause to a verdict.
245
+
246
+ ### Contract
247
+
248
+ - **A could-not-run gate now blocks only when blocking could lead to a fix.**
249
+ Previously every could-not-run verdict blocked, in both harnesses, on the
250
+ reasoning that a gate which stops gating is a vacuous green. That reasoning
251
+ holds only while the block can *cause* the fix, and for a whole class of causes
252
+ it cannot: the harness re-prompts the same agent, which has no lever to pull.
253
+ The observed end of that road is an unbounded retry against an impossible
254
+ remedy, and then a contributor removing the gate.
255
+
256
+ Launch refusals are now classified by who can clear them. A **repo** refusal —
257
+ no `check` script, no package.json — blocks exactly as before, because an agent
258
+ holding the edit tools can fix it. (A missing `check` script stays blocking for
259
+ a second reason: it is the one refusal an agent could otherwise *create* to
260
+ escape the gate.) An **environment** refusal — an `engines` pin the hook's Node
261
+ fails, a corepack that will not verify the package manager, a package manager
262
+ absent from `PATH` — stands down: exit 0, no `decision`, `green: false`.
263
+
264
+ Two properties are promised about the stand-down and neither may be weakened:
265
+ it is never silent (Claude Code gets a `systemMessage`, Cursor gets stderr,
266
+ both stating that nothing was verified), and it is never reported as green.
267
+ `docs/contract.md` and `test/contract/gate-verdict.contract.test.ts` are
268
+ updated deliberately; a hook script that reads only the exit status will now
269
+ let a turn end on an environment refusal, which is the intended change.
270
+
271
+ ### Changed
272
+
273
+ - **`init` now pins markdownlint-cli2 0.23.2 for the `docs` slot**, up from
274
+ 0.23.1 — the version that resolves a vulnerable `js-yaml`
275
+ (GHSA-pm4m-ph32-ghv5) transitively into every repo it sets up. An existing
276
+ repo keeps whatever it installed; re-run `checkride init` or bump it yourself.
277
+
278
+ ### Fixed
279
+
280
+ - **An uninstalled repo no longer traps the agent in an unbreakable loop.** A
281
+ developer who pulled a branch that newly added checkride — or cloned fresh —
282
+ and started a session before `<pm> install` got a gate that blocked every turn
283
+ with `checkride gate is not resolvable in this repo. Install checkride (or
284
+ re-run checkride agent-setup)`. Every part of that was a dead end: the
285
+ suggested remedy needs the missing binary, and in a repo with a customized
286
+ AGENTS.md stanza `agent-setup` would refuse and exit 2 even if it could run.
287
+ The actual fix, `<pm> install`, was never named. Nothing bounded the retry.
288
+
289
+ The generated gate now distinguishes the two ways checkride can fail to answer.
290
+ Dependencies never installed → it names `<pm> install`, says checkride is a
291
+ devDependency, and stands down. Installed but not answering → it blocks, as
292
+ before, since there is no remedy to name. Yarn PnP is recognized, so a healthy
293
+ PnP repo is not misread as uninstalled.
294
+
295
+ - **Both generated gates bound every could-not-run cause.** The scripts now read
296
+ the Stop payload from stdin and consult Claude Code's `stop_hook_active` and
297
+ Cursor's `loop_count`. On the second consecutive could-not-run, whatever the
298
+ cause, the gate stands down instead of blocking again — the previous script
299
+ read no stdin at all, so `stop_hook_active` was discarded and
300
+ `"loop_limit": null` left the Cursor gate unbounded by design. A **red**
301
+ pipeline is deliberately still unbounded: that loop is the gate working.
302
+
303
+ - **The claude gate no longer forwards the launcher's error as a hook body.** On
304
+ a missing install pnpm writes `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL` to *stdout*
305
+ — ahead of a stray `undefined` on pnpm 11 — which the script captured into
306
+ `$body` and printed where Claude Code expects JSON. The status is now checked
307
+ before the body is forwarded, so a body travels only when checkride wrote it.
308
+
309
+ - **A refusal the repo can fix is no longer explained by the hook's Node.** The
310
+ `nodeClause` paragraph rode on every refusal, so "this repo has no `check`
311
+ script" was followed by an account of non-login shells and interpreter
312
+ alignment. It is now attached to environment refusals only.
313
+
314
+ - **A red run could be reported as "could not run" on Linux.** `isFresh` judged
315
+ the summary this run wrote by comparing its mtime against a `Date.now()`
316
+ reading taken just before the check spawned — and those are not the same
317
+ clock. Linux mtimes come from the kernel's coarse timer-tick clock, so a
318
+ summary written *after* `startedAt` routinely landed a few milliseconds behind
319
+ it: 934 of 2000 writes in a container, and 0 of 2000 on APFS. When it misfired
320
+ the gate read its own summary as a previous run's, believed an `engines`-pin
321
+ marker the check had merely printed, and blamed the environment for broken
322
+ code. `MTIME_TOLERANCE_MS` already guarded the same comparison in `triage/`;
323
+ the gate's own copy had dropped it. This is what turned the Linux CI legs red
324
+ on v0.10.2 and v0.10.3 while both macOS legs stayed green.
325
+
326
+ - **A green run that skipped most of its slots says so.** `✔ all checks passed`
327
+ is a true sentence about the checks that ran and a misleading one about the
328
+ repo, and the gap was invisible exactly where it matters: a repo configuring
329
+ almost nothing reported the same confident green as one configuring
330
+ everything. The line now reads `✔ all checks passed in 13ms — only 1 of 8
331
+ checks ran, 7 skipped` whenever any slot sat out. `--strict` is unchanged; its
332
+ floor is *zero* checks, which an always-available slot like `links` keeps a
333
+ repo off by itself.
334
+
335
+ ### Internal
336
+
337
+ - CI dumps a failing slot's captured streams into collapsible log groups and
338
+ keeps `.check/` as an artifact. A red run logged `✘ test` and nothing else,
339
+ and on a hosted runner the directory holding the reason died with the
340
+ workspace — so the Linux-only gate failure above had to be reproduced in a
341
+ container before it could be read at all.
342
+
343
+ - Dependency audit cleared and mostly retired. Two high advisories were open
344
+ (`fast-uri` GHSA-7p8r-x3mc-p8w7, `brace-expansion` GHSA-rgw5-rvv9-x895), both
345
+ already carrying an override whose range the advisory had since outgrown.
346
+ Re-resolving showed four of the five overrides were dead weight — the parents'
347
+ own ranges reach patched versions — so they were removed rather than widened,
348
+ leaving one. No runtime `dependencies` are declared, so none of this ever
349
+ reached the tarball.
350
+
351
+ ## [0.10.3] - 2026-08-01
352
+
353
+ ### Added
354
+
355
+ - **Both Cursor guards now cover the shell.** `protect` and `dirty` each take a
356
+ second `.cursor/hooks.json` entry — on `beforeShellExecution` and
357
+ `afterShellExecution` — whose matchers run against the command string rather
358
+ than a tool name. This closes the older of the two documented Cursor gaps:
359
+ `echo … > checkride.baseline.json` is a shell call, not a `Write`, so no
360
+ file-tool matcher could ever see it, and a turn that wrote only through the
361
+ shell used to skip the gate entirely.
362
+
363
+ The standing objection was that matching the shell means parsing arbitrary
364
+ command lines, and a guard that fires on a wrong parse is worse than one with
365
+ a known hole. That objection is answered rather than dropped. `protect`'s
366
+ matcher admits only commands already naming an accounting path — reads
367
+ included — and the script then denies solely on demonstrated write intent: a
368
+ `>`/`>>` redirect target, or a positional argument of `rm`, `mv`, `tee`,
369
+ `sed -i`, `dd of=` and kin. Anything it cannot read that way is allowed, so
370
+ `cat`, `jq`, `grep` and `cp .check/summary.json /tmp/x` keep working, as
371
+ triage requires. `dirty` decides from its matcher alone and biases the other
372
+ way, toward marking a turn dirty, because there the recoverable error is one
373
+ extra pipeline run.
374
+
375
+ Both entries share their sibling's name and script: one guard, one
376
+ `--remove-hook protect`, one `.cjs` branching on `hook_event_name`. The gate
377
+ takes no shell entry — it is not per-call. Claude Code's half of the gap stays
378
+ open; `permissions.deny` is a file-path list, and a `Bash` matcher there would
379
+ mean shipping a generated script that harness does not currently need.
380
+
381
+ ### Changed
382
+
383
+ - **The AGENTS.md stanza no longer promises the gate ran everything.** It said
384
+ the stop-gate hook "runs the full `pnpm check`" and told the agent to "let the
385
+ hook run the authoritative pipeline once at the end" — written before gate
386
+ profiles existed, and false in any repo that declares one. The cost of that is
387
+ specific: the stanza is what an agent reads *first*, so it pre-authorized
388
+ reading a profiled `✔ green` as done, and the `NOT the full check` clause
389
+ arrived after the agent had already been told the hook was the last word. The
390
+ disclosure and the contract disagreed, and the contract was the one being read
391
+ in advance.
392
+
393
+ The paragraph now sends the reader to the gate's own verdict — "read the
394
+ gate's verdict rather than assuming it covered everything" — and says plainly
395
+ that a narrowed green is not the "done" defined above. It names no profile
396
+ deliberately: a repo can add `gate` to `checkride.config.json` without
397
+ re-running `agent-setup`, so any stanza that spelled out a specific profile
398
+ would drift back into over-promising, while the verdict is generated per run
399
+ and cannot.
400
+
401
+ ## [0.10.2] - 2026-07-31
402
+
403
+ ### Added
404
+
405
+ - **A gate profile, for repos where the full pipeline is too slow to run every
406
+ turn.** `checkride.config.json` gains a `gate` key — `only`, `skip`, `changed`
407
+ — that applies to the stop-gate run and nothing else:
408
+
409
+ ```json
410
+ { "gate": { "only": ["types", "lint", "struct"], "changed": true } }
411
+ ```
412
+
413
+ The flags are appended after the check script's own, so the profile wins over
414
+ anything the script already carried. The intended shape is a fast gate per
415
+ turn with the full check still binding at commit or in CI; the gate is not the
416
+ only place "done" can be enforced, and in a large repo it should not be the
417
+ expensive one.
418
+
419
+ **Every verdict a profiled gate emits says it was a profile**, in those words:
420
+ `checkride ✔ green in 4.1s — gate profile: only types, lint — NOT the full
421
+ check`. That is the price of the feature and it is deliberately not
422
+ configurable. A gate that runs three of eighteen slots and reports a bare
423
+ green has told the reader the work is done, which is the one thing it does not
424
+ know — the vacuous pass this tool exists to prevent, reached from the
425
+ comfortable direction rather than the alarming one. A narrowed red points past
426
+ itself for the same reason. A profile that narrows nothing is treated as no
427
+ profile, so the warning never appears on a run that was in fact complete.
428
+
429
+ - **checkride aligns the gate to the repo's Node pin.** Before running the check
430
+ script, if the repo names an exact interpreter (`.nvmrc` or `.node-version`)
431
+ and the running Node does not satisfy it, checkride puts a matching installed
432
+ Node in front of the child's `PATH` — so a hook that arrived on the wrong Node
433
+ runs the pipeline on the right one instead of failing to run it at all.
434
+
435
+ Four rules bound it, because choosing the interpreter a repo's whole pipeline
436
+ runs on is not a small thing to do silently: only on an explicit pin file
437
+ (`engines.node` is a *range* — a compatibility declaration, not an instruction
438
+ to switch interpreters — and is read for diagnosis only); only when the running
439
+ Node does not already satisfy it; only an interpreter already installed under a
440
+ known layout (nvm, fnm, nodenv, asdf, volta, n — nothing is downloaded and no
441
+ version manager is invoked, since under a non-login shell `nvm` is a shell
442
+ function and not a binary); and never without a line on stderr saying what it
443
+ aligned to and why.
444
+
445
+ `CHECKRIDE_NODE_BIN` is the escape hatch in both directions: a directory to use
446
+ verbatim — the documented wrapping point for a layout checkride does not know —
447
+ or `off` to disable alignment entirely. When alignment cannot help, nothing
448
+ changes and the refusal above names the pin, the Node that ran, and the lever.
449
+
450
+ - **`doctor` reports the Node a hook would get, not just this shell's.** Every
451
+ other row describes the terminal `doctor` was typed into, which is how it said
452
+ `environment ok` about a repo whose gate could not start — the bug was
453
+ invisible to every read-only command checkride had. The new `node pin` row asks
454
+ the question that *is* answerable from any shell: given the repo's pin, could
455
+ checkride put the right Node back if a hook arrived with the wrong one? It is
456
+ never required, so it flags a risk without failing a repo that works.
457
+
458
+ - **`checkride hooks add <a,b>` / `checkride hooks remove <a,b>` — hook
459
+ management that never touches AGENTS.md.** `agent-setup` writes four things at
460
+ once (the `check` alias, the stanza, the hooks, the Cursor skills) with the
461
+ stanza guard in front of all of them, so managing a hook inherited the stanza's
462
+ problems: a repo whose AGENTS.md had been edited could not remove a hook *at
463
+ all* — the run refused and, by contract, wrote nothing — and a repo mid-upgrade
464
+ had its stanza rewritten by a command it had asked to touch hooks.
465
+
466
+ The new command writes only the harness config entries and the generated
467
+ scripts. It never reads or writes AGENTS.md, so no state of that file can block
468
+ it and no run of it can change that file. `--harness <a,b>` and `--dry-run`
469
+ both apply, and both directions are idempotent. `add` defaults to every hook;
470
+ `remove` requires an explicit list, because a bare command that tore out the
471
+ gate would be the silent un-gating checkride exists to prevent.
472
+
473
+ `agent-setup` is unchanged, flags and refusal included.
474
+
475
+ ### Changed
476
+
477
+ - **checkride generates one file for Claude Code, not four.** `protect` is now a
478
+ pair of `permissions.deny` rules and `dirty` is an inline command, so
479
+ `.claude/hooks/checkride-protect.cjs` and `.claude/hooks/checkride-dirty.sh`
480
+ are gone. Both are deleted automatically on the next `agent-setup`, `init`, or
481
+ `hooks add`.
482
+
483
+ `protect` gains more than tidiness from the move. Claude Code evaluates a deny
484
+ rule **regardless of what a `PreToolUse` hook returns**, so the same paths are
485
+ now guarded a layer below the hook that used to guard them — and at no cost
486
+ per tool call, where the script spawned Node on every single edit while
487
+ allowing essentially all of them. The rules are also a list checkride
488
+ *appends* to and removes only its own entries from, so a repo can add its own
489
+ suppression files beside them (a `fallow.baseline`, a lint baseline) and keep
490
+ them across every refresh. The generated script could never offer that: it was
491
+ checkride-owned and overwritten wholesale on every run.
492
+
493
+ ```json
494
+ "permissions": {
495
+ "deny": ["Edit(**/checkride.baseline.json)", "Edit(**/.check/**)"]
496
+ }
497
+ ```
498
+
499
+ `Edit(...)` is the only correct spelling, and getting it wrong fails silently:
500
+ Claude Code checks file paths against `Edit` and `Read` rules only, and a
501
+ `Write(...)` or `NotebookEdit(...)` path rule is accepted, never consulted, and
502
+ warns at startup. An `Edit` rule covers every file-editing tool. `Read` deny
503
+ rules stay deliberately absent — triage reads `.check/` artifacts.
504
+
505
+ Nothing moves in the user-facing surface: `--hook protect`,
506
+ `--remove-hook protect`, and `checkride hooks add|remove protect` all mean what
507
+ they meant. **Cursor is unchanged** and still gets all three scripts; its
508
+ config is hooks and nothing else, with no documented file-path deny list to
509
+ move `protect` into.
510
+
511
+ - **`checkride hooks` counts files changed, not files deleted.** Removing a hook
512
+ a harness expresses as configuration rewrites the config and deletes nothing,
513
+ and the old wording reported that as `removed 0 file(s)` — a run that did
514
+ exactly what was asked, reading as a no-op. The line now names the hooks and
515
+ counts every file it touched.
516
+
517
+ - **The AGENTS.md stanza no longer prescribes an architecture.** It carried
518
+ checkride's own house style as if it were law — "Named exports only; no
519
+ classes; `.js` extensions on relative imports", plus the barrel-`index.ts`
520
+ rules — into every repo that ran `init` or `agent-setup`. For a repo that uses
521
+ classes, default exports, or bundler-style extensionless imports, that is a
522
+ contract file instructing the agent to write the wrong code. The stanza is
523
+ also the one region a repo cannot correct: editing it is what makes the next
524
+ refresh refuse.
525
+
526
+ The `### Module boundaries` section now names where the rules live instead of
527
+ what they say — the `struct` check runs whatever ast-grep rules `sgconfig.yml`
528
+ points at, and those files are the convention. It is emitted only when
529
+ `struct` is an active check; a repo without it gets no section at all. Delete
530
+ `rules/no-class.yml` and the stanza stays true, which is the property it was
531
+ missing.
532
+
533
+ `init` still *scaffolds* those rules as its opinionated default — that part is
534
+ a starter you own and can edit, and is unchanged.
535
+
536
+ - **`--add struct` scaffolds the boundary rule and stops.** It used to write
537
+ four ast-grep rules, three of which are style, not structure: `no-class`,
538
+ `no-default-export`, and `require-js-extension`. A repo that uses classes,
539
+ default exports, or bundler resolution went red the moment it adopted the
540
+ slot — for code it had always written, over a convention it never chose, which
541
+ reads as checkride being broken rather than as a finding. `--add struct` now
542
+ writes `sgconfig.yml` and `rules/no-deep-sibling-import.yml`: the rule that
543
+ makes `struct` the slot it is, and one whose failures mean what they say.
544
+
545
+ New-mode `init` still writes all four. That is a different bargain — it is
546
+ creating the package, so there is no prior decision to override — and the
547
+ other rules are listed in `docs/deep-modules.md` for any repo that wants them.
548
+
549
+ ### Fixed
550
+
551
+ - **A package manager that refuses to start the check script is no longer
552
+ reported as a red pipeline.** In any repo pinning `engines.node`, pnpm answers
553
+ a Node it does not accept with `ERR_PNPM_UNSUPPORTED_ENGINE` and **exit 1** —
554
+ the same exit code a failing test uses. The gate read that as a verdict on the
555
+ code and said so: `checkride ✘ red in 265ms`, exit 2, turn blocked, every turn,
556
+ regardless of what was written. It then sent the reader to
557
+ `.check/summary.json`, which no run had written, because no check had run.
558
+
559
+ That is the vacuous gate inverted — not a silent green but a permanent red no
560
+ code change can clear, whose rational response is to switch the gate off. And
561
+ it is not one repo's problem: agent harnesses run hooks in a **non-login
562
+ shell**, which never sources the rc file a version manager puts its shims in,
563
+ so the hook gets the machine's default Node rather than the contributor's. On
564
+ nvm that is the norm, not the exception.
565
+
566
+ `gate` now answers a third verdict — **could not run** — for a launch that
567
+ never happened, naming the cause and saying plainly that nothing ran and no
568
+ artifact describes this turn. It still blocks, in both harnesses: an unrunnable
569
+ gate has never been a pass. `triage` gained the matching `could-not-start`
570
+ verdict, since it runs the same script and had folded the same exit into `red`,
571
+ confirming the gate's wrong answer to whoever came asking why.
572
+
573
+ The classification is guarded on both sides. Only pnpm's and npm's own error
574
+ codes are matched — npm's `EBADENGINE` *warning* deliberately is not, because
575
+ npm runs the script anyway unless `engine-strict` is set — and a summary
576
+ written by the current run vetoes the whole thing, so a check that merely
577
+ *printed* one of these strings can never be reclassified into an environment
578
+ problem.
579
+
580
+ - **Upgrading no longer looks like an edit.** 0.10.1 started stamping the stanza
581
+ so a refresh could tell its own output from a customization — but a stanza
582
+ written *before* the stamp carries no evidence either way, so every upgrading
583
+ repo read as `unstamped`, refused, and by contract wrote **nothing**: not the
584
+ stanza, not the config, not the hooks. That is how 0.10.1's own gate-timeout
585
+ fix failed to reach the repos it was written for. The guard protecting AGENTS.md
586
+ prose was withholding a fix for a gate that had silently stopped gating.
587
+
588
+ checkride now recognizes the wordings it actually shipped — four distinct texts
589
+ across v0.1.1–v0.10.0, matched on everything but the generated
590
+ `Active checks in this repo:` line, so a repo's own slot list is irrelevant. A
591
+ pre-stamp stanza that matches one is checkride's own output, refreshes without
592
+ `--force`, and comes back stamped; one that matches nothing has been edited and
593
+ is still refused, with a message that now says so plainly instead of hedging
594
+ about indistinguishable wordings.
595
+
596
+ The list is closed and needs no maintenance: every release from 0.10.1 stamps
597
+ what it writes, so no future version can add an unstamped wording.
598
+
599
+ - **The AGENTS.md stanza is package-manager-agnostic.** It hardcoded `pnpm
600
+ check` and `pnpm exec checkride triage` regardless of the repo's package
601
+ manager, while checkride detects that manager everywhere else and README
602
+ promises the tool is agnostic. The stanza is a list of commands an agent is
603
+ told to run, so in an npm repo it was an instruction that cannot succeed: the
604
+ agent either invents a substitute or reports the pipeline as broken. It now
605
+ renders `npm run check` / `npx --no-install checkride triage`, `yarn run
606
+ check`, or `bun run check` from the detected manager, and `pnpm check` is
607
+ unchanged for pnpm repos.
608
+
609
+ The Claude Code gate's `statusMessage` had the same bug from the same source
610
+ and is fixed with it — the spinner said `npm check`, which is not a command,
611
+ and `yarn check` names Yarn 1's integrity checker rather than the repo's
612
+ script.
613
+
614
+ - **The `spell` check no longer trips over checkride's own stanza hash.** The
615
+ `hash=v1…` marker is 16 hex digits, so sooner or later it spells something
616
+ cspell does not recognize (`…fedf…`) and fails a slot for a string checkride
617
+ itself generated. The shipped `cspell.json` now ignores the marker.
618
+
619
+ ### Contract
620
+
621
+ - **A gate that could not run answers "could not run", not "red"**
622
+ (docs/contract.md §CLI). The exit codes are unchanged and remain the promise —
623
+ 2 while blocked under `--harness claude`, always 0 under `--harness cursor` —
624
+ because an unrunnable gate has always blocked. What is new is that the verdict
625
+ distinguishes a red pipeline from a launch that never happened, so anything
626
+ reading the `systemMessage` / `followup_message` body sees a cause instead of a
627
+ pointer to an artifact this run did not write. Additive: a hook script that
628
+ gates on the exit code alone is unaffected.
629
+
630
+ - **`checkride.config.json` promises a `gate` key** (docs/contract.md §CLI), and
631
+ with it the disclosure: while a profile is active, every gate verdict names the
632
+ profile and states that it was not the full check.
633
+
634
+ - **`CHECKRIDE_NODE_BIN`** is promised as the hook author's wrapping point: a
635
+ directory prepended to the check run's `PATH`, or `off` to disable checkride's
636
+ own Node-pin alignment.
637
+
638
+ - **What a hook *is* belongs to the harness** (docs/contract.md §CLI). The three
639
+ hook names stay promised; whether one lands as a config entry, a permission
640
+ rule, or a generated script is checkride's to choose per harness and may change
641
+ again. What is promised about the Claude Code deny rules is the part a consumer
642
+ builds on: checkride appends its own and removes only those. Anyone scripting
643
+ against the *path* `.claude/hooks/checkride-protect.cjs` should read
644
+ `permissions.deny` instead.
645
+
646
+ ## [0.10.1] - 2026-07-31
647
+
648
+ ### Added
649
+
650
+ - **`init` and `agent-setup` refuse to overwrite an AGENTS.md stanza that has
651
+ been edited.** Repos routinely need a line or two of their own in the contract
652
+ — a directory the `spell` check is allowed to skip, what a custom check means
653
+ here — and the refresh rewrote the marked region blind, taking those additions
654
+ with it. Idempotent, as promised, and lossy: the second run reported
655
+ `changed: false` precisely because the first one had already discarded the
656
+ edit.
657
+
658
+ The begin marker now carries a hash of the body checkride generated
659
+ (`<!-- checkride:begin hash=v1… -->`), so a later run can tell its own output
660
+ from a block someone has since changed. A changed one stops the run: exit 2, a
661
+ message naming the file and the two ways forward, and **nothing written at
662
+ all** — not the config, not the hooks — so a refused run leaves the repo
663
+ exactly as it found it, the rule new-mode `init` already followed for scaffold
664
+ collisions. `--force` accepts the loss and refreshes, and now carries that
665
+ meaning on `agent-setup` too, where the flag previously reached nothing.
666
+
667
+ Two things are deliberately *not* edits. Reformatting: line endings and
668
+ trailing whitespace are normalized away before hashing, so a Prettier run over
669
+ AGENTS.md does not read as a customization. And anything outside the markers,
670
+ which stays the right home for repo-specific additions — checkride has never
671
+ rewritten a line out there and still does not.
672
+
673
+ One-time cost on upgrade: a stanza written by 0.10.0 or earlier carries no
674
+ hash, and an older version's wording is indistinguishable from an edit, so the
675
+ first run after upgrading refuses with its own wording of the message.
676
+ `--force` once stamps it; detection is automatic from then on.
677
+
678
+ - **`--remove-hook <a,b>` tears an installed hook back out** — the config entry
679
+ and the generated script both. `--hook` only ever chose what to *write*, so
680
+ there was no supported way to drop the gate after the fact. It pairs with
681
+ `--no-hook` to remove without refreshing anything else, and removing `dirty`
682
+ rewrites a surviving gate unguarded, so the gate cannot silently disarm itself
683
+ on the way out.
684
+
685
+ - **A `note` field on any check entry, and at the config root**, for commentary
686
+ aimed at whoever reads `checkride.config.json` next. It is validated as a
687
+ string and then dropped — no code path carries it onto an Adapter, so it
688
+ cannot reach the status line or `summary.json`. `description` keeps its
689
+ user-facing role; this repo's own config dogfoods the split.
690
+
691
+ - **The gate states its verdict in words**, not just an exit code: the wall
692
+ clock and the failing slots, read back from `.check/summary.json` —
693
+ `checkride red in 41.7s, 2 of 15 failed: lint, test`. Under Claude Code it
694
+ rides in the hook body as `systemMessage`, which moves the block onto
695
+ `decision: "block"`, since hook JSON is parsed only on exit 0. `checkride
696
+ gate`'s own exit codes are unchanged: the generated script does the
697
+ translation and falls back to exit-2 blocking when no body comes back, so an
698
+ unrefreshed repo or an older checkride keeps gating either way. Cursor gets
699
+ the same line atop its `followup_message` on red; a green Cursor gate stays
700
+ silent, because its only stop-hook output field submits a new turn, and the
701
+ gap is recorded in docs/cursor.md rather than papered over.
702
+
703
+ - **The Claude Code Stop entry carries `statusMessage` and `timeout: 900`.** The
704
+ spinner now names the command rather than leaving a multi-minute pipeline
705
+ indistinguishable from a hung model, and the timeout closes a live hole: the
706
+ platform default is 600s, and a hook cancelled by it exits non-zero, which
707
+ Claude Code reads as a broken hook and lets the turn end. Any repo whose
708
+ pipeline crossed ten minutes had a gate that had quietly stopped gating.
709
+
710
+ ### Fixed
711
+
712
+ - **`triage` no longer reports an unparseable summary as a run with no
713
+ failures.** A repo whose gate is a homegrown script writing a checkride-shaped
714
+ `.check/` — same file names, no `schema_version` — made the reader announce
715
+ "checkride never ran, a compound script short-circuited" about a gate that had
716
+ run all eight of its checks and failed three. An empty slot table now reads as
717
+ "nothing was read", not "nothing failed": the short-circuit verdict is
718
+ reserved for a genuinely absent summary, `covered:` renders `unknown` rather
719
+ than a count nobody measured, a missing `schema_version` becomes its own
720
+ `foreign` state, and a new `.check/ contents` section lists the directory when
721
+ the summary is not an index.
722
+
723
+ - **The gate ignores a summary older than its own start time.** A check script
724
+ shaped `tsc --build && checkride` leaves `summary.json` untouched when the
725
+ build fails, and trusting it would report the previous run's failing slots as
726
+ this run's.
727
+
728
+ ### Contract
729
+
730
+ - **checkride owns the AGENTS.md stanza and only the stanza**, and the boundary
731
+ is now enforced rather than documented (docs/contract.md §CLI, locked by
732
+ `test/contract/flags.contract.test.ts`). The promise a consumer can build on:
733
+ a stanza carrying local edits is never overwritten without `--force`, and the
734
+ run that refuses writes nothing. Behavioural break for anyone scripting
735
+ `agent-setup` over a repo with a hand-edited or pre-0.10.1 stanza — that
736
+ invocation exited 0 and now exits 2 until it is re-run with `--force`.
737
+
738
+ ## [0.10.0] - 2026-07-31
739
+
740
+ ### Added
741
+
742
+ - **Cursor support — hooks and skills.** `init` and `agent-setup` now write the
743
+ same three hooks (`gate`, `dirty`, `protect`) into `.cursor/hooks.json` as
744
+ they do into `.claude/settings.json`, and write the bundled `check` and `qa`
745
+ skills into `.cursor/skills/` as `checkride-check` and `checkride-qa` —
746
+ Cursor has no plugin system to install them from. Which harnesses get wired
747
+ is detected (Claude Code always, Cursor when `.cursor/` exists) and
748
+ overridable with `--harness <a,b>`.
749
+
750
+ The gate had to learn a second protocol to get there. Claude Code blocks a
751
+ turn on **exit 2** and reads stderr; Cursor treats any non-zero stop hook as a
752
+ *broken* hook and lets the turn end, so its verdict rides in a
753
+ `{"followup_message": …}` body on stdout instead. Same decision, two wire
754
+ formats.
755
+
756
+ Cursor's defaults would have made that gate advisory, so the entry overrides
757
+ three of them: `timeout` (a pipeline is minutes), `loop_limit: null` (the
758
+ default of 5 stops the gate replying after five turns, where Claude Code
759
+ re-blocks indefinitely) and `failClosed: true` (Cursor is fail-open, so a
760
+ crashed or timed-out gate would end the turn silently). The `dirty` and
761
+ `protect` guards keep the fail-open default, deliberately.
762
+
763
+ - **[Cursor](docs/cursor.md).** A page for the parts that are not symmetric with
764
+ Claude Code — the reply-don't-block stop protocol, the three overridden
765
+ defaults, the third-party-configs setting, and an explicit list of the
766
+ assumptions Cursor's own docs leave open (chiefly: the `tool_input` path key
767
+ `protect` matches on is a guess, and a wrong guess fails open silently).
768
+
769
+ - **`checkride gate`, `checkride triage`, `checkride qa`.** The gate's body —
770
+ the edit-marker check, the `check` run, the digest-or-summary choice — moved
771
+ out of the generated shell script into a real command, so it is testable and
772
+ every harness's hook script is now a thin adapter over it. `triage` and `qa`
773
+ promote the two bundled plugin readers to first-class commands, so a skill no
774
+ longer needs `${CLAUDE_PLUGIN_ROOT}` (or a hardcoded `node_modules/` path) to
775
+ find them.
776
+
777
+ ### Fixed
778
+
779
+ - **The gate no longer fails open when checkride cannot run.** An uninstalled
780
+ or unresolvable checkride made the hook exit 1, which Claude Code reads as
781
+ "hook failed, carry on" — leaving a repo whose gate had silently stopped
782
+ gating. Any exit outside the gate's own 0/2 now blocks, naming the cause.
783
+ (`protect` still fails open, deliberately: a broken protect hook must not
784
+ become a repo where nothing can be written.)
785
+ - **A repo wired for both harnesses no longer runs two gates per turn.** Cursor
786
+ loads `.claude/settings.json` hooks when third-party configs are enabled, maps
787
+ `Stop` onto `stop`, and runs *every* matching source — so the default wiring
788
+ (Claude Code is always selected) fired two full pipelines concurrently into one
789
+ `.check/`, racing on the artifacts the orchestrator clears per slot and on the
790
+ edit marker. `checkride gate --harness claude` now stands down when Cursor is
791
+ running it and a native Cursor gate is registered. Narrow on purpose: a stale
792
+ `CURSOR_PROJECT_DIR` costs a duplicate run, never the gate.
793
+ - **A gate that cannot enter the repo now reports it.** The `cd` guard was a bare
794
+ `exit 2`, which said nothing under Claude Code and — because Cursor reads any
795
+ non-zero stop hook as a broken one — ended the turn silently under Cursor. It
796
+ now emits the same "could not run" verdict as every other way the gate can fail
797
+ to start, in each harness's protocol.
798
+ - **`protect` no longer misses paths under a symlinked repo root.** It compared
799
+ the harness's spelling of a path against the environment's, which on macOS
800
+ routinely differ (`/var/…` vs `/private/var/…`), making every in-repo path
801
+ look external — so an edit to `checkride.baseline.json` was allowed through.
802
+ Both sides are now resolved before comparison.
803
+
804
+ ### Contract
805
+
806
+ - The command set gains `gate`, `triage` and `qa`; `init`/`agent-setup` gain
807
+ `--harness <a,b>`. All additive. `gate` is documented as the one command
808
+ outside the 0/1/2 exit split, because it answers a harness's hook protocol
809
+ rather than checkride's own — see `docs/contract.md` §CLI. Its one documented
810
+ no-op is promised there too: `--harness claude` stands down for a registered
811
+ native Cursor gate.
812
+
813
+ ## [0.9.6] - 2026-07-30
814
+
815
+ ### Fixed
816
+
817
+ - **A slot refused for a missing tool was reported as a harness problem.** The
818
+ refusal carried `exit_code: -1`, which is reserved for a spawn failure or
819
+ timeout — so `triage` matched it and printed "it failed to spawn or timed out.
820
+ That is a harness problem, not a finding," telling the reader to discount the
821
+ one failure the pre-flight exists to make them act on, remediation and all. It
822
+ now exits **1**, a finding like any other. `-1` keeps meaning the harness
823
+ broke, and a triage test now pins the two apart.
824
+ - **A tool installed above the repo root satisfied the pre-flight.** The upward
825
+ search for `node_modules/.bin/<tool>` stopped only at the filesystem root, so
826
+ a stray install in a home or projects directory counted — the same
827
+ machine-state dependence the local resolution was added to remove, reached by
828
+ a different route: the slot passed for whoever had that directory above their
829
+ clone and failed on the clean checkout. The search now stops at the repo root,
830
+ marked by a `.git` or a lockfile, and searches that root before stopping.
831
+ - **A timed-out `yarn bin` probe reported the tool as missing.** Under Yarn PnP
832
+ `doctor` folded every probe failure into "not a dependency", so a slow package
833
+ manager produced a confident `missing` and told you to install a tool that was
834
+ already there. A timeout is now `unknown`, with a hint naming it — the
835
+ treatment the `--version` probe alongside it already had.
836
+ - **`doctor` reported a healthy Yarn PnP project as broken.** Both of its
837
+ install questions were path tests against a `node_modules/` tree, which a PnP
838
+ project does not have: `install` asserted the directory and so reported
839
+ `lockfile only`, and each slot's tool was looked for at
840
+ `node_modules/.bin/<tool>` and so reported missing — on a repo where every
841
+ tool was installed and every check ran green. The two commands therefore
842
+ contradicted each other, `doctor` calling the environment broken while the
843
+ gate passed. `install` is now satisfied by `.pnp.cjs` plus the lockfile, and a
844
+ tool is resolved with `yarn bin <tool>`, whose exit code carries the answer.
845
+ A tool that genuinely does not resolve still reports missing — the looser
846
+ question must not become a softer one, or this would trade a false red for a
847
+ false green, which is the worse of the two. Detection is gated on yarn so a
848
+ `.pnp.cjs` left by a migration off Yarn cannot reroute an npm or pnpm repo.
849
+
850
+ ## [0.9.5] - 2026-07-30
851
+
852
+ ### Contract
853
+
854
+ - **A slot's tool is now resolved in the local tree before the check spawns,
855
+ under `npx` and `bunx`.** 0.9.3 stopped those launchers *fetching* a missing
856
+ tool, but `--no-install` bounds the download and not the per-user cache, so
857
+ both still run a copy left behind by some earlier unrelated invocation. The
858
+ gate's verdict therefore depended on machine state nobody declared: a repo
859
+ whose `docs` slot was detected from `.markdownlint-cli2.jsonc` but never
860
+ installed `markdownlint-cli2` passed for every developer holding a cached copy
861
+ and failed on the clean checkout — which is the CI runner, the most expensive
862
+ place to find out. The tool is now looked up at `node_modules/.bin/<tool>`,
863
+ searched from the check's directory upward so a workspace tool hoisted to the
864
+ repo root still counts, and a slot whose tool is absent fails there with a
865
+ message naming the slot, the path searched, and the PM's own install command
866
+ instead of the launcher's `npx canceled due to missing packages`. **Behavior
867
+ change:** a slot passing only because of a cached tool now fails until that
868
+ tool is a declared dependency — the same trade 0.9.3 made, applied to the
869
+ fallback it left open, and it fails on the developer's machine rather than
870
+ only in CI. Scoped to the two launchers that keep such a cache: `pnpm exec`
871
+ and `yarn` resolve from the project tree already and are not pre-flighted,
872
+ which also leaves Yarn PnP — where no `node_modules/.bin` exists to find —
873
+ working as before. See `docs/contract.md` §Vacuous green and `docs/tools.md`
874
+ §Package managers.
875
+
876
+ ### Fixed
877
+
878
+ - **`doctor` told npm, yarn and bun repos to run `pnpm install`.** The
879
+ remediation hint on a missing-tool row was a hardcoded string, so the one
880
+ actionable line on a red row named a package manager the repo does not use —
881
+ while `checkInstall` two functions above it had been PM-aware all along. It
882
+ now names the detected manager and the exact dev-dependency install.
883
+ - **`doctor` reported a hoisted workspace tool as missing.** The probe tested
884
+ `<cwd>/node_modules/.bin/<tool>` and nothing above it, but pnpm and npm both
885
+ hoist a shared tool's bin to the workspace root — so a correctly installed
886
+ monorepo showed a false `missing` for every tool from any package
887
+ subdirectory, on exactly the repos the workspace presets generate. It now
888
+ walks upward like the package managers do, sharing one resolver with the run
889
+ path so the two cannot disagree about where a tool lives.
890
+
891
+ ## [0.9.4] - 2026-07-30
892
+
893
+ ### Fixed
894
+
895
+ - **Member-scoped fallow findings now key on `<container>.<leaf>` instead of a
896
+ line and column.** fallow reports an unused class/enum/store member as
897
+ `parent_name` + `member_name`, a component binding as `component_name` +
898
+ `prop_name`/`input_name`/`output_name`/`emit_name`/`event_name`, and a catalog
899
+ entry as `catalog_name` + `entry_name` — none of which the extractor
900
+ recognized, so every such finding fell through to the `<path>:<line>:<col>`
901
+ fallback. That key moves whenever code above it does, so editing anything
902
+ earlier in a file re-keyed its grandfathered members and re-surfaced them as
903
+ new findings. They are now line-free
904
+ (`dead-code:unused_class_members:src/a.ts:Svc.unusedOne`) and survive the edit,
905
+ as every other category already did. The same pass gave the report's remaining
906
+ `*_name` categories a stable identity too, so they key on a symbol rather than
907
+ a position as well. **This changes the `dead` slot's fingerprints — re-run
908
+ `checkride baseline` to re-capture, or those findings will report as new
909
+ once.**
910
+ - **Report arrays that fallow does not count are no longer fingerprinted.**
911
+ `workspace_diagnostics` carries a `path`, and the opt-in `thin_wrappers` and
912
+ `duplicate_prop_shapes` rules a `file`, so all three produced keys while
913
+ fallow's issue registry marks them `counts_in_total: false`. The extra keys
914
+ pushed the key count past the issue count, where the un-keyable-findings guard
915
+ clamped the difference to zero — so a spurious key could offset a finding that
916
+ genuinely had no stable identity, and the baseline masked a slot green that it
917
+ had never grandfathered. The guard now counts un-keyable findings directly
918
+ rather than inferring them from the totals, which closes that offset even if a
919
+ future fallow adds an uncounted array this version does not know to skip.
920
+
921
+ ## [0.9.3] - 2026-07-29
922
+
923
+ ### Contract
924
+
925
+ - **An empty slot selection is now a usage error.** `--only`, `--skip` or
926
+ `--include` given a value that names nothing after trimming (`--only ,`,
927
+ `--only ''`, `--only ' '`) exits **2**, naming the flag. It previously
928
+ parsed to an empty list, which is truthy, so `--only ,` filtered every check
929
+ out and exited **0** having verified nothing — the same vacuous green that
930
+ `--only lints` was fixed for in 0.5.0, reached by a different route. The two
931
+ empty spellings also disagreed: `--only ''` ran the whole pipeline. The rule
932
+ now holds for a programmatic caller too — `runChecks({ only: [] })` throws
933
+ rather than selecting nothing. See `docs/contract.md` §CLI and §Exit codes.
934
+ - **`summary.json`'s published schema described `total_duration_ms` as a sum.**
935
+ It is, and has been since concurrency landed, the **wall-clock** span of the
936
+ run; the two diverge whenever checks overlap. No behavior changed — the
937
+ description in `schema/checkride.summary.schema.json` was simply wrong, and a
938
+ consumer implementing against it would derive the wrong run-start
939
+ (`timestamp - total_duration_ms`) and read stale artifacts as fresh. The
940
+ semantics are now pinned by a test, not just prose. See `docs/contract.md`
941
+ §`.check/summary.json`.
942
+
943
+ ### Fixed
944
+
945
+ - **Tools are no longer installed from the registry mid-run under npm or bun.**
946
+ The exec translation emitted a bare `npx <tool>` / `bunx <tool>`, and both
947
+ launchers fetch a missing package and run it rather than failing — checks are
948
+ spawned without a TTY, which is precisely the non-interactive case where
949
+ neither stops to prompt. A repo missing a tool could therefore have its gate
950
+ silently execute an unpinned `latest`. Both now carry `--no-install`, which
951
+ bounds the download but not the launcher's global cache — the guarantee is
952
+ "nothing new is fetched mid-run". **Behavior change:** a tool that would have
953
+ to be downloaded now fails its slot. `pnpm exec` and `yarn` have no such cache
954
+ path and are unchanged.
955
+ - **The triage reader's gate timeout could not reap what it started.** It
956
+ spawned without `detached` and sent a bare `SIGTERM` with no escalation, so
957
+ the signal reached the package-manager wrapper and left the checks themselves
958
+ running — which also meant `close` could wait on their inherited pipes
959
+ forever, hanging the reader the timeout existed to protect. It now leads its
960
+ own process group and escalates to `SIGKILL`, the same discipline the
961
+ orchestrator already applied; both share the new `killGroup` helper.
962
+ - **`.check/` artifacts are now flushed before the rename that publishes them.**
963
+ The atomic write promised each file was "either the previous complete version
964
+ or the new complete version", but without an `fsync` a crash could leave the
965
+ renamed inode holding nothing — the torn read it exists to prevent, relocated.
966
+ - **A committed baseline whose `slots` is an array is rejected instead of being
967
+ keyed by index.** Two of the four copies of the `isRecord` guard accepted
968
+ arrays (`typeof [] === 'object'`); the baseline read was one of them, so such
969
+ a file loaded cleanly and masked nothing. All four now share one definition.
970
+ - **A baseline from a newer schema version is dropped rather than half-read.**
971
+ `schema_version` was recorded and never checked. Dropping it fails closed: the
972
+ run reports the diagnostics that are really there, where guessing risks
973
+ masking findings the author never grandfathered.
974
+ - **AGENTS.md described a tree the repo does not have.** It named four folder
975
+ modules where there are seven (`artifacts/`, `qa/` and `triage/` were
976
+ missing), and stated a test-placement rule — colocate beside the source file —
977
+ that no test in the repo has ever followed. Both are corrected, and both are
978
+ now checked against the tree.
979
+ - **Report columns no longer break on long check names.** Fixed widths were
980
+ sized for catalogue slots, so a config custom check (`typecheck-tests`, or
981
+ `custom:typecheck-tests` in the adapter column) shunted every later column
982
+ right on its own row. `doctor` and the run status lines now size each column
983
+ to the rows it holds.
984
+
985
+ ### Internal
986
+
987
+ - **This repo's own gate now runs the `security` slot.** `pnpm audit` takes well
988
+ under a second and reports zero advisories at every level, and a package that
989
+ publishes to npm with provenance should be auditing its own dependency tree as
990
+ part of "done" rather than out of band. Note the trade-off: the gate now needs
991
+ the registry to be reachable, and a failure to *verify* is a failure, not a
992
+ pass — so a network outage turns the run red rather than quietly skipping the
993
+ check.
994
+ - **The stryker break threshold moves 55 → 68.** The measured score is 73.2%
995
+ (up from 71.7%), so the old gate sat 18 points below reality and could not
996
+ have failed on anything short of deleting a suite. It now sits ~5 points
997
+ under — enough headroom that a mutant timing out on a loaded machine does not
998
+ turn the run red, which is the only reason not to set it tighter. The two
999
+ bundled-plugin bins are excluded from the mutate set: they are three lines
1000
+ each, do their work at module top level, and are unreachable from the unit
1001
+ runner stryker drives, so they reported a permanent 0% no test could move.
1002
+ They are covered as processes by `test/e2e/plugin-readers.e2e.test.ts`.
1003
+ - Tests for the last uncovered code in the package. The two bundled-plugin
1004
+ entry points (`dist/triage/cli.js`, `dist/qa/cli.js`) are exercised as
1005
+ processes, which is the only way to reach a bin that works at module top
1006
+ level; `checkride fix` gained CLI-dispatch coverage it never had; and the
1007
+ three `qa/` extractors gained tests for their truncation caps and
1008
+ malformed-input fallbacks — the bounded-reader behavior those modules exist
1009
+ for. Branch coverage 84.3% → 85.5%.
1010
+ - New guards for the claims that drifted: `test/conventions.test.ts` checks
1011
+ AGENTS.md's folder-module list and test placement against the tree, and the
1012
+ summary contract suite now pins `total_duration_ms` behaviorally — wall-clock
1013
+ under concurrency, equal to the summed durations when sequential. Both fail
1014
+ when reverted, which is the point.
1015
+ - `test/e2e/defaults.e2e.test.ts` resolves a fixture repo that has the tool
1016
+ configs but **no `checkride.config.json`**, so the shipped catalogue and the
1017
+ detection path are exercised. This repo's own config names all twenty slots,
1018
+ which is deliberate but meant daily runs never walked a consumer's path.
1019
+ - First tests for `src/triage/env.ts`, previously the only module in the package
1020
+ with no coverage at all.
1021
+ - Two new single-file modules: `src/proc.ts` (process-group kill and escalation,
1022
+ shared by both spawners) and `src/json.ts` (the one `isRecord`).
1023
+ - Long-form rationale moved out of source comments and into the docs that own
1024
+ it: the pnpm stdout-narration investigation to `docs/tools.md` §Launcher
1025
+ quirks, and the freshness-window derivation to `docs/plugin.md`. The comments
1026
+ keep the "why" and point at the rest.
1027
+
1028
+ ## [0.9.2] - 2026-07-28
1029
+
1030
+ ### Changed
1031
+
1032
+ - **`checkride init` now writes a newer toolchain into generated projects.**
1033
+ The pinned versions a new project receives move to oxlint 1.74.0 (with
1034
+ oxlint-tsgolint 0.25.0), fallow 3.9.1, ast-grep 0.45.0, vitest 4.1.10,
1035
+ markdownlint-cli2 0.23.1, cspell 10.0.1, prettier 3.9.6, biome 2.5.6, jest
1036
+ 30.4.2, publint 0.3.22, attw 0.18.5, and `@types/node` 22.20.1. A freshly
1037
+ generated project still installs and passes green out of the box. TypeScript
1038
+ stays at 6.0.3, and the eslint and knip pins stay on their current majors —
1039
+ all three are deliberate holds, now marked as such in the adapter registry.
1040
+ - **The documented fallow pin is 3.9.1.** The supported floor is unchanged:
1041
+ fallow ≥ 3.5, JSON `schema_version` 7. Every release from 3.5.0 through 3.9.1
1042
+ emits schema 7 with identical report layouts, so an existing baseline keeps
1043
+ masking and no consumer has to move.
1044
+
1045
+ ### Fixed
1046
+
1047
+ - **The bundled `check` skill quoted a markdownlint summary line that no longer
1048
+ exists.** Its guidance on reading a failing `docs` slot cited
1049
+ `Summary: 1 error(s)`; markdownlint-cli2 0.23 emits `Summary: 1 issue in 1
1050
+ file`. The surrounding advice was still right — that tool puts the count on
1051
+ stdout and the `file:line:rule` you actually need on stderr — but the quoted
1052
+ string sent agents looking for text no run produces.
1053
+
1054
+ ### Internal
1055
+
1056
+ - Development dependencies upgraded across the board, in separate verified
1057
+ steps: fallow's report schema and layouts confirmed unchanged, ast-grep's
1058
+ rules confirmed to still fire (repo *and* shipped template copies), and
1059
+ oxlint's diagnostic text confirmed byte-identical for the baseline fixtures
1060
+ it fingerprints.
1061
+ - Nine new oxlint errors from the 1.61 → 1.74 jump were fixed at the root
1062
+ rather than suppressed: two genuinely unnecessary type assertions, and seven
1063
+ test helpers hoisted out of the scopes they never captured.
1064
+ - `pnpm audit` is clean again. The `brace-expansion` override had gone stale as
1065
+ its advisory widened, `fast-uri`, `postcss`, and a js-yaml 5.x entry were
1066
+ added, and the `markdown-it` and js-yaml 4.x overrides were retired as
1067
+ redundant — each verified by removal and re-audit, not by inspection.
1068
+ - Every GitHub Actions ref is now SHA-pinned with a version comment. `ci.yml`
1069
+ alone had floated on mutable major tags, which made the workflow gating every
1070
+ merge the one an upstream tag move could reach. CI's pnpm moves to 11.17.0,
1071
+ verified to accept the committed lockfile unmodified.
1072
+
1073
+ ## [0.9.1] - 2026-07-28
1074
+
1075
+ ### Fixed
1076
+
1077
+ - **The `links` check no longer flags example links inside code blocks.** A
1078
+ `[text](target)` shown inside a fenced block or inline backticks was resolved
1079
+ against disk like any real link, so documentation that demonstrates Markdown
1080
+ syntax could only pass by enumerating every example target in
1081
+ `checks.links.allowlist`. Fence state is now tracked across the file —
1082
+ honoring CommonMark on marker length, tilde fences, and info strings — and
1083
+ inline code spans are masked before parsing. Two limits are deliberate and
1084
+ tested: 4-space indented blocks are still checked, since indentation is
1085
+ ambiguous with list continuation, and code spans straddling a newline stay
1086
+ unmatched. The check only became less strict, so a repo passing on 0.9.0
1087
+ still passes.
1088
+
1089
+ ## [0.9.0] - 2026-07-28
1090
+
1091
+ ### Added
1092
+
1093
+ - **A PreToolUse deny hook guards checkride's accounting files.** "Never add
1094
+ to the baseline to make a check pass" was README advice that an agent
1095
+ editing the file has every local incentive to ignore; it is now
1096
+ enforcement. Edits (Edit/Write/NotebookEdit) to `checkride.baseline.json`
1097
+ and `.check/**` are blocked with a message explaining the ratchet; reads
1098
+ are never denied — the stanza's own triage procedure and both plugin
1099
+ skills read `.check/` artifacts. Both paths are unambiguous, so the deny
1100
+ is exact; module-boundary rules deliberately stay in ast-grep, where they
1101
+ are enforced over parsed code rather than approximated by paths.
1102
+ - **The gate now skips turns that edited nothing.** A new PostToolUse hook
1103
+ (`dirty`) touches `.check/.dirty` on every Edit/Write/NotebookEdit; the gate
1104
+ script exits 0 immediately when the marker is absent and clears it after a
1105
+ green run. Stop fires at the end of *every* turn, including
1106
+ pure-conversation ones — on a real repo that was a multi-second pipeline
1107
+ tax (plus formatter writes) for a turn that changed nothing, the single
1108
+ best reason to disable the gate. File writes made through Bash don't set
1109
+ the marker (a known, accepted gap — matching Bash would fire on every
1110
+ command); the next tool-edited turn re-covers it. A `--hook gate`
1111
+ selection without `dirty` writes an unconditional script, so the guard can
1112
+ never disarm a gate that has no marker source.
1113
+ - **The generated gate runs `--strict --digest`.** The Stop hook is a gate,
1114
+ and the contract says anything that gates should pass `--strict` — now
1115
+ checkride's own generated hook does. `--digest` writes the token-bounded
1116
+ `.check/digest.md` on red, and the guidance message points there when it
1117
+ exists (summary.json otherwise) and names `/checkride:check` for full
1118
+ triage. npm repos get the `--` passthrough form; pnpm/yarn/bun forward
1119
+ flags directly.
1120
+
1121
+ ### Changed
1122
+
1123
+ - **The Stop-hook gate moved into a checkride-owned script,
1124
+ `.claude/hooks/checkride-gate.sh`.** The settings.json entry is now a stable
1125
+ one-liner invoking it (`sh "${CLAUDE_PROJECT_DIR:-.}/…"`), so refreshing is
1126
+ genuinely lossless: checkride overwrites its script freely, and consumer
1127
+ customization has a natural home (a sibling script, or the environment)
1128
+ instead of an inline command the next `agent-setup` would clobber. Repos
1129
+ carrying the old inline form are migrated in place on the next
1130
+ `agent-setup`/`init` — detected by the existing sentinel and replaced, never
1131
+ duplicated. `init`/`agent-setup` grow `--hook <a,b>` to select which hooks
1132
+ to write (default: all); `--no-hook` remains the write-none escape.
1133
+
1134
+ ### Fixed
1135
+
1136
+ - **The `security` slot now gates at `--audit-level`, not at any advisory.**
1137
+ pnpm's JSON mode exits 1 on *any* advisory regardless of `--audit-level`
1138
+ (only table mode lets the level gate the exit code — reproduced with a
1139
+ single-moderate lockfile: `--json` exits 1, table exits 0), so the slot
1140
+ judged by exit code effectively gated at zero advisories of any severity
1141
+ rather than the declared `high`. `security` is now a built-in evaluator: it
1142
+ runs the audit with `--json`, parses `metadata.vulnerabilities`, and fails
1143
+ only on advisories at or above the threshold parsed from the adapter's own
1144
+ `--audit-level` arg — so a consumer override keeps meaning what it says,
1145
+ and "audit could not run" (registry unreachable, malformed output) stays a
1146
+ failure rather than a silent pass. Consumers who dropped `--json` to work
1147
+ around this can return to the default args and get the
1148
+ `.check/security.json` artifact back (`--json` is appended if an override
1149
+ omits it).
1150
+ - **`pack` no longer fails hard under pnpm older than 10.26.** pnpm only
1151
+ learned `pack --dry-run` in 10.26.0; every earlier pnpm — including 10.18.x
1152
+ lines consumers actually pin — rejects the flag with
1153
+ `Unknown option: 'dry-run'`, and the slot failed on a manager checkride
1154
+ has promised since 0.5.0 (reported by a real consumer on 10.18.1). On that
1155
+ rejection checkride now falls back to a real pack into a temp destination
1156
+ outside the repo (`pnpm pack --json --pack-destination <tmp>` — same
1157
+ npm-shaped `files[].path` JSON, verified back to 10.18.1), with lifecycle
1158
+ scripts suppressed the same way, and deletes the tarball afterward whether
1159
+ the check passes or not. Capability fallback, not version sniffing, so it
1160
+ keeps working wherever the flag is missing.
1161
+ - **The default concurrency no longer collapses to 1 on a 2-core CI runner.**
1162
+ `defaultConcurrency` reserved a core to keep the machine responsive — sound
1163
+ on a laptop, inapplicable on a hosted runner with no human at it. A
1164
+ standard GitHub-hosted runner reports 2 CPUs, so the pool was 1 and a
1165
+ wave-scheduled config executed fully sequentially (measured on a real
1166
+ consumer: 27.4s wall against 27.4s of summed check time — 1.00x), on
1167
+ exactly the machine class the docs say to gate on. With `CI` set (every
1168
+ provider sets it) no core is reserved, so that runner now gets a pool of 2.
1169
+ The cap of 4, the `--concurrency` override, and `--bail`'s sequential
1170
+ behavior are unchanged. How much a 2-wide pool on 2 cores buys depends on
1171
+ the mix: checks dominated by process startup and file discovery overlap
1172
+ well; two CPU-bound tools may be closer to a wash. It is never slower than
1173
+ the serialized pool it replaces, and `order` waves now schedule as
1174
+ documented. docs/ci.md explains the runner-size effect and how to read
1175
+ `total_duration_ms` against the per-check sum.
1176
+ - **The AGENTS.md stanza reports the configured gate, not the detected one.**
1177
+ `init` and `agent-setup` derived the stanza's "Active checks in this repo"
1178
+ line from the adoption inventory, which has detection semantics and never
1179
+ reads `checkride.config.json` — so opt-in slots a config entry opts in
1180
+ (`format`, `build`, …) and non-catalogue custom checks were silently missing
1181
+ from the agent-facing contract while the gate ran them. The stanza now
1182
+ derives from the same selection a default run makes (the pattern `doctor`
1183
+ already used), so what it names and what `pnpm check` runs agree.
1184
+
1185
+ ### Internal
1186
+
1187
+ - The design record for this release is
1188
+ [docs/spec-agent-setup-upgrade.md](./docs/spec-agent-setup-upgrade.md): two
1189
+ consumers' reports validated against source, with the measured pnpm version
1190
+ boundaries. docs/getting-started.md's hard-gate section was rewritten for
1191
+ the three-hook suite.
1192
+
1193
+ ## [0.8.1] - 2026-07-28
1194
+
1195
+ ### Fixed
1196
+
1197
+ - **A package-manager banner on stdout no longer fails the JSON-reading slots.**
1198
+ pnpm verifies dependencies before `exec`/`run` and narrates it on **stdout**
1199
+ (`Already up to date`, `Done in Xms using pnpm vN`) whenever no outer pnpm
1200
+ process has already done so. That preamble landed ahead of the tool's own
1201
+ JSON, so `checkride` invoked directly — `node dist/cli.js`, which is what the
1202
+ bundled triage reader and any non-pnpm-wrapped call does — failed `dead`,
1203
+ `dupes` and `health` with "fallow did not emit valid JSON" while the tools
1204
+ themselves exited 0, and silently cost `lint`, `struct` and `attw` their JSON
1205
+ artifacts. The same gate under `pnpm run check` passed, which is what made it
1206
+ hard to see. Fixed at both ends: checkride now passes
1207
+ `--config.verify-deps-before-run=false` ahead of `exec`/`run` under pnpm (the
1208
+ only form that works — `--silent` does not suppress it, and after `exec` pnpm
1209
+ reads it as the tool's argument), and every stdout-JSON parse tolerates up to
1210
+ ten leading non-JSON lines, because a consumer's launcher is not checkride's
1211
+ to pin. The `<slot>.json` artifact is written from the first JSON character
1212
+ on, so it parses on its own; nothing inside the tool's output is altered.
1213
+ - **`summary.json`'s `output_file` no longer names files that were never
1214
+ written.** It was copied from the adapter's declaration, so any run where a
1215
+ JSON-declaring tool emitted something else — a warning first, a crash, or the
1216
+ banner above — left the summary pointing at a `<slot>.json` that did not
1217
+ exist while the real bytes sat in `<slot>.stdout.txt` beside it. It is now
1218
+ what the run actually wrote, and `null` when that is nothing (including for a
1219
+ skipped check, which writes nothing at all). This is the field's documented
1220
+ meaning; see [docs/contract.md](./docs/contract.md).
1221
+
1222
+ ## [0.8.0] - 2026-07-28
1223
+
1224
+ ### Added
1225
+
1226
+ - **A bundled Claude Code plugin, shipped from the package root.** The published
1227
+ package now carries `.claude-plugin/plugin.json` and a `skills/` directory, so
1228
+ `checkride` installs as a plugin from the same npm tarball you already depend
1229
+ on (`/plugin marketplace add robmclarty/agent-tools`, then
1230
+ `/plugin install checkride@robmclarty`). It is bundled rather than published
1231
+ separately so the readers version in lockstep with the `.check/` contract they
1232
+ parse; a test asserts the manifest's version equals `package.json`'s. Nothing
1233
+ about the CLI, the exit codes or the `.check/` contract changes, installed or
1234
+ not. See [docs/plugin.md](./docs/plugin.md).
1235
+ - **`/checkride:check` — triage a red gate.** Runs the repo's own `check` script
1236
+ (the definition of done, which may carry deliberate `--only`/`--skip`), branches
1237
+ on the promised 0/1/2 exit split, and names one root cause plus what it is
1238
+ deliberately not reading. It covers the contract corners a hand-read of
1239
+ `summary.json` gets wrong — exit 2 versus exit 1, vacuous green, a narrow run's
1240
+ green, `baselined` counts, `skipped` slots, `exit_code: -1`, an unexpected
1241
+ `schema_version`, stale artifacts, and a red gate whose compound `check` script
1242
+ died before checkride ran — then opens exactly one raw file. Artifacts are
1243
+ measured, never opened: a green 17-slot run renders in about 2 kB.
1244
+ - **`/checkride:qa` — read the quality signal.** Folds `mutation.json`,
1245
+ `dead.json`, `dupes.json` and `health.json` into a report under 8 kB (2.2 MB of
1246
+ mutation data becomes a ranked page), runs nothing at all, and opens with a
1247
+ present / stale / not-opted-in ledger naming the command or config entry that
1248
+ would produce each missing artifact — three of the four come from opt-in slots,
1249
+ so partial data is the normal case.
1250
+ - **Two dependency-free readers behind those skills**, shipped prebuilt in
1251
+ `dist/` so a plugin install needs no build step, and runnable without Claude
1252
+ Code: `node node_modules/checkride/dist/triage/cli.js` and
1253
+ `node node_modules/checkride/dist/qa/cli.js`, each taking an optional repo
1254
+ path. Both use `node:` builtins only. Every artifact read is bounded, dated
1255
+ against the run that claims it (an artifact older than the run's start is
1256
+ labelled stale with its age, never silently dropped), and resolved by the
1257
+ documented `output_file` → `<slot>.json` → `<slot>.stdout.txt`/`.stderr.txt`
1258
+ convention, which matters because `output_file` is null for 8 of this repo's
1259
+ 17 slots — `test` among them.
1260
+ - **`examples/` — eight runnable projects, each demonstrating one thing.** They
1261
+ are standalone packages rather than workspace members, so you `cd` in, install,
1262
+ and run one without the rest of the repo: `agent-loop` (what an agent reads
1263
+ when the gate is red), `existing-repo-baseline` (adopting checkride on a repo
1264
+ that already has findings), `custom-checks` (the escape hatch — a bespoke
1265
+ formatter ahead of the built-ins, `detect`-gated checks that stand down),
1266
+ `shared-preset` (org-wide policy as one versioned package), `polyglot` (a
1267
+ Python repo where the TypeScript built-ins stand down and ast-grep enforces
1268
+ boundaries in Python), and `module-boundaries` / `dal-boundaries` /
1269
+ `dal-boundaries-declarative` for boundary enforcement — the last expressing a
1270
+ data-access policy with fallow zones and ast-grep rules alone, no custom script.
1271
+ Each declares the exit code it expects and `test/e2e/examples.e2e.test.ts`
1272
+ asserts it on every push, so an example that stops behaving as its README
1273
+ claims fails CI.
1274
+
1275
+ ### Changed
1276
+
1277
+ - **The AGENTS.md stanza written by `checkride init` and `checkride agent-setup`
1278
+ names `/checkride:check`.** Exactly one added line; the prose procedure is
1279
+ unchanged and still works standalone in a repo that never installs the plugin.
1280
+
1281
+ ### Internal
1282
+
1283
+ - `/version` now folds an existing `## [Unreleased]` changelog section into the
1284
+ new release heading instead of inserting above it.
1285
+
1286
+ ## [0.7.0] - 2026-07-22
1287
+
1288
+ ### Changed
1289
+
1290
+ - **checkride is now licensed [Apache-2.0](./LICENSE), previously MIT.** No code
1291
+ or API changed; the terms you receive it under did. Apache-2.0 adds an express
1292
+ patent grant and, via section 5, makes contributions inbound under the same
1293
+ license without a CLA — the reason for the switch, ahead of accepting outside
1294
+ contributions. Consumers redistributing checkride should note the section 4
1295
+ conditions (include the license, mark modified files); there is deliberately
1296
+ no `NOTICE` file, so nothing to propagate. `checkride init --license` still
1297
+ defaults to `MIT` for scaffolded projects — that remains the user's choice and
1298
+ is unaffected.
1299
+
1300
+ ### Internal
1301
+
1302
+ - `CONTRIBUTING.md` gains a licensing section: how section 5 works, why an
1303
+ employer may own contributions written on their time, and DCO sign-off via
1304
+ `git commit -s`.
1305
+
1306
+ ## [0.6.0] - 2026-07-21
1307
+
1308
+ ### Added
1309
+
1310
+ - **`optIn` override to configure a slot without opting it into the default
1311
+ run.** A slot entry (both the `{ use }` and custom `{ command }` forms) now
1312
+ takes `optIn?: boolean`: `true` configures the slot but keeps it reachable
1313
+ only via `--all`/`--include` (so giving `attw` a profile no longer drags
1314
+ `attw`/`build`/`pack`/`smoke` into a plain `checkride` run), and `false`
1315
+ forces a slot into the default run. Resolution-side only, so selection, the
1316
+ vacuous-green warning, and `doctor` all adjust automatically.
1317
+ - **Config-driven `exclude` and `allowlist` for the built-in `links` check.**
1318
+ `exclude` adds directory names to skip on top of the built-in set, and
1319
+ `allowlist` takes regex sources whose matching link targets count as valid
1320
+ (for generated or illustrative markdown that never resolves on disk).
1321
+ Patterns are compiled and validated at config load, so a bad one is a
1322
+ friendly exit-2 error rather than a mid-run crash. Both are declared in the
1323
+ JSON schema.
1324
+ - **`profile` shortcut for the `attw` slot.** A `profile` string on the config
1325
+ entry appends `--profile <name>` to attw's invocation instead of re-typing
1326
+ the full args array (a no-op on other slots; a non-string is a friendly
1327
+ config error).
1328
+
1329
+ ### Changed
1330
+
1331
+ - **`publint` and `attw` stand down when their tool isn't installed.** Both
1332
+ were always-available and hard-failed an `--all` run with "Command not found"
1333
+ on a repo that never installed them; they now gate on their package
1334
+ (`publint`, `@arethetypeswrong/cli`) and skip cleanly when absent, matching
1335
+ checkride's skip-not-fail principle for opt-in slots. Naming one explicitly
1336
+ in config still forces it to run.
1337
+
1338
+ ### Fixed
1339
+
1340
+ - **A single duplicate fingerprint no longer disables baselining for a whole
1341
+ fallow slot.** The baseline gate guarded against un-fingerprintable findings by
1342
+ comparing the deduplicated key count to the raw issue count, but that
1343
+ comparison could not tell a finding that produced *no* key apart from two
1344
+ findings that produced the *same* key. One key collision (two `<arrow>`
1345
+ functions in a file, or two symbol-less `unused_class_members`) made the slot
1346
+ look untracked, so `checkride baseline` followed by `checkride` stayed red
1347
+ forever. The gate now tracks an explicit un-keyed count instead, so a collision
1348
+ merely coarsens the baseline (grandfathering one finding grandfathers its twin)
1349
+ rather than disabling it, while a genuinely un-keyable finding still holds the
1350
+ slot red.
1351
+ - **Colliding fallow fingerprints are now disambiguated.** Anonymous health
1352
+ functions (`<arrow>`, `<anonymous>`) and symbol-less dead-code findings
1353
+ (`unused_class_members`) append their line and column, so a real sibling
1354
+ finding can no longer be masked by a baseline captured for a different one.
1355
+ Named functions and symbol-bearing findings keep their line-free keys.
1356
+
1357
+ This changes the fingerprint strings for the `dead` and `health` slots, which
1358
+ **invalidates existing `checkride.baseline.json` entries for those slots**.
1359
+ Re-run `checkride baseline` to recapture them.
1360
+
1361
+ ### Internal
1362
+
1363
+ - **Cleared all `pnpm audit` advisories** via `pnpm-workspace.yaml` overrides:
1364
+ `brace-expansion` (GHSA-3jxr-9vmj-r5cp) and `js-yaml` (GHSA-52cp-r559-cp3m)
1365
+ were high, `qs` (GHSA-q8mj-m7cp-5q26) and `markdown-it` (GHSA-6v5v-wf23-fmfq)
1366
+ moderate. Each is a transitive **dev** dependency (of stryker and
1367
+ markdownlint-cli2), so none reached the published package; each override is
1368
+ scoped to its advisory's vulnerable range and capped to the compatible major.
1369
+ - Docs: added a deep-modules guide and a fleet-presets guide for org-wide
1370
+ rollout, reframed swappable module boundaries as the default, dropped the
1371
+ "TypeScript-only" framing from the README, homepage, and npm package
1372
+ description, clarified the README for first-time readers, and pointed
1373
+ `package.json` `homepage` at the GitHub Pages site.
1374
+
1375
+ ## [0.5.3] - 2026-07-19
1376
+
1377
+ ### Internal
1378
+
1379
+ - **Docs: `docs/ordering-in-practice.md`** — a worked guide to `order`
1380
+ scheduling waves, now with a second example where waves encode a *dependency*
1381
+ (`build` produces `dist/`; the publish bundle — `publint`, `attw`, `pack`,
1382
+ `smoke`, `snippets-dist` — consumes it) rather than just a duration tier.
1383
+ - Adopt explicit ordering waves in checkride's own `checkride.config.json`, so
1384
+ the gate config states its execution plan instead of leaning on adapter
1385
+ defaults and pool arithmetic.
1386
+ - CI: bump `upload-pages-artifact` to v5 and drop the Node 20 pages runtime.
1387
+ - Expand the `cspell` dictionary with words the docs introduced.
1388
+
7
1389
  ## [0.5.2] - 2026-07-19
8
1390
 
9
1391
  ### Internal
@@ -465,6 +1847,28 @@ The first real release. (`0.0.0` was a name-claim placeholder.)
465
1847
  - Flags: `--only`, `--skip`, `--bail`, `--json`, `--changed`, `--all`,
466
1848
  `--include`.
467
1849
 
1850
+ [0.12.2]: https://www.npmjs.com/package/checkride/v/0.12.2
1851
+ [0.12.1]: https://www.npmjs.com/package/checkride/v/0.12.1
1852
+ [0.12.0]: https://www.npmjs.com/package/checkride/v/0.12.0
1853
+ [0.11.2]: https://www.npmjs.com/package/checkride/v/0.11.2
1854
+ [0.11.1]: https://www.npmjs.com/package/checkride/v/0.11.1
1855
+ [0.11.0]: https://www.npmjs.com/package/checkride/v/0.11.0
1856
+ [0.10.3]: https://www.npmjs.com/package/checkride/v/0.10.3
1857
+ [0.10.2]: https://www.npmjs.com/package/checkride/v/0.10.2
1858
+ [0.10.1]: https://www.npmjs.com/package/checkride/v/0.10.1
1859
+ [0.10.0]: https://www.npmjs.com/package/checkride/v/0.10.0
1860
+ [0.9.6]: https://www.npmjs.com/package/checkride/v/0.9.6
1861
+ [0.9.5]: https://www.npmjs.com/package/checkride/v/0.9.5
1862
+ [0.9.4]: https://www.npmjs.com/package/checkride/v/0.9.4
1863
+ [0.9.3]: https://www.npmjs.com/package/checkride/v/0.9.3
1864
+ [0.9.2]: https://www.npmjs.com/package/checkride/v/0.9.2
1865
+ [0.9.1]: https://www.npmjs.com/package/checkride/v/0.9.1
1866
+ [0.9.0]: https://www.npmjs.com/package/checkride/v/0.9.0
1867
+ [0.8.1]: https://www.npmjs.com/package/checkride/v/0.8.1
1868
+ [0.8.0]: https://www.npmjs.com/package/checkride/v/0.8.0
1869
+ [0.7.0]: https://www.npmjs.com/package/checkride/v/0.7.0
1870
+ [0.6.0]: https://www.npmjs.com/package/checkride/v/0.6.0
1871
+ [0.5.3]: https://www.npmjs.com/package/checkride/v/0.5.3
468
1872
  [0.5.2]: https://www.npmjs.com/package/checkride/v/0.5.2
469
1873
  [0.5.1]: https://www.npmjs.com/package/checkride/v/0.5.1
470
1874
  [0.5.0]: https://www.npmjs.com/package/checkride/v/0.5.0