plumbbob 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +15 -13
  3. package/dist/cli-core.js +19 -2
  4. package/dist/lib/check.js +46 -13
  5. package/dist/lib/intent.js +1 -1
  6. package/dist/lib/latch.js +3 -3
  7. package/dist/lib/reentry.js +70 -0
  8. package/dist/verbs/agent.js +2 -2
  9. package/dist/verbs/doctor.js +4 -4
  10. package/dist/verbs/finish.js +1 -1
  11. package/dist/verbs/recover.js +297 -0
  12. package/dist/verbs/revert.js +2 -2
  13. package/dist/verbs/spike.js +8 -2
  14. package/hooks/post-edit.sh +4 -4
  15. package/hooks/pre-bash-commit.sh +3 -3
  16. package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
  17. package/node_modules/checkride/CHANGELOG.md +1003 -0
  18. package/node_modules/checkride/LICENSE +201 -21
  19. package/node_modules/checkride/README.md +171 -30
  20. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
  21. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
  22. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
  23. package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
  24. package/node_modules/checkride/dist/adapters.d.ts +18 -6
  25. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  26. package/node_modules/checkride/dist/adapters.js +42 -23
  27. package/node_modules/checkride/dist/adapters.js.map +1 -1
  28. package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
  29. package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
  30. package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
  31. package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
  32. package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
  33. package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
  34. package/node_modules/checkride/dist/agent-setup/command.js +77 -0
  35. package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
  36. package/node_modules/checkride/dist/agent-setup/cursor.d.ts +61 -0
  37. package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
  38. package/node_modules/checkride/dist/agent-setup/cursor.js +189 -0
  39. package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
  40. package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
  41. package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
  42. package/node_modules/checkride/dist/agent-setup/files.js +80 -0
  43. package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
  44. package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
  45. package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
  46. package/node_modules/checkride/dist/agent-setup/harness.js +84 -0
  47. package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
  48. package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
  49. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  50. package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
  51. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  52. package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
  53. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  54. package/node_modules/checkride/dist/agent-setup/index.js +9 -4
  55. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  56. package/node_modules/checkride/dist/agent-setup/scripts.d.ts +58 -0
  57. package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
  58. package/node_modules/checkride/dist/agent-setup/scripts.js +299 -0
  59. package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
  60. package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
  61. package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
  62. package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
  63. package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
  64. package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
  65. package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
  66. package/node_modules/checkride/dist/artifacts/configured.js +45 -0
  67. package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
  68. package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
  69. package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
  70. package/node_modules/checkride/dist/artifacts/format.js +56 -0
  71. package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
  72. package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
  73. package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
  74. package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
  75. package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
  76. package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
  77. package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
  78. package/node_modules/checkride/dist/artifacts/index.js +19 -0
  79. package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
  80. package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
  81. package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
  82. package/node_modules/checkride/dist/artifacts/json.js +47 -0
  83. package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
  84. package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
  85. package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
  86. package/node_modules/checkride/dist/artifacts/raw.js +91 -0
  87. package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
  88. package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
  89. package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
  90. package/node_modules/checkride/dist/artifacts/summary.js +108 -0
  91. package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
  92. package/node_modules/checkride/dist/atomic.d.ts +16 -1
  93. package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
  94. package/node_modules/checkride/dist/atomic.js +25 -3
  95. package/node_modules/checkride/dist/atomic.js.map +1 -1
  96. package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
  97. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
  98. package/node_modules/checkride/dist/baseline/fallow.js +161 -58
  99. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
  100. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +7 -7
  101. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  102. package/node_modules/checkride/dist/baseline/fingerprint.js +16 -19
  103. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  104. package/node_modules/checkride/dist/baseline/index.d.ts +4 -4
  105. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  106. package/node_modules/checkride/dist/baseline/index.js +4 -4
  107. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  108. package/node_modules/checkride/dist/baseline/store.d.ts +12 -4
  109. package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
  110. package/node_modules/checkride/dist/baseline/store.js +16 -9
  111. package/node_modules/checkride/dist/baseline/store.js.map +1 -1
  112. package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
  113. package/node_modules/checkride/dist/baseline-command.js +7 -7
  114. package/node_modules/checkride/dist/baseline-command.js.map +1 -1
  115. package/node_modules/checkride/dist/cli.d.ts +17 -4
  116. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  117. package/node_modules/checkride/dist/cli.js +259 -18
  118. package/node_modules/checkride/dist/cli.js.map +1 -1
  119. package/node_modules/checkride/dist/config.d.ts +84 -8
  120. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  121. package/node_modules/checkride/dist/config.js +122 -18
  122. package/node_modules/checkride/dist/config.js.map +1 -1
  123. package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
  124. package/node_modules/checkride/dist/digest/digest.js +5 -5
  125. package/node_modules/checkride/dist/digest/index.d.ts +2 -2
  126. package/node_modules/checkride/dist/digest/index.js +2 -2
  127. package/node_modules/checkride/dist/doctor.d.ts +25 -6
  128. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  129. package/node_modules/checkride/dist/doctor.js +193 -27
  130. package/node_modules/checkride/dist/doctor.js.map +1 -1
  131. package/node_modules/checkride/dist/gate.d.ts +135 -0
  132. package/node_modules/checkride/dist/gate.d.ts.map +1 -0
  133. package/node_modules/checkride/dist/gate.js +490 -0
  134. package/node_modules/checkride/dist/gate.js.map +1 -0
  135. package/node_modules/checkride/dist/init.d.ts +72 -12
  136. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  137. package/node_modules/checkride/dist/init.js +324 -81
  138. package/node_modules/checkride/dist/init.js.map +1 -1
  139. package/node_modules/checkride/dist/json.d.ts +21 -0
  140. package/node_modules/checkride/dist/json.d.ts.map +1 -0
  141. package/node_modules/checkride/dist/json.js +21 -0
  142. package/node_modules/checkride/dist/json.js.map +1 -0
  143. package/node_modules/checkride/dist/links.d.ts +31 -5
  144. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  145. package/node_modules/checkride/dist/links.js +141 -32
  146. package/node_modules/checkride/dist/links.js.map +1 -1
  147. package/node_modules/checkride/dist/node-pin.d.ts +129 -0
  148. package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
  149. package/node_modules/checkride/dist/node-pin.js +211 -0
  150. package/node_modules/checkride/dist/node-pin.js.map +1 -0
  151. package/node_modules/checkride/dist/orchestrator.d.ts +40 -10
  152. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  153. package/node_modules/checkride/dist/orchestrator.js +196 -102
  154. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  155. package/node_modules/checkride/dist/pack.d.ts +8 -9
  156. package/node_modules/checkride/dist/pack.d.ts.map +1 -1
  157. package/node_modules/checkride/dist/pack.js +53 -17
  158. package/node_modules/checkride/dist/pack.js.map +1 -1
  159. package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
  160. package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
  161. package/node_modules/checkride/dist/pm/detect.js +6 -1
  162. package/node_modules/checkride/dist/pm/detect.js.map +1 -1
  163. package/node_modules/checkride/dist/pm/index.d.ts +6 -2
  164. package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
  165. package/node_modules/checkride/dist/pm/index.js +6 -2
  166. package/node_modules/checkride/dist/pm/index.js.map +1 -1
  167. package/node_modules/checkride/dist/pm/launch.d.ts +51 -0
  168. package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
  169. package/node_modules/checkride/dist/pm/launch.js +92 -0
  170. package/node_modules/checkride/dist/pm/launch.js.map +1 -0
  171. package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
  172. package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
  173. package/node_modules/checkride/dist/pm/tools.js +124 -0
  174. package/node_modules/checkride/dist/pm/tools.js.map +1 -0
  175. package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
  176. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  177. package/node_modules/checkride/dist/pm/translate.js +78 -18
  178. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  179. package/node_modules/checkride/dist/proc.d.ts +30 -0
  180. package/node_modules/checkride/dist/proc.d.ts.map +1 -0
  181. package/node_modules/checkride/dist/proc.js +42 -0
  182. package/node_modules/checkride/dist/proc.js.map +1 -0
  183. package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
  184. package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
  185. package/node_modules/checkride/dist/qa/cli.js +24 -0
  186. package/node_modules/checkride/dist/qa/cli.js.map +1 -0
  187. package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
  188. package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
  189. package/node_modules/checkride/dist/qa/dead.js +88 -0
  190. package/node_modules/checkride/dist/qa/dead.js.map +1 -0
  191. package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
  192. package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
  193. package/node_modules/checkride/dist/qa/dupes.js +62 -0
  194. package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
  195. package/node_modules/checkride/dist/qa/health.d.ts +85 -0
  196. package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
  197. package/node_modules/checkride/dist/qa/health.js +109 -0
  198. package/node_modules/checkride/dist/qa/health.js.map +1 -0
  199. package/node_modules/checkride/dist/qa/index.d.ts +23 -0
  200. package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
  201. package/node_modules/checkride/dist/qa/index.js +22 -0
  202. package/node_modules/checkride/dist/qa/index.js.map +1 -0
  203. package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
  204. package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
  205. package/node_modules/checkride/dist/qa/mutation.js +195 -0
  206. package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
  207. package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
  208. package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
  209. package/node_modules/checkride/dist/qa/qa.js +158 -0
  210. package/node_modules/checkride/dist/qa/qa.js.map +1 -0
  211. package/node_modules/checkride/dist/qa/read.d.ts +40 -0
  212. package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
  213. package/node_modules/checkride/dist/qa/read.js +47 -0
  214. package/node_modules/checkride/dist/qa/read.js.map +1 -0
  215. package/node_modules/checkride/dist/qa/render.d.ts +26 -0
  216. package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
  217. package/node_modules/checkride/dist/qa/render.js +310 -0
  218. package/node_modules/checkride/dist/qa/render.js.map +1 -0
  219. package/node_modules/checkride/dist/security.d.ts +45 -0
  220. package/node_modules/checkride/dist/security.d.ts.map +1 -0
  221. package/node_modules/checkride/dist/security.js +86 -0
  222. package/node_modules/checkride/dist/security.js.map +1 -0
  223. package/node_modules/checkride/dist/smoke.d.ts +5 -5
  224. package/node_modules/checkride/dist/smoke.js +5 -5
  225. package/node_modules/checkride/dist/snippets.d.ts +26 -29
  226. package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
  227. package/node_modules/checkride/dist/snippets.js +26 -30
  228. package/node_modules/checkride/dist/snippets.js.map +1 -1
  229. package/node_modules/checkride/dist/tool-json.d.ts +33 -0
  230. package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
  231. package/node_modules/checkride/dist/tool-json.js +58 -0
  232. package/node_modules/checkride/dist/tool-json.js.map +1 -0
  233. package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
  234. package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
  235. package/node_modules/checkride/dist/triage/cli.js +26 -0
  236. package/node_modules/checkride/dist/triage/cli.js.map +1 -0
  237. package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
  238. package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
  239. package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
  240. package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
  241. package/node_modules/checkride/dist/triage/env.d.ts +54 -0
  242. package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
  243. package/node_modules/checkride/dist/triage/env.js +81 -0
  244. package/node_modules/checkride/dist/triage/env.js.map +1 -0
  245. package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
  246. package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
  247. package/node_modules/checkride/dist/triage/gate.js +121 -0
  248. package/node_modules/checkride/dist/triage/gate.js.map +1 -0
  249. package/node_modules/checkride/dist/triage/index.d.ts +22 -0
  250. package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
  251. package/node_modules/checkride/dist/triage/index.js +20 -0
  252. package/node_modules/checkride/dist/triage/index.js.map +1 -0
  253. package/node_modules/checkride/dist/triage/render.d.ts +19 -0
  254. package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
  255. package/node_modules/checkride/dist/triage/render.js +419 -0
  256. package/node_modules/checkride/dist/triage/render.js.map +1 -0
  257. package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
  258. package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
  259. package/node_modules/checkride/dist/triage/triage.js +131 -0
  260. package/node_modules/checkride/dist/triage/triage.js.map +1 -0
  261. package/node_modules/checkride/package.json +17 -15
  262. package/node_modules/checkride/schema/checkride.config.schema.json +62 -5
  263. package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
  264. package/node_modules/checkride/skills/check/SKILL.md +257 -0
  265. package/node_modules/checkride/skills/qa/SKILL.md +231 -0
  266. package/node_modules/checkride/templates/shared/cspell.json +1 -0
  267. package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
  268. package/package.json +2 -2
  269. package/skills/build/SKILL.md +23 -19
  270. package/skills/finish/SKILL.md +3 -1
  271. package/skills/harvest/SKILL.md +2 -1
  272. package/skills/plan/SKILL.md +8 -7
  273. package/skills/recover/SKILL.md +28 -0
  274. package/skills/refine/SKILL.md +5 -4
  275. package/skills/spike/SKILL.md +1 -1
  276. package/skills/step/SKILL.md +4 -4
  277. package/skills/verify/SKILL.md +19 -14
  278. package/templates/intent.md +8 -7
