claude-ketchup 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +172 -0
  2. package/bin/cli.ts +6 -0
  3. package/bin/postinstall.ts +8 -0
  4. package/bin/preuninstall.ts +8 -0
  5. package/commands/ketchup.md +107 -0
  6. package/dist/bin/cli.d.ts +3 -0
  7. package/dist/bin/cli.d.ts.map +1 -0
  8. package/dist/bin/cli.js +7 -0
  9. package/dist/bin/cli.js.map +1 -0
  10. package/dist/bin/postinstall.d.ts +3 -0
  11. package/dist/bin/postinstall.d.ts.map +1 -0
  12. package/dist/bin/postinstall.js +9 -0
  13. package/dist/bin/postinstall.js.map +1 -0
  14. package/dist/bin/preuninstall.d.ts +3 -0
  15. package/dist/bin/preuninstall.d.ts.map +1 -0
  16. package/dist/bin/preuninstall.js +9 -0
  17. package/dist/bin/preuninstall.js.map +1 -0
  18. package/dist/scripts/auto-continue.d.ts +3 -0
  19. package/dist/scripts/auto-continue.d.ts.map +1 -0
  20. package/dist/scripts/auto-continue.js +62 -0
  21. package/dist/scripts/auto-continue.js.map +1 -0
  22. package/dist/scripts/generate-changeset.d.ts +13 -0
  23. package/dist/scripts/generate-changeset.d.ts.map +1 -0
  24. package/dist/scripts/generate-changeset.js +322 -0
  25. package/dist/scripts/generate-changeset.js.map +1 -0
  26. package/dist/scripts/pre-tool-use.d.ts +3 -0
  27. package/dist/scripts/pre-tool-use.d.ts.map +1 -0
  28. package/dist/scripts/pre-tool-use.js +68 -0
  29. package/dist/scripts/pre-tool-use.js.map +1 -0
  30. package/dist/scripts/session-start.d.ts +3 -0
  31. package/dist/scripts/session-start.d.ts.map +1 -0
  32. package/dist/scripts/session-start.js +71 -0
  33. package/dist/scripts/session-start.js.map +1 -0
  34. package/dist/scripts/user-prompt-submit.d.ts +3 -0
  35. package/dist/scripts/user-prompt-submit.d.ts.map +1 -0
  36. package/dist/scripts/user-prompt-submit.js +71 -0
  37. package/dist/scripts/user-prompt-submit.js.map +1 -0
  38. package/dist/src/activity-logger.d.ts +2 -0
  39. package/dist/src/activity-logger.d.ts.map +1 -0
  40. package/dist/src/activity-logger.js +47 -0
  41. package/dist/src/activity-logger.js.map +1 -0
  42. package/dist/src/activity-logger.test.d.ts +2 -0
  43. package/dist/src/activity-logger.test.d.ts.map +1 -0
  44. package/dist/src/activity-logger.test.js +121 -0
  45. package/dist/src/activity-logger.test.js.map +1 -0
  46. package/dist/src/clean-logs.d.ts +6 -0
  47. package/dist/src/clean-logs.d.ts.map +1 -0
  48. package/dist/src/clean-logs.js +38 -0
  49. package/dist/src/clean-logs.js.map +1 -0
  50. package/dist/src/clean-logs.test.d.ts +2 -0
  51. package/dist/src/clean-logs.test.d.ts.map +1 -0
  52. package/dist/src/clean-logs.test.js +101 -0
  53. package/dist/src/clean-logs.test.js.map +1 -0
  54. package/dist/src/cli/cli.d.ts +3 -0
  55. package/dist/src/cli/cli.d.ts.map +1 -0
  56. package/dist/src/cli/cli.js +26 -0
  57. package/dist/src/cli/cli.js.map +1 -0
  58. package/dist/src/cli/cli.test.d.ts +2 -0
  59. package/dist/src/cli/cli.test.d.ts.map +1 -0
  60. package/dist/src/cli/cli.test.js +20 -0
  61. package/dist/src/cli/cli.test.js.map +1 -0
  62. package/dist/src/cli/doctor.d.ts +7 -0
  63. package/dist/src/cli/doctor.d.ts.map +1 -0
  64. package/dist/src/cli/doctor.js +67 -0
  65. package/dist/src/cli/doctor.js.map +1 -0
  66. package/dist/src/cli/doctor.test.d.ts +2 -0
  67. package/dist/src/cli/doctor.test.d.ts.map +1 -0
  68. package/dist/src/cli/doctor.test.js +87 -0
  69. package/dist/src/cli/doctor.test.js.map +1 -0
  70. package/dist/src/cli/install.d.ts +8 -0
  71. package/dist/src/cli/install.d.ts.map +1 -0
  72. package/dist/src/cli/install.js +8 -0
  73. package/dist/src/cli/install.js.map +1 -0
  74. package/dist/src/cli/install.test.d.ts +2 -0
  75. package/dist/src/cli/install.test.d.ts.map +1 -0
  76. package/dist/src/cli/install.test.js +106 -0
  77. package/dist/src/cli/install.test.js.map +1 -0
  78. package/dist/src/cli/reminders.d.ts +12 -0
  79. package/dist/src/cli/reminders.d.ts.map +1 -0
  80. package/dist/src/cli/reminders.js +52 -0
  81. package/dist/src/cli/reminders.js.map +1 -0
  82. package/dist/src/cli/reminders.test.d.ts +2 -0
  83. package/dist/src/cli/reminders.test.d.ts.map +1 -0
  84. package/dist/src/cli/reminders.test.js +72 -0
  85. package/dist/src/cli/reminders.test.js.map +1 -0
  86. package/dist/src/cli/repair.d.ts +11 -0
  87. package/dist/src/cli/repair.d.ts.map +1 -0
  88. package/dist/src/cli/repair.js +91 -0
  89. package/dist/src/cli/repair.js.map +1 -0
  90. package/dist/src/cli/repair.test.d.ts +2 -0
  91. package/dist/src/cli/repair.test.d.ts.map +1 -0
  92. package/dist/src/cli/repair.test.js +96 -0
  93. package/dist/src/cli/repair.test.js.map +1 -0
  94. package/dist/src/cli/status.d.ts +10 -0
  95. package/dist/src/cli/status.d.ts.map +1 -0
  96. package/dist/src/cli/status.js +55 -0
  97. package/dist/src/cli/status.js.map +1 -0
  98. package/dist/src/cli/status.test.d.ts +2 -0
  99. package/dist/src/cli/status.test.d.ts.map +1 -0
  100. package/dist/src/cli/status.test.js +83 -0
  101. package/dist/src/cli/status.test.js.map +1 -0
  102. package/dist/src/clue-collector.d.ts +23 -0
  103. package/dist/src/clue-collector.d.ts.map +1 -0
  104. package/dist/src/clue-collector.js +221 -0
  105. package/dist/src/clue-collector.js.map +1 -0
  106. package/dist/src/clue-collector.test.d.ts +2 -0
  107. package/dist/src/clue-collector.test.d.ts.map +1 -0
  108. package/dist/src/clue-collector.test.js +278 -0
  109. package/dist/src/clue-collector.test.js.map +1 -0
  110. package/dist/src/commit-validator.d.ts +35 -0
  111. package/dist/src/commit-validator.d.ts.map +1 -0
  112. package/dist/src/commit-validator.js +147 -0
  113. package/dist/src/commit-validator.js.map +1 -0
  114. package/dist/src/commit-validator.test.d.ts +2 -0
  115. package/dist/src/commit-validator.test.d.ts.map +1 -0
  116. package/dist/src/commit-validator.test.js +443 -0
  117. package/dist/src/commit-validator.test.js.map +1 -0
  118. package/dist/src/config-loader.d.ts +15 -0
  119. package/dist/src/config-loader.d.ts.map +1 -0
  120. package/dist/src/config-loader.js +12 -0
  121. package/dist/src/config-loader.js.map +1 -0
  122. package/dist/src/config-loader.test.d.ts +2 -0
  123. package/dist/src/config-loader.test.d.ts.map +1 -0
  124. package/dist/src/config-loader.test.js +69 -0
  125. package/dist/src/config-loader.test.js.map +1 -0
  126. package/dist/src/debug-logger.d.ts +2 -0
  127. package/dist/src/debug-logger.d.ts.map +1 -0
  128. package/dist/src/debug-logger.js +23 -0
  129. package/dist/src/debug-logger.js.map +1 -0
  130. package/dist/src/debug-logger.test.d.ts +2 -0
  131. package/dist/src/debug-logger.test.d.ts.map +1 -0
  132. package/dist/src/debug-logger.test.js +63 -0
  133. package/dist/src/debug-logger.test.js.map +1 -0
  134. package/dist/src/default-validators.test.d.ts +2 -0
  135. package/dist/src/default-validators.test.d.ts.map +1 -0
  136. package/dist/src/default-validators.test.js +119 -0
  137. package/dist/src/default-validators.test.js.map +1 -0
  138. package/dist/src/deny-list.d.ts +3 -0
  139. package/dist/src/deny-list.d.ts.map +1 -0
  140. package/dist/src/deny-list.js +62 -0
  141. package/dist/src/deny-list.js.map +1 -0
  142. package/dist/src/deny-list.test.d.ts +2 -0
  143. package/dist/src/deny-list.test.d.ts.map +1 -0
  144. package/dist/src/deny-list.test.js +93 -0
  145. package/dist/src/deny-list.test.js.map +1 -0
  146. package/dist/src/e2e.test.d.ts +2 -0
  147. package/dist/src/e2e.test.d.ts.map +1 -0
  148. package/dist/src/e2e.test.js +89 -0
  149. package/dist/src/e2e.test.js.map +1 -0
  150. package/dist/src/gitignore-manager.d.ts +2 -0
  151. package/dist/src/gitignore-manager.d.ts.map +1 -0
  152. package/dist/src/gitignore-manager.js +45 -0
  153. package/dist/src/gitignore-manager.js.map +1 -0
  154. package/dist/src/gitignore-manager.test.d.ts +2 -0
  155. package/dist/src/gitignore-manager.test.d.ts.map +1 -0
  156. package/dist/src/gitignore-manager.test.js +65 -0
  157. package/dist/src/gitignore-manager.test.js.map +1 -0
  158. package/dist/src/hook-input.d.ts +9 -0
  159. package/dist/src/hook-input.d.ts.map +1 -0
  160. package/dist/src/hook-input.js +7 -0
  161. package/dist/src/hook-input.js.map +1 -0
  162. package/dist/src/hook-input.test.d.ts +2 -0
  163. package/dist/src/hook-input.test.d.ts.map +1 -0
  164. package/dist/src/hook-input.test.js +20 -0
  165. package/dist/src/hook-input.test.js.map +1 -0
  166. package/dist/src/hook-logger.d.ts +16 -0
  167. package/dist/src/hook-logger.d.ts.map +1 -0
  168. package/dist/src/hook-logger.js +91 -0
  169. package/dist/src/hook-logger.js.map +1 -0
  170. package/dist/src/hook-logger.test.d.ts +2 -0
  171. package/dist/src/hook-logger.test.d.ts.map +1 -0
  172. package/dist/src/hook-logger.test.js +184 -0
  173. package/dist/src/hook-logger.test.js.map +1 -0
  174. package/dist/src/hook-state.d.ts +43 -0
  175. package/dist/src/hook-state.d.ts.map +1 -0
  176. package/dist/src/hook-state.js +124 -0
  177. package/dist/src/hook-state.js.map +1 -0
  178. package/dist/src/hook-state.test.d.ts +2 -0
  179. package/dist/src/hook-state.test.d.ts.map +1 -0
  180. package/dist/src/hook-state.test.js +190 -0
  181. package/dist/src/hook-state.test.js.map +1 -0
  182. package/dist/src/hooks/auto-continue.d.ts +21 -0
  183. package/dist/src/hooks/auto-continue.d.ts.map +1 -0
  184. package/dist/src/hooks/auto-continue.js +70 -0
  185. package/dist/src/hooks/auto-continue.js.map +1 -0
  186. package/dist/src/hooks/auto-continue.test.d.ts +2 -0
  187. package/dist/src/hooks/auto-continue.test.d.ts.map +1 -0
  188. package/dist/src/hooks/auto-continue.test.js +171 -0
  189. package/dist/src/hooks/auto-continue.test.js.map +1 -0
  190. package/dist/src/hooks/pre-tool-use.d.ts +14 -0
  191. package/dist/src/hooks/pre-tool-use.d.ts.map +1 -0
  192. package/dist/src/hooks/pre-tool-use.js +62 -0
  193. package/dist/src/hooks/pre-tool-use.js.map +1 -0
  194. package/dist/src/hooks/pre-tool-use.test.d.ts +2 -0
  195. package/dist/src/hooks/pre-tool-use.test.d.ts.map +1 -0
  196. package/dist/src/hooks/pre-tool-use.test.js +168 -0
  197. package/dist/src/hooks/pre-tool-use.test.js.map +1 -0
  198. package/dist/src/hooks/session-start.d.ts +20 -0
  199. package/dist/src/hooks/session-start.d.ts.map +1 -0
  200. package/dist/src/hooks/session-start.js +27 -0
  201. package/dist/src/hooks/session-start.js.map +1 -0
  202. package/dist/src/hooks/session-start.test.d.ts +2 -0
  203. package/dist/src/hooks/session-start.test.d.ts.map +1 -0
  204. package/dist/src/hooks/session-start.test.js +125 -0
  205. package/dist/src/hooks/session-start.test.js.map +1 -0
  206. package/dist/src/hooks/user-prompt-submit.d.ts +17 -0
  207. package/dist/src/hooks/user-prompt-submit.d.ts.map +1 -0
  208. package/dist/src/hooks/user-prompt-submit.js +28 -0
  209. package/dist/src/hooks/user-prompt-submit.js.map +1 -0
  210. package/dist/src/hooks/user-prompt-submit.test.d.ts +2 -0
  211. package/dist/src/hooks/user-prompt-submit.test.d.ts.map +1 -0
  212. package/dist/src/hooks/user-prompt-submit.test.js +119 -0
  213. package/dist/src/hooks/user-prompt-submit.test.js.map +1 -0
  214. package/dist/src/hooks/validate-commit.d.ts +12 -0
  215. package/dist/src/hooks/validate-commit.d.ts.map +1 -0
  216. package/dist/src/hooks/validate-commit.js +58 -0
  217. package/dist/src/hooks/validate-commit.js.map +1 -0
  218. package/dist/src/hooks/validate-commit.test.d.ts +2 -0
  219. package/dist/src/hooks/validate-commit.test.d.ts.map +1 -0
  220. package/dist/src/hooks/validate-commit.test.js +150 -0
  221. package/dist/src/hooks/validate-commit.test.js.map +1 -0
  222. package/dist/src/index.d.ts +15 -0
  223. package/dist/src/index.d.ts.map +1 -0
  224. package/dist/src/index.js +39 -0
  225. package/dist/src/index.js.map +1 -0
  226. package/dist/src/linker.d.ts +6 -0
  227. package/dist/src/linker.d.ts.map +1 -0
  228. package/dist/src/linker.js +78 -0
  229. package/dist/src/linker.js.map +1 -0
  230. package/dist/src/linker.test.d.ts +2 -0
  231. package/dist/src/linker.test.d.ts.map +1 -0
  232. package/dist/src/linker.test.js +192 -0
  233. package/dist/src/linker.test.js.map +1 -0
  234. package/dist/src/logger.d.ts +21 -0
  235. package/dist/src/logger.d.ts.map +1 -0
  236. package/dist/src/logger.js +117 -0
  237. package/dist/src/logger.js.map +1 -0
  238. package/dist/src/logger.test.d.ts +2 -0
  239. package/dist/src/logger.test.d.ts.map +1 -0
  240. package/dist/src/logger.test.js +159 -0
  241. package/dist/src/logger.test.js.map +1 -0
  242. package/dist/src/npm-install.test.d.ts +2 -0
  243. package/dist/src/npm-install.test.d.ts.map +1 -0
  244. package/dist/src/npm-install.test.js +70 -0
  245. package/dist/src/npm-install.test.js.map +1 -0
  246. package/dist/src/path-resolver.d.ts +11 -0
  247. package/dist/src/path-resolver.d.ts.map +1 -0
  248. package/dist/src/path-resolver.js +65 -0
  249. package/dist/src/path-resolver.js.map +1 -0
  250. package/dist/src/postinstall.d.ts +8 -0
  251. package/dist/src/postinstall.d.ts.map +1 -0
  252. package/dist/src/postinstall.js +112 -0
  253. package/dist/src/postinstall.js.map +1 -0
  254. package/dist/src/postinstall.test.d.ts +2 -0
  255. package/dist/src/postinstall.test.d.ts.map +1 -0
  256. package/dist/src/postinstall.test.js +203 -0
  257. package/dist/src/postinstall.test.js.map +1 -0
  258. package/dist/src/preuninstall.d.ts +3 -0
  259. package/dist/src/preuninstall.d.ts.map +1 -0
  260. package/dist/src/preuninstall.js +85 -0
  261. package/dist/src/preuninstall.js.map +1 -0
  262. package/dist/src/preuninstall.test.d.ts +2 -0
  263. package/dist/src/preuninstall.test.d.ts.map +1 -0
  264. package/dist/src/preuninstall.test.js +114 -0
  265. package/dist/src/preuninstall.test.js.map +1 -0
  266. package/dist/src/reminder-loader.d.ts +24 -0
  267. package/dist/src/reminder-loader.d.ts.map +1 -0
  268. package/dist/src/reminder-loader.js +84 -0
  269. package/dist/src/reminder-loader.js.map +1 -0
  270. package/dist/src/reminder-loader.test.d.ts +2 -0
  271. package/dist/src/reminder-loader.test.d.ts.map +1 -0
  272. package/dist/src/reminder-loader.test.js +152 -0
  273. package/dist/src/reminder-loader.test.js.map +1 -0
  274. package/dist/src/root-finder.d.ts +2 -0
  275. package/dist/src/root-finder.d.ts.map +1 -0
  276. package/dist/src/root-finder.js +71 -0
  277. package/dist/src/root-finder.js.map +1 -0
  278. package/dist/src/root-finder.test.d.ts +2 -0
  279. package/dist/src/root-finder.test.d.ts.map +1 -0
  280. package/dist/src/root-finder.test.js +111 -0
  281. package/dist/src/root-finder.test.js.map +1 -0
  282. package/dist/src/settings-merger.d.ts +2 -0
  283. package/dist/src/settings-merger.d.ts.map +1 -0
  284. package/dist/src/settings-merger.js +133 -0
  285. package/dist/src/settings-merger.js.map +1 -0
  286. package/dist/src/settings-merger.test.d.ts +2 -0
  287. package/dist/src/settings-merger.test.d.ts.map +1 -0
  288. package/dist/src/settings-merger.test.js +379 -0
  289. package/dist/src/settings-merger.test.js.map +1 -0
  290. package/dist/src/settings-template.test.d.ts +2 -0
  291. package/dist/src/settings-template.test.d.ts.map +1 -0
  292. package/dist/src/settings-template.test.js +88 -0
  293. package/dist/src/settings-template.test.js.map +1 -0
  294. package/dist/src/state-manager.d.ts +5 -0
  295. package/dist/src/state-manager.d.ts.map +1 -0
  296. package/dist/src/state-manager.js +55 -0
  297. package/dist/src/state-manager.js.map +1 -0
  298. package/dist/src/state-manager.test.d.ts +2 -0
  299. package/dist/src/state-manager.test.d.ts.map +1 -0
  300. package/dist/src/state-manager.test.js +85 -0
  301. package/dist/src/state-manager.test.js.map +1 -0
  302. package/dist/src/subagent-classifier.d.ts +4 -0
  303. package/dist/src/subagent-classifier.d.ts.map +1 -0
  304. package/dist/src/subagent-classifier.js +53 -0
  305. package/dist/src/subagent-classifier.js.map +1 -0
  306. package/dist/src/subagent-classifier.test.d.ts +2 -0
  307. package/dist/src/subagent-classifier.test.d.ts.map +1 -0
  308. package/dist/src/subagent-classifier.test.js +84 -0
  309. package/dist/src/subagent-classifier.test.js.map +1 -0
  310. package/dist/src/validator-loader.d.ts +9 -0
  311. package/dist/src/validator-loader.d.ts.map +1 -0
  312. package/dist/src/validator-loader.js +71 -0
  313. package/dist/src/validator-loader.js.map +1 -0
  314. package/dist/src/validator-loader.test.d.ts +2 -0
  315. package/dist/src/validator-loader.test.d.ts.map +1 -0
  316. package/dist/src/validator-loader.test.js +140 -0
  317. package/dist/src/validator-loader.test.js.map +1 -0
  318. package/package.json +90 -0
  319. package/reminders/ketchup.md +24 -0
  320. package/reminders/reminder-documentation.md +30 -0
  321. package/reminders/reminder-emergent-design.md +41 -0
  322. package/reminders/reminder-extreme-ownership.md +27 -0
  323. package/reminders/reminder-ide-diagnostics.md +25 -0
  324. package/reminders/reminder-ketchup.md +145 -0
  325. package/reminders/reminder-parallelization.md +27 -0
  326. package/reminders/reminder-rethink-after-revert.md +25 -0
  327. package/reminders/reminder-sub-agent-rules.md +27 -0
  328. package/reminders/reminder-test-title-matches-spec.md +37 -0
  329. package/scripts/auto-continue.ts +34 -0
  330. package/scripts/generate-changeset.ts +405 -0
  331. package/scripts/pre-tool-use.ts +35 -0
  332. package/scripts/session-start.ts +38 -0
  333. package/scripts/tail-logs.sh +17 -0
  334. package/scripts/test-hooks.sh +910 -0
  335. package/scripts/user-prompt-submit.ts +38 -0
  336. package/templates/settings.json +48 -0
  337. package/validators/appeal-system.md +55 -0
  338. package/validators/backwards-compat.md +33 -0
  339. package/validators/burst-atomicity.md +37 -0
  340. package/validators/coverage-rules.md +34 -0
  341. package/validators/dead-code.md +36 -0
  342. package/validators/hygiene.md +34 -0
  343. package/validators/infra-commit-format.md +37 -0
  344. package/validators/ketchup-plan-format.md +42 -0
  345. package/validators/new-code-requires-tests.md +36 -0
  346. package/validators/no-comments.md +35 -0
  347. package/validators/no-dangerous-git.md +35 -0
  348. package/validators/tcr-workflow.md +31 -0
  349. package/validators/testing-no-state-peeking.md +37 -0
  350. package/validators/testing-structure.md +37 -0
  351. package/validators/testing-stubs-over-mocks.md +42 -0
  352. package/validators/testing-weak-assertions.md +36 -0
  353. package/validators/type-organization.md +30 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env npx tsx
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+
5
+ import { parseHookInput } from '../src/hook-input.js';
6
+ import { writeHookLog } from '../src/hook-logger.js';
7
+ import { handleUserPromptSubmit } from '../src/hooks/user-prompt-submit.js';
8
+
9
+ const input = parseHookInput(fs.readFileSync(0, 'utf-8'));
10
+ const claudeDir = path.resolve(process.cwd(), '.claude');
11
+ const startTime = Date.now();
12
+
13
+ handleUserPromptSubmit(claudeDir, input.session_id, input.prompt || '')
14
+ .then(({ diagnostics, ...result }) => {
15
+ writeHookLog(claudeDir, {
16
+ hookName: 'user-prompt-submit',
17
+ timestamp: new Date().toISOString(),
18
+ input: { ...input, prompt: input.prompt ? `[${input.prompt.length} chars]` : undefined },
19
+ resolvedPaths: diagnostics.resolvedPaths,
20
+ reminderFiles: diagnostics.reminderFiles,
21
+ matchedReminders: diagnostics.matchedReminders,
22
+ output: { resultLength: result.result.length },
23
+ durationMs: Date.now() - startTime,
24
+ });
25
+ console.log(JSON.stringify(result));
26
+ })
27
+ .catch((err) => {
28
+ writeHookLog(claudeDir, {
29
+ hookName: 'user-prompt-submit',
30
+ timestamp: new Date().toISOString(),
31
+ input: { ...input, prompt: input.prompt ? `[${input.prompt.length} chars]` : undefined },
32
+ output: null,
33
+ error: String(err),
34
+ durationMs: Date.now() - startTime,
35
+ });
36
+ console.error('user-prompt-submit hook failed:', err);
37
+ process.exit(1);
38
+ });
@@ -0,0 +1,48 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node .claude/scripts/session-start.js"
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "PreToolUse": [
15
+ {
16
+ "matcher": "Edit|Write|NotebookEdit|Bash",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node .claude/scripts/pre-tool-use.js"
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "UserPromptSubmit": [
26
+ {
27
+ "matcher": "",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "node .claude/scripts/user-prompt-submit.js"
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "Stop": [
37
+ {
38
+ "matcher": "",
39
+ "hooks": [
40
+ {
41
+ "type": "command",
42
+ "command": "node .claude/scripts/auto-continue.js"
43
+ }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: appeal-system
3
+ description: Evaluates appeals against validator NACKs
4
+ enabled: true
5
+ ---
6
+
7
+ You are the appeal system. You receive validator results and an appeal, and decide whether the appeal justifies overriding the NACKs.
8
+
9
+ You MUST respond with ONLY a JSON object, no other text.
10
+
11
+ Valid responses:
12
+ {"decision":"ACK"}
13
+ {"decision":"NACK","reason":"one sentence explanation"}
14
+
15
+ **Input you receive:**
16
+ - Full commit context (diff, files, commit message)
17
+ - All validator results (which validators ran, their decisions, their reasons)
18
+ - Appeal text extracted from the commit message
19
+
20
+ **Valid appeal scenarios:**
21
+
22
+ 1. **Coherence**: Files must be committed together for coherence. The changes are tightly coupled and splitting them would create an inconsistent state.
23
+
24
+ 2. **Existing Gap**: Existing code lacked coverage before these changes. The violation existed before this commit and is not introduced by the current changes.
25
+
26
+ 3. **Debug Branchless**: Test coverage not required for branchless debug statements. Simple logging or debug output that contains no conditional logic.
27
+
28
+ **ALWAYS NACK if:**
29
+ - The `no-dangerous-git` validator appears in the results with a NACK (this is non-appealable - dangerous git operations cannot be overridden)
30
+ - The appeal text does not describe one of the valid scenarios above
31
+ - The appeal does not justify each NACK - every rejected validator must be addressed by the appeal
32
+
33
+ **ACK if:**
34
+ - The appeal text describes a valid scenario from the list above
35
+ - The appeal reasonably justifies each appealable NACK
36
+ - The `no-dangerous-git` validator either did not run or returned ACK
37
+
38
+ **Examples:**
39
+
40
+ Appeal: "these files are tightly coupled and must be committed together"
41
+ → Valid coherence appeal
42
+
43
+ Appeal: "the coverage gap existed before my changes"
44
+ → Valid existing-gap appeal
45
+
46
+ Appeal: "this is just a console.log with no branches"
47
+ → Valid debug-branchless appeal
48
+
49
+ Appeal: "I don't have time to write tests"
50
+ → Invalid - not a valid scenario
51
+
52
+ Appeal: "please let this through"
53
+ → Invalid - not a valid scenario
54
+
55
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: backwards-compat
3
+ description: Enforces clean breaks over compatibility hacks
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate any .ts file changes in the diff.
14
+
15
+ Default to clean breaks. No silent preservation of old APIs.
16
+
17
+ **NACK if the diff contains:**
18
+ - Re-exports of moved/renamed symbols for backwards compatibility
19
+ - `@deprecated` comments or JSDoc tags
20
+ - Wrapper functions that only delegate to new implementations
21
+ - Unused parameters with `_` prefix kept for signature compatibility
22
+ - Shim files that import from new location and re-export
23
+ - `// removed` or similar comments for deleted code
24
+
25
+ **ACK if:**
26
+ - The diff makes clean changes without compatibility layers
27
+ - Old code is simply removed, not shimmed
28
+ - The diff only contains non-.ts files
29
+ - Refactoring removes old code entirely
30
+
31
+ **Plea override:** If files must be committed together for coherence during a rename/refactor, use `plea: files must be committed together for coherence` in the commit message.
32
+
33
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: burst-atomicity
3
+ description: Validates commits are single focused bursts
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate that each commit represents a single, focused burst.
14
+
15
+ Enforce the burst principle: One test, one behavior, one commit.
16
+
17
+ **ACK if:**
18
+ - The commit appears to be one logical unit of work
19
+ - Files changed are related to the same feature/fix/refactor
20
+ - Test files accompany their implementation files (this is expected, not a violation)
21
+ - ketchup-plan.md updates accompany the code they describe (per workflow)
22
+ - E2E tests combine 2 tightly-coupled behaviors (this exception is allowed)
23
+
24
+ **NACK if:**
25
+ - The commit combines clearly unrelated changes (e.g., feature code + unrelated config cleanup)
26
+ - Multiple distinct features or fixes are bundled together
27
+ - Files from different subsystems are modified without clear connection
28
+ - The commit tries to do too much in one burst
29
+
30
+ **Burst qualities to verify:**
31
+ - Independent: can be understood alone
32
+ - Valuable: adds something useful
33
+ - Small: minimal scope
34
+ - Testable: has corresponding test
35
+ - Reviewable: can be verified quickly
36
+
37
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: coverage-rules
3
+ description: Enforces 100% code coverage requirements
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Only validate .ts and .tsx files in the diff (ignore .md, .json, config files).
14
+
15
+ Enforce 100% coverage with no escape hatches:
16
+
17
+ **NACK if the diff contains:**
18
+ - `@ts-ignore` or `@ts-expect-error` comments
19
+ - `any` type annotations (except in test mocks at system boundaries)
20
+ - `as SomeType` type casts that bypass type safety
21
+ - Coverage exclusion patterns: `/* istanbul ignore */`, `/* c8 ignore */`, `/* v8 ignore */`
22
+
23
+ **Allowed exclusions (do not NACK):**
24
+ - Barrel `index.ts` files that only contain re-exports
25
+ - `*.test.ts` and `*.test.tsx` files
26
+
27
+ **ACK if:**
28
+ - No forbidden patterns are found in .ts/.tsx source files
29
+ - The diff only contains .md, .json, or config files
30
+ - The forbidden patterns appear only in allowed exclusion files
31
+
32
+ **Note:** This validator checks for escape hatches. Actual coverage percentage is verified by the test runner.
33
+
34
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: dead-code
3
+ description: Detects unused code that should be removed
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate .ts and .tsx files in the diff.
14
+
15
+ Remove dead code after each change. No unused code kept "just in case."
16
+
17
+ **NACK if the diff contains:**
18
+ - Unused imports (imported but never referenced)
19
+ - Unused variables (declared but never used)
20
+ - Unused functions (defined but never called)
21
+ - Commented-out code blocks
22
+ - Unreachable code after return/throw statements
23
+ - Empty function bodies with no implementation
24
+
25
+ **ACK if:**
26
+ - All imports are used
27
+ - All declared variables are referenced
28
+ - All functions are called or exported
29
+ - No commented-out code exists
30
+ - The diff only contains .md, .json, or config files
31
+
32
+ **Note:** This validator checks for obvious dead code patterns. Complex unused code detection may require static analysis tools.
33
+
34
+ **Exception:** Exported functions in library code that are part of the public API are not dead code even if not used internally.
35
+
36
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: hygiene
3
+ description: Enforces codebase hygiene and organization rules
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate file organization and commit message content.
14
+
15
+ Enforce these hygiene rules:
16
+
17
+ **NACK if the diff contains:**
18
+ - `.js` files outside of `dist/` directory (JS files should only be build output)
19
+ - Commit message contains any of:
20
+ - "Co-Authored-By: Claude"
21
+ - "Generated with Claude Code"
22
+ - "Claude" attribution of any kind
23
+ - AI/LLM attribution in commit messages
24
+
25
+ **ACK if:**
26
+ - All `.js` files are within `dist/` directory
27
+ - Commit message has no Claude/AI attribution
28
+ - The diff only modifies existing files without violating the above rules
29
+
30
+ **Rationale:**
31
+ - Source code should be TypeScript (.ts), not JavaScript
32
+ - Commits should not contain AI attribution per project rules
33
+
34
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: infra-commit-format
3
+ description: Validates commit message format for config-only commits
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** This validator ONLY applies when ALL changed files are infrastructure/config files.
14
+
15
+ **Config files include:**
16
+ - package.json, package-lock.json, pnpm-lock.yaml
17
+ - tsconfig.json, tsconfig.*.json
18
+ - *.config.ts, *.config.js (vite.config.ts, vitest.config.ts, etc.)
19
+ - wrangler.toml
20
+ - .gitignore, .npmignore, .eslintrc*, .prettierrc*
21
+ - ketchup-plan.md
22
+ - Any file in .claude/ directory
23
+
24
+ **ACK immediately if:**
25
+ - ANY .ts or .tsx file (not *.config.ts) is in the changed files list
26
+ - The commit includes behavioral code, not just config
27
+
28
+ **When ONLY config files are changed:**
29
+ - ACK if commit message starts with `chore(scope):` or `chore:` format
30
+ - NACK if commit message uses `feat:`, `fix:`, `test:` for config-only changes
31
+
32
+ **Examples:**
33
+ - Good: `chore(deps): update vitest to 1.0`
34
+ - Good: `chore: add ketchup-plan.md`
35
+ - Bad: `feat: update package.json` (for config-only commit)
36
+
37
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: ketchup-plan-format
3
+ description: Validates ketchup-plan.md structure and format
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate ketchup-plan.md when it is included in the commit.
14
+
15
+ **ACK immediately if:**
16
+ - ketchup-plan.md is not in the changed files
17
+
18
+ **When ketchup-plan.md is changed, validate:**
19
+
20
+ **Required structure:**
21
+ - Must have `## TODO` section
22
+ - Must have `## DONE` section
23
+
24
+ **Burst format:**
25
+ - Bursts should include `[depends: ...]` notation
26
+ - Format: `- [ ] Burst N: description [depends: none]` or `[depends: N, M]`
27
+ - Completed bursts in DONE should show commit hash: `- [x] Burst N: description (abc1234)`
28
+
29
+ **Bottle naming:**
30
+ - Bottles should be named by capability, not sequence number
31
+ - Format: `### Bottle: SettingsMerger` not `### Bottle 1`
32
+
33
+ **NACK if:**
34
+ - ketchup-plan.md lacks TODO or DONE sections
35
+ - Bursts are missing dependency notation
36
+ - Bottles are named by number instead of capability
37
+
38
+ **ACK if:**
39
+ - ketchup-plan.md follows the required structure
40
+ - Or ketchup-plan.md is not in the diff
41
+
42
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: new-code-requires-tests
3
+ description: Enforces that new behavioral code has accompanying tests
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate .ts and .tsx files (not config files) in the diff.
14
+
15
+ Let tests drive all code. New behavioral code requires tests.
16
+
17
+ **NACK if:**
18
+ - New .ts/.tsx files are added without corresponding .test.ts/.test.tsx files
19
+ - New conditional branches (`if`, `else`, `? :`, `??`, `||`) appear without test coverage
20
+ - Functions are added without any test calling them
21
+
22
+ **ACK if:**
23
+ - New .ts files have accompanying .test.ts files in the commit
24
+ - The diff only modifies existing code (not adding new files)
25
+ - The diff only contains config files (*.config.ts, tsconfig.json, etc.)
26
+ - The diff only contains test files
27
+ - The new code is in an `index.ts` barrel file (re-exports only)
28
+
29
+ **Config files that don't need tests:**
30
+ - package.json, tsconfig.json, wrangler.toml
31
+ - vite.config.ts, vitest.config.ts
32
+ - .gitignore, ketchup-plan.md
33
+
34
+ **First behavioral .ts file rule:** The first non-config .ts file in a project must have a test.
35
+
36
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: no-comments
3
+ description: Enforces self-documenting code without inline comments
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Only validate .ts and .tsx files in the diff (not .test.ts, not .md, not config files).
14
+
15
+ Code should be self-documenting. No comments to explain what code does.
16
+
17
+ **NACK if the diff adds:**
18
+ - Single-line comments: `// explanation`
19
+ - Multi-line comments: `/* explanation */`
20
+ - TODO comments: `// TODO:`, `// FIXME:`, `// HACK:`
21
+ - Explanatory comments describing what the code does
22
+
23
+ **ACK if:**
24
+ - No inline comments are added in .ts/.tsx source files
25
+ - The diff only contains .md, .json, .test.ts, or config files
26
+ - JSDoc comments for exported public APIs (these document the contract, not implementation)
27
+ - Comments that are part of string literals or template strings
28
+ - License headers at the top of files
29
+
30
+ **Refactoring guidance:** If code needs a comment to explain it, the code should be refactored instead:
31
+ - Rename variables to be self-describing
32
+ - Extract functions with descriptive names
33
+ - Simplify complex logic
34
+
35
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: no-dangerous-git
3
+ description: Blocks dangerous git commands
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate git commands for dangerous operations.
14
+
15
+ These operations are forbidden and cannot be overridden:
16
+
17
+ **ALWAYS NACK if the command contains:**
18
+ - `--force` or `-f` with push (destroys remote history)
19
+ - `--no-verify` (bypasses pre-commit hooks)
20
+ - `git reset --hard` on shared branches
21
+ - `git push` to main/master with `--force`
22
+ - `git commit --amend` on already-pushed commits
23
+
24
+ **NACK reasoning:**
25
+ - `--force` can destroy team members' work
26
+ - `--no-verify` bypasses all safety checks
27
+ - These are explicitly listed as "DO NOT EVER ALLOW" in CLAUDE.md
28
+
29
+ **ACK if:**
30
+ - The git command does not contain dangerous flags
31
+ - The command is a normal commit, push, or other safe operation
32
+
33
+ **No plea system override:** These rules cannot be bypassed with a plea. They require explicit human approval outside the commit validator system.
34
+
35
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: tcr-workflow
3
+ description: Enforces Test && Commit || Revert workflow
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Validate commit context for TCR workflow compliance.
14
+
15
+ Enforce the TCR (Test && Commit || Revert) workflow:
16
+
17
+ **Core principle:** Red → Green → TCR → Refactor → TCR → Done
18
+
19
+ **NACK if:**
20
+ - The commit message or context indicates tests are failing
21
+ - The commit appears to be "patching" failing code rather than reverting and rethinking
22
+ - Evidence suggests the commit bypasses the test-first workflow
23
+
24
+ **ACK if:**
25
+ - The commit appears to follow the TCR pattern
26
+ - Tests are expected to pass (behavioral code has accompanying tests)
27
+ - The commit represents a clean, tested change
28
+
29
+ **Note:** This validator cannot run tests itself. It validates the commit context and message for TCR compliance indicators.
30
+
31
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: testing-no-state-peeking
3
+ description: Prohibits testing internal state instead of behavior
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Only validate .test.ts and .test.tsx files in the diff.
14
+
15
+ Tests must verify observable behavior, not internal state.
16
+
17
+ **NACK if the diff contains:**
18
+ - Private field access: `obj["privateField"]`, `service["internalMap"]`
19
+ - Bracket notation to access non-public properties
20
+ - Calls to methods that exist solely for testing: `.getCount()`, `.getActiveSessionCount()`, `.getInternalState()`
21
+ - Direct assertions on internal collections: `expect(tracker.items.length)`
22
+
23
+ **Litmus test:** "If I changed the internal data structure (e.g., Map to Array), would this test still pass?" If no, it's testing implementation.
24
+
25
+ **ACK if:**
26
+ - Tests verify via return values from public methods
27
+ - Tests verify via callbacks/events
28
+ - Tests verify via thrown exceptions
29
+ - Tests use the public API only
30
+ - The diff only contains non-test files
31
+
32
+ **Allowed patterns:**
33
+ - `tracker.onEvent(e => events.push(e))` - observable via callback
34
+ - `expect(createUser({name: 'Alice'})).toEqual({...})` - observable via return value
35
+ - Testing cleanup by re-triggering and verifying callback fires again
36
+
37
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: testing-structure
3
+ description: Enforces test structure and whole object assertions
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Only validate .test.ts and .test.tsx files in the diff.
14
+
15
+ Enforce proper test structure:
16
+
17
+ **NACK if the diff contains:**
18
+ - Multiple execute/verify cycles in one test (squint test violation)
19
+ - Cherry-picked property assertions instead of whole object assertions
20
+ - Bad: `expect(result.id).toBe(1); expect(result.name).toBe('foo');`
21
+ - Good: `expect(result).toEqual({ id: 1, name: 'foo' })`
22
+ - Tests with multiple `expect()` calls on different operations
23
+
24
+ **Required structure (Squint Test):**
25
+ 1. SETUP - prepare test data
26
+ 2. EXECUTE - single function call
27
+ 3. VERIFY - whole object assertion
28
+
29
+ **ACK if:**
30
+ - Tests follow SETUP → EXECUTE → VERIFY pattern
31
+ - Whole objects are asserted with `toEqual({...})`
32
+ - One assertion per behavior
33
+ - The diff only contains non-test files
34
+
35
+ **Note:** Multiple `expect()` calls are OK if they assert different aspects of the SAME operation result (e.g., checking both return value and side effect callback).
36
+
37
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: testing-stubs-over-mocks
3
+ description: Prefers deterministic stubs over mocks
4
+ enabled: true
5
+ ---
6
+
7
+ You are a commit validator. You MUST respond with ONLY a JSON object, no other text.
8
+
9
+ Valid responses:
10
+ {"decision":"ACK"}
11
+ {"decision":"NACK","reason":"one sentence explanation"}
12
+
13
+ **Scope:** Only validate .test.ts and .test.tsx files in the diff.
14
+
15
+ Prefer deterministic stubs over mocks. Mock only at system boundaries.
16
+
17
+ **NACK if the diff contains:**
18
+ - Heavy mocking of internal modules/functions that could use stubs
19
+ - `jest.mock()` or `vi.mock()` for internal application code
20
+ - Mock implementations that could be replaced with simple stub objects
21
+ - Spy assertions on internal function calls (`toHaveBeenCalledWith` on non-boundary code)
22
+
23
+ **ACK if:**
24
+ - Deterministic stubs are used (plain objects/functions with predictable behavior)
25
+ - Mocks are only used at system boundaries:
26
+ - External APIs (fetch, axios calls)
27
+ - Databases
28
+ - File system
29
+ - Third-party services
30
+ - The diff only contains non-test files
31
+
32
+ **Stub example (preferred):**
33
+ ```typescript
34
+ const fakeRepo = { findById: (id) => ({ id, name: 'Test' }) }
35
+ ```
36
+
37
+ **Mock example (only at boundaries):**
38
+ ```typescript
39
+ vi.mock('./external-api', () => ({ fetch: vi.fn() }))
40
+ ```
41
+
42
+ RESPOND WITH JSON ONLY - NO PROSE, NO MARKDOWN, NO EXPLANATION OUTSIDE THE JSON.