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
@@ -0,0 +1,305 @@
1
+ /**
2
+ * The Claude Code hook writer — `.claude/settings.json`, and the one script its
3
+ * entries still invoke.
4
+ *
5
+ * Merging is surgical: each hook is identified by a per-hook sentinel substring
6
+ * in its command (legacy forms included, so repos carrying an older spelling are
7
+ * migrated rather than duplicated). Unrelated hooks, sibling groups, and every
8
+ * other settings key are preserved, and re-applying is a no-op.
9
+ *
10
+ * Claude Code's schema nests: an event maps to a list of *groups*, each with an
11
+ * optional tool `matcher` and its own list of `{type, command}` hooks. Cursor's
12
+ * is flat, which is why the two writers do not share a merge (see `./cursor.ts`).
13
+ *
14
+ * **Two of the three hooks are configuration here, not code.** `protect` is a
15
+ * pair of `permissions.deny` rules and `dirty` is an inline one-liner, so
16
+ * neither puts a file in the consumer's repo. Only the gate keeps a script, and
17
+ * only because it has a branch config cannot express — see {@link GATE_SCRIPT_FILE}.
18
+ */
19
+ import { posix } from 'node:path';
20
+ import { DIRTY_MARKER } from '../gate.js';
21
+ import { gateStatusMessage, GATE_TIMEOUT_SECONDS, HOOK_NAMES, writeHarnessHooks } from './harness.js';
22
+ /** Project-shared Claude Code settings (committed), relative to the repo root. */
23
+ export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
24
+ /**
25
+ * The checkride-owned Stop-hook gate script, relative to the repo root — the
26
+ * only file checkride still generates for Claude Code.
27
+ *
28
+ * It survives the move to configuration because it carries a branch nothing in
29
+ * settings.json can: when `checkride gate` never runs at all — not installed, a
30
+ * launcher that died — the command exits with neither of the gate's two codes,
31
+ * and Claude Code reads a plain non-zero hook as *hook failed, carry on*. The
32
+ * script is what turns that into a block. Inlining it would mean a `sh -c` of a
33
+ * dozen lines in a settings file, which is configuration only in the sense that
34
+ * it is JSON.
35
+ */
36
+ export const GATE_SCRIPT_FILE = '.claude/hooks/checkride-gate.sh';
37
+ /** Scripts an earlier checkride wrote here; both are configuration now. */
38
+ const DIRTY_SCRIPT_FILE = '.claude/hooks/checkride-dirty.sh';
39
+ export const PROTECT_SCRIPT_FILE = '.claude/hooks/checkride-protect.cjs';
40
+ const HARNESS = 'claude';
41
+ /** The `protect` hook's name, which is now a settings region rather than a hook. */
42
+ const PROTECT = 'protect';
43
+ /**
44
+ * What `protect` denies, as Claude Code permission rules.
45
+ *
46
+ * `Edit(...)` and nothing else: Claude Code checks file permissions against
47
+ * `Edit(path)` and `Read(path)` rules **only**, and an `Edit` rule covers every
48
+ * file-editing tool. A `Write(...)` or `NotebookEdit(...)` path rule is accepted
49
+ * but never consulted — it would warn at startup and silently protect nothing,
50
+ * which is the failure mode this list must not have.
51
+ *
52
+ * The leading `**` prefixes are deliberate. Without them the matching depth depends
53
+ * on the rule *type* — a bare single-segment directory pattern matches at any
54
+ * depth in a deny rule but only at the anchor in an allow rule — and a
55
+ * protection whose reach changes with its category is one nobody can reason
56
+ * about. Spelled this way it means the same thing everywhere, and covers a
57
+ * monorepo's per-package `.check/` as well as the root's.
58
+ */
59
+ const PROTECT_DENY_RULES = [
60
+ 'Edit(**/checkride.baseline.json)',
61
+ 'Edit(**/.check/**)',
62
+ ];
63
+ /**
64
+ * Sentinel carried by the legacy inline Stop command (pre-script versions rewrote
65
+ * the whole command in settings.json). Still matched so migration replaces that
66
+ * entry in place instead of adding a second gate.
67
+ */
68
+ const LEGACY_GATE_SENTINEL = 'checkride: the gate is red';
69
+ /**
70
+ * The `dirty` hook was an inline `mkdir … && touch .check/.dirty` before it
71
+ * became a script. The marker path is what that spelling carried, so matching it
72
+ * migrates those repos in place.
73
+ */
74
+ const LEGACY_DIRTY_SENTINEL = DIRTY_MARKER;
75
+ /**
76
+ * `sh` (not the exec bit) runs the script so a clone that lost file modes still
77
+ * gates; `CLAUDE_PROJECT_DIR` anchors it when the session cwd is a subdirectory,
78
+ * falling back to cwd for older harnesses.
79
+ */
80
+ const run = (interpreter, file) => `${interpreter} "\${CLAUDE_PROJECT_DIR:-.}/${file}"`;
81
+ /**
82
+ * The tool names Claude Code exposes for file mutation. `Edit` and `Write` cover
83
+ * source; `NotebookEdit` is a distinct tool rather than a mode of the other two.
84
+ *
85
+ * This is a hook *matcher*, which is a different vocabulary from a permission
86
+ * rule: matchers name tools, and {@link PROTECT_DENY_RULES} does not, because
87
+ * only `Edit` rules are consulted for file paths.
88
+ */
89
+ const EDIT_TOOLS = 'Edit|Write|NotebookEdit';
90
+ /**
91
+ * The `dirty` hook, inline. It is three shell words — make the directory, touch
92
+ * the marker — with nothing in it a repo would want to customize, so a generated
93
+ * file to hold it was a file for its own sake. It was inline before it became a
94
+ * script, and {@link LEGACY_DIRTY_SENTINEL} recognizes both spellings.
95
+ *
96
+ * `|| true` is load-bearing: a PostToolUse hook that exits non-zero reports a
97
+ * failure against the edit it was only observing. Failing to record a marker
98
+ * costs one skipped gate; failing an edit costs the edit.
99
+ */
100
+ const DIRTY_COMMAND = `mkdir -p "\${CLAUDE_PROJECT_DIR:-.}/${posix.dirname(DIRTY_MARKER)}" && ` +
101
+ `touch "\${CLAUDE_PROJECT_DIR:-.}/${DIRTY_MARKER}" || true`;
102
+ /**
103
+ * The hook registry. Order is write order; sentinels key identity in settings.
104
+ *
105
+ * Only the gate carries `timeout` and `statusMessage`, and for the same reason
106
+ * Cursor's gate entry carries three fields of its own: the platform defaults are
107
+ * tuned for hooks that observe, and the gate does not observe. It runs for
108
+ * minutes (Claude Code's 600s default can cut a slow pipeline short, and a
109
+ * cancelled Stop hook exits non-zero, which Claude Code reads as a *broken* hook
110
+ * and lets the turn end — a silent vacuous green), and it is the one hook whose
111
+ * runtime the user actually waits on, so it names itself in the spinner instead
112
+ * of leaving them to guess whether the model hung. The two guards are fast and
113
+ * invisible, and take the defaults.
114
+ */
115
+ function hookSpecs(pm) {
116
+ return [
117
+ {
118
+ name: 'gate',
119
+ event: 'Stop',
120
+ sentinels: [GATE_SCRIPT_FILE, LEGACY_GATE_SENTINEL],
121
+ command: run('sh', GATE_SCRIPT_FILE),
122
+ timeout: GATE_TIMEOUT_SECONDS,
123
+ statusMessage: gateStatusMessage(pm),
124
+ },
125
+ {
126
+ name: 'dirty',
127
+ event: 'PostToolUse',
128
+ matcher: EDIT_TOOLS,
129
+ // Current form first: the inline command contains the marker path, which
130
+ // is also what the pre-script spelling carried. The script path is listed
131
+ // after it so a repo on that form is migrated in place, not duplicated.
132
+ sentinels: [LEGACY_DIRTY_SENTINEL, DIRTY_SCRIPT_FILE],
133
+ command: DIRTY_COMMAND,
134
+ },
135
+ ];
136
+ }
137
+ /**
138
+ * The `PreToolUse` entry an earlier checkride wrote for `protect`. It is never
139
+ * applied — only recognized, so adopting the deny rules takes the hook it
140
+ * replaces with it instead of leaving two mechanisms guarding one path.
141
+ */
142
+ const RETIRED_PROTECT_HOOK = {
143
+ name: PROTECT,
144
+ event: 'PreToolUse',
145
+ matcher: EDIT_TOOLS,
146
+ sentinels: [PROTECT_SCRIPT_FILE],
147
+ command: '',
148
+ };
149
+ /** True when a hook entry belongs to `spec` (matched by any of its sentinels). */
150
+ function isSpecHook(spec, hook) {
151
+ const command = hook.command;
152
+ return hook.type === 'command' && typeof command === 'string' && spec.sentinels.some((s) => command.includes(s));
153
+ }
154
+ /** The event's group list, tolerating a malformed (non-array) settings value. */
155
+ function eventGroups(settings, event) {
156
+ const groups = settings.hooks?.[event];
157
+ return Array.isArray(groups) ? groups.map((g) => ({ ...g })) : [];
158
+ }
159
+ /** The canonical fields for `spec`, omitting the optional ones it doesn't set. */
160
+ function specFields(spec) {
161
+ return {
162
+ command: spec.command,
163
+ ...(spec.timeout !== undefined ? { timeout: spec.timeout } : {}),
164
+ ...(spec.statusMessage !== undefined ? { statusMessage: spec.statusMessage } : {}),
165
+ };
166
+ }
167
+ /**
168
+ * Merge one hook into parsed settings, idempotently. An existing entry (found by
169
+ * sentinel, legacy forms included) has its checkride-owned fields refreshed in
170
+ * place and its group's matcher normalized; otherwise a new group is appended.
171
+ * Sibling hooks in the same group, other groups, keys checkride does not own, and
172
+ * every unrelated settings key are left untouched, so applying twice yields
173
+ * deep-equal settings.
174
+ */
175
+ function applyHook(settings, spec) {
176
+ const hooks = { ...settings.hooks };
177
+ const groups = eventGroups(settings, spec.event);
178
+ const idx = groups.findIndex((g) => (g.hooks ?? []).some((h) => isSpecHook(spec, h)));
179
+ const existing = idx >= 0 ? groups[idx] : undefined;
180
+ if (existing) {
181
+ groups[idx] = {
182
+ ...existing,
183
+ ...(spec.matcher !== undefined ? { matcher: spec.matcher } : {}),
184
+ hooks: (existing.hooks ?? []).map((h) => (isSpecHook(spec, h) ? { ...h, ...specFields(spec) } : h)),
185
+ };
186
+ }
187
+ else {
188
+ groups.push({
189
+ ...(spec.matcher !== undefined ? { matcher: spec.matcher } : {}),
190
+ hooks: [{ type: 'command', ...specFields(spec) }],
191
+ });
192
+ }
193
+ return { ...settings, hooks: { ...hooks, [spec.event]: groups } };
194
+ }
195
+ /** The rules in `settings.permissions.deny`, tolerating a malformed value. */
196
+ function denyRules(settings) {
197
+ const deny = settings.permissions?.deny;
198
+ return Array.isArray(deny) ? [...deny] : [];
199
+ }
200
+ /** Rebuild `permissions` around `deny`, dropping keys that would be left empty. */
201
+ function withDeny(settings, deny) {
202
+ const permissions = { ...settings.permissions };
203
+ if (deny.length > 0)
204
+ permissions.deny = [...deny];
205
+ else
206
+ delete permissions.deny;
207
+ // Scaffolding a repo never asked for reads worse than leaving no trace, so an
208
+ // emptied `permissions` is dropped rather than left behind as `{}`.
209
+ if (Object.keys(permissions).length === 0) {
210
+ const { permissions: _dropped, ...rest } = settings;
211
+ return rest;
212
+ }
213
+ return { ...settings, permissions };
214
+ }
215
+ /**
216
+ * Add checkride's deny rules, appending only the ones that are missing.
217
+ *
218
+ * Appending — rather than replacing the array — is what makes this a list the
219
+ * repo shares rather than one checkride owns. A team that adds its own
220
+ * suppression file (a `fallow.baseline`, a lint baseline) alongside these keeps
221
+ * it across every refresh, which the generated script could never offer: that
222
+ * file was overwritten wholesale on every run.
223
+ */
224
+ function applyDeny(settings) {
225
+ const current = denyRules(settings);
226
+ const missing = PROTECT_DENY_RULES.filter((rule) => !current.includes(rule));
227
+ return missing.length === 0 ? settings : withDeny(settings, [...current, ...missing]);
228
+ }
229
+ /** Strip checkride's deny rules by exact match, leaving every rule it did not write. */
230
+ function removeDeny(settings) {
231
+ const current = denyRules(settings);
232
+ const kept = current.filter((rule) => !PROTECT_DENY_RULES.includes(rule));
233
+ return kept.length === current.length ? settings : withDeny(settings, kept);
234
+ }
235
+ /**
236
+ * Merge the selected hooks into parsed settings (see {@link applyHook}). `pm`
237
+ * only reaches the gate's spinner text, so it defaults to the near-universal
238
+ * `pnpm` for callers (tests, the removal path) that have no repo to detect one from.
239
+ *
240
+ * `protect` takes the other branch: it is a permission rule, not a hook, so it
241
+ * lands in `permissions.deny` and takes the retired hook entry away with it.
242
+ * Claude Code evaluates deny rules regardless of what a `PreToolUse` hook
243
+ * returns, so this is enforcement at a lower level than the script it replaces,
244
+ * at no cost per tool call.
245
+ */
246
+ export function applyHooks(settings = {}, names = HOOK_NAMES, pm = 'pnpm') {
247
+ const applied = hookSpecs(pm)
248
+ .filter((spec) => names.includes(spec.name))
249
+ .reduce((acc, spec) => applyHook(acc, spec), settings);
250
+ return names.includes(PROTECT) ? applyDeny(removeHook(applied, RETIRED_PROTECT_HOOK)) : applied;
251
+ }
252
+ /**
253
+ * Strip one hook from parsed settings. A group that is left with no hooks is
254
+ * dropped, and an event left with no groups loses its key — a settings file that
255
+ * accumulates empty scaffolding is worse than one that reads as if the hook was
256
+ * never there. Sibling hooks sharing the group survive, so removing `protect`
257
+ * cannot take a co-located `PreToolUse` hook with it.
258
+ */
259
+ function removeHook(settings, spec) {
260
+ const hooks = { ...settings.hooks };
261
+ const groups = eventGroups(settings, spec.event)
262
+ .map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !isSpecHook(spec, h)) }))
263
+ .filter((g) => g.hooks.length > 0);
264
+ if (groups.length > 0)
265
+ hooks[spec.event] = groups;
266
+ else
267
+ delete hooks[spec.event];
268
+ // The pruning runs all the way up. Without this last step a repo with no hooks
269
+ // left still carries `"hooks": {}` — and now that `protect` is a permission
270
+ // rule, applying it alone would *introduce* that empty key into a settings
271
+ // file that had none, which is scaffolding for a hook that no longer exists.
272
+ if (Object.keys(hooks).length === 0) {
273
+ const { hooks: _dropped, ...rest } = settings;
274
+ return rest;
275
+ }
276
+ return { ...settings, hooks };
277
+ }
278
+ /**
279
+ * Remove the named hooks from parsed settings — the inverse of
280
+ * {@link applyHooks}, and idempotent in the same way. The package manager is
281
+ * irrelevant here: entries are matched by sentinel, never by their rendered
282
+ * fields, so a repo whose gate was written under a different launcher still has
283
+ * its gate found and removed.
284
+ */
285
+ export function removeHooks(settings = {}, names = HOOK_NAMES) {
286
+ const removed = hookSpecs('pnpm')
287
+ .filter((spec) => names.includes(spec.name))
288
+ .reduce((acc, spec) => removeHook(acc, spec), settings);
289
+ // Both spellings of `protect` go: the deny rules this version writes, and the
290
+ // hook entry an older one did. Uninstalling must not depend on which you had.
291
+ return names.includes(PROTECT) ? removeDeny(removeHook(removed, RETIRED_PROTECT_HOOK)) : removed;
292
+ }
293
+ const SPEC = {
294
+ name: HARNESS,
295
+ configFile: CLAUDE_SETTINGS_FILE,
296
+ scripts: { gate: GATE_SCRIPT_FILE },
297
+ retired: { dirty: DIRTY_SCRIPT_FILE, protect: PROTECT_SCRIPT_FILE },
298
+ apply: applyHooks,
299
+ remove: removeHooks,
300
+ };
301
+ /** Write or refresh the selected Claude Code hooks in `cwd`. */
302
+ export function writeClaudeHooks(cwd, opts) {
303
+ return writeHarnessHooks(cwd, SPEC, opts);
304
+ }
305
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/agent-setup/claude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The `hooks` command — install or remove agent hooks, and nothing else.
3
+ *
4
+ * `agent-setup` writes four things at once: the `check` script alias, the
5
+ * AGENTS.md stanza, the hooks, and the Cursor skills. That bundle is right for
6
+ * adopting checkride and wrong for the day you want one hook gone, because the
7
+ * stanza guard sits in front of the whole run — so a repo whose AGENTS.md had
8
+ * been edited could not remove a hook at all, and a repo mid-upgrade had its
9
+ * stanza rewritten by a command it had asked to touch hooks.
10
+ *
11
+ * Hook management is not stanza management. This command reads and writes the
12
+ * harness config and the generated scripts, never AGENTS.md, so no state of that
13
+ * file can block it and no run of it can change that file.
14
+ */
15
+ import type { HarnessName } from '../gate.js';
16
+ import { type HookName } from './hook.js';
17
+ /** What the command does to the named hooks. */
18
+ export type HooksAction = 'add' | 'remove';
19
+ export type HooksOptions = {
20
+ action: HooksAction;
21
+ cwd?: string;
22
+ /** Which hooks; defaults to all of them on `add`, and is required on `remove`. */
23
+ hooks?: readonly HookName[];
24
+ harnesses?: readonly HarnessName[];
25
+ dryRun?: boolean;
26
+ stdout?: {
27
+ write: (chunk: string) => void;
28
+ };
29
+ };
30
+ export type HooksResult = {
31
+ written: string[];
32
+ removed: string[];
33
+ skipped: string[];
34
+ exitCode: number;
35
+ };
36
+ /**
37
+ * Install or remove the named hooks for the selected harnesses (default:
38
+ * detected). Idempotent in both directions — a second `add` reports every file
39
+ * unchanged, a second `remove` reports every file absent — and `dryRun` computes
40
+ * the same answer without touching the disk.
41
+ */
42
+ export declare function runHooks(options: HooksOptions): Promise<HooksResult>;
43
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/agent-setup/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,WAAW,CAAC;AAEtD,gDAAgD;AAChD,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE3C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAkCF;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAoB1E"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The `hooks` command — install or remove agent hooks, and nothing else.
3
+ *
4
+ * `agent-setup` writes four things at once: the `check` script alias, the
5
+ * AGENTS.md stanza, the hooks, and the Cursor skills. That bundle is right for
6
+ * adopting checkride and wrong for the day you want one hook gone, because the
7
+ * stanza guard sits in front of the whole run — so a repo whose AGENTS.md had
8
+ * been edited could not remove a hook at all, and a repo mid-upgrade had its
9
+ * stanza rewritten by a command it had asked to touch hooks.
10
+ *
11
+ * Hook management is not stanza management. This command reads and writes the
12
+ * harness config and the generated scripts, never AGENTS.md, so no state of that
13
+ * file can block it and no run of it can change that file.
14
+ */
15
+ import { writeHooks } from './hook.js';
16
+ /** Split the writer's per-file report into the three lists the caller reports. */
17
+ function partition(files) {
18
+ const out = { written: [], removed: [], skipped: [] };
19
+ for (const f of files) {
20
+ if (!f.changed)
21
+ out.skipped.push(`${f.path} (${f.removed === true ? 'absent' : 'unchanged'})`);
22
+ else if (f.removed === true)
23
+ out.removed.push(f.path);
24
+ else
25
+ out.written.push(f.path);
26
+ }
27
+ return out;
28
+ }
29
+ /**
30
+ * What to hand the writer for this action.
31
+ *
32
+ * Removal passes an empty write set alongside `remove`: tearing out one hook
33
+ * must not refresh the others as a side effect, or `hooks remove protect` would
34
+ * quietly rewrite the gate script it was never asked about.
35
+ *
36
+ * `remove` requires an explicit list. Defaulting it to every hook would let a
37
+ * bare `checkride hooks remove` silently tear out the gate, which is the one
38
+ * thing making "done" mean anything here; `add` defaults to all because the
39
+ * worst it can do is rewrite a file that was already correct.
40
+ */
41
+ function hookSelection(options) {
42
+ const names = options.hooks;
43
+ if (options.action !== 'remove')
44
+ return names ? { hooks: names } : {};
45
+ if (!names || names.length === 0) {
46
+ throw new Error('hooks remove: name the hooks to remove (e.g. `checkride hooks remove gate`)');
47
+ }
48
+ return { hooks: [], remove: names };
49
+ }
50
+ /**
51
+ * Install or remove the named hooks for the selected harnesses (default:
52
+ * detected). Idempotent in both directions — a second `add` reports every file
53
+ * unchanged, a second `remove` reports every file absent — and `dryRun` computes
54
+ * the same answer without touching the disk.
55
+ */
56
+ export async function runHooks(options) {
57
+ const cwd = options.cwd ?? process.cwd();
58
+ const { files } = await writeHooks(cwd, {
59
+ dryRun: options.dryRun ?? false,
60
+ ...(options.harnesses ? { harnesses: options.harnesses } : {}),
61
+ ...hookSelection(options),
62
+ });
63
+ const parts = partition(files);
64
+ if (options.stdout) {
65
+ // Counted as *files changed*, not files deleted. A hook a harness expresses
66
+ // as configuration — Claude Code's `protect` is a `permissions.deny` rule —
67
+ // is torn out by rewriting the config and deleting nothing, and a report of
68
+ // "removed 0 file(s)" for a run that did exactly what was asked reads as a
69
+ // no-op.
70
+ const verb = options.action === 'remove' ? 'removed' : 'wrote';
71
+ const count = parts.written.length + parts.removed.length;
72
+ const dry = options.dryRun === true ? ' [dry run]' : '';
73
+ options.stdout.write(`checkride hooks: ${verb} ${options.hooks?.join(', ') ?? 'every hook'} — ${count} file(s) changed${dry}.\n`);
74
+ }
75
+ return { ...parts, exitCode: 0 };
76
+ }
77
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/agent-setup/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAiB,UAAU,EAAE,MAAM,WAAW,CAAC;AAsBtD,kFAAkF;AAClF,SAAS,SAAS,CAAC,KAA0B;IAC3C,MAAM,GAAG,GAAkC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACrF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,CAAC,OAAO;YAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC;aAC1F,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI;YAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;;YACjD,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,aAAa,CAAC,OAAqB;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE;QACtC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,aAAa,CAAC,OAAO,CAAC;KAC1B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,4EAA4E;QAC5E,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,SAAS;QACT,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAC1D,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,MAAM,KAAK,mBAAmB,GAAG,KAAK,CAAC,CAAC;IACpI,CAAC;IACD,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The Cursor hook writer — `.cursor/hooks.json` plus the scripts its entries
3
+ * invoke.
4
+ *
5
+ * Same contract as the Claude writer (`./claude.ts`): sentinel-keyed identity,
6
+ * in-place refresh, every unrelated key preserved, re-applying is a no-op. The
7
+ * schema is what differs, and it differs enough that sharing the merge would
8
+ * cost more than it saved:
9
+ *
10
+ * - Cursor requires a top-level `"version": 1`.
11
+ * - An event maps to a flat list of `{command, matcher?, timeout?}` entries —
12
+ * there is no group wrapper and no `type` discriminator, so a hook is one
13
+ * object where Claude Code needs two levels.
14
+ * - The events are lowercase and differently cut: `stop`, `afterFileEdit`
15
+ * (purpose-built, no matcher) and `preToolUse` (matched against Cursor's own
16
+ * tool names — `Shell`, `Read`, `Write`, `Grep`, `Delete`, `Task`, `MCP:*`).
17
+ *
18
+ * The gate entry carries three fields the Claude writer has no equivalent for —
19
+ * `timeout`, `loop_limit` and `failClosed` — because Cursor's defaults for all
20
+ * three end a turn that checkride would keep blocking. See {@link hookSpecs}.
21
+ */
22
+ import { CURSOR_HOOKS_FILE } from '../gate.js';
23
+ import type { WriteOptions } from './harness.js';
24
+ import type { HookFile } from './files.js';
25
+ /**
26
+ * Project-shared Cursor hook config (committed), relative to the repo root.
27
+ * Declared in `../gate.js`, which reads it to detect a native Cursor gate, and
28
+ * re-exported here so this module stays the one place a caller looks for it.
29
+ */
30
+ export { CURSOR_HOOKS_FILE };
31
+ /** The Cursor hook events checkride writes under. */
32
+ type HookEvent = 'stop' | 'afterFileEdit' | 'preToolUse';
33
+ /**
34
+ * One Cursor hook entry. Unknown keys pass through untouched.
35
+ *
36
+ * `loop_limit` is `number | null` in Cursor's schema, where `null` carries the
37
+ * meaning — no cap — so it cannot be modelled as "absent".
38
+ */
39
+ type CursorHook = {
40
+ command?: string;
41
+ matcher?: string;
42
+ timeout?: number;
43
+ loop_limit?: number | null;
44
+ failClosed?: boolean;
45
+ } & Record<string, unknown>;
46
+ /** The subset of `.cursor/hooks.json` we touch; every other key is preserved. */
47
+ export type CursorHooks = {
48
+ version?: number;
49
+ hooks?: Partial<Record<HookEvent, CursorHook[]>> & Record<string, unknown>;
50
+ } & Record<string, unknown>;
51
+ /**
52
+ * Merge the selected hooks into a parsed config, seeding `version` when absent.
53
+ * An existing `version` is never overwritten: if a future Cursor bumps it, the
54
+ * repo's value is the one that matches the rest of the file.
55
+ */
56
+ export declare function applyCursorHooks(config?: CursorHooks, names?: readonly string[]): CursorHooks;
57
+ /** Remove the named hooks from a parsed config — the inverse of {@link applyCursorHooks}. */
58
+ export declare function removeCursorHooks(config?: CursorHooks, names?: readonly string[]): CursorHooks;
59
+ /** Write or refresh the selected Cursor hooks in `cwd`. */
60
+ export declare function writeCursorHooks(cwd: string, opts: WriteOptions): Promise<HookFile[]>;
61
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/agent-setup/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAW7B,qDAAqD;AACrD,KAAK,SAAS,GAAG,MAAM,GAAG,eAAe,GAAG,YAAY,CAAC;AAEzD;;;;;GAKG;AACH,KAAK,UAAU,GAAG;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5E,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA+H5B;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,WAAgB,EAAE,KAAK,GAAE,SAAS,MAAM,EAAe,GAAG,WAAW,CAK7G;AAgBD,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,WAAgB,EACxB,KAAK,GAAE,SAAS,MAAM,EAAe,GACpC,WAAW,CAIb;AAYD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAErF"}