@@ -4,6 +4,994 @@ 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.10.2] - 2026-07-31
8
+
9
+ ### Added
10
+
11
+ - **A gate profile, for repos where the full pipeline is too slow to run every
12
+ turn.** `checkride.config.json` gains a `gate` key — `only`, `skip`, `changed`
13
+ — that applies to the stop-gate run and nothing else:
14
+
15
+ ```json
16
+ { "gate": { "only": ["types", "lint", "struct"], "changed": true } }
17
+ ```
18
+
19
+ The flags are appended after the check script's own, so the profile wins over
20
+ anything the script already carried. The intended shape is a fast gate per
21
+ turn with the full check still binding at commit or in CI; the gate is not the
22
+ only place "done" can be enforced, and in a large repo it should not be the
23
+ expensive one.
24
+
25
+ **Every verdict a profiled gate emits says it was a profile**, in those words:
26
+ `checkride ✔ green in 4.1s — gate profile: only types, lint — NOT the full
27
+ check`. That is the price of the feature and it is deliberately not
28
+ configurable. A gate that runs three of eighteen slots and reports a bare
29
+ green has told the reader the work is done, which is the one thing it does not
30
+ know — the vacuous pass this tool exists to prevent, reached from the
31
+ comfortable direction rather than the alarming one. A narrowed red points past
32
+ itself for the same reason. A profile that narrows nothing is treated as no
33
+ profile, so the warning never appears on a run that was in fact complete.
34
+
35
+ - **checkride aligns the gate to the repo's Node pin.** Before running the check
36
+ script, if the repo names an exact interpreter (`.nvmrc` or `.node-version`)
37
+ and the running Node does not satisfy it, checkride puts a matching installed
38
+ Node in front of the child's `PATH` — so a hook that arrived on the wrong Node
39
+ runs the pipeline on the right one instead of failing to run it at all.
40
+
41
+ Four rules bound it, because choosing the interpreter a repo's whole pipeline
42
+ runs on is not a small thing to do silently: only on an explicit pin file
43
+ (`engines.node` is a *range* — a compatibility declaration, not an instruction
44
+ to switch interpreters — and is read for diagnosis only); only when the running
45
+ Node does not already satisfy it; only an interpreter already installed under a
46
+ known layout (nvm, fnm, nodenv, asdf, volta, n — nothing is downloaded and no
47
+ version manager is invoked, since under a non-login shell `nvm` is a shell
48
+ function and not a binary); and never without a line on stderr saying what it
49
+ aligned to and why.
50
+
51
+ `CHECKRIDE_NODE_BIN` is the escape hatch in both directions: a directory to use
52
+ verbatim — the documented wrapping point for a layout checkride does not know —
53
+ or `off` to disable alignment entirely. When alignment cannot help, nothing
54
+ changes and the refusal above names the pin, the Node that ran, and the lever.
55
+
56
+ - **`doctor` reports the Node a hook would get, not just this shell's.** Every
57
+ other row describes the terminal `doctor` was typed into, which is how it said
58
+ `environment ok` about a repo whose gate could not start — the bug was
59
+ invisible to every read-only command checkride had. The new `node pin` row asks
60
+ the question that *is* answerable from any shell: given the repo's pin, could
61
+ checkride put the right Node back if a hook arrived with the wrong one? It is
62
+ never required, so it flags a risk without failing a repo that works.
63
+
64
+ - **`checkride hooks add <a,b>` / `checkride hooks remove <a,b>` — hook
65
+ management that never touches AGENTS.md.** `agent-setup` writes four things at
66
+ once (the `check` alias, the stanza, the hooks, the Cursor skills) with the
67
+ stanza guard in front of all of them, so managing a hook inherited the stanza's
68
+ problems: a repo whose AGENTS.md had been edited could not remove a hook *at
69
+ all* — the run refused and, by contract, wrote nothing — and a repo mid-upgrade
70
+ had its stanza rewritten by a command it had asked to touch hooks.
71
+
72
+ The new command writes only the harness config entries and the generated
73
+ scripts. It never reads or writes AGENTS.md, so no state of that file can block
74
+ it and no run of it can change that file. `--harness <a,b>` and `--dry-run`
75
+ both apply, and both directions are idempotent. `add` defaults to every hook;
76
+ `remove` requires an explicit list, because a bare command that tore out the
77
+ gate would be the silent un-gating checkride exists to prevent.
78
+
79
+ `agent-setup` is unchanged, flags and refusal included.
80
+
81
+ ### Changed
82
+
83
+ - **checkride generates one file for Claude Code, not four.** `protect` is now a
84
+ pair of `permissions.deny` rules and `dirty` is an inline command, so
85
+ `.claude/hooks/checkride-protect.cjs` and `.claude/hooks/checkride-dirty.sh`
86
+ are gone. Both are deleted automatically on the next `agent-setup`, `init`, or
87
+ `hooks add`.
88
+
89
+ `protect` gains more than tidiness from the move. Claude Code evaluates a deny
90
+ rule **regardless of what a `PreToolUse` hook returns**, so the same paths are
91
+ now guarded a layer below the hook that used to guard them — and at no cost
92
+ per tool call, where the script spawned Node on every single edit while
93
+ allowing essentially all of them. The rules are also a list checkride
94
+ *appends* to and removes only its own entries from, so a repo can add its own
95
+ suppression files beside them (a `fallow.baseline`, a lint baseline) and keep
96
+ them across every refresh. The generated script could never offer that: it was
97
+ checkride-owned and overwritten wholesale on every run.
98
+
99
+ ```json
100
+ "permissions": {
101
+ "deny": ["Edit(**/checkride.baseline.json)", "Edit(**/.check/**)"]
102
+ }
103
+ ```
104
+
105
+ `Edit(...)` is the only correct spelling, and getting it wrong fails silently:
106
+ Claude Code checks file paths against `Edit` and `Read` rules only, and a
107
+ `Write(...)` or `NotebookEdit(...)` path rule is accepted, never consulted, and
108
+ warns at startup. An `Edit` rule covers every file-editing tool. `Read` deny
109
+ rules stay deliberately absent — triage reads `.check/` artifacts.
110
+
111
+ Nothing moves in the user-facing surface: `--hook protect`,
112
+ `--remove-hook protect`, and `checkride hooks add|remove protect` all mean what
113
+ they meant. **Cursor is unchanged** and still gets all three scripts; its
114
+ config is hooks and nothing else, with no documented file-path deny list to
115
+ move `protect` into.
116
+
117
+ - **`checkride hooks` counts files changed, not files deleted.** Removing a hook
118
+ a harness expresses as configuration rewrites the config and deletes nothing,
119
+ and the old wording reported that as `removed 0 file(s)` — a run that did
120
+ exactly what was asked, reading as a no-op. The line now names the hooks and
121
+ counts every file it touched.
122
+
123
+ - **The AGENTS.md stanza no longer prescribes an architecture.** It carried
124
+ checkride's own house style as if it were law — "Named exports only; no
125
+ classes; `.js` extensions on relative imports", plus the barrel-`index.ts`
126
+ rules — into every repo that ran `init` or `agent-setup`. For a repo that uses
127
+ classes, default exports, or bundler-style extensionless imports, that is a
128
+ contract file instructing the agent to write the wrong code. The stanza is
129
+ also the one region a repo cannot correct: editing it is what makes the next
130
+ refresh refuse.
131
+
132
+ The `### Module boundaries` section now names where the rules live instead of
133
+ what they say — the `struct` check runs whatever ast-grep rules `sgconfig.yml`
134
+ points at, and those files are the convention. It is emitted only when
135
+ `struct` is an active check; a repo without it gets no section at all. Delete
136
+ `rules/no-class.yml` and the stanza stays true, which is the property it was
137
+ missing.
138
+
139
+ `init` still *scaffolds* those rules as its opinionated default — that part is
140
+ a starter you own and can edit, and is unchanged.
141
+
142
+ - **`--add struct` scaffolds the boundary rule and stops.** It used to write
143
+ four ast-grep rules, three of which are style, not structure: `no-class`,
144
+ `no-default-export`, and `require-js-extension`. A repo that uses classes,
145
+ default exports, or bundler resolution went red the moment it adopted the
146
+ slot — for code it had always written, over a convention it never chose, which
147
+ reads as checkride being broken rather than as a finding. `--add struct` now
148
+ writes `sgconfig.yml` and `rules/no-deep-sibling-import.yml`: the rule that
149
+ makes `struct` the slot it is, and one whose failures mean what they say.
150
+
151
+ New-mode `init` still writes all four. That is a different bargain — it is
152
+ creating the package, so there is no prior decision to override — and the
153
+ other rules are listed in `docs/deep-modules.md` for any repo that wants them.
154
+
155
+ ### Fixed
156
+
157
+ - **A package manager that refuses to start the check script is no longer
158
+ reported as a red pipeline.** In any repo pinning `engines.node`, pnpm answers
159
+ a Node it does not accept with `ERR_PNPM_UNSUPPORTED_ENGINE` and **exit 1** —
160
+ the same exit code a failing test uses. The gate read that as a verdict on the
161
+ code and said so: `checkride ✘ red in 265ms`, exit 2, turn blocked, every turn,
162
+ regardless of what was written. It then sent the reader to
163
+ `.check/summary.json`, which no run had written, because no check had run.
164
+
165
+ That is the vacuous gate inverted — not a silent green but a permanent red no
166
+ code change can clear, whose rational response is to switch the gate off. And
167
+ it is not one repo's problem: agent harnesses run hooks in a **non-login
168
+ shell**, which never sources the rc file a version manager puts its shims in,
169
+ so the hook gets the machine's default Node rather than the contributor's. On
170
+ nvm that is the norm, not the exception.
171
+
172
+ `gate` now answers a third verdict — **could not run** — for a launch that
173
+ never happened, naming the cause and saying plainly that nothing ran and no
174
+ artifact describes this turn. It still blocks, in both harnesses: an unrunnable
175
+ gate has never been a pass. `triage` gained the matching `could-not-start`
176
+ verdict, since it runs the same script and had folded the same exit into `red`,
177
+ confirming the gate's wrong answer to whoever came asking why.
178
+
179
+ The classification is guarded on both sides. Only pnpm's and npm's own error
180
+ codes are matched — npm's `EBADENGINE` *warning* deliberately is not, because
181
+ npm runs the script anyway unless `engine-strict` is set — and a summary
182
+ written by the current run vetoes the whole thing, so a check that merely
183
+ *printed* one of these strings can never be reclassified into an environment
184
+ problem.
185
+
186
+ - **Upgrading no longer looks like an edit.** 0.10.1 started stamping the stanza
187
+ so a refresh could tell its own output from a customization — but a stanza
188
+ written *before* the stamp carries no evidence either way, so every upgrading
189
+ repo read as `unstamped`, refused, and by contract wrote **nothing**: not the
190
+ stanza, not the config, not the hooks. That is how 0.10.1's own gate-timeout
191
+ fix failed to reach the repos it was written for. The guard protecting AGENTS.md
192
+ prose was withholding a fix for a gate that had silently stopped gating.
193
+
194
+ checkride now recognizes the wordings it actually shipped — four distinct texts
195
+ across v0.1.1–v0.10.0, matched on everything but the generated
196
+ `Active checks in this repo:` line, so a repo's own slot list is irrelevant. A
197
+ pre-stamp stanza that matches one is checkride's own output, refreshes without
198
+ `--force`, and comes back stamped; one that matches nothing has been edited and
199
+ is still refused, with a message that now says so plainly instead of hedging
200
+ about indistinguishable wordings.
201
+
202
+ The list is closed and needs no maintenance: every release from 0.10.1 stamps
203
+ what it writes, so no future version can add an unstamped wording.
204
+
205
+ - **The AGENTS.md stanza is package-manager-agnostic.** It hardcoded `pnpm
206
+ check` and `pnpm exec checkride triage` regardless of the repo's package
207
+ manager, while checkride detects that manager everywhere else and README
208
+ promises the tool is agnostic. The stanza is a list of commands an agent is
209
+ told to run, so in an npm repo it was an instruction that cannot succeed: the
210
+ agent either invents a substitute or reports the pipeline as broken. It now
211
+ renders `npm run check` / `npx --no-install checkride triage`, `yarn run
212
+ check`, or `bun run check` from the detected manager, and `pnpm check` is
213
+ unchanged for pnpm repos.
214
+
215
+ The Claude Code gate's `statusMessage` had the same bug from the same source
216
+ and is fixed with it — the spinner said `npm check`, which is not a command,
217
+ and `yarn check` names Yarn 1's integrity checker rather than the repo's
218
+ script.
219
+
220
+ - **The `spell` check no longer trips over checkride's own stanza hash.** The
221
+ `hash=v1…` marker is 16 hex digits, so sooner or later it spells something
222
+ cspell does not recognize (`…fedf…`) and fails a slot for a string checkride
223
+ itself generated. The shipped `cspell.json` now ignores the marker.
224
+
225
+ ### Contract
226
+
227
+ - **A gate that could not run answers "could not run", not "red"**
228
+ (docs/contract.md §CLI). The exit codes are unchanged and remain the promise —
229
+ 2 while blocked under `--harness claude`, always 0 under `--harness cursor` —
230
+ because an unrunnable gate has always blocked. What is new is that the verdict
231
+ distinguishes a red pipeline from a launch that never happened, so anything
232
+ reading the `systemMessage` / `followup_message` body sees a cause instead of a
233
+ pointer to an artifact this run did not write. Additive: a hook script that
234
+ gates on the exit code alone is unaffected.
235
+
236
+ - **`checkride.config.json` promises a `gate` key** (docs/contract.md §CLI), and
237
+ with it the disclosure: while a profile is active, every gate verdict names the
238
+ profile and states that it was not the full check.
239
+
240
+ - **`CHECKRIDE_NODE_BIN`** is promised as the hook author's wrapping point: a
241
+ directory prepended to the check run's `PATH`, or `off` to disable checkride's
242
+ own Node-pin alignment.
243
+
244
+ - **What a hook *is* belongs to the harness** (docs/contract.md §CLI). The three
245
+ hook names stay promised; whether one lands as a config entry, a permission
246
+ rule, or a generated script is checkride's to choose per harness and may change
247
+ again. What is promised about the Claude Code deny rules is the part a consumer
248
+ builds on: checkride appends its own and removes only those. Anyone scripting
249
+ against the *path* `.claude/hooks/checkride-protect.cjs` should read
250
+ `permissions.deny` instead.
251
+
252
+ ## [0.10.1] - 2026-07-31
253
+
254
+ ### Added
255
+
256
+ - **`init` and `agent-setup` refuse to overwrite an AGENTS.md stanza that has
257
+ been edited.** Repos routinely need a line or two of their own in the contract
258
+ — a directory the `spell` check is allowed to skip, what a custom check means
259
+ here — and the refresh rewrote the marked region blind, taking those additions
260
+ with it. Idempotent, as promised, and lossy: the second run reported
261
+ `changed: false` precisely because the first one had already discarded the
262
+ edit.
263
+
264
+ The begin marker now carries a hash of the body checkride generated
265
+ (`<!-- checkride:begin hash=v1… -->`), so a later run can tell its own output
266
+ from a block someone has since changed. A changed one stops the run: exit 2, a
267
+ message naming the file and the two ways forward, and **nothing written at
268
+ all** — not the config, not the hooks — so a refused run leaves the repo
269
+ exactly as it found it, the rule new-mode `init` already followed for scaffold
270
+ collisions. `--force` accepts the loss and refreshes, and now carries that
271
+ meaning on `agent-setup` too, where the flag previously reached nothing.
272
+
273
+ Two things are deliberately *not* edits. Reformatting: line endings and
274
+ trailing whitespace are normalized away before hashing, so a Prettier run over
275
+ AGENTS.md does not read as a customization. And anything outside the markers,
276
+ which stays the right home for repo-specific additions — checkride has never
277
+ rewritten a line out there and still does not.
278
+
279
+ One-time cost on upgrade: a stanza written by 0.10.0 or earlier carries no
280
+ hash, and an older version's wording is indistinguishable from an edit, so the
281
+ first run after upgrading refuses with its own wording of the message.
282
+ `--force` once stamps it; detection is automatic from then on.
283
+
284
+ - **`--remove-hook <a,b>` tears an installed hook back out** — the config entry
285
+ and the generated script both. `--hook` only ever chose what to *write*, so
286
+ there was no supported way to drop the gate after the fact. It pairs with
287
+ `--no-hook` to remove without refreshing anything else, and removing `dirty`
288
+ rewrites a surviving gate unguarded, so the gate cannot silently disarm itself
289
+ on the way out.
290
+
291
+ - **A `note` field on any check entry, and at the config root**, for commentary
292
+ aimed at whoever reads `checkride.config.json` next. It is validated as a
293
+ string and then dropped — no code path carries it onto an Adapter, so it
294
+ cannot reach the status line or `summary.json`. `description` keeps its
295
+ user-facing role; this repo's own config dogfoods the split.
296
+
297
+ - **The gate states its verdict in words**, not just an exit code: the wall
298
+ clock and the failing slots, read back from `.check/summary.json` —
299
+ `checkride red in 41.7s, 2 of 15 failed: lint, test`. Under Claude Code it
300
+ rides in the hook body as `systemMessage`, which moves the block onto
301
+ `decision: "block"`, since hook JSON is parsed only on exit 0. `checkride
302
+ gate`'s own exit codes are unchanged: the generated script does the
303
+ translation and falls back to exit-2 blocking when no body comes back, so an
304
+ unrefreshed repo or an older checkride keeps gating either way. Cursor gets
305
+ the same line atop its `followup_message` on red; a green Cursor gate stays
306
+ silent, because its only stop-hook output field submits a new turn, and the
307
+ gap is recorded in docs/cursor.md rather than papered over.
308
+
309
+ - **The Claude Code Stop entry carries `statusMessage` and `timeout: 900`.** The
310
+ spinner now names the command rather than leaving a multi-minute pipeline
311
+ indistinguishable from a hung model, and the timeout closes a live hole: the
312
+ platform default is 600s, and a hook cancelled by it exits non-zero, which
313
+ Claude Code reads as a broken hook and lets the turn end. Any repo whose
314
+ pipeline crossed ten minutes had a gate that had quietly stopped gating.
315
+
316
+ ### Fixed
317
+
318
+ - **`triage` no longer reports an unparseable summary as a run with no
319
+ failures.** A repo whose gate is a homegrown script writing a checkride-shaped
320
+ `.check/` — same file names, no `schema_version` — made the reader announce
321
+ "checkride never ran, a compound script short-circuited" about a gate that had
322
+ run all eight of its checks and failed three. An empty slot table now reads as
323
+ "nothing was read", not "nothing failed": the short-circuit verdict is
324
+ reserved for a genuinely absent summary, `covered:` renders `unknown` rather
325
+ than a count nobody measured, a missing `schema_version` becomes its own
326
+ `foreign` state, and a new `.check/ contents` section lists the directory when
327
+ the summary is not an index.
328
+
329
+ - **The gate ignores a summary older than its own start time.** A check script
330
+ shaped `tsc --build && checkride` leaves `summary.json` untouched when the
331
+ build fails, and trusting it would report the previous run's failing slots as
332
+ this run's.
333
+
334
+ ### Contract
335
+
336
+ - **checkride owns the AGENTS.md stanza and only the stanza**, and the boundary
337
+ is now enforced rather than documented (docs/contract.md §CLI, locked by
338
+ `test/contract/flags.contract.test.ts`). The promise a consumer can build on:
339
+ a stanza carrying local edits is never overwritten without `--force`, and the
340
+ run that refuses writes nothing. Behavioural break for anyone scripting
341
+ `agent-setup` over a repo with a hand-edited or pre-0.10.1 stanza — that
342
+ invocation exited 0 and now exits 2 until it is re-run with `--force`.
343
+
344
+ ## [0.10.0] - 2026-07-31
345
+
346
+ ### Added
347
+
348
+ - **Cursor support — hooks and skills.** `init` and `agent-setup` now write the
349
+ same three hooks (`gate`, `dirty`, `protect`) into `.cursor/hooks.json` as
350
+ they do into `.claude/settings.json`, and write the bundled `check` and `qa`
351
+ skills into `.cursor/skills/` as `checkride-check` and `checkride-qa` —
352
+ Cursor has no plugin system to install them from. Which harnesses get wired
353
+ is detected (Claude Code always, Cursor when `.cursor/` exists) and
354
+ overridable with `--harness <a,b>`.
355
+
356
+ The gate had to learn a second protocol to get there. Claude Code blocks a
357
+ turn on **exit 2** and reads stderr; Cursor treats any non-zero stop hook as a
358
+ *broken* hook and lets the turn end, so its verdict rides in a
359
+ `{"followup_message": …}` body on stdout instead. Same decision, two wire
360
+ formats.
361
+
362
+ Cursor's defaults would have made that gate advisory, so the entry overrides
363
+ three of them: `timeout` (a pipeline is minutes), `loop_limit: null` (the
364
+ default of 5 stops the gate replying after five turns, where Claude Code
365
+ re-blocks indefinitely) and `failClosed: true` (Cursor is fail-open, so a
366
+ crashed or timed-out gate would end the turn silently). The `dirty` and
367
+ `protect` guards keep the fail-open default, deliberately.
368
+
369
+ - **[Cursor](docs/cursor.md).** A page for the parts that are not symmetric with
370
+ Claude Code — the reply-don't-block stop protocol, the three overridden
371
+ defaults, the third-party-configs setting, and an explicit list of the
372
+ assumptions Cursor's own docs leave open (chiefly: the `tool_input` path key
373
+ `protect` matches on is a guess, and a wrong guess fails open silently).
374
+
375
+ - **`checkride gate`, `checkride triage`, `checkride qa`.** The gate's body —
376
+ the edit-marker check, the `check` run, the digest-or-summary choice — moved
377
+ out of the generated shell script into a real command, so it is testable and
378
+ every harness's hook script is now a thin adapter over it. `triage` and `qa`
379
+ promote the two bundled plugin readers to first-class commands, so a skill no
380
+ longer needs `${CLAUDE_PLUGIN_ROOT}` (or a hardcoded `node_modules/` path) to
381
+ find them.
382
+
383
+ ### Fixed
384
+
385
+ - **The gate no longer fails open when checkride cannot run.** An uninstalled
386
+ or unresolvable checkride made the hook exit 1, which Claude Code reads as
387
+ "hook failed, carry on" — leaving a repo whose gate had silently stopped
388
+ gating. Any exit outside the gate's own 0/2 now blocks, naming the cause.
389
+ (`protect` still fails open, deliberately: a broken protect hook must not
390
+ become a repo where nothing can be written.)
391
+ - **A repo wired for both harnesses no longer runs two gates per turn.** Cursor
392
+ loads `.claude/settings.json` hooks when third-party configs are enabled, maps
393
+ `Stop` onto `stop`, and runs *every* matching source — so the default wiring
394
+ (Claude Code is always selected) fired two full pipelines concurrently into one
395
+ `.check/`, racing on the artifacts the orchestrator clears per slot and on the
396
+ edit marker. `checkride gate --harness claude` now stands down when Cursor is
397
+ running it and a native Cursor gate is registered. Narrow on purpose: a stale
398
+ `CURSOR_PROJECT_DIR` costs a duplicate run, never the gate.
399
+ - **A gate that cannot enter the repo now reports it.** The `cd` guard was a bare
400
+ `exit 2`, which said nothing under Claude Code and — because Cursor reads any
401
+ non-zero stop hook as a broken one — ended the turn silently under Cursor. It
402
+ now emits the same "could not run" verdict as every other way the gate can fail
403
+ to start, in each harness's protocol.
404
+ - **`protect` no longer misses paths under a symlinked repo root.** It compared
405
+ the harness's spelling of a path against the environment's, which on macOS
406
+ routinely differ (`/var/…` vs `/private/var/…`), making every in-repo path
407
+ look external — so an edit to `checkride.baseline.json` was allowed through.
408
+ Both sides are now resolved before comparison.
409
+
410
+ ### Contract
411
+
412
+ - The command set gains `gate`, `triage` and `qa`; `init`/`agent-setup` gain
413
+ `--harness <a,b>`. All additive. `gate` is documented as the one command
414
+ outside the 0/1/2 exit split, because it answers a harness's hook protocol
415
+ rather than checkride's own — see `docs/contract.md` §CLI. Its one documented
416
+ no-op is promised there too: `--harness claude` stands down for a registered
417
+ native Cursor gate.
418
+
419
+ ## [0.9.6] - 2026-07-30
420
+
421
+ ### Fixed
422
+
423
+ - **A slot refused for a missing tool was reported as a harness problem.** The
424
+ refusal carried `exit_code: -1`, which is reserved for a spawn failure or
425
+ timeout — so `triage` matched it and printed "it failed to spawn or timed out.
426
+ That is a harness problem, not a finding," telling the reader to discount the
427
+ one failure the pre-flight exists to make them act on, remediation and all. It
428
+ now exits **1**, a finding like any other. `-1` keeps meaning the harness
429
+ broke, and a triage test now pins the two apart.
430
+ - **A tool installed above the repo root satisfied the pre-flight.** The upward
431
+ search for `node_modules/.bin/<tool>` stopped only at the filesystem root, so
432
+ a stray install in a home or projects directory counted — the same
433
+ machine-state dependence the local resolution was added to remove, reached by
434
+ a different route: the slot passed for whoever had that directory above their
435
+ clone and failed on the clean checkout. The search now stops at the repo root,
436
+ marked by a `.git` or a lockfile, and searches that root before stopping.
437
+ - **A timed-out `yarn bin` probe reported the tool as missing.** Under Yarn PnP
438
+ `doctor` folded every probe failure into "not a dependency", so a slow package
439
+ manager produced a confident `missing` and told you to install a tool that was
440
+ already there. A timeout is now `unknown`, with a hint naming it — the
441
+ treatment the `--version` probe alongside it already had.
442
+ - **`doctor` reported a healthy Yarn PnP project as broken.** Both of its
443
+ install questions were path tests against a `node_modules/` tree, which a PnP
444
+ project does not have: `install` asserted the directory and so reported
445
+ `lockfile only`, and each slot's tool was looked for at
446
+ `node_modules/.bin/<tool>` and so reported missing — on a repo where every
447
+ tool was installed and every check ran green. The two commands therefore
448
+ contradicted each other, `doctor` calling the environment broken while the
449
+ gate passed. `install` is now satisfied by `.pnp.cjs` plus the lockfile, and a
450
+ tool is resolved with `yarn bin <tool>`, whose exit code carries the answer.
451
+ A tool that genuinely does not resolve still reports missing — the looser
452
+ question must not become a softer one, or this would trade a false red for a
453
+ false green, which is the worse of the two. Detection is gated on yarn so a
454
+ `.pnp.cjs` left by a migration off Yarn cannot reroute an npm or pnpm repo.
455
+
456
+ ## [0.9.5] - 2026-07-30
457
+
458
+ ### Contract
459
+
460
+ - **A slot's tool is now resolved in the local tree before the check spawns,
461
+ under `npx` and `bunx`.** 0.9.3 stopped those launchers *fetching* a missing
462
+ tool, but `--no-install` bounds the download and not the per-user cache, so
463
+ both still run a copy left behind by some earlier unrelated invocation. The
464
+ gate's verdict therefore depended on machine state nobody declared: a repo
465
+ whose `docs` slot was detected from `.markdownlint-cli2.jsonc` but never
466
+ installed `markdownlint-cli2` passed for every developer holding a cached copy
467
+ and failed on the clean checkout — which is the CI runner, the most expensive
468
+ place to find out. The tool is now looked up at `node_modules/.bin/<tool>`,
469
+ searched from the check's directory upward so a workspace tool hoisted to the
470
+ repo root still counts, and a slot whose tool is absent fails there with a
471
+ message naming the slot, the path searched, and the PM's own install command
472
+ instead of the launcher's `npx canceled due to missing packages`. **Behavior
473
+ change:** a slot passing only because of a cached tool now fails until that
474
+ tool is a declared dependency — the same trade 0.9.3 made, applied to the
475
+ fallback it left open, and it fails on the developer's machine rather than
476
+ only in CI. Scoped to the two launchers that keep such a cache: `pnpm exec`
477
+ and `yarn` resolve from the project tree already and are not pre-flighted,
478
+ which also leaves Yarn PnP — where no `node_modules/.bin` exists to find —
479
+ working as before. See `docs/contract.md` §Vacuous green and `docs/tools.md`
480
+ §Package managers.
481
+
482
+ ### Fixed
483
+
484
+ - **`doctor` told npm, yarn and bun repos to run `pnpm install`.** The
485
+ remediation hint on a missing-tool row was a hardcoded string, so the one
486
+ actionable line on a red row named a package manager the repo does not use —
487
+ while `checkInstall` two functions above it had been PM-aware all along. It
488
+ now names the detected manager and the exact dev-dependency install.
489
+ - **`doctor` reported a hoisted workspace tool as missing.** The probe tested
490
+ `<cwd>/node_modules/.bin/<tool>` and nothing above it, but pnpm and npm both
491
+ hoist a shared tool's bin to the workspace root — so a correctly installed
492
+ monorepo showed a false `missing` for every tool from any package
493
+ subdirectory, on exactly the repos the workspace presets generate. It now
494
+ walks upward like the package managers do, sharing one resolver with the run
495
+ path so the two cannot disagree about where a tool lives.
496
+
497
+ ## [0.9.4] - 2026-07-30
498
+
499
+ ### Fixed
500
+
501
+ - **Member-scoped fallow findings now key on `<container>.<leaf>` instead of a
502
+ line and column.** fallow reports an unused class/enum/store member as
503
+ `parent_name` + `member_name`, a component binding as `component_name` +
504
+ `prop_name`/`input_name`/`output_name`/`emit_name`/`event_name`, and a catalog
505
+ entry as `catalog_name` + `entry_name` — none of which the extractor
506
+ recognized, so every such finding fell through to the `<path>:<line>:<col>`
507
+ fallback. That key moves whenever code above it does, so editing anything
508
+ earlier in a file re-keyed its grandfathered members and re-surfaced them as
509
+ new findings. They are now line-free
510
+ (`dead-code:unused_class_members:src/a.ts:Svc.unusedOne`) and survive the edit,
511
+ as every other category already did. The same pass gave the report's remaining
512
+ `*_name` categories a stable identity too, so they key on a symbol rather than
513
+ a position as well. **This changes the `dead` slot's fingerprints — re-run
514
+ `checkride baseline` to re-capture, or those findings will report as new
515
+ once.**
516
+ - **Report arrays that fallow does not count are no longer fingerprinted.**
517
+ `workspace_diagnostics` carries a `path`, and the opt-in `thin_wrappers` and
518
+ `duplicate_prop_shapes` rules a `file`, so all three produced keys while
519
+ fallow's issue registry marks them `counts_in_total: false`. The extra keys
520
+ pushed the key count past the issue count, where the un-keyable-findings guard
521
+ clamped the difference to zero — so a spurious key could offset a finding that
522
+ genuinely had no stable identity, and the baseline masked a slot green that it
523
+ had never grandfathered. The guard now counts un-keyable findings directly
524
+ rather than inferring them from the totals, which closes that offset even if a
525
+ future fallow adds an uncounted array this version does not know to skip.
526
+
527
+ ## [0.9.3] - 2026-07-29
528
+
529
+ ### Contract
530
+
531
+ - **An empty slot selection is now a usage error.** `--only`, `--skip` or
532
+ `--include` given a value that names nothing after trimming (`--only ,`,
533
+ `--only ''`, `--only ' '`) exits **2**, naming the flag. It previously
534
+ parsed to an empty list, which is truthy, so `--only ,` filtered every check
535
+ out and exited **0** having verified nothing — the same vacuous green that
536
+ `--only lints` was fixed for in 0.5.0, reached by a different route. The two
537
+ empty spellings also disagreed: `--only ''` ran the whole pipeline. The rule
538
+ now holds for a programmatic caller too — `runChecks({ only: [] })` throws
539
+ rather than selecting nothing. See `docs/contract.md` §CLI and §Exit codes.
540
+ - **`summary.json`'s published schema described `total_duration_ms` as a sum.**
541
+ It is, and has been since concurrency landed, the **wall-clock** span of the
542
+ run; the two diverge whenever checks overlap. No behavior changed — the
543
+ description in `schema/checkride.summary.schema.json` was simply wrong, and a
544
+ consumer implementing against it would derive the wrong run-start
545
+ (`timestamp - total_duration_ms`) and read stale artifacts as fresh. The
546
+ semantics are now pinned by a test, not just prose. See `docs/contract.md`
547
+ §`.check/summary.json`.
548
+
549
+ ### Fixed
550
+
551
+ - **Tools are no longer installed from the registry mid-run under npm or bun.**
552
+ The exec translation emitted a bare `npx <tool>` / `bunx <tool>`, and both
553
+ launchers fetch a missing package and run it rather than failing — checks are
554
+ spawned without a TTY, which is precisely the non-interactive case where
555
+ neither stops to prompt. A repo missing a tool could therefore have its gate
556
+ silently execute an unpinned `latest`. Both now carry `--no-install`, which
557
+ bounds the download but not the launcher's global cache — the guarantee is
558
+ "nothing new is fetched mid-run". **Behavior change:** a tool that would have
559
+ to be downloaded now fails its slot. `pnpm exec` and `yarn` have no such cache
560
+ path and are unchanged.
561
+ - **The triage reader's gate timeout could not reap what it started.** It
562
+ spawned without `detached` and sent a bare `SIGTERM` with no escalation, so
563
+ the signal reached the package-manager wrapper and left the checks themselves
564
+ running — which also meant `close` could wait on their inherited pipes
565
+ forever, hanging the reader the timeout existed to protect. It now leads its
566
+ own process group and escalates to `SIGKILL`, the same discipline the
567
+ orchestrator already applied; both share the new `killGroup` helper.
568
+ - **`.check/` artifacts are now flushed before the rename that publishes them.**
569
+ The atomic write promised each file was "either the previous complete version
570
+ or the new complete version", but without an `fsync` a crash could leave the
571
+ renamed inode holding nothing — the torn read it exists to prevent, relocated.
572
+ - **A committed baseline whose `slots` is an array is rejected instead of being
573
+ keyed by index.** Two of the four copies of the `isRecord` guard accepted
574
+ arrays (`typeof [] === 'object'`); the baseline read was one of them, so such
575
+ a file loaded cleanly and masked nothing. All four now share one definition.
576
+ - **A baseline from a newer schema version is dropped rather than half-read.**
577
+ `schema_version` was recorded and never checked. Dropping it fails closed: the
578
+ run reports the diagnostics that are really there, where guessing risks
579
+ masking findings the author never grandfathered.
580
+ - **AGENTS.md described a tree the repo does not have.** It named four folder
581
+ modules where there are seven (`artifacts/`, `qa/` and `triage/` were
582
+ missing), and stated a test-placement rule — colocate beside the source file —
583
+ that no test in the repo has ever followed. Both are corrected, and both are
584
+ now checked against the tree.
585
+ - **Report columns no longer break on long check names.** Fixed widths were
586
+ sized for catalogue slots, so a config custom check (`typecheck-tests`, or
587
+ `custom:typecheck-tests` in the adapter column) shunted every later column
588
+ right on its own row. `doctor` and the run status lines now size each column
589
+ to the rows it holds.
590
+
591
+ ### Internal
592
+
593
+ - **This repo's own gate now runs the `security` slot.** `pnpm audit` takes well
594
+ under a second and reports zero advisories at every level, and a package that
595
+ publishes to npm with provenance should be auditing its own dependency tree as
596
+ part of "done" rather than out of band. Note the trade-off: the gate now needs
597
+ the registry to be reachable, and a failure to *verify* is a failure, not a
598
+ pass — so a network outage turns the run red rather than quietly skipping the
599
+ check.
600
+ - **The stryker break threshold moves 55 → 68.** The measured score is 73.2%
601
+ (up from 71.7%), so the old gate sat 18 points below reality and could not
602
+ have failed on anything short of deleting a suite. It now sits ~5 points
603
+ under — enough headroom that a mutant timing out on a loaded machine does not
604
+ turn the run red, which is the only reason not to set it tighter. The two
605
+ bundled-plugin bins are excluded from the mutate set: they are three lines
606
+ each, do their work at module top level, and are unreachable from the unit
607
+ runner stryker drives, so they reported a permanent 0% no test could move.
608
+ They are covered as processes by `test/e2e/plugin-readers.e2e.test.ts`.
609
+ - Tests for the last uncovered code in the package. The two bundled-plugin
610
+ entry points (`dist/triage/cli.js`, `dist/qa/cli.js`) are exercised as
611
+ processes, which is the only way to reach a bin that works at module top
612
+ level; `checkride fix` gained CLI-dispatch coverage it never had; and the
613
+ three `qa/` extractors gained tests for their truncation caps and
614
+ malformed-input fallbacks — the bounded-reader behavior those modules exist
615
+ for. Branch coverage 84.3% → 85.5%.
616
+ - New guards for the claims that drifted: `test/conventions.test.ts` checks
617
+ AGENTS.md's folder-module list and test placement against the tree, and the
618
+ summary contract suite now pins `total_duration_ms` behaviorally — wall-clock
619
+ under concurrency, equal to the summed durations when sequential. Both fail
620
+ when reverted, which is the point.
621
+ - `test/e2e/defaults.e2e.test.ts` resolves a fixture repo that has the tool
622
+ configs but **no `checkride.config.json`**, so the shipped catalogue and the
623
+ detection path are exercised. This repo's own config names all twenty slots,
624
+ which is deliberate but meant daily runs never walked a consumer's path.
625
+ - First tests for `src/triage/env.ts`, previously the only module in the package
626
+ with no coverage at all.
627
+ - Two new single-file modules: `src/proc.ts` (process-group kill and escalation,
628
+ shared by both spawners) and `src/json.ts` (the one `isRecord`).
629
+ - Long-form rationale moved out of source comments and into the docs that own
630
+ it: the pnpm stdout-narration investigation to `docs/tools.md` §Launcher
631
+ quirks, and the freshness-window derivation to `docs/plugin.md`. The comments
632
+ keep the "why" and point at the rest.
633
+
634
+ ## [0.9.2] - 2026-07-28
635
+
636
+ ### Changed
637
+
638
+ - **`checkride init` now writes a newer toolchain into generated projects.**
639
+ The pinned versions a new project receives move to oxlint 1.74.0 (with
640
+ oxlint-tsgolint 0.25.0), fallow 3.9.1, ast-grep 0.45.0, vitest 4.1.10,
641
+ markdownlint-cli2 0.23.1, cspell 10.0.1, prettier 3.9.6, biome 2.5.6, jest
642
+ 30.4.2, publint 0.3.22, attw 0.18.5, and `@types/node` 22.20.1. A freshly
643
+ generated project still installs and passes green out of the box. TypeScript
644
+ stays at 6.0.3, and the eslint and knip pins stay on their current majors —
645
+ all three are deliberate holds, now marked as such in the adapter registry.
646
+ - **The documented fallow pin is 3.9.1.** The supported floor is unchanged:
647
+ fallow ≥ 3.5, JSON `schema_version` 7. Every release from 3.5.0 through 3.9.1
648
+ emits schema 7 with identical report layouts, so an existing baseline keeps
649
+ masking and no consumer has to move.
650
+
651
+ ### Fixed
652
+
653
+ - **The bundled `check` skill quoted a markdownlint summary line that no longer
654
+ exists.** Its guidance on reading a failing `docs` slot cited
655
+ `Summary: 1 error(s)`; markdownlint-cli2 0.23 emits `Summary: 1 issue in 1
656
+ file`. The surrounding advice was still right — that tool puts the count on
657
+ stdout and the `file:line:rule` you actually need on stderr — but the quoted
658
+ string sent agents looking for text no run produces.
659
+
660
+ ### Internal
661
+
662
+ - Development dependencies upgraded across the board, in separate verified
663
+ steps: fallow's report schema and layouts confirmed unchanged, ast-grep's
664
+ rules confirmed to still fire (repo *and* shipped template copies), and
665
+ oxlint's diagnostic text confirmed byte-identical for the baseline fixtures
666
+ it fingerprints.
667
+ - Nine new oxlint errors from the 1.61 → 1.74 jump were fixed at the root
668
+ rather than suppressed: two genuinely unnecessary type assertions, and seven
669
+ test helpers hoisted out of the scopes they never captured.
670
+ - `pnpm audit` is clean again. The `brace-expansion` override had gone stale as
671
+ its advisory widened, `fast-uri`, `postcss`, and a js-yaml 5.x entry were
672
+ added, and the `markdown-it` and js-yaml 4.x overrides were retired as
673
+ redundant — each verified by removal and re-audit, not by inspection.
674
+ - Every GitHub Actions ref is now SHA-pinned with a version comment. `ci.yml`
675
+ alone had floated on mutable major tags, which made the workflow gating every
676
+ merge the one an upstream tag move could reach. CI's pnpm moves to 11.17.0,
677
+ verified to accept the committed lockfile unmodified.
678
+
679
+ ## [0.9.1] - 2026-07-28
680
+
681
+ ### Fixed
682
+
683
+ - **The `links` check no longer flags example links inside code blocks.** A
684
+ `[text](target)` shown inside a fenced block or inline backticks was resolved
685
+ against disk like any real link, so documentation that demonstrates Markdown
686
+ syntax could only pass by enumerating every example target in
687
+ `checks.links.allowlist`. Fence state is now tracked across the file —
688
+ honoring CommonMark on marker length, tilde fences, and info strings — and
689
+ inline code spans are masked before parsing. Two limits are deliberate and
690
+ tested: 4-space indented blocks are still checked, since indentation is
691
+ ambiguous with list continuation, and code spans straddling a newline stay
692
+ unmatched. The check only became less strict, so a repo passing on 0.9.0
693
+ still passes.
694
+
695
+ ## [0.9.0] - 2026-07-28
696
+
697
+ ### Added
698
+
699
+ - **A PreToolUse deny hook guards checkride's accounting files.** "Never add
700
+ to the baseline to make a check pass" was README advice that an agent
701
+ editing the file has every local incentive to ignore; it is now
702
+ enforcement. Edits (Edit/Write/NotebookEdit) to `checkride.baseline.json`
703
+ and `.check/**` are blocked with a message explaining the ratchet; reads
704
+ are never denied — the stanza's own triage procedure and both plugin
705
+ skills read `.check/` artifacts. Both paths are unambiguous, so the deny
706
+ is exact; module-boundary rules deliberately stay in ast-grep, where they
707
+ are enforced over parsed code rather than approximated by paths.
708
+ - **The gate now skips turns that edited nothing.** A new PostToolUse hook
709
+ (`dirty`) touches `.check/.dirty` on every Edit/Write/NotebookEdit; the gate
710
+ script exits 0 immediately when the marker is absent and clears it after a
711
+ green run. Stop fires at the end of *every* turn, including
712
+ pure-conversation ones — on a real repo that was a multi-second pipeline
713
+ tax (plus formatter writes) for a turn that changed nothing, the single
714
+ best reason to disable the gate. File writes made through Bash don't set
715
+ the marker (a known, accepted gap — matching Bash would fire on every
716
+ command); the next tool-edited turn re-covers it. A `--hook gate`
717
+ selection without `dirty` writes an unconditional script, so the guard can
718
+ never disarm a gate that has no marker source.
719
+ - **The generated gate runs `--strict --digest`.** The Stop hook is a gate,
720
+ and the contract says anything that gates should pass `--strict` — now
721
+ checkride's own generated hook does. `--digest` writes the token-bounded
722
+ `.check/digest.md` on red, and the guidance message points there when it
723
+ exists (summary.json otherwise) and names `/checkride:check` for full
724
+ triage. npm repos get the `--` passthrough form; pnpm/yarn/bun forward
725
+ flags directly.
726
+
727
+ ### Changed
728
+
729
+ - **The Stop-hook gate moved into a checkride-owned script,
730
+ `.claude/hooks/checkride-gate.sh`.** The settings.json entry is now a stable
731
+ one-liner invoking it (`sh "${CLAUDE_PROJECT_DIR:-.}/…"`), so refreshing is
732
+ genuinely lossless: checkride overwrites its script freely, and consumer
733
+ customization has a natural home (a sibling script, or the environment)
734
+ instead of an inline command the next `agent-setup` would clobber. Repos
735
+ carrying the old inline form are migrated in place on the next
736
+ `agent-setup`/`init` — detected by the existing sentinel and replaced, never
737
+ duplicated. `init`/`agent-setup` grow `--hook <a,b>` to select which hooks
738
+ to write (default: all); `--no-hook` remains the write-none escape.
739
+
740
+ ### Fixed
741
+
742
+ - **The `security` slot now gates at `--audit-level`, not at any advisory.**
743
+ pnpm's JSON mode exits 1 on *any* advisory regardless of `--audit-level`
744
+ (only table mode lets the level gate the exit code — reproduced with a
745
+ single-moderate lockfile: `--json` exits 1, table exits 0), so the slot
746
+ judged by exit code effectively gated at zero advisories of any severity
747
+ rather than the declared `high`. `security` is now a built-in evaluator: it
748
+ runs the audit with `--json`, parses `metadata.vulnerabilities`, and fails
749
+ only on advisories at or above the threshold parsed from the adapter's own
750
+ `--audit-level` arg — so a consumer override keeps meaning what it says,
751
+ and "audit could not run" (registry unreachable, malformed output) stays a
752
+ failure rather than a silent pass. Consumers who dropped `--json` to work
753
+ around this can return to the default args and get the
754
+ `.check/security.json` artifact back (`--json` is appended if an override
755
+ omits it).
756
+ - **`pack` no longer fails hard under pnpm older than 10.26.** pnpm only
757
+ learned `pack --dry-run` in 10.26.0; every earlier pnpm — including 10.18.x
758
+ lines consumers actually pin — rejects the flag with
759
+ `Unknown option: 'dry-run'`, and the slot failed on a manager checkride
760
+ has promised since 0.5.0 (reported by a real consumer on 10.18.1). On that
761
+ rejection checkride now falls back to a real pack into a temp destination
762
+ outside the repo (`pnpm pack --json --pack-destination <tmp>` — same
763
+ npm-shaped `files[].path` JSON, verified back to 10.18.1), with lifecycle
764
+ scripts suppressed the same way, and deletes the tarball afterward whether
765
+ the check passes or not. Capability fallback, not version sniffing, so it
766
+ keeps working wherever the flag is missing.
767
+ - **The default concurrency no longer collapses to 1 on a 2-core CI runner.**
768
+ `defaultConcurrency` reserved a core to keep the machine responsive — sound
769
+ on a laptop, inapplicable on a hosted runner with no human at it. A
770
+ standard GitHub-hosted runner reports 2 CPUs, so the pool was 1 and a
771
+ wave-scheduled config executed fully sequentially (measured on a real
772
+ consumer: 27.4s wall against 27.4s of summed check time — 1.00x), on
773
+ exactly the machine class the docs say to gate on. With `CI` set (every
774
+ provider sets it) no core is reserved, so that runner now gets a pool of 2.
775
+ The cap of 4, the `--concurrency` override, and `--bail`'s sequential
776
+ behavior are unchanged. How much a 2-wide pool on 2 cores buys depends on
777
+ the mix: checks dominated by process startup and file discovery overlap
778
+ well; two CPU-bound tools may be closer to a wash. It is never slower than
779
+ the serialized pool it replaces, and `order` waves now schedule as
780
+ documented. docs/ci.md explains the runner-size effect and how to read
781
+ `total_duration_ms` against the per-check sum.
782
+ - **The AGENTS.md stanza reports the configured gate, not the detected one.**
783
+ `init` and `agent-setup` derived the stanza's "Active checks in this repo"
784
+ line from the adoption inventory, which has detection semantics and never
785
+ reads `checkride.config.json` — so opt-in slots a config entry opts in
786
+ (`format`, `build`, …) and non-catalogue custom checks were silently missing
787
+ from the agent-facing contract while the gate ran them. The stanza now
788
+ derives from the same selection a default run makes (the pattern `doctor`
789
+ already used), so what it names and what `pnpm check` runs agree.
790
+
791
+ ### Internal
792
+
793
+ - The design record for this release is
794
+ [docs/spec-agent-setup-upgrade.md](./docs/spec-agent-setup-upgrade.md): two
795
+ consumers' reports validated against source, with the measured pnpm version
796
+ boundaries. docs/getting-started.md's hard-gate section was rewritten for
797
+ the three-hook suite.
798
+
799
+ ## [0.8.1] - 2026-07-28
800
+
801
+ ### Fixed
802
+
803
+ - **A package-manager banner on stdout no longer fails the JSON-reading slots.**
804
+ pnpm verifies dependencies before `exec`/`run` and narrates it on **stdout**
805
+ (`Already up to date`, `Done in Xms using pnpm vN`) whenever no outer pnpm
806
+ process has already done so. That preamble landed ahead of the tool's own
807
+ JSON, so `checkride` invoked directly — `node dist/cli.js`, which is what the
808
+ bundled triage reader and any non-pnpm-wrapped call does — failed `dead`,
809
+ `dupes` and `health` with "fallow did not emit valid JSON" while the tools
810
+ themselves exited 0, and silently cost `lint`, `struct` and `attw` their JSON
811
+ artifacts. The same gate under `pnpm run check` passed, which is what made it
812
+ hard to see. Fixed at both ends: checkride now passes
813
+ `--config.verify-deps-before-run=false` ahead of `exec`/`run` under pnpm (the
814
+ only form that works — `--silent` does not suppress it, and after `exec` pnpm
815
+ reads it as the tool's argument), and every stdout-JSON parse tolerates up to
816
+ ten leading non-JSON lines, because a consumer's launcher is not checkride's
817
+ to pin. The `<slot>.json` artifact is written from the first JSON character
818
+ on, so it parses on its own; nothing inside the tool's output is altered.
819
+ - **`summary.json`'s `output_file` no longer names files that were never
820
+ written.** It was copied from the adapter's declaration, so any run where a
821
+ JSON-declaring tool emitted something else — a warning first, a crash, or the
822
+ banner above — left the summary pointing at a `<slot>.json` that did not
823
+ exist while the real bytes sat in `<slot>.stdout.txt` beside it. It is now
824
+ what the run actually wrote, and `null` when that is nothing (including for a
825
+ skipped check, which writes nothing at all). This is the field's documented
826
+ meaning; see [docs/contract.md](./docs/contract.md).
827
+
828
+ ## [0.8.0] - 2026-07-28
829
+
830
+ ### Added
831
+
832
+ - **A bundled Claude Code plugin, shipped from the package root.** The published
833
+ package now carries `.claude-plugin/plugin.json` and a `skills/` directory, so
834
+ `checkride` installs as a plugin from the same npm tarball you already depend
835
+ on (`/plugin marketplace add robmclarty/agent-tools`, then
836
+ `/plugin install checkride@robmclarty`). It is bundled rather than published
837
+ separately so the readers version in lockstep with the `.check/` contract they
838
+ parse; a test asserts the manifest's version equals `package.json`'s. Nothing
839
+ about the CLI, the exit codes or the `.check/` contract changes, installed or
840
+ not. See [docs/plugin.md](./docs/plugin.md).
841
+ - **`/checkride:check` — triage a red gate.** Runs the repo's own `check` script
842
+ (the definition of done, which may carry deliberate `--only`/`--skip`), branches
843
+ on the promised 0/1/2 exit split, and names one root cause plus what it is
844
+ deliberately not reading. It covers the contract corners a hand-read of
845
+ `summary.json` gets wrong — exit 2 versus exit 1, vacuous green, a narrow run's
846
+ green, `baselined` counts, `skipped` slots, `exit_code: -1`, an unexpected
847
+ `schema_version`, stale artifacts, and a red gate whose compound `check` script
848
+ died before checkride ran — then opens exactly one raw file. Artifacts are
849
+ measured, never opened: a green 17-slot run renders in about 2 kB.
850
+ - **`/checkride:qa` — read the quality signal.** Folds `mutation.json`,
851
+ `dead.json`, `dupes.json` and `health.json` into a report under 8 kB (2.2 MB of
852
+ mutation data becomes a ranked page), runs nothing at all, and opens with a
853
+ present / stale / not-opted-in ledger naming the command or config entry that
854
+ would produce each missing artifact — three of the four come from opt-in slots,
855
+ so partial data is the normal case.
856
+ - **Two dependency-free readers behind those skills**, shipped prebuilt in
857
+ `dist/` so a plugin install needs no build step, and runnable without Claude
858
+ Code: `node node_modules/checkride/dist/triage/cli.js` and
859
+ `node node_modules/checkride/dist/qa/cli.js`, each taking an optional repo
860
+ path. Both use `node:` builtins only. Every artifact read is bounded, dated
861
+ against the run that claims it (an artifact older than the run's start is
862
+ labelled stale with its age, never silently dropped), and resolved by the
863
+ documented `output_file` → `<slot>.json` → `<slot>.stdout.txt`/`.stderr.txt`
864
+ convention, which matters because `output_file` is null for 8 of this repo's
865
+ 17 slots — `test` among them.
866
+ - **`examples/` — eight runnable projects, each demonstrating one thing.** They
867
+ are standalone packages rather than workspace members, so you `cd` in, install,
868
+ and run one without the rest of the repo: `agent-loop` (what an agent reads
869
+ when the gate is red), `existing-repo-baseline` (adopting checkride on a repo
870
+ that already has findings), `custom-checks` (the escape hatch — a bespoke
871
+ formatter ahead of the built-ins, `detect`-gated checks that stand down),
872
+ `shared-preset` (org-wide policy as one versioned package), `polyglot` (a
873
+ Python repo where the TypeScript built-ins stand down and ast-grep enforces
874
+ boundaries in Python), and `module-boundaries` / `dal-boundaries` /
875
+ `dal-boundaries-declarative` for boundary enforcement — the last expressing a
876
+ data-access policy with fallow zones and ast-grep rules alone, no custom script.
877
+ Each declares the exit code it expects and `test/e2e/examples.e2e.test.ts`
878
+ asserts it on every push, so an example that stops behaving as its README
879
+ claims fails CI.
880
+
881
+ ### Changed
882
+
883
+ - **The AGENTS.md stanza written by `checkride init` and `checkride agent-setup`
884
+ names `/checkride:check`.** Exactly one added line; the prose procedure is
885
+ unchanged and still works standalone in a repo that never installs the plugin.
886
+
887
+ ### Internal
888
+
889
+ - `/version` now folds an existing `## [Unreleased]` changelog section into the
890
+ new release heading instead of inserting above it.
891
+
892
+ ## [0.7.0] - 2026-07-22
893
+
894
+ ### Changed
895
+
896
+ - **checkride is now licensed [Apache-2.0](./LICENSE), previously MIT.** No code
897
+ or API changed; the terms you receive it under did. Apache-2.0 adds an express
898
+ patent grant and, via section 5, makes contributions inbound under the same
899
+ license without a CLA — the reason for the switch, ahead of accepting outside
900
+ contributions. Consumers redistributing checkride should note the section 4
901
+ conditions (include the license, mark modified files); there is deliberately
902
+ no `NOTICE` file, so nothing to propagate. `checkride init --license` still
903
+ defaults to `MIT` for scaffolded projects — that remains the user's choice and
904
+ is unaffected.
905
+
906
+ ### Internal
907
+
908
+ - `CONTRIBUTING.md` gains a licensing section: how section 5 works, why an
909
+ employer may own contributions written on their time, and DCO sign-off via
910
+ `git commit -s`.
911
+
912
+ ## [0.6.0] - 2026-07-21
913
+
914
+ ### Added
915
+
916
+ - **`optIn` override to configure a slot without opting it into the default
917
+ run.** A slot entry (both the `{ use }` and custom `{ command }` forms) now
918
+ takes `optIn?: boolean`: `true` configures the slot but keeps it reachable
919
+ only via `--all`/`--include` (so giving `attw` a profile no longer drags
920
+ `attw`/`build`/`pack`/`smoke` into a plain `checkride` run), and `false`
921
+ forces a slot into the default run. Resolution-side only, so selection, the
922
+ vacuous-green warning, and `doctor` all adjust automatically.
923
+ - **Config-driven `exclude` and `allowlist` for the built-in `links` check.**
924
+ `exclude` adds directory names to skip on top of the built-in set, and
925
+ `allowlist` takes regex sources whose matching link targets count as valid
926
+ (for generated or illustrative markdown that never resolves on disk).
927
+ Patterns are compiled and validated at config load, so a bad one is a
928
+ friendly exit-2 error rather than a mid-run crash. Both are declared in the
929
+ JSON schema.
930
+ - **`profile` shortcut for the `attw` slot.** A `profile` string on the config
931
+ entry appends `--profile <name>` to attw's invocation instead of re-typing
932
+ the full args array (a no-op on other slots; a non-string is a friendly
933
+ config error).
934
+
935
+ ### Changed
936
+
937
+ - **`publint` and `attw` stand down when their tool isn't installed.** Both
938
+ were always-available and hard-failed an `--all` run with "Command not found"
939
+ on a repo that never installed them; they now gate on their package
940
+ (`publint`, `@arethetypeswrong/cli`) and skip cleanly when absent, matching
941
+ checkride's skip-not-fail principle for opt-in slots. Naming one explicitly
942
+ in config still forces it to run.
943
+
944
+ ### Fixed
945
+
946
+ - **A single duplicate fingerprint no longer disables baselining for a whole
947
+ fallow slot.** The baseline gate guarded against un-fingerprintable findings by
948
+ comparing the deduplicated key count to the raw issue count, but that
949
+ comparison could not tell a finding that produced *no* key apart from two
950
+ findings that produced the *same* key. One key collision (two `<arrow>`
951
+ functions in a file, or two symbol-less `unused_class_members`) made the slot
952
+ look untracked, so `checkride baseline` followed by `checkride` stayed red
953
+ forever. The gate now tracks an explicit un-keyed count instead, so a collision
954
+ merely coarsens the baseline (grandfathering one finding grandfathers its twin)
955
+ rather than disabling it, while a genuinely un-keyable finding still holds the
956
+ slot red.
957
+ - **Colliding fallow fingerprints are now disambiguated.** Anonymous health
958
+ functions (`<arrow>`, `<anonymous>`) and symbol-less dead-code findings
959
+ (`unused_class_members`) append their line and column, so a real sibling
960
+ finding can no longer be masked by a baseline captured for a different one.
961
+ Named functions and symbol-bearing findings keep their line-free keys.
962
+
963
+ This changes the fingerprint strings for the `dead` and `health` slots, which
964
+ **invalidates existing `checkride.baseline.json` entries for those slots**.
965
+ Re-run `checkride baseline` to recapture them.
966
+
967
+ ### Internal
968
+
969
+ - **Cleared all `pnpm audit` advisories** via `pnpm-workspace.yaml` overrides:
970
+ `brace-expansion` (GHSA-3jxr-9vmj-r5cp) and `js-yaml` (GHSA-52cp-r559-cp3m)
971
+ were high, `qs` (GHSA-q8mj-m7cp-5q26) and `markdown-it` (GHSA-6v5v-wf23-fmfq)
972
+ moderate. Each is a transitive **dev** dependency (of stryker and
973
+ markdownlint-cli2), so none reached the published package; each override is
974
+ scoped to its advisory's vulnerable range and capped to the compatible major.
975
+ - Docs: added a deep-modules guide and a fleet-presets guide for org-wide
976
+ rollout, reframed swappable module boundaries as the default, dropped the
977
+ "TypeScript-only" framing from the README, homepage, and npm package
978
+ description, clarified the README for first-time readers, and pointed
979
+ `package.json` `homepage` at the GitHub Pages site.
980
+
981
+ ## [0.5.3] - 2026-07-19
982
+
983
+ ### Internal
984
+
985
+ - **Docs: `docs/ordering-in-practice.md`** — a worked guide to `order`
986
+ scheduling waves, now with a second example where waves encode a *dependency*
987
+ (`build` produces `dist/`; the publish bundle — `publint`, `attw`, `pack`,
988
+ `smoke`, `snippets-dist` — consumes it) rather than just a duration tier.
989
+ - Adopt explicit ordering waves in checkride's own `checkride.config.json`, so
990
+ the gate config states its execution plan instead of leaning on adapter
991
+ defaults and pool arithmetic.
992
+ - CI: bump `upload-pages-artifact` to v5 and drop the Node 20 pages runtime.
993
+ - Expand the `cspell` dictionary with words the docs introduced.
994
+
7
995
  ## [0.5.2] - 2026-07-19
8
996
 
9
997
  ### Internal
@@ -465,6 +1453,21 @@ The first real release. (`0.0.0` was a name-claim placeholder.)
465
1453
  - Flags: `--only`, `--skip`, `--bail`, `--json`, `--changed`, `--all`,
466
1454
  `--include`.
467
1455
 
1456
+ [0.10.2]: https://www.npmjs.com/package/checkride/v/0.10.2
1457
+ [0.10.1]: https://www.npmjs.com/package/checkride/v/0.10.1
1458
+ [0.10.0]: https://www.npmjs.com/package/checkride/v/0.10.0
1459
+ [0.9.6]: https://www.npmjs.com/package/checkride/v/0.9.6
1460
+ [0.9.5]: https://www.npmjs.com/package/checkride/v/0.9.5
1461
+ [0.9.4]: https://www.npmjs.com/package/checkride/v/0.9.4
1462
+ [0.9.3]: https://www.npmjs.com/package/checkride/v/0.9.3
1463
+ [0.9.2]: https://www.npmjs.com/package/checkride/v/0.9.2
1464
+ [0.9.1]: https://www.npmjs.com/package/checkride/v/0.9.1
1465
+ [0.9.0]: https://www.npmjs.com/package/checkride/v/0.9.0
1466
+ [0.8.1]: https://www.npmjs.com/package/checkride/v/0.8.1
1467
+ [0.8.0]: https://www.npmjs.com/package/checkride/v/0.8.0
1468
+ [0.7.0]: https://www.npmjs.com/package/checkride/v/0.7.0
1469
+ [0.6.0]: https://www.npmjs.com/package/checkride/v/0.6.0
1470
+ [0.5.3]: https://www.npmjs.com/package/checkride/v/0.5.3
468
1471
  [0.5.2]: https://www.npmjs.com/package/checkride/v/0.5.2
469
1472
  [0.5.1]: https://www.npmjs.com/package/checkride/v/0.5.1
470
1473
  [0.5.0]: https://www.npmjs.com/package/checkride/v/0.5.0