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,189 @@
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 { GATE_TIMEOUT_SECONDS, HOOK_NAMES, writeHarnessHooks } from './harness.js';
24
+ /**
25
+ * Project-shared Cursor hook config (committed), relative to the repo root.
26
+ * Declared in `../gate.js`, which reads it to detect a native Cursor gate, and
27
+ * re-exported here so this module stays the one place a caller looks for it.
28
+ */
29
+ export { CURSOR_HOOKS_FILE };
30
+ const GATE_SCRIPT_FILE = '.cursor/hooks/checkride-gate.sh';
31
+ const DIRTY_SCRIPT_FILE = '.cursor/hooks/checkride-dirty.sh';
32
+ const PROTECT_SCRIPT_FILE = '.cursor/hooks/checkride-protect.cjs';
33
+ const HARNESS = 'cursor';
34
+ /** The schema version Cursor's loader expects at the top of the file. */
35
+ const SCHEMA_VERSION = 1;
36
+ /**
37
+ * `sh` (not the exec bit) runs the script so a clone that lost file modes still
38
+ * gates; `CURSOR_PROJECT_DIR` anchors it when the session cwd is a subdirectory.
39
+ *
40
+ * **Unverified against a live Cursor.** Cursor documents `command` as "a shell
41
+ * string, an absolute path, or a relative path", but every documented example is
42
+ * a bare path, and it states separately that project hooks already run from the
43
+ * project root. If Cursor ever spawns these without a shell, `${…}` stays
44
+ * literal, the spawn fails, and Cursor's fail-open default ends the turn with no
45
+ * signal at all — the one failure mode `failClosed` cannot cover, because the
46
+ * hook it would guard never started. `failClosed` is set regardless; if that
47
+ * turns out not to catch it, drop both the interpreter and the variable and
48
+ * write the bare `.cursor/hooks/checkride-gate.sh` the docs show, accepting the
49
+ * dependence on the exec bit. See docs/cursor.md.
50
+ */
51
+ const run = (interpreter, file) => `${interpreter} "\${CURSOR_PROJECT_DIR:-.}/${file}"`;
52
+ /**
53
+ * Cursor's file-mutating tool names. There is no separate `Edit` tool — `Write`
54
+ * covers both creating and modifying — and `Delete` can remove an accounting
55
+ * file just as effectively as a write can overwrite it.
56
+ */
57
+ const EDIT_TOOLS = 'Write|Delete';
58
+ /**
59
+ * Cursor's defaults are tuned for observers; the gate is not one, so all three
60
+ * of its non-command fields are set against the default rather than with it:
61
+ *
62
+ * - `timeout` — a full pipeline run is minutes, and the platform default is not.
63
+ * - `loop_limit: null` — the default of **5** stops the auto-followup after five
64
+ * turns, at which point a red repo finishes anyway. Claude Code re-blocks
65
+ * indefinitely; without this the two harnesses are not the same gate, and the
66
+ * Cursor one degrades into five nudges.
67
+ * - `failClosed: true` — Cursor's default is fail-*open*: a hook that crashes,
68
+ * times out, or emits unparseable JSON lets the turn end silently. That is the
69
+ * vacuous green the whole contract exists to prevent (docs/contract.md).
70
+ *
71
+ * The two guards keep Cursor's fail-open default on purpose — see `dirty` and
72
+ * `protect` in `./scripts.ts` for why each must never block an edit.
73
+ */
74
+ function hookSpecs() {
75
+ return [
76
+ {
77
+ name: 'gate',
78
+ event: 'stop',
79
+ timeout: GATE_TIMEOUT_SECONDS,
80
+ loopLimit: null,
81
+ failClosed: true,
82
+ sentinels: [GATE_SCRIPT_FILE],
83
+ command: run('sh', GATE_SCRIPT_FILE),
84
+ },
85
+ {
86
+ // `afterFileEdit` rather than `postToolUse`: it is purpose-built for "a
87
+ // file changed", needs no matcher, and so cannot drift when Cursor renames
88
+ // a tool.
89
+ name: 'dirty',
90
+ event: 'afterFileEdit',
91
+ sentinels: [DIRTY_SCRIPT_FILE],
92
+ command: run('sh', DIRTY_SCRIPT_FILE),
93
+ },
94
+ {
95
+ name: 'protect',
96
+ event: 'preToolUse',
97
+ matcher: EDIT_TOOLS,
98
+ sentinels: [PROTECT_SCRIPT_FILE],
99
+ command: run('node', PROTECT_SCRIPT_FILE),
100
+ },
101
+ ];
102
+ }
103
+ /** True when an entry belongs to `spec` (matched by any of its sentinels). */
104
+ function isSpecHook(spec, hook) {
105
+ const command = hook.command;
106
+ return typeof command === 'string' && spec.sentinels.some((s) => command.includes(s));
107
+ }
108
+ /** The event's entry list, tolerating a malformed (non-array) config value. */
109
+ function eventHooks(config, event) {
110
+ const entries = config.hooks?.[event];
111
+ return Array.isArray(entries) ? entries.map((h) => ({ ...h })) : [];
112
+ }
113
+ /**
114
+ * The canonical fields for `spec`, omitting the optional ones it doesn't set.
115
+ * `loopLimit` is compared against `undefined` rather than truth-tested, because
116
+ * the value checkride writes is `null`.
117
+ */
118
+ function specFields(spec) {
119
+ return {
120
+ command: spec.command,
121
+ ...(spec.matcher !== undefined ? { matcher: spec.matcher } : {}),
122
+ ...(spec.timeout !== undefined ? { timeout: spec.timeout } : {}),
123
+ ...(spec.loopLimit !== undefined ? { loop_limit: spec.loopLimit } : {}),
124
+ ...(spec.failClosed !== undefined ? { failClosed: spec.failClosed } : {}),
125
+ };
126
+ }
127
+ /**
128
+ * Merge one hook into a parsed config, idempotently. An existing entry (found by
129
+ * sentinel) has its fields refreshed in place, keeping any key checkride does not
130
+ * own; otherwise a new entry is appended.
131
+ */
132
+ function applyHook(config, spec) {
133
+ const hooks = { ...config.hooks };
134
+ const entries = eventHooks(config, spec.event);
135
+ const idx = entries.findIndex((h) => isSpecHook(spec, h));
136
+ if (idx >= 0) {
137
+ entries[idx] = { ...entries[idx], ...specFields(spec) };
138
+ }
139
+ else {
140
+ entries.push(specFields(spec));
141
+ }
142
+ return { ...config, hooks: { ...hooks, [spec.event]: entries } };
143
+ }
144
+ /**
145
+ * Merge the selected hooks into a parsed config, seeding `version` when absent.
146
+ * An existing `version` is never overwritten: if a future Cursor bumps it, the
147
+ * repo's value is the one that matches the rest of the file.
148
+ */
149
+ export function applyCursorHooks(config = {}, names = HOOK_NAMES) {
150
+ const seeded = config.version === undefined ? { version: SCHEMA_VERSION, ...config } : config;
151
+ return hookSpecs()
152
+ .filter((spec) => names.includes(spec.name))
153
+ .reduce((acc, spec) => applyHook(acc, spec), seeded);
154
+ }
155
+ /**
156
+ * Strip one hook from a parsed config. An event left with no entries loses its
157
+ * key; `version` and every unrelated entry stay. Cursor's schema is flat, so
158
+ * there is no empty group to prune — the Claude writer's extra step has no
159
+ * counterpart here.
160
+ */
161
+ function removeHook(config, spec) {
162
+ const hooks = { ...config.hooks };
163
+ const entries = eventHooks(config, spec.event).filter((h) => !isSpecHook(spec, h));
164
+ if (entries.length > 0)
165
+ hooks[spec.event] = entries;
166
+ else
167
+ delete hooks[spec.event];
168
+ return { ...config, hooks };
169
+ }
170
+ /** Remove the named hooks from a parsed config — the inverse of {@link applyCursorHooks}. */
171
+ export function removeCursorHooks(config = {}, names = HOOK_NAMES) {
172
+ return hookSpecs()
173
+ .filter((spec) => names.includes(spec.name))
174
+ .reduce((acc, spec) => removeHook(acc, spec), config);
175
+ }
176
+ const SPEC = {
177
+ name: HARNESS,
178
+ configFile: CURSOR_HOOKS_FILE,
179
+ // Cursor's config has no field that renders the gate's command in its UI, so
180
+ // the package manager reaches its hooks only through the generated script.
181
+ apply: applyCursorHooks,
182
+ scripts: { gate: GATE_SCRIPT_FILE, dirty: DIRTY_SCRIPT_FILE, protect: PROTECT_SCRIPT_FILE },
183
+ remove: removeCursorHooks,
184
+ };
185
+ /** Write or refresh the selected Cursor hooks in `cwd`. */
186
+ export function writeCursorHooks(cwd, opts) {
187
+ return writeHarnessHooks(cwd, SPEC, opts);
188
+ }
189
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agent-setup/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Filesystem helpers shared by the per-harness hook writers.
3
+ *
4
+ * Every write `agent-setup` makes is idempotent — a second run must report
5
+ * `changed: false` for every file — so the writers never write blind: they read
6
+ * first, compare, and only then touch the disk. `dryRun` computes the same
7
+ * result without writing anything.
8
+ */
9
+ /**
10
+ * One file a hook writer produced, and whether this run actually changed it.
11
+ * `removed` marks a deletion, so a caller reporting the run can tell a file that
12
+ * was written from one that was taken away — both are changes.
13
+ */
14
+ export type HookFile = {
15
+ path: string;
16
+ changed: boolean;
17
+ removed?: boolean;
18
+ };
19
+ export declare function readIfExists(path: string): Promise<string | null>;
20
+ /** Write `content` to `cwd/rel` (creating directories) unless it already matches. */
21
+ export declare function putFile(cwd: string, rel: string, content: string, opts: {
22
+ dryRun: boolean;
23
+ executable?: boolean;
24
+ }): Promise<HookFile>;
25
+ /**
26
+ * Delete `cwd/rel` if it is there. Reports `changed: true` only when a file was
27
+ * actually removed, so a second removal is the no-op every other write here is.
28
+ */
29
+ export declare function dropFile(cwd: string, rel: string, opts: {
30
+ dryRun: boolean;
31
+ }): Promise<HookFile>;
32
+ /**
33
+ * Merge `next` into the JSON config at `cwd/rel` and write it back when it
34
+ * changed. `merge` receives the parsed file — or `undefined` when there is none
35
+ * — and returns the merged value; it must be idempotent.
36
+ *
37
+ * `skipIfMissing` suppresses creating the file at all: a run that only *removes*
38
+ * hooks has nothing to say to a repo that never had that harness configured, and
39
+ * writing `{}` into a `.claude/settings.json` that did not exist would be a
40
+ * confusing way to say "nothing to remove".
41
+ *
42
+ * A malformed file is named in the error, so a consumer sees
43
+ * `invalid .claude/settings.json: <reason>` instead of a bare `SyntaxError`
44
+ * stack (mirrors `invalidConfig` in `config.ts`).
45
+ */
46
+ export declare function putJson<T>(cwd: string, rel: string, merge: (current?: T) => T, opts: {
47
+ dryRun: boolean;
48
+ skipIfMissing?: boolean;
49
+ }): Promise<HookFile>;
50
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/agent-setup/files.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7E,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMvE;AAED,qFAAqF;AACrF,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9C,OAAO,CAAC,QAAQ,CAAC,CAUnB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACxB,OAAO,CAAC,QAAQ,CAAC,CAKnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EACzB,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD,OAAO,CAAC,QAAQ,CAAC,CAoBnB"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Filesystem helpers shared by the per-harness hook writers.
3
+ *
4
+ * Every write `agent-setup` makes is idempotent — a second run must report
5
+ * `changed: false` for every file — so the writers never write blind: they read
6
+ * first, compare, and only then touch the disk. `dryRun` computes the same
7
+ * result without writing anything.
8
+ */
9
+ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
10
+ import { dirname, join } from 'node:path';
11
+ export async function readIfExists(path) {
12
+ try {
13
+ return await readFile(path, 'utf8');
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ /** Write `content` to `cwd/rel` (creating directories) unless it already matches. */
20
+ export async function putFile(cwd, rel, content, opts) {
21
+ const path = join(cwd, rel);
22
+ const raw = await readIfExists(path);
23
+ const changed = raw !== content;
24
+ if (changed && !opts.dryRun) {
25
+ await mkdir(dirname(path), { recursive: true });
26
+ await writeFile(path, content);
27
+ if (opts.executable)
28
+ await chmod(path, 0o755);
29
+ }
30
+ return { path: rel, changed };
31
+ }
32
+ /**
33
+ * Delete `cwd/rel` if it is there. Reports `changed: true` only when a file was
34
+ * actually removed, so a second removal is the no-op every other write here is.
35
+ */
36
+ export async function dropFile(cwd, rel, opts) {
37
+ const path = join(cwd, rel);
38
+ const changed = (await readIfExists(path)) !== null;
39
+ if (changed && !opts.dryRun)
40
+ await rm(path, { force: true });
41
+ return { path: rel, changed, removed: true };
42
+ }
43
+ /**
44
+ * Merge `next` into the JSON config at `cwd/rel` and write it back when it
45
+ * changed. `merge` receives the parsed file — or `undefined` when there is none
46
+ * — and returns the merged value; it must be idempotent.
47
+ *
48
+ * `skipIfMissing` suppresses creating the file at all: a run that only *removes*
49
+ * hooks has nothing to say to a repo that never had that harness configured, and
50
+ * writing `{}` into a `.claude/settings.json` that did not exist would be a
51
+ * confusing way to say "nothing to remove".
52
+ *
53
+ * A malformed file is named in the error, so a consumer sees
54
+ * `invalid .claude/settings.json: <reason>` instead of a bare `SyntaxError`
55
+ * stack (mirrors `invalidConfig` in `config.ts`).
56
+ */
57
+ export async function putJson(cwd, rel, merge, opts) {
58
+ const path = join(cwd, rel);
59
+ const raw = await readIfExists(path);
60
+ if (raw === null && opts.skipIfMissing === true)
61
+ return { path: rel, changed: false };
62
+ let current;
63
+ if (raw !== null) {
64
+ try {
65
+ current = JSON.parse(raw);
66
+ }
67
+ catch (err) {
68
+ const reason = err instanceof Error ? err.message : String(err);
69
+ throw new Error(`invalid ${rel}: ${reason}`, { cause: err });
70
+ }
71
+ }
72
+ const nextRaw = `${JSON.stringify(merge(current), null, 2)}\n`;
73
+ const changed = raw !== nextRaw;
74
+ if (changed && !opts.dryRun) {
75
+ await mkdir(dirname(path), { recursive: true });
76
+ await writeFile(path, nextRaw);
77
+ }
78
+ return { path: rel, changed };
79
+ }
80
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/agent-setup/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The shape a harness writer fills in, and the one routine that drives it.
3
+ *
4
+ * Every harness needs the same two writes — merge its hook entries into its
5
+ * config file, then drop whichever scripts those entries invoke — and differs
6
+ * only in *where* each lands, *how* its config merges, and how much of the work
7
+ * its config can do without a script at all. Keeping that common body here is
8
+ * what makes a new harness a description rather than a copy: a `HarnessSpec`
9
+ * plus a merge function, and nothing else.
10
+ */
11
+ import type { HarnessName } from '../gate.js';
12
+ import { type PackageManager } from '../pm/index.js';
13
+ import type { HookFile } from './files.js';
14
+ /**
15
+ * Every hook `agent-setup` can write; `--hook <a,b>` selects a subset. It lives
16
+ * here rather than in `./hook.ts` so the per-harness writers can name it without
17
+ * importing the module that imports them.
18
+ */
19
+ export declare const HOOK_NAMES: readonly ["gate", "dirty", "protect"];
20
+ export type HookName = (typeof HOOK_NAMES)[number];
21
+ /**
22
+ * Ceiling for the gate run, in seconds, for any harness whose config carries
23
+ * one. Generous on purpose: it exists to break a hang, not to police a slow
24
+ * repo, and checkride's own per-check timeouts fire long before it does.
25
+ *
26
+ * Both harnesses default lower than a real pipeline takes — Cursor's default is
27
+ * short, and Claude Code's is 600s — and both read a timed-out stop hook as a
28
+ * *broken* one, which under Claude Code means the turn simply ends. A gate that
29
+ * quietly stops gating on the slowest repos is the vacuous green the contract
30
+ * exists to prevent, so neither default is left in place.
31
+ */
32
+ export declare const GATE_TIMEOUT_SECONDS = 900;
33
+ /**
34
+ * The spinner text a harness shows while the gate runs.
35
+ *
36
+ * Without it the only feedback during a full pipeline run is the harness's
37
+ * default spinner, which is indistinguishable from a hung model — the user is
38
+ * left guessing whether anything is happening at all. Naming the command that
39
+ * is actually running answers that in the one place they are already looking.
40
+ */
41
+ export declare function gateStatusMessage(pm: PackageManager): string;
42
+ /**
43
+ * Where one harness keeps its hooks, and how to merge into its config.
44
+ *
45
+ * `T` is that harness's config shape, so each writer keeps its own types all
46
+ * the way through — `apply` is never handed a config it does not understand.
47
+ */
48
+ export type HarnessSpec<T> = {
49
+ name: HarnessName;
50
+ /** The harness's hook config, relative to the repo root. */
51
+ configFile: string;
52
+ /**
53
+ * Where each hook's script lives, relative to the repo root — for the hooks
54
+ * that still need one.
55
+ *
56
+ * A hook is absent here when the harness can express it as *configuration*
57
+ * instead: Claude Code's `protect` is a `permissions.deny` rule and its
58
+ * `dirty` is an inline one-liner, so neither writes a file. Generated code in
59
+ * a consumer's repo is a cost — a file to review, to keep in sync, and to
60
+ * wonder about — and it should be paid only where config cannot do the job.
61
+ */
62
+ scripts: Partial<Record<HookName, string>>;
63
+ /**
64
+ * Scripts an earlier checkride wrote for this harness that this one does not.
65
+ * Deleted whenever their hook is written or removed, so a repo upgrading into
66
+ * the config-only form does not keep a dead file it will never run again.
67
+ */
68
+ retired?: Partial<Record<HookName, string>>;
69
+ /**
70
+ * Merge the named hooks into a parsed config. Must be idempotent. `pm` is the
71
+ * repo's package manager, which a harness that renders the gate's command in
72
+ * its own UI (Claude Code's `statusMessage`) needs in the config, not just in
73
+ * the script.
74
+ */
75
+ apply: (config: T | undefined, names: readonly string[], pm: PackageManager) => T;
76
+ /** Strip the named hooks from a parsed config. Must be idempotent. */
77
+ remove: (config: T | undefined, names: readonly string[]) => T;
78
+ };
79
+ export type WriteOptions = {
80
+ pm?: PackageManager;
81
+ dryRun?: boolean;
82
+ hooks: readonly string[];
83
+ /**
84
+ * Hooks to tear out: their config entries are stripped and their scripts
85
+ * deleted. Removal wins over `hooks`, so a name in both is removed — the
86
+ * caller-facing flags (`--hook`/`--remove-hook`) reject that combination
87
+ * before it gets here, and this is the safe reading if one ever slips through.
88
+ */
89
+ remove?: readonly string[];
90
+ };
91
+ /**
92
+ * Write or refresh `spec`'s hooks in `cwd` and tear out the ones marked for
93
+ * removal: the config entries first, then only the scripts the selected hooks
94
+ * actually invoke. Every write is idempotent, and `dryRun` computes the result
95
+ * without touching the disk.
96
+ *
97
+ * A run that only removes never *creates* the harness's config file — a repo
98
+ * that was never wired for this harness has nothing to un-wire.
99
+ */
100
+ export declare function writeHarnessHooks<T extends object>(cwd: string, spec: HarnessSpec<T>, opts: WriteOptions): Promise<HookFile[]>;
101
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/agent-setup/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAwB,KAAK,cAAc,EAAa,MAAM,gBAAgB,CAAC;AACtF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C;;;;GAIG;AACH,eAAO,MAAM,UAAU,uCAAwC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAE5D;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,cAAc,KAAK,CAAC,CAAC;IAClF,sEAAsE;IACtE,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC;CAChE,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EACtD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EACpB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAgDrB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The shape a harness writer fills in, and the one routine that drives it.
3
+ *
4
+ * Every harness needs the same two writes — merge its hook entries into its
5
+ * config file, then drop whichever scripts those entries invoke — and differs
6
+ * only in *where* each lands, *how* its config merges, and how much of the work
7
+ * its config can do without a script at all. Keeping that common body here is
8
+ * what makes a new harness a description rather than a copy: a `HarnessSpec`
9
+ * plus a merge function, and nothing else.
10
+ */
11
+ import { detectPackageManager, runScript } from '../pm/index.js';
12
+ import { dropFile, putFile, putJson } from './files.js';
13
+ import { dirtyScript, gateScript, protectScript } from './scripts.js';
14
+ /**
15
+ * Every hook `agent-setup` can write; `--hook <a,b>` selects a subset. It lives
16
+ * here rather than in `./hook.ts` so the per-harness writers can name it without
17
+ * importing the module that imports them.
18
+ */
19
+ export const HOOK_NAMES = ['gate', 'dirty', 'protect'];
20
+ /**
21
+ * Ceiling for the gate run, in seconds, for any harness whose config carries
22
+ * one. Generous on purpose: it exists to break a hang, not to police a slow
23
+ * repo, and checkride's own per-check timeouts fire long before it does.
24
+ *
25
+ * Both harnesses default lower than a real pipeline takes — Cursor's default is
26
+ * short, and Claude Code's is 600s — and both read a timed-out stop hook as a
27
+ * *broken* one, which under Claude Code means the turn simply ends. A gate that
28
+ * quietly stops gating on the slowest repos is the vacuous green the contract
29
+ * exists to prevent, so neither default is left in place.
30
+ */
31
+ export const GATE_TIMEOUT_SECONDS = 900;
32
+ /**
33
+ * The spinner text a harness shows while the gate runs.
34
+ *
35
+ * Without it the only feedback during a full pipeline run is the harness's
36
+ * default spinner, which is indistinguishable from a hung model — the user is
37
+ * left guessing whether anything is happening at all. Naming the command that
38
+ * is actually running answers that in the one place they are already looking.
39
+ */
40
+ export function gateStatusMessage(pm) {
41
+ return `checkride gate — running \`${runScript(pm, 'check')}\``;
42
+ }
43
+ /**
44
+ * Write or refresh `spec`'s hooks in `cwd` and tear out the ones marked for
45
+ * removal: the config entries first, then only the scripts the selected hooks
46
+ * actually invoke. Every write is idempotent, and `dryRun` computes the result
47
+ * without touching the disk.
48
+ *
49
+ * A run that only removes never *creates* the harness's config file — a repo
50
+ * that was never wired for this harness has nothing to un-wire.
51
+ */
52
+ export async function writeHarnessHooks(cwd, spec, opts) {
53
+ const pm = opts.pm ?? detectPackageManager({ cwd });
54
+ const dryRun = opts.dryRun ?? false;
55
+ const gone = opts.remove ?? [];
56
+ const names = opts.hooks.filter((name) => !gone.includes(name));
57
+ const bodies = {
58
+ // The gate guards on the edit marker only when the hook that sets it is
59
+ // written too; alone, the guard would disarm the gate entirely. Removing
60
+ // `dirty` therefore rewrites a surviving gate unguarded, which is why the
61
+ // config merge and the script bodies both read the post-removal selection.
62
+ gate: () => gateScript(pm, { harness: spec.name, dirtyGuard: names.includes('dirty') }),
63
+ dirty: dirtyScript,
64
+ protect: protectScript,
65
+ };
66
+ const config = await putJson(cwd, spec.configFile, (c) => spec.apply(spec.remove(c, gone), names, pm), { dryRun, skipIfMissing: names.length === 0 });
67
+ // A hook this harness expresses as config has no script to write, and may have
68
+ // a retired one to clear away. `touched` is both directions at once, because a
69
+ // dead file is worth deleting whether the hook is arriving or leaving.
70
+ const touched = HOOK_NAMES.filter((name) => names.includes(name) || gone.includes(name));
71
+ const [written, removed, swept] = await Promise.all([
72
+ Promise.all(HOOK_NAMES.filter((name) => names.includes(name) && spec.scripts[name] !== undefined).map((name) => putFile(cwd, spec.scripts[name] ?? '', bodies[name](), { dryRun, executable: true }))),
73
+ Promise.all(HOOK_NAMES.filter((name) => gone.includes(name) && spec.scripts[name] !== undefined).map((name) => dropFile(cwd, spec.scripts[name] ?? '', { dryRun }))),
74
+ Promise.all(touched
75
+ .map((name) => spec.retired?.[name])
76
+ .filter((path) => path !== undefined)
77
+ .map((path) => dropFile(cwd, path, { dryRun }))),
78
+ ]);
79
+ // Only report a retired script that was actually there. Every repo set up
80
+ // after these became configuration has none, and listing files that never
81
+ // existed as "absent" would make the common run read like a cleanup.
82
+ return [config, ...written, ...removed, ...swept.filter((f) => f.changed)];
83
+ }
84
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/agent-setup/harness.ts"],"names":[],"mappings":"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"}