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,299 @@
1
+ /**
2
+ * The hook scripts checkride generates into a consumer repo.
3
+ *
4
+ * checkride owns these files and overwrites them on every `agent-setup`/`init`,
5
+ * so consumer customization belongs beside them, never in them. That is also why
6
+ * the settings entry each harness gets is a stable one-liner invoking a script
7
+ * rather than the behavior inline: a refresh rewrites the script freely without
8
+ * ever clobbering an entry a human may have edited.
9
+ *
10
+ * **Every script here is one a harness's configuration could not express.** A
11
+ * generated file in someone's repo is a standing cost — reviewed, kept in sync,
12
+ * wondered about — so it is written only where config falls short. Claude Code
13
+ * takes just the gate: its `protect` is a `permissions.deny` rule and its
14
+ * `dirty` an inline command. Cursor, whose config is hooks and nothing else,
15
+ * still takes all three. The gate is the one both need, because it alone has a
16
+ * branch neither config can carry (see {@link gateTail}).
17
+ */
18
+ import { DIRTY_MARKER } from '../gate.js';
19
+ import { execCommand } from '../pm/index.js';
20
+ /**
21
+ * The repo root, however the calling harness spells it. Cursor sets
22
+ * `CURSOR_PROJECT_DIR` (and `CLAUDE_PROJECT_DIR` as an alias); Claude Code sets
23
+ * only the latter. The final `.` fallback keeps the script working under an
24
+ * older harness that sets neither, where the cwd is already the project.
25
+ */
26
+ const PROJECT_DIR = '"${CURSOR_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"';
27
+ /**
28
+ * `<pm> exec checkride <args…>`, spelled for `pm`'s launcher. Quieted: the gate
29
+ * parses this command's stdout, so pnpm's narration must not lead it.
30
+ */
31
+ function checkrideCommand(pm, args) {
32
+ return execCommand(pm, ['checkride', ...args], { quiet: true });
33
+ }
34
+ /**
35
+ * What a harness is told when `checkride gate` itself could not run.
36
+ *
37
+ * Emitted through `printf '%s\n' '…'` — single-quoted, never double — because
38
+ * the text carries backticks and `sh` runs a backtick span inside double quotes
39
+ * as a command. The first draft of this message used `echo "…"` and the shell
40
+ * silently ate the two quoted command names, substituting their output. Keep it
41
+ * free of single quotes for the same reason.
42
+ */
43
+ const UNRUNNABLE = 'checkride: the gate could not run — `checkride gate` is not resolvable in this repo. ' +
44
+ 'Install checkride (or re-run `checkride agent-setup`) before finishing.';
45
+ /**
46
+ * The one line that hands `harness` the {@link UNRUNNABLE} verdict, and the exit
47
+ * status that goes with it.
48
+ *
49
+ * The statuses differ because the harnesses do: Claude Code blocks on 2, while
50
+ * Cursor reads any non-zero stop hook as a *broken* hook and ends the turn —
51
+ * there, blocking has to ride in the body with an exit of 0. Kept as a pair so
52
+ * no branch can pick one without the other.
53
+ */
54
+ function unrunnable(harness) {
55
+ return harness === 'cursor'
56
+ ? { emit: `printf '%s\\n' '${JSON.stringify({ followup_message: UNRUNNABLE })}'`, exit: 0 }
57
+ : { emit: `printf '%s\\n' '${UNRUNNABLE}' >&2`, exit: 2 };
58
+ }
59
+ /**
60
+ * Translate the gate's exit status into `harness`'s protocol.
61
+ *
62
+ * The interesting branch is the last one. `checkride gate` exits 0 or 2 by
63
+ * design; anything else means it never ran — an uninstalled checkride, a broken
64
+ * launcher — and that must **block**, not pass. Claude Code reads a plain exit 1
65
+ * as "hook failed, carry on", which would leave a repo whose gate had silently
66
+ * stopped gating: precisely the vacuous green checkride exists to prevent. So
67
+ * the gate alone fails closed. (`protect` fails open, deliberately: the cost of
68
+ * a broken protect hook is a repo where nothing can be written at all.)
69
+ */
70
+ function gateTail(harness) {
71
+ const { emit, exit } = unrunnable(harness);
72
+ if (harness === 'cursor') {
73
+ return [
74
+ '# On success checkride has already written its JSON to stdout, which is what',
75
+ '# Cursor reads. Only the never-ran case is left to answer for.',
76
+ '[ "$status" -eq 0 ] && exit 0',
77
+ emit,
78
+ `exit ${exit}`,
79
+ ];
80
+ }
81
+ return [
82
+ '# Claude Code parses a hook body only on exit 0, and only a body can carry a',
83
+ '# user-visible message alongside the block. So when checkride produced one,',
84
+ '# forward it and exit 0: the verdict rides in `decision`, not in the status.',
85
+ 'if [ -n "$body" ]; then',
86
+ " printf '%s\\n' \"$body\"",
87
+ ' case "$status" in',
88
+ ' 0|2) exit 0 ;;',
89
+ ' esac',
90
+ 'fi',
91
+ '',
92
+ '# No body — an older checkride that reports only through the exit code.',
93
+ '[ "$status" -eq 0 ] && exit 0',
94
+ '[ "$status" -eq 2 ] && exit 2',
95
+ '',
96
+ "# Neither of checkride's two gate codes: it never ran. Block anyway.",
97
+ emit,
98
+ `exit ${exit}`,
99
+ ];
100
+ }
101
+ /**
102
+ * Enter the repo, or report the same "could not run" verdict as any other way
103
+ * the gate can fail to start.
104
+ *
105
+ * Spelled as a block rather than `cd … || exit 2` for two reasons: a bare status
106
+ * tells the agent nothing, and under Cursor a non-zero stop hook is a *broken*
107
+ * hook, so the one branch that used to exit 2 there was the one branch that
108
+ * silently let a turn end.
109
+ */
110
+ function enterRepo(harness) {
111
+ const { emit, exit } = unrunnable(harness);
112
+ return [`if ! cd ${PROJECT_DIR}; then`, ` ${emit}`, ` exit ${exit}`, 'fi'];
113
+ }
114
+ /**
115
+ * How the gate's stdout is taken.
116
+ *
117
+ * Cursor's script lets it stream straight through: Cursor reads the hook's own
118
+ * stdout, and there is nothing for the script to decide. Claude Code's captures
119
+ * it, because the script has to *choose an exit code based on whether a body
120
+ * came back* — see {@link gateTail}. Capturing costs nothing that is visible:
121
+ * the pipeline's human-readable progress is on stderr and streams live either
122
+ * way; stdout carries only the one-line JSON verdict, written at the very end.
123
+ */
124
+ function invokeGate(pm, harness, args) {
125
+ const command = checkrideCommand(pm, args);
126
+ return harness === 'cursor'
127
+ ? [command, 'status=$?']
128
+ : [`body=$(${command})`, 'status=$?'];
129
+ }
130
+ /**
131
+ * The gate script: `cd` to the repo, hand off to `checkride gate`, and translate
132
+ * the result into the harness's protocol.
133
+ *
134
+ * `dirtyGuard` (on when the `dirty` hook is written alongside — the default)
135
+ * makes the gate conditional on the edit marker, so pure-conversation turns
136
+ * don't pay for a full pipeline run. Without the marker hook the guard would
137
+ * disarm the gate entirely, so a `--hook gate` selection writes it unguarded.
138
+ */
139
+ export function gateScript(pm, opts) {
140
+ const args = ['gate', '--harness', opts.harness, ...((opts.dirtyGuard ?? true) ? ['--if-dirty'] : [])];
141
+ return [
142
+ '#!/bin/sh',
143
+ `# checkride-gate.sh — the ${opts.harness} stop-hook gate.`,
144
+ '#',
145
+ '# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
146
+ '# overwrite it on every run. Customize via a sibling script or the',
147
+ '# environment, not by editing here — edits are lost on the next refresh.',
148
+ '#',
149
+ "# Runs the repo's `check` script as a hard gate, and reports the verdict in",
150
+ `# ${opts.harness}'s hook protocol. See \`checkride gate --help\`.`,
151
+ '',
152
+ ...enterRepo(opts.harness),
153
+ '',
154
+ ...invokeGate(pm, opts.harness, args),
155
+ ...gateTail(opts.harness),
156
+ '',
157
+ ].join('\n');
158
+ }
159
+ /**
160
+ * The dirty script: mark that this turn edited a file. A stop hook fires on
161
+ * every turn, including pure-conversation ones; the marker is what lets the gate
162
+ * skip those instead of taxing every reply with a full pipeline run.
163
+ *
164
+ * It exits 0 unconditionally. A failure to record an edit must never block the
165
+ * edit itself — the cost of a missed marker is one skipped gate, and the cost of
166
+ * a hard failure here is a repo where no file can be written.
167
+ */
168
+ export function dirtyScript() {
169
+ return [
170
+ '#!/bin/sh',
171
+ '# checkride-dirty.sh — record that this turn edited a file.',
172
+ '#',
173
+ '# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
174
+ '# overwrite it on every run.',
175
+ '#',
176
+ '# The gate reads this marker under `--if-dirty` and clears it on green.',
177
+ '',
178
+ `cd ${PROJECT_DIR} || exit 0`,
179
+ `mkdir -p "$(dirname ${DIRTY_MARKER})" && touch ${DIRTY_MARKER}`,
180
+ 'exit 0',
181
+ '',
182
+ ].join('\n');
183
+ }
184
+ /**
185
+ * Keys Cursor may use for the target path in a tool call's `tool_input`.
186
+ *
187
+ * Cursor documents the shape for `Shell` but not for `Write`/`Delete`, so the
188
+ * list is deliberately broad and order-independent: the script takes the first
189
+ * string it finds. It keeps Claude Code's documented `file_path` and
190
+ * `notebook_path` too, since a harness that borrowed the schema would send
191
+ * those. An unrecognized shape yields no target and the script fails open, which
192
+ * is the correct failure — a protect hook that cannot read its input must not
193
+ * become a repo where nothing can be written.
194
+ *
195
+ * **The Cursor half of this list is a guess, and a wrong guess is silent**: an
196
+ * unmatched shape allows the write with no output, so `protect` would simply
197
+ * stop protecting and nothing would say so. The tests here can only assert the
198
+ * keys checkride *chose*, never the ones Cursor sends, so this is settled by one
199
+ * live `preToolUse` payload and not by anything in this repo. Until it is,
200
+ * `protect` is best-effort under Cursor — see docs/cursor.md. (Neither harness
201
+ * matches its shell tool, so a redirect writes the baseline under both; that gap
202
+ * is older and accepted.)
203
+ */
204
+ const PATH_KEYS = ['file_path', 'notebook_path', 'path', 'target_file', 'filePath', 'relative_workspace_path'];
205
+ /** The deny message, shared by both harnesses. */
206
+ const DENY_MESSAGE = "'checkride: ' + rel + ' is checkride-owned accounting. Never edit the baseline or .check ' +\n" +
207
+ " 'artifacts to make a check pass — fix the finding instead (the ratchet prunes the baseline on its own).'";
208
+ /**
209
+ * How Cursor spells "deny this tool call": a JSON body on stdout with exit 0. It
210
+ * reads a non-zero hook as a *broken* hook and proceeds, so the denial has to
211
+ * travel in the body, where `agent_message` is what the model sees.
212
+ */
213
+ function denyTail() {
214
+ return [
215
+ ' const message = ' + DENY_MESSAGE + ';',
216
+ ' process.stdout.write(JSON.stringify({',
217
+ " permission: 'deny',",
218
+ ' user_message: message,',
219
+ ' agent_message: message,',
220
+ ' }));',
221
+ ' process.exit(0);',
222
+ ];
223
+ }
224
+ /**
225
+ * The `protect` deny script: "never add to the baseline to make a check pass" as
226
+ * enforcement, not README advice. A Node script (not sh) because the hook
227
+ * protocol is JSON on stdin, and every checkride repo has node by construction;
228
+ * `.cjs` so it runs regardless of the repo's module type. Only edit tools are
229
+ * matched — reads are never denied, because triage depends on reading `.check/`
230
+ * artifacts.
231
+ *
232
+ * **Cursor only.** Claude Code enforces the same paths through
233
+ * `permissions.deny`, which is checked below the hook layer and costs nothing
234
+ * per tool call, so it needs no script. Cursor has no documented equivalent —
235
+ * its config is hooks — so the script survives for the harness that still
236
+ * requires one. If Cursor grows a file-path deny list, this file goes away.
237
+ */
238
+ export function protectScript() {
239
+ return [
240
+ '#!/usr/bin/env node',
241
+ "// checkride-protect.cjs — deny agent edits to checkride's accounting files.",
242
+ '// checkride owns this file: `checkride agent-setup` (and `checkride init`)',
243
+ '// overwrite it on every run.',
244
+ '//',
245
+ '// Pre-tool hook for Cursor. Reads the tool call as JSON on stdin and',
246
+ '// denies the write when it targets the baseline or .check/. Reads are never',
247
+ '// matched — triage depends on reading .check artifacts.',
248
+ "'use strict';",
249
+ "const { realpathSync } = require('node:fs');",
250
+ "const { basename, dirname, isAbsolute, relative, resolve, sep } = require('node:path');",
251
+ '',
252
+ `const PATH_KEYS = ${JSON.stringify(PATH_KEYS)};`,
253
+ '',
254
+ '// Resolve symlinks in `p`, so a symlinked prefix cannot make an in-repo path',
255
+ '// look external. On macOS the repo root routinely arrives in two spellings —',
256
+ '// the harness passes /var/…, the environment reports /private/var/… — and a',
257
+ '// raw string comparison across the two rejects every path in the repo.',
258
+ '//',
259
+ '// `p` need not exist: this hook runs *before* the write, and the target is',
260
+ '// often a file (or a whole directory) that is about to be created. So walk up',
261
+ '// to the first ancestor that does exist, resolve that, and re-append the rest.',
262
+ 'function canon(p) {',
263
+ ' let dir = resolve(p);',
264
+ ' const rest = [];',
265
+ ' for (;;) {',
266
+ ' try {',
267
+ ' return resolve(realpathSync(dir), ...rest);',
268
+ ' } catch {',
269
+ ' const up = dirname(dir);',
270
+ ' if (up === dir) return resolve(p);',
271
+ ' rest.unshift(basename(dir));',
272
+ ' dir = up;',
273
+ ' }',
274
+ ' }',
275
+ '}',
276
+ '',
277
+ "let raw = '';",
278
+ "process.stdin.on('data', (chunk) => { raw += chunk; });",
279
+ "process.stdin.on('end', () => {",
280
+ ' let input;',
281
+ ' try {',
282
+ ' input = JSON.parse(raw);',
283
+ ' } catch {',
284
+ ' process.exit(0); // fail open: a broken hook must not brick every edit',
285
+ ' }',
286
+ ' const tool = (input && input.tool_input) || {};',
287
+ " const target = PATH_KEYS.map((k) => tool[k]).find((v) => typeof v === 'string' && v.length > 0);",
288
+ ' if (target === undefined) process.exit(0);',
289
+ ' const root = canon(process.env.CURSOR_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd());',
290
+ ' const abs = canon(isAbsolute(target) ? target : resolve(root, target));',
291
+ " const rel = relative(root, abs).split(sep).join('/');",
292
+ " const denied = rel === 'checkride.baseline.json' || rel === '.check' || rel.startsWith('.check/');",
293
+ ' if (!denied) process.exit(0);',
294
+ ...denyTail(),
295
+ '});',
296
+ '',
297
+ ].join('\n');
298
+ }
299
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/agent-setup/scripts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Skill delivery for harnesses that have no plugin system.
3
+ *
4
+ * Claude Code gets `check` and `qa` from the bundled plugin
5
+ * (`.claude-plugin/plugin.json`), installed once and shared across every repo.
6
+ * Cursor has no equivalent — it discovers skills from directories on disk — so
7
+ * for Cursor the same two skills are written into the repo, from the same source
8
+ * files the plugin ships. One authored copy, two delivery mechanisms.
9
+ *
10
+ * The only edit made on the way through is the frontmatter `name`: a bare
11
+ * `check` would take the `/check` slash command in every repo it lands in, which
12
+ * is not a name checkride should be squatting. The plugin gets namespacing for
13
+ * free (`/checkride:check`); here it has to be spelled out.
14
+ */
15
+ import type { HookFile } from './files.js';
16
+ /** Where Cursor discovers project skills, relative to the repo root. */
17
+ export declare const CURSOR_SKILLS_DIR = ".cursor/skills";
18
+ /**
19
+ * Rewrite the `name:` field inside the leading frontmatter block, and only
20
+ * there — a `name:` further down is prose in the skill body, not metadata.
21
+ */
22
+ export declare function renameSkill(body: string, name: string): string;
23
+ /**
24
+ * Write the bundled skills into `cwd`'s Cursor skills directory. A source skill
25
+ * that cannot be read is skipped rather than fatal: a broken or trimmed install
26
+ * should cost the skills, not the whole `agent-setup`.
27
+ */
28
+ export declare function writeCursorSkills(cwd: string, opts?: {
29
+ dryRun?: boolean;
30
+ }): Promise<HookFile[]>;
31
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/agent-setup/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAalD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAWzG"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Skill delivery for harnesses that have no plugin system.
3
+ *
4
+ * Claude Code gets `check` and `qa` from the bundled plugin
5
+ * (`.claude-plugin/plugin.json`), installed once and shared across every repo.
6
+ * Cursor has no equivalent — it discovers skills from directories on disk — so
7
+ * for Cursor the same two skills are written into the repo, from the same source
8
+ * files the plugin ships. One authored copy, two delivery mechanisms.
9
+ *
10
+ * The only edit made on the way through is the frontmatter `name`: a bare
11
+ * `check` would take the `/check` slash command in every repo it lands in, which
12
+ * is not a name checkride should be squatting. The plugin gets namespacing for
13
+ * free (`/checkride:check`); here it has to be spelled out.
14
+ */
15
+ import { dirname, join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { putFile, readIfExists } from './files.js';
18
+ /** Where Cursor discovers project skills, relative to the repo root. */
19
+ export const CURSOR_SKILLS_DIR = '.cursor/skills';
20
+ const HERE = dirname(fileURLToPath(import.meta.url));
21
+ /** The package's own `skills/` directory, which ships in the published tarball. */
22
+ const SKILLS_DIR = join(HERE, '..', '..', 'skills');
23
+ /** Source skill → the name it takes once it is a repo-local Cursor skill. */
24
+ const SKILLS = [
25
+ { source: 'check', name: 'checkride-check' },
26
+ { source: 'qa', name: 'checkride-qa' },
27
+ ];
28
+ /**
29
+ * Rewrite the `name:` field inside the leading frontmatter block, and only
30
+ * there — a `name:` further down is prose in the skill body, not metadata.
31
+ */
32
+ export function renameSkill(body, name) {
33
+ const frontmatter = /^(---\r?\n)([\s\S]*?)(\r?\n---)/.exec(body);
34
+ if (!frontmatter)
35
+ return body;
36
+ const [, open, fields, close] = frontmatter;
37
+ const renamed = (fields ?? '').replace(/^name:.*$/m, `name: ${name}`);
38
+ return `${open}${renamed}${close}${body.slice(frontmatter[0].length)}`;
39
+ }
40
+ /**
41
+ * Write the bundled skills into `cwd`'s Cursor skills directory. A source skill
42
+ * that cannot be read is skipped rather than fatal: a broken or trimmed install
43
+ * should cost the skills, not the whole `agent-setup`.
44
+ */
45
+ export async function writeCursorSkills(cwd, opts = {}) {
46
+ const dryRun = opts.dryRun ?? false;
47
+ const written = await Promise.all(SKILLS.map(async (skill) => {
48
+ const body = await readIfExists(join(SKILLS_DIR, skill.source, 'SKILL.md'));
49
+ if (body === null)
50
+ return null;
51
+ const rel = `${CURSOR_SKILLS_DIR}/${skill.name}/SKILL.md`;
52
+ return putFile(cwd, rel, renameSkill(body, skill.name), { dryRun });
53
+ }));
54
+ return written.filter((f) => f !== null);
55
+ }
56
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/agent-setup/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAElD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,mFAAmF;AACnF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,MAAM,MAAM,GAAgD;IAC1D,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC5C,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE;CACvC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,WAAW,GAAG,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC;IAC5C,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACtE,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAA6B,EAAE;IAClF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACzB,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;QAC5E,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,GAAG,GAAG,GAAG,iBAAiB,IAAI,KAAK,CAAC,IAAI,WAAW,CAAC;QAC1D,OAAO,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC,CACH,CAAC;IACF,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What the repo *declared* it checks — as opposed to what a given run selected.
3
+ *
4
+ * The summary records the slots a run selected; it says nothing about the ones
5
+ * the repo opted into and did not run. Both readers need that difference:
6
+ * triage uses it to name the slots a narrow run left uncovered, and the quality
7
+ * extractor uses it to tell "opted in but not run this time" apart from "never
8
+ * opted in at all" — three of its four artifacts come from opt-in slots, so the
9
+ * distinction decides whether a gap is a finding or the repo's normal state.
10
+ *
11
+ * `null` throughout means *unknowable*, never *empty*: a repo with no
12
+ * `checkride.config.json` runs the default catalogue, and neither reader will
13
+ * guess at it.
14
+ *
15
+ * The `../artifacts` barrel is this module's only public surface.
16
+ */
17
+ /**
18
+ * Slots the repo names in `checkride.config.json` and has not switched off
19
+ * (`false` disables one), sorted. `null` when the repo has no config file, or
20
+ * when the config is unparseable — that is the gate's problem to report, not
21
+ * the reader's to crash on.
22
+ *
23
+ * `cwd` is resolved first because `loadConfig` throws outright on a relative
24
+ * one, and both readers take their `cwd` from a command-line argument. Left
25
+ * unresolved, `dist/<reader>/cli.js .` would land in the catch below and report
26
+ * the repo's slots as *unknowable* — a silently degraded answer, which is the
27
+ * failure mode these readers exist to prevent.
28
+ */
29
+ export declare function configuredSlots(cwd: string): string[] | null;
30
+ //# sourceMappingURL=configured.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../../src/artifacts/configured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAW5D"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What the repo *declared* it checks — as opposed to what a given run selected.
3
+ *
4
+ * The summary records the slots a run selected; it says nothing about the ones
5
+ * the repo opted into and did not run. Both readers need that difference:
6
+ * triage uses it to name the slots a narrow run left uncovered, and the quality
7
+ * extractor uses it to tell "opted in but not run this time" apart from "never
8
+ * opted in at all" — three of its four artifacts come from opt-in slots, so the
9
+ * distinction decides whether a gap is a finding or the repo's normal state.
10
+ *
11
+ * `null` throughout means *unknowable*, never *empty*: a repo with no
12
+ * `checkride.config.json` runs the default catalogue, and neither reader will
13
+ * guess at it.
14
+ *
15
+ * The `../artifacts` barrel is this module's only public surface.
16
+ */
17
+ import { resolve } from 'node:path';
18
+ import { loadConfig } from '../config.js';
19
+ /**
20
+ * Slots the repo names in `checkride.config.json` and has not switched off
21
+ * (`false` disables one), sorted. `null` when the repo has no config file, or
22
+ * when the config is unparseable — that is the gate's problem to report, not
23
+ * the reader's to crash on.
24
+ *
25
+ * `cwd` is resolved first because `loadConfig` throws outright on a relative
26
+ * one, and both readers take their `cwd` from a command-line argument. Left
27
+ * unresolved, `dist/<reader>/cli.js .` would land in the catch below and report
28
+ * the repo's slots as *unknowable* — a silently degraded answer, which is the
29
+ * failure mode these readers exist to prevent.
30
+ */
31
+ export function configuredSlots(cwd) {
32
+ try {
33
+ const checks = loadConfig(resolve(cwd))?.checks;
34
+ if (!checks)
35
+ return null;
36
+ return Object.entries(checks)
37
+ .filter(([, value]) => value !== false)
38
+ .map(([slot]) => slot)
39
+ .toSorted();
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ //# sourceMappingURL=configured.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configured.js","sourceRoot":"","sources":["../../src/artifacts/configured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;QAChD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC;aACtC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC;aACrB,QAAQ,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Human-scale rendering of the quantities both readers report: file sizes,
3
+ * elapsed time, and a bounded tail of captured output.
4
+ *
5
+ * Sizes and ages carry the judgment in a reader's table — "2.3 MB" and "4d" are
6
+ * the whole reason an agent knows not to open a file — so they get one
7
+ * implementation rather than one per reader.
8
+ *
9
+ * {@link tail} is truncation, never normalization: it drops whole lines from
10
+ * the front and says how many, leaving the bytes it keeps exactly as the tool
11
+ * emitted them.
12
+ *
13
+ * The `../artifacts` barrel is this module's only public surface.
14
+ */
15
+ /** `483 B`, `5.1 KB`, `2.3 MB` — one decimal above a kilobyte, none below. */
16
+ export declare function formatBytes(bytes: number): string;
17
+ /** `340ms`, `1.4s`, `12m`, `4.1d` — the coarsest unit that keeps a whole part. */
18
+ export declare function formatDuration(ms: number): string;
19
+ /** A bounded excerpt of captured output, with what it left out stated. */
20
+ export type Excerpt = {
21
+ text: string;
22
+ /** Lines dropped from the front, so the caller can say so rather than imply completeness. */
23
+ omittedLines: number;
24
+ /** Size of the full input, so "1.2 KB of 40 KB" is sayable. */
25
+ totalBytes: number;
26
+ };
27
+ /**
28
+ * The last `maxLines` non-blank lines of `text`, further trimmed from the front
29
+ * until the excerpt fits `maxBytes`. A compiler, a test runner and a crashing
30
+ * harness all put the thing worth reading at the end.
31
+ */
32
+ export declare function tail(text: string, maxLines: number, maxBytes: number): Excerpt;
33
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/artifacts/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AASD,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,0EAA0E;AAC1E,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ9E"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Human-scale rendering of the quantities both readers report: file sizes,
3
+ * elapsed time, and a bounded tail of captured output.
4
+ *
5
+ * Sizes and ages carry the judgment in a reader's table — "2.3 MB" and "4d" are
6
+ * the whole reason an agent knows not to open a file — so they get one
7
+ * implementation rather than one per reader.
8
+ *
9
+ * {@link tail} is truncation, never normalization: it drops whole lines from
10
+ * the front and says how many, leaving the bytes it keeps exactly as the tool
11
+ * emitted them.
12
+ *
13
+ * The `../artifacts` barrel is this module's only public surface.
14
+ */
15
+ const UNITS = [
16
+ { limit: 1024 ** 3, suffix: 'GB' },
17
+ { limit: 1024 ** 2, suffix: 'MB' },
18
+ { limit: 1024, suffix: 'KB' },
19
+ ];
20
+ /** `483 B`, `5.1 KB`, `2.3 MB` — one decimal above a kilobyte, none below. */
21
+ export function formatBytes(bytes) {
22
+ for (const { limit, suffix } of UNITS) {
23
+ if (bytes >= limit)
24
+ return `${(bytes / limit).toFixed(1)} ${suffix}`;
25
+ }
26
+ return `${bytes} B`;
27
+ }
28
+ const DURATIONS = [
29
+ { limit: 86_400_000, suffix: 'd' },
30
+ { limit: 3_600_000, suffix: 'h' },
31
+ { limit: 60_000, suffix: 'm' },
32
+ { limit: 1000, suffix: 's' },
33
+ ];
34
+ /** `340ms`, `1.4s`, `12m`, `4.1d` — the coarsest unit that keeps a whole part. */
35
+ export function formatDuration(ms) {
36
+ for (const { limit, suffix } of DURATIONS) {
37
+ if (ms >= limit)
38
+ return `${(ms / limit).toFixed(1)}${suffix}`;
39
+ }
40
+ return `${Math.round(ms)}ms`;
41
+ }
42
+ /**
43
+ * The last `maxLines` non-blank lines of `text`, further trimmed from the front
44
+ * until the excerpt fits `maxBytes`. A compiler, a test runner and a crashing
45
+ * harness all put the thing worth reading at the end.
46
+ */
47
+ export function tail(text, maxLines, maxBytes) {
48
+ const lines = text.split('\n').filter((line) => line.trim() !== '');
49
+ const totalBytes = Buffer.byteLength(text, 'utf8');
50
+ let kept = lines.slice(-maxLines);
51
+ while (kept.length > 1 && Buffer.byteLength(kept.join('\n'), 'utf8') > maxBytes) {
52
+ kept = kept.slice(1);
53
+ }
54
+ return { text: kept.join('\n'), omittedLines: lines.length - kept.length, totalBytes };
55
+ }
56
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/artifacts/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,KAAK,GAAiD;IAC1D,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;IAClC,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;IAClC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;CAC9B,CAAC;AAEF,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACtC,IAAI,KAAK,IAAI,KAAK;YAAE,OAAO,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,KAAK,IAAI,CAAC;AACtB,CAAC;AAED,MAAM,SAAS,GAAiD;IAC9D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE;IAClC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;IACjC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;IAC9B,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;CAC7B,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC1C,IAAI,EAAE,IAAI,KAAK;YAAE,OAAO,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;AAC/B,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,QAAgB,EAAE,QAAgB;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,IAAI,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;QAChF,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AACzF,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The freshness window — which `.check/` files belong to the run that wrote the
3
+ * summary sitting beside them.
4
+ *
5
+ * The window is `[timestamp - total_duration_ms, ∞)`: the run's *start*, not
6
+ * its end, because `timestamp` is stamped once every check has finished (see
7
+ * `buildSummary` in `../orchestrator.ts`) and every artifact the run just wrote
8
+ * is therefore older than it. Anything outside the window is labelled with its
9
+ * age, never silently dropped.
10
+ *
11
+ * Why a window at all, and why this derivation is contract-legal:
12
+ * `docs/plugin.md` §How the readers decide an artifact is stale.
13
+ *
14
+ * The `../artifacts` barrel is this module's only public surface.
15
+ */
16
+ import type { Summary } from '../orchestrator.js';
17
+ /** An artifact's standing relative to the run that wrote the summary. */
18
+ export type Freshness = {
19
+ /** `unknown` when the summary's `timestamp` could not be parsed. */
20
+ state: 'fresh' | 'stale' | 'unknown';
21
+ /** How long before the run started the file was written; `null` unless stale. */
22
+ ageMs: number | null;
23
+ };
24
+ /**
25
+ * Epoch milliseconds at which the run began, or `null` when `timestamp` is not
26
+ * a parseable date. Callers treat `null` as "cannot judge freshness" rather
27
+ * than guessing in either direction.
28
+ */
29
+ export declare function runWindowStart(summary: Summary): number | null;
30
+ /** Classify one artifact's mtime against the window from {@link runWindowStart}. */
31
+ export declare function classifyFreshness(mtimeMs: number, windowStart: number | null): Freshness;
32
+ //# sourceMappingURL=freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../src/artifacts/freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG;IACtB,oEAAoE;IACpE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,iFAAiF;IACjF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAKF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI9D;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAIxF"}