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,405 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * AI-powered changeset generation script
4
+ *
5
+ * This script:
6
+ * 1. Gets commits since last changeset/tag
7
+ * 2. Parses conventional commits (feat/fix/etc)
8
+ * 3. Determines bump type (breaking→major, feat→minor, else→patch)
9
+ * 4. Generates changelog via Claude CLI (with simple fallback)
10
+ * 5. Creates .changeset/*.md file
11
+ */
12
+
13
+ import { execSync } from 'node:child_process';
14
+ import { randomBytes } from 'node:crypto';
15
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+
18
+ // Exit codes
19
+ const EXIT_CODE = {
20
+ SUCCESS: 0,
21
+ ERROR: 1,
22
+ NO_COMMITS: 11,
23
+ NO_CONVENTIONAL_COMMITS: 12,
24
+ };
25
+
26
+ // Types
27
+ type CommitType =
28
+ | 'feat'
29
+ | 'fix'
30
+ | 'docs'
31
+ | 'style'
32
+ | 'refactor'
33
+ | 'perf'
34
+ | 'test'
35
+ | 'build'
36
+ | 'ci'
37
+ | 'chore'
38
+ | 'revert';
39
+ type BumpType = 'major' | 'minor' | 'patch';
40
+
41
+ interface ConventionalCommit {
42
+ hash: string;
43
+ type: CommitType;
44
+ scope?: string;
45
+ subject: string;
46
+ body?: string;
47
+ breaking: boolean;
48
+ fullMessage: string;
49
+ }
50
+
51
+ // Conventional commit pattern
52
+ const CONVENTIONAL_PATTERN = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(([^)]+)\))?: (.+)/;
53
+
54
+ // Configuration
55
+ const CHANGESET_DIR = '.changeset';
56
+ const PACKAGE_NAME = 'claude-ketchup';
57
+
58
+ // Logging utilities
59
+ function logStep(message: string): void {
60
+ console.log(`\nšŸ“‹ ${message}`);
61
+ }
62
+
63
+ function logInfo(message: string): void {
64
+ console.log(` ${message}`);
65
+ }
66
+
67
+ function logSuccess(message: string): void {
68
+ console.log(`āœ… ${message}`);
69
+ }
70
+
71
+ function logWarning(message: string): void {
72
+ console.warn(`āš ļø ${message}`);
73
+ }
74
+
75
+ function logError(message: string): void {
76
+ console.error(`āŒ ${message}`);
77
+ }
78
+
79
+ // Git utilities
80
+ function isGitRepository(): boolean {
81
+ try {
82
+ execSync('git rev-parse --git-dir', { stdio: 'pipe' });
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ function getCommitMessage(hash: string): string {
90
+ return execSync(`git log -1 --format=%B ${hash}`, { encoding: 'utf8' }).trim();
91
+ }
92
+
93
+ function getCommitsSinceLastChangeset(): string[] {
94
+ try {
95
+ const changesetPath = join(process.cwd(), CHANGESET_DIR);
96
+
97
+ if (!existsSync(changesetPath)) {
98
+ return getCommitsSinceLastTag();
99
+ }
100
+
101
+ const files = readdirSync(changesetPath).filter((f) => f.endsWith('.md') && f !== 'README.md');
102
+
103
+ if (files.length === 0) {
104
+ return getCommitsSinceLastTag();
105
+ }
106
+
107
+ // Get the most recent changeset file by git commit time
108
+ const latestChangeset = files
109
+ .map((f) => {
110
+ try {
111
+ const time = execSync(`git log -1 --format=%ct -- ${CHANGESET_DIR}/${f}`, {
112
+ encoding: 'utf8',
113
+ stdio: ['pipe', 'pipe', 'pipe'],
114
+ }).trim();
115
+ return { file: f, time: Number(time) || 0 };
116
+ } catch {
117
+ return { file: f, time: 0 };
118
+ }
119
+ })
120
+ .filter((f) => f.time > 0)
121
+ .sort((a, b) => b.time - a.time)[0];
122
+
123
+ if (!latestChangeset) {
124
+ return getCommitsSinceLastTag();
125
+ }
126
+
127
+ // Get commits since that changeset was added
128
+ const changesetCommit = execSync(`git log -1 --format=%H -- ${CHANGESET_DIR}/${latestChangeset.file}`, {
129
+ encoding: 'utf8',
130
+ stdio: ['pipe', 'pipe', 'pipe'],
131
+ }).trim();
132
+
133
+ return getCommitsInRange(changesetCommit, 'HEAD');
134
+ } catch (error) {
135
+ logWarning(`Could not get commits since last changeset: ${(error as Error).message}`);
136
+ return [];
137
+ }
138
+ }
139
+
140
+ function getCommitsInRange(since: string, until = 'HEAD'): string[] {
141
+ try {
142
+ const output = execSync(`git log ${since}..${until} --format=%H`, {
143
+ encoding: 'utf8',
144
+ stdio: ['pipe', 'pipe', 'pipe'],
145
+ }).trim();
146
+
147
+ return output ? output.split('\n').filter(Boolean) : [];
148
+ } catch {
149
+ return [];
150
+ }
151
+ }
152
+
153
+ function getCommitsSinceLastTag(): string[] {
154
+ try {
155
+ const lastTag = execSync('git describe --tags --abbrev=0', {
156
+ encoding: 'utf8',
157
+ stdio: ['pipe', 'pipe', 'pipe'],
158
+ }).trim();
159
+
160
+ return getCommitsInRange(lastTag, 'HEAD');
161
+ } catch {
162
+ // No tags exist, get recent commits (limit to 50)
163
+ try {
164
+ const output = execSync('git log -50 --format=%H', {
165
+ encoding: 'utf8',
166
+ stdio: ['pipe', 'pipe', 'pipe'],
167
+ }).trim();
168
+
169
+ return output ? output.split('\n').filter(Boolean) : [];
170
+ } catch {
171
+ return [];
172
+ }
173
+ }
174
+ }
175
+
176
+ // Parsing utilities
177
+ function parseConventionalCommit(hash: string): ConventionalCommit | null {
178
+ try {
179
+ const fullMessage = getCommitMessage(hash);
180
+ const match = fullMessage.match(CONVENTIONAL_PATTERN);
181
+
182
+ if (!match) {
183
+ return null;
184
+ }
185
+
186
+ const [, type, , scope, subject] = match;
187
+ const body = fullMessage.split('\n').slice(1).join('\n').trim();
188
+ const breaking = fullMessage.includes('BREAKING CHANGE:') || fullMessage.includes('!:');
189
+
190
+ return {
191
+ hash,
192
+ type: type as CommitType,
193
+ scope,
194
+ subject,
195
+ body,
196
+ breaking,
197
+ fullMessage,
198
+ };
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+
204
+ function parseConventionalCommits(hashes: string[]): ConventionalCommit[] {
205
+ return hashes.map((hash) => parseConventionalCommit(hash)).filter((c): c is ConventionalCommit => c !== null);
206
+ }
207
+
208
+ // Semver utilities
209
+ function determineBumpType(commits: ConventionalCommit[]): BumpType {
210
+ if (commits.some((c) => c.breaking)) {
211
+ return 'major';
212
+ }
213
+
214
+ if (commits.some((c) => c.type === 'feat')) {
215
+ return 'minor';
216
+ }
217
+
218
+ return 'patch';
219
+ }
220
+
221
+ // Changelog generation
222
+ async function generateChangelogWithClaude(commits: ConventionalCommit[]): Promise<string | null> {
223
+ try {
224
+ // Check if claude CLI is available
225
+ execSync('which claude', { stdio: 'pipe' });
226
+
227
+ const commitSummary = commits
228
+ .map((c) => `- ${c.type}${c.scope ? `(${c.scope})` : ''}: ${c.subject}\n ${c.body || '(no additional details)'}`)
229
+ .join('\n\n');
230
+
231
+ const prompt = `You are analyzing git commits to generate a changelog entry. Here are the commits:
232
+
233
+ ${commitSummary}
234
+
235
+ Generate a concise changelog description as bullet points. Rules:
236
+ - Use 2-5 bullet points maximum
237
+ - Focus on user-facing changes and impact
238
+ - Group related changes together
239
+ - Use clear, non-technical language where possible
240
+ - Start each bullet with a dash and capitalize the first word
241
+ - Do NOT include commit hashes, types, or scopes
242
+ - Do NOT use markdown formatting besides the dashes
243
+
244
+ Example format:
245
+ - Added user authentication with OAuth support
246
+ - Fixed critical bug in data synchronization
247
+ - Improved performance of search queries by 50%
248
+
249
+ Now generate the changelog for the commits above:`;
250
+
251
+ const tempFile = join(process.cwd(), `.changeset-prompt-${Date.now()}.txt`);
252
+ writeFileSync(tempFile, prompt);
253
+
254
+ try {
255
+ const result = execSync(`claude -p "$(cat ${tempFile})"`, {
256
+ encoding: 'utf8',
257
+ stdio: ['pipe', 'pipe', 'pipe'],
258
+ timeout: 30000,
259
+ });
260
+
261
+ return result.trim();
262
+ } finally {
263
+ try {
264
+ execSync(`rm ${tempFile}`, { stdio: 'pipe' });
265
+ } catch {
266
+ // Ignore cleanup errors
267
+ }
268
+ }
269
+ } catch {
270
+ return null;
271
+ }
272
+ }
273
+
274
+ function generateSimpleChangelog(commits: ConventionalCommit[]): string {
275
+ const features = commits.filter((c) => c.type === 'feat');
276
+ const fixes = commits.filter((c) => c.type === 'fix');
277
+ const others = commits.filter((c) => !['feat', 'fix'].includes(c.type));
278
+
279
+ const lines: string[] = [];
280
+
281
+ if (features.length > 0) {
282
+ lines.push(...features.map((c) => `- ${c.scope ? `**${c.scope}**: ` : ''}${c.subject}`));
283
+ }
284
+
285
+ if (fixes.length > 0) {
286
+ lines.push(...fixes.map((c) => `- ${c.scope ? `**${c.scope}**: ` : ''}${c.subject}`));
287
+ }
288
+
289
+ if (others.length > 0 && lines.length < 5) {
290
+ lines.push(...others.map((c) => `- ${c.scope ? `**${c.scope}**: ` : ''}${c.subject}`).slice(0, 5 - lines.length));
291
+ }
292
+
293
+ return lines.slice(0, 5).join('\n');
294
+ }
295
+
296
+ async function generateChangelog(commits: ConventionalCommit[]): Promise<string> {
297
+ logInfo('Attempting to generate changelog with Claude CLI...');
298
+
299
+ const aiChangelog = await generateChangelogWithClaude(commits);
300
+
301
+ if (aiChangelog) {
302
+ logSuccess('Changelog generated with Claude CLI');
303
+ return aiChangelog;
304
+ }
305
+
306
+ logWarning('Claude CLI not available, using simple changelog generation');
307
+ return generateSimpleChangelog(commits);
308
+ }
309
+
310
+ // Changeset file creation
311
+ function createChangesetFile(bumpType: BumpType, description: string): { filename: string; content: string } {
312
+ const changesetDir = join(process.cwd(), CHANGESET_DIR);
313
+
314
+ if (!existsSync(changesetDir)) {
315
+ mkdirSync(changesetDir, { recursive: true });
316
+ }
317
+
318
+ const id = randomBytes(8).toString('hex');
319
+ const filename = `auto-${id}.md`;
320
+ const filepath = join(changesetDir, filename);
321
+
322
+ const content = `---
323
+ "${PACKAGE_NAME}": ${bumpType}
324
+ ---
325
+
326
+ ${description}
327
+ `;
328
+
329
+ writeFileSync(filepath, content);
330
+
331
+ return { filename, content };
332
+ }
333
+
334
+ // Main function
335
+ async function main(): Promise<void> {
336
+ const args = process.argv.slice(2);
337
+ const dryRun = args.includes('--dry-run');
338
+
339
+ try {
340
+ if (!isGitRepository()) {
341
+ logError('Not a git repository');
342
+ process.exit(EXIT_CODE.ERROR);
343
+ }
344
+
345
+ logStep('Checking for commits that need changesets...');
346
+
347
+ const commitHashes = getCommitsSinceLastChangeset();
348
+
349
+ if (commitHashes.length === 0) {
350
+ logInfo('No new commits found. Nothing to do.');
351
+ process.exit(EXIT_CODE.NO_COMMITS);
352
+ }
353
+
354
+ logInfo(`Found ${commitHashes.length} commit(s) to process`);
355
+
356
+ const commits = parseConventionalCommits(commitHashes);
357
+
358
+ const skippedCount = commitHashes.length - commits.length;
359
+ if (skippedCount > 0) {
360
+ logWarning(`Skipped ${skippedCount} non-conventional commit(s). Use format: type(scope): subject`);
361
+ }
362
+
363
+ if (commits.length === 0) {
364
+ logWarning('No conventional commits found. Skipping changeset generation.');
365
+ logInfo('Commits must follow format: type(scope): subject (e.g., feat(cli): add new command)');
366
+ process.exit(EXIT_CODE.NO_CONVENTIONAL_COMMITS);
367
+ }
368
+
369
+ logSuccess(`Found ${commits.length} valid conventional commit(s)`);
370
+
371
+ const bumpType = determineBumpType(commits);
372
+ logInfo(`Determined version bump: ${bumpType}`);
373
+
374
+ const description = await generateChangelog(commits);
375
+
376
+ if (dryRun) {
377
+ console.log('\nšŸ“‹ Changeset Preview:');
378
+ console.log('---');
379
+ console.log(`Bump type: ${bumpType}`);
380
+ console.log(`Package: ${PACKAGE_NAME}`);
381
+ console.log('\nChangelog:');
382
+ console.log(description);
383
+ console.log('---');
384
+ process.exit(EXIT_CODE.SUCCESS);
385
+ }
386
+
387
+ const result = createChangesetFile(bumpType, description);
388
+
389
+ logSuccess(`Created changeset: ${result.filename}`);
390
+ logInfo(` Bump type: ${bumpType}`);
391
+ logInfo(` Commits: ${commits.length}`);
392
+
393
+ console.log('\nšŸ“‹ Changelog preview:');
394
+ console.log('---');
395
+ console.log(description);
396
+ console.log('---');
397
+
398
+ process.exit(EXIT_CODE.SUCCESS);
399
+ } catch (error) {
400
+ logError(`Failed to generate changeset: ${(error as Error).message}`);
401
+ process.exit(EXIT_CODE.ERROR);
402
+ }
403
+ }
404
+
405
+ main();
@@ -0,0 +1,35 @@
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 { handlePreToolUse } from '../src/hooks/pre-tool-use.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
+ handlePreToolUse(claudeDir, input.session_id, input.tool_input || {})
14
+ .then((result) => {
15
+ writeHookLog(claudeDir, {
16
+ hookName: 'pre-tool-use',
17
+ timestamp: new Date().toISOString(),
18
+ input,
19
+ output: result,
20
+ durationMs: Date.now() - startTime,
21
+ });
22
+ console.log(JSON.stringify(result));
23
+ })
24
+ .catch((err) => {
25
+ writeHookLog(claudeDir, {
26
+ hookName: 'pre-tool-use',
27
+ timestamp: new Date().toISOString(),
28
+ input,
29
+ output: null,
30
+ error: String(err),
31
+ durationMs: Date.now() - startTime,
32
+ });
33
+ console.error('pre-tool-use hook failed:', err);
34
+ process.exit(1);
35
+ });
@@ -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 { handleSessionStart } from '../src/hooks/session-start.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
+ handleSessionStart(claudeDir, input.session_id)
14
+ .then(({ diagnostics, ...result }) => {
15
+ writeHookLog(claudeDir, {
16
+ hookName: 'session-start',
17
+ timestamp: new Date().toISOString(),
18
+ input,
19
+ resolvedPaths: diagnostics.resolvedPaths,
20
+ reminderFiles: diagnostics.reminderFiles,
21
+ matchedReminders: diagnostics.matchedReminders,
22
+ output: result,
23
+ durationMs: Date.now() - startTime,
24
+ });
25
+ console.log(JSON.stringify(result));
26
+ })
27
+ .catch((err) => {
28
+ writeHookLog(claudeDir, {
29
+ hookName: 'session-start',
30
+ timestamp: new Date().toISOString(),
31
+ input,
32
+ output: null,
33
+ error: String(err),
34
+ durationMs: Date.now() - startTime,
35
+ });
36
+ console.error('session-start hook failed:', err);
37
+ process.exit(1);
38
+ });
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ # Tail all ketchup log files in real-time
3
+ # Usage: ./tail-logs.sh [logs-dir]
4
+
5
+ LOGS_DIR="${1:-.claude/logs}"
6
+
7
+ if [ ! -d "$LOGS_DIR" ]; then
8
+ echo "Logs directory not found: $LOGS_DIR"
9
+ exit 1
10
+ fi
11
+
12
+ echo "Tailing logs in $LOGS_DIR..."
13
+ echo "Press Ctrl+C to stop"
14
+ echo
15
+
16
+ # Use tail -f with all .log files, showing which file each line comes from
17
+ find "$LOGS_DIR" -name "*.log" -exec tail -f {} + 2>/dev/null