@urateam/core 0.1.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 (620) hide show
  1. package/dist/__tests__/assembler.test.d.ts +2 -0
  2. package/dist/__tests__/assembler.test.d.ts.map +1 -0
  3. package/dist/__tests__/assembler.test.js +63 -0
  4. package/dist/__tests__/assembler.test.js.map +1 -0
  5. package/dist/__tests__/auth-check.test.d.ts +2 -0
  6. package/dist/__tests__/auth-check.test.d.ts.map +1 -0
  7. package/dist/__tests__/auth-check.test.js +88 -0
  8. package/dist/__tests__/auth-check.test.js.map +1 -0
  9. package/dist/__tests__/auto-merge.test.d.ts +15 -0
  10. package/dist/__tests__/auto-merge.test.d.ts.map +1 -0
  11. package/dist/__tests__/auto-merge.test.js +428 -0
  12. package/dist/__tests__/auto-merge.test.js.map +1 -0
  13. package/dist/__tests__/bec89-unified-schema.test.d.ts +2 -0
  14. package/dist/__tests__/bec89-unified-schema.test.d.ts.map +1 -0
  15. package/dist/__tests__/bec89-unified-schema.test.js +235 -0
  16. package/dist/__tests__/bec89-unified-schema.test.js.map +1 -0
  17. package/dist/__tests__/conflict-detector.test.d.ts +2 -0
  18. package/dist/__tests__/conflict-detector.test.d.ts.map +1 -0
  19. package/dist/__tests__/conflict-detector.test.js +206 -0
  20. package/dist/__tests__/conflict-detector.test.js.map +1 -0
  21. package/dist/__tests__/coordination.test.d.ts +2 -0
  22. package/dist/__tests__/coordination.test.d.ts.map +1 -0
  23. package/dist/__tests__/coordination.test.js +257 -0
  24. package/dist/__tests__/coordination.test.js.map +1 -0
  25. package/dist/__tests__/db-postgres.test.d.ts +14 -0
  26. package/dist/__tests__/db-postgres.test.d.ts.map +1 -0
  27. package/dist/__tests__/db-postgres.test.js +289 -0
  28. package/dist/__tests__/db-postgres.test.js.map +1 -0
  29. package/dist/__tests__/db.test.d.ts +2 -0
  30. package/dist/__tests__/db.test.d.ts.map +1 -0
  31. package/dist/__tests__/db.test.js +182 -0
  32. package/dist/__tests__/db.test.js.map +1 -0
  33. package/dist/__tests__/deep-review.test.d.ts +2 -0
  34. package/dist/__tests__/deep-review.test.d.ts.map +1 -0
  35. package/dist/__tests__/deep-review.test.js +322 -0
  36. package/dist/__tests__/deep-review.test.js.map +1 -0
  37. package/dist/__tests__/devcontainer.test.d.ts +2 -0
  38. package/dist/__tests__/devcontainer.test.d.ts.map +1 -0
  39. package/dist/__tests__/devcontainer.test.js +89 -0
  40. package/dist/__tests__/devcontainer.test.js.map +1 -0
  41. package/dist/__tests__/distributed-lock.test.d.ts +18 -0
  42. package/dist/__tests__/distributed-lock.test.d.ts.map +1 -0
  43. package/dist/__tests__/distributed-lock.test.js +237 -0
  44. package/dist/__tests__/distributed-lock.test.js.map +1 -0
  45. package/dist/__tests__/e2e-pipeline.test.d.ts +25 -0
  46. package/dist/__tests__/e2e-pipeline.test.d.ts.map +1 -0
  47. package/dist/__tests__/e2e-pipeline.test.js +517 -0
  48. package/dist/__tests__/e2e-pipeline.test.js.map +1 -0
  49. package/dist/__tests__/error-classifier.test.d.ts +2 -0
  50. package/dist/__tests__/error-classifier.test.d.ts.map +1 -0
  51. package/dist/__tests__/error-classifier.test.js +33 -0
  52. package/dist/__tests__/error-classifier.test.js.map +1 -0
  53. package/dist/__tests__/executor-integration.test.d.ts +11 -0
  54. package/dist/__tests__/executor-integration.test.d.ts.map +1 -0
  55. package/dist/__tests__/executor-integration.test.js +246 -0
  56. package/dist/__tests__/executor-integration.test.js.map +1 -0
  57. package/dist/__tests__/executor-issue-id.test.d.ts +13 -0
  58. package/dist/__tests__/executor-issue-id.test.d.ts.map +1 -0
  59. package/dist/__tests__/executor-issue-id.test.js +211 -0
  60. package/dist/__tests__/executor-issue-id.test.js.map +1 -0
  61. package/dist/__tests__/executor.test.d.ts +2 -0
  62. package/dist/__tests__/executor.test.d.ts.map +1 -0
  63. package/dist/__tests__/executor.test.js +164 -0
  64. package/dist/__tests__/executor.test.js.map +1 -0
  65. package/dist/__tests__/extract-handoff.test.d.ts +2 -0
  66. package/dist/__tests__/extract-handoff.test.d.ts.map +1 -0
  67. package/dist/__tests__/extract-handoff.test.js +131 -0
  68. package/dist/__tests__/extract-handoff.test.js.map +1 -0
  69. package/dist/__tests__/fail-on-auto-commit.test.d.ts +2 -0
  70. package/dist/__tests__/fail-on-auto-commit.test.d.ts.map +1 -0
  71. package/dist/__tests__/fail-on-auto-commit.test.js +156 -0
  72. package/dist/__tests__/fail-on-auto-commit.test.js.map +1 -0
  73. package/dist/__tests__/fixtures/webhook-comment.json +5 -0
  74. package/dist/__tests__/fixtures/webhook-state-change.json +15 -0
  75. package/dist/__tests__/force-push-agent-branches.test.d.ts +12 -0
  76. package/dist/__tests__/force-push-agent-branches.test.d.ts.map +1 -0
  77. package/dist/__tests__/force-push-agent-branches.test.js +348 -0
  78. package/dist/__tests__/force-push-agent-branches.test.js.map +1 -0
  79. package/dist/__tests__/github-webhook.test.d.ts +2 -0
  80. package/dist/__tests__/github-webhook.test.d.ts.map +1 -0
  81. package/dist/__tests__/github-webhook.test.js +370 -0
  82. package/dist/__tests__/github-webhook.test.js.map +1 -0
  83. package/dist/__tests__/gitlab.test.d.ts +28 -0
  84. package/dist/__tests__/gitlab.test.d.ts.map +1 -0
  85. package/dist/__tests__/gitlab.test.js +241 -0
  86. package/dist/__tests__/gitlab.test.js.map +1 -0
  87. package/dist/__tests__/integration/auto-commit.test.d.ts +2 -0
  88. package/dist/__tests__/integration/auto-commit.test.d.ts.map +1 -0
  89. package/dist/__tests__/integration/auto-commit.test.js +207 -0
  90. package/dist/__tests__/integration/auto-commit.test.js.map +1 -0
  91. package/dist/__tests__/integration/bec99-cross-worktree-guard.test.d.ts +10 -0
  92. package/dist/__tests__/integration/bec99-cross-worktree-guard.test.d.ts.map +1 -0
  93. package/dist/__tests__/integration/bec99-cross-worktree-guard.test.js +183 -0
  94. package/dist/__tests__/integration/bec99-cross-worktree-guard.test.js.map +1 -0
  95. package/dist/__tests__/integration/reproduce-bec99.test.d.ts +32 -0
  96. package/dist/__tests__/integration/reproduce-bec99.test.d.ts.map +1 -0
  97. package/dist/__tests__/integration/reproduce-bec99.test.js +243 -0
  98. package/dist/__tests__/integration/reproduce-bec99.test.js.map +1 -0
  99. package/dist/__tests__/integration/vitest-changed.test.d.ts +10 -0
  100. package/dist/__tests__/integration/vitest-changed.test.d.ts.map +1 -0
  101. package/dist/__tests__/integration/vitest-changed.test.js +128 -0
  102. package/dist/__tests__/integration/vitest-changed.test.js.map +1 -0
  103. package/dist/__tests__/license.test.d.ts +2 -0
  104. package/dist/__tests__/license.test.d.ts.map +1 -0
  105. package/dist/__tests__/license.test.js +53 -0
  106. package/dist/__tests__/license.test.js.map +1 -0
  107. package/dist/__tests__/mcp-resolver.test.d.ts +2 -0
  108. package/dist/__tests__/mcp-resolver.test.d.ts.map +1 -0
  109. package/dist/__tests__/mcp-resolver.test.js +65 -0
  110. package/dist/__tests__/mcp-resolver.test.js.map +1 -0
  111. package/dist/__tests__/migrator.test.d.ts +2 -0
  112. package/dist/__tests__/migrator.test.d.ts.map +1 -0
  113. package/dist/__tests__/migrator.test.js +300 -0
  114. package/dist/__tests__/migrator.test.js.map +1 -0
  115. package/dist/__tests__/notifier-discord.test.d.ts +2 -0
  116. package/dist/__tests__/notifier-discord.test.d.ts.map +1 -0
  117. package/dist/__tests__/notifier-discord.test.js +166 -0
  118. package/dist/__tests__/notifier-discord.test.js.map +1 -0
  119. package/dist/__tests__/notifier-slack.test.d.ts +2 -0
  120. package/dist/__tests__/notifier-slack.test.d.ts.map +1 -0
  121. package/dist/__tests__/notifier-slack.test.js +157 -0
  122. package/dist/__tests__/notifier-slack.test.js.map +1 -0
  123. package/dist/__tests__/notifier.test.d.ts +2 -0
  124. package/dist/__tests__/notifier.test.d.ts.map +1 -0
  125. package/dist/__tests__/notifier.test.js +207 -0
  126. package/dist/__tests__/notifier.test.js.map +1 -0
  127. package/dist/__tests__/pipeline-config.test.d.ts +2 -0
  128. package/dist/__tests__/pipeline-config.test.d.ts.map +1 -0
  129. package/dist/__tests__/pipeline-config.test.js +143 -0
  130. package/dist/__tests__/pipeline-config.test.js.map +1 -0
  131. package/dist/__tests__/pipeline-runner.test.d.ts +2 -0
  132. package/dist/__tests__/pipeline-runner.test.d.ts.map +1 -0
  133. package/dist/__tests__/pipeline-runner.test.js +359 -0
  134. package/dist/__tests__/pipeline-runner.test.js.map +1 -0
  135. package/dist/__tests__/pm-approvals-n1.repro.test.d.ts +9 -0
  136. package/dist/__tests__/pm-approvals-n1.repro.test.d.ts.map +1 -0
  137. package/dist/__tests__/pm-approvals-n1.repro.test.js +175 -0
  138. package/dist/__tests__/pm-approvals-n1.repro.test.js.map +1 -0
  139. package/dist/__tests__/pm-approvals.test.d.ts +2 -0
  140. package/dist/__tests__/pm-approvals.test.d.ts.map +1 -0
  141. package/dist/__tests__/pm-approvals.test.js +162 -0
  142. package/dist/__tests__/pm-approvals.test.js.map +1 -0
  143. package/dist/__tests__/pm-budget.test.d.ts +2 -0
  144. package/dist/__tests__/pm-budget.test.d.ts.map +1 -0
  145. package/dist/__tests__/pm-budget.test.js +65 -0
  146. package/dist/__tests__/pm-budget.test.js.map +1 -0
  147. package/dist/__tests__/pm-conflict.test.d.ts +2 -0
  148. package/dist/__tests__/pm-conflict.test.d.ts.map +1 -0
  149. package/dist/__tests__/pm-conflict.test.js +87 -0
  150. package/dist/__tests__/pm-conflict.test.js.map +1 -0
  151. package/dist/__tests__/pm-promote.test.d.ts +2 -0
  152. package/dist/__tests__/pm-promote.test.d.ts.map +1 -0
  153. package/dist/__tests__/pm-promote.test.js +82 -0
  154. package/dist/__tests__/pm-promote.test.js.map +1 -0
  155. package/dist/__tests__/pm-recover.test.d.ts +2 -0
  156. package/dist/__tests__/pm-recover.test.d.ts.map +1 -0
  157. package/dist/__tests__/pm-recover.test.js +100 -0
  158. package/dist/__tests__/pm-recover.test.js.map +1 -0
  159. package/dist/__tests__/pm-scheduler.test.d.ts +2 -0
  160. package/dist/__tests__/pm-scheduler.test.d.ts.map +1 -0
  161. package/dist/__tests__/pm-scheduler.test.js +112 -0
  162. package/dist/__tests__/pm-scheduler.test.js.map +1 -0
  163. package/dist/__tests__/pm-slack-interface.test.d.ts +2 -0
  164. package/dist/__tests__/pm-slack-interface.test.d.ts.map +1 -0
  165. package/dist/__tests__/pm-slack-interface.test.js +372 -0
  166. package/dist/__tests__/pm-slack-interface.test.js.map +1 -0
  167. package/dist/__tests__/pm-slack.test.d.ts +2 -0
  168. package/dist/__tests__/pm-slack.test.d.ts.map +1 -0
  169. package/dist/__tests__/pm-slack.test.js +83 -0
  170. package/dist/__tests__/pm-slack.test.js.map +1 -0
  171. package/dist/__tests__/pm-triage.test.d.ts +2 -0
  172. package/dist/__tests__/pm-triage.test.d.ts.map +1 -0
  173. package/dist/__tests__/pm-triage.test.js +198 -0
  174. package/dist/__tests__/pm-triage.test.js.map +1 -0
  175. package/dist/__tests__/pm-types.test.d.ts +2 -0
  176. package/dist/__tests__/pm-types.test.d.ts.map +1 -0
  177. package/dist/__tests__/pm-types.test.js +76 -0
  178. package/dist/__tests__/pm-types.test.js.map +1 -0
  179. package/dist/__tests__/pr-automerge.test.d.ts +18 -0
  180. package/dist/__tests__/pr-automerge.test.d.ts.map +1 -0
  181. package/dist/__tests__/pr-automerge.test.js +645 -0
  182. package/dist/__tests__/pr-automerge.test.js.map +1 -0
  183. package/dist/__tests__/pr-description.test.d.ts +2 -0
  184. package/dist/__tests__/pr-description.test.d.ts.map +1 -0
  185. package/dist/__tests__/pr-description.test.js +728 -0
  186. package/dist/__tests__/pr-description.test.js.map +1 -0
  187. package/dist/__tests__/prompt-injection.test.d.ts +2 -0
  188. package/dist/__tests__/prompt-injection.test.d.ts.map +1 -0
  189. package/dist/__tests__/prompt-injection.test.js +446 -0
  190. package/dist/__tests__/prompt-injection.test.js.map +1 -0
  191. package/dist/__tests__/ralph-gate.test.d.ts +19 -0
  192. package/dist/__tests__/ralph-gate.test.d.ts.map +1 -0
  193. package/dist/__tests__/ralph-gate.test.js +593 -0
  194. package/dist/__tests__/ralph-gate.test.js.map +1 -0
  195. package/dist/__tests__/ralph-review-fix-regression.test.d.ts +18 -0
  196. package/dist/__tests__/ralph-review-fix-regression.test.d.ts.map +1 -0
  197. package/dist/__tests__/ralph-review-fix-regression.test.js +306 -0
  198. package/dist/__tests__/ralph-review-fix-regression.test.js.map +1 -0
  199. package/dist/__tests__/ralph.test.d.ts +2 -0
  200. package/dist/__tests__/ralph.test.d.ts.map +1 -0
  201. package/dist/__tests__/ralph.test.js +96 -0
  202. package/dist/__tests__/ralph.test.js.map +1 -0
  203. package/dist/__tests__/recover-stuck.test.d.ts +8 -0
  204. package/dist/__tests__/recover-stuck.test.d.ts.map +1 -0
  205. package/dist/__tests__/recover-stuck.test.js +399 -0
  206. package/dist/__tests__/recover-stuck.test.js.map +1 -0
  207. package/dist/__tests__/repo.test.d.ts +2 -0
  208. package/dist/__tests__/repo.test.d.ts.map +1 -0
  209. package/dist/__tests__/repo.test.js +295 -0
  210. package/dist/__tests__/repo.test.js.map +1 -0
  211. package/dist/__tests__/repro-bec58-n-plus-one.test.d.ts +2 -0
  212. package/dist/__tests__/repro-bec58-n-plus-one.test.d.ts.map +1 -0
  213. package/dist/__tests__/repro-bec58-n-plus-one.test.js +187 -0
  214. package/dist/__tests__/repro-bec58-n-plus-one.test.js.map +1 -0
  215. package/dist/__tests__/reproduce-bec113-pagination-warning.test.d.ts +16 -0
  216. package/dist/__tests__/reproduce-bec113-pagination-warning.test.d.ts.map +1 -0
  217. package/dist/__tests__/reproduce-bec113-pagination-warning.test.js +226 -0
  218. package/dist/__tests__/reproduce-bec113-pagination-warning.test.js.map +1 -0
  219. package/dist/__tests__/reproduce-bec43-updatedat.test.d.ts +2 -0
  220. package/dist/__tests__/reproduce-bec43-updatedat.test.d.ts.map +1 -0
  221. package/dist/__tests__/reproduce-bec43-updatedat.test.js +76 -0
  222. package/dist/__tests__/reproduce-bec43-updatedat.test.js.map +1 -0
  223. package/dist/__tests__/reproduce-bec48-distributed-race.test.d.ts +18 -0
  224. package/dist/__tests__/reproduce-bec48-distributed-race.test.d.ts.map +1 -0
  225. package/dist/__tests__/reproduce-bec48-distributed-race.test.js +178 -0
  226. package/dist/__tests__/reproduce-bec48-distributed-race.test.js.map +1 -0
  227. package/dist/__tests__/reproduce-bec62.test.d.ts +2 -0
  228. package/dist/__tests__/reproduce-bec62.test.d.ts.map +1 -0
  229. package/dist/__tests__/reproduce-bec62.test.js +86 -0
  230. package/dist/__tests__/reproduce-bec62.test.js.map +1 -0
  231. package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.d.ts +13 -0
  232. package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.d.ts.map +1 -0
  233. package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js +220 -0
  234. package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js.map +1 -0
  235. package/dist/__tests__/review-feedback.test.d.ts +2 -0
  236. package/dist/__tests__/review-feedback.test.d.ts.map +1 -0
  237. package/dist/__tests__/review-feedback.test.js +383 -0
  238. package/dist/__tests__/review-feedback.test.js.map +1 -0
  239. package/dist/__tests__/sanitizer.test.d.ts +2 -0
  240. package/dist/__tests__/sanitizer.test.d.ts.map +1 -0
  241. package/dist/__tests__/sanitizer.test.js +162 -0
  242. package/dist/__tests__/sanitizer.test.js.map +1 -0
  243. package/dist/__tests__/security.test.d.ts +2 -0
  244. package/dist/__tests__/security.test.d.ts.map +1 -0
  245. package/dist/__tests__/security.test.js +52 -0
  246. package/dist/__tests__/security.test.js.map +1 -0
  247. package/dist/__tests__/server.test.d.ts +2 -0
  248. package/dist/__tests__/server.test.d.ts.map +1 -0
  249. package/dist/__tests__/server.test.js +61 -0
  250. package/dist/__tests__/server.test.js.map +1 -0
  251. package/dist/__tests__/slack-alerts.test.d.ts +2 -0
  252. package/dist/__tests__/slack-alerts.test.d.ts.map +1 -0
  253. package/dist/__tests__/slack-alerts.test.js +214 -0
  254. package/dist/__tests__/slack-alerts.test.js.map +1 -0
  255. package/dist/__tests__/stage-models.test.d.ts +14 -0
  256. package/dist/__tests__/stage-models.test.d.ts.map +1 -0
  257. package/dist/__tests__/stage-models.test.js +244 -0
  258. package/dist/__tests__/stage-models.test.js.map +1 -0
  259. package/dist/__tests__/start-todo.test.d.ts +2 -0
  260. package/dist/__tests__/start-todo.test.d.ts.map +1 -0
  261. package/dist/__tests__/start-todo.test.js +175 -0
  262. package/dist/__tests__/start-todo.test.js.map +1 -0
  263. package/dist/__tests__/tech-stack.test.d.ts +2 -0
  264. package/dist/__tests__/tech-stack.test.d.ts.map +1 -0
  265. package/dist/__tests__/tech-stack.test.js +75 -0
  266. package/dist/__tests__/tech-stack.test.js.map +1 -0
  267. package/dist/__tests__/templates.test.d.ts +2 -0
  268. package/dist/__tests__/templates.test.d.ts.map +1 -0
  269. package/dist/__tests__/templates.test.js +161 -0
  270. package/dist/__tests__/templates.test.js.map +1 -0
  271. package/dist/__tests__/test-quality.test.d.ts +2 -0
  272. package/dist/__tests__/test-quality.test.d.ts.map +1 -0
  273. package/dist/__tests__/test-quality.test.js +329 -0
  274. package/dist/__tests__/test-quality.test.js.map +1 -0
  275. package/dist/__tests__/token-budget.test.d.ts +2 -0
  276. package/dist/__tests__/token-budget.test.d.ts.map +1 -0
  277. package/dist/__tests__/token-budget.test.js +198 -0
  278. package/dist/__tests__/token-budget.test.js.map +1 -0
  279. package/dist/__tests__/types.test.d.ts +2 -0
  280. package/dist/__tests__/types.test.d.ts.map +1 -0
  281. package/dist/__tests__/types.test.js +156 -0
  282. package/dist/__tests__/types.test.js.map +1 -0
  283. package/dist/__tests__/validate.test.d.ts +2 -0
  284. package/dist/__tests__/validate.test.d.ts.map +1 -0
  285. package/dist/__tests__/validate.test.js +128 -0
  286. package/dist/__tests__/validate.test.js.map +1 -0
  287. package/dist/__tests__/webhook-handler.test.d.ts +2 -0
  288. package/dist/__tests__/webhook-handler.test.d.ts.map +1 -0
  289. package/dist/__tests__/webhook-handler.test.js +286 -0
  290. package/dist/__tests__/webhook-handler.test.js.map +1 -0
  291. package/dist/__tests__/webhook.test.d.ts +2 -0
  292. package/dist/__tests__/webhook.test.d.ts.map +1 -0
  293. package/dist/__tests__/webhook.test.js +58 -0
  294. package/dist/__tests__/webhook.test.js.map +1 -0
  295. package/dist/db/client.d.ts +56 -0
  296. package/dist/db/client.d.ts.map +1 -0
  297. package/dist/db/client.js +201 -0
  298. package/dist/db/client.js.map +1 -0
  299. package/dist/db/index.d.ts +4 -0
  300. package/dist/db/index.d.ts.map +1 -0
  301. package/dist/db/index.js +4 -0
  302. package/dist/db/index.js.map +1 -0
  303. package/dist/db/migrations/postgres/001_initial_schema.sql +78 -0
  304. package/dist/db/migrations/postgres/002_pg_timestamps.sql +78 -0
  305. package/dist/db/migrations/postgres/003_retry_count.sql +10 -0
  306. package/dist/db/migrations/postgres/004_review_feedback.sql +20 -0
  307. package/dist/db/migrations/postgres/005_auto_merge.sql +15 -0
  308. package/dist/db/migrations/sqlite/001_initial_schema.sql +78 -0
  309. package/dist/db/migrations/sqlite/002_retry_count.sql +5 -0
  310. package/dist/db/migrations/sqlite/003_review_feedback.sql +7 -0
  311. package/dist/db/migrations/sqlite/004_auto_merge.sql +6 -0
  312. package/dist/db/migrator.d.ts +51 -0
  313. package/dist/db/migrator.d.ts.map +1 -0
  314. package/dist/db/migrator.js +188 -0
  315. package/dist/db/migrator.js.map +1 -0
  316. package/dist/db/schema.d.ts +1114 -0
  317. package/dist/db/schema.d.ts.map +1 -0
  318. package/dist/db/schema.js +129 -0
  319. package/dist/db/schema.js.map +1 -0
  320. package/dist/entrypoint.d.ts +2 -0
  321. package/dist/entrypoint.d.ts.map +1 -0
  322. package/dist/entrypoint.js +113 -0
  323. package/dist/entrypoint.js.map +1 -0
  324. package/dist/executor/agent-config.d.ts +10 -0
  325. package/dist/executor/agent-config.d.ts.map +1 -0
  326. package/dist/executor/agent-config.js +81 -0
  327. package/dist/executor/agent-config.js.map +1 -0
  328. package/dist/executor/agent-stream.d.ts +65 -0
  329. package/dist/executor/agent-stream.d.ts.map +1 -0
  330. package/dist/executor/agent-stream.js +101 -0
  331. package/dist/executor/agent-stream.js.map +1 -0
  332. package/dist/executor/auth-check.d.ts +10 -0
  333. package/dist/executor/auth-check.d.ts.map +1 -0
  334. package/dist/executor/auth-check.js +52 -0
  335. package/dist/executor/auth-check.js.map +1 -0
  336. package/dist/executor/deep-review.d.ts +61 -0
  337. package/dist/executor/deep-review.d.ts.map +1 -0
  338. package/dist/executor/deep-review.js +308 -0
  339. package/dist/executor/deep-review.js.map +1 -0
  340. package/dist/executor/executor.d.ts +27 -0
  341. package/dist/executor/executor.d.ts.map +1 -0
  342. package/dist/executor/executor.js +168 -0
  343. package/dist/executor/executor.js.map +1 -0
  344. package/dist/executor/extract-handoff.d.ts +14 -0
  345. package/dist/executor/extract-handoff.d.ts.map +1 -0
  346. package/dist/executor/extract-handoff.js +80 -0
  347. package/dist/executor/extract-handoff.js.map +1 -0
  348. package/dist/executor/handoff.d.ts +24 -0
  349. package/dist/executor/handoff.d.ts.map +1 -0
  350. package/dist/executor/handoff.js +63 -0
  351. package/dist/executor/handoff.js.map +1 -0
  352. package/dist/executor/index.d.ts +8 -0
  353. package/dist/executor/index.d.ts.map +1 -0
  354. package/dist/executor/index.js +8 -0
  355. package/dist/executor/index.js.map +1 -0
  356. package/dist/executor/mcp-resolver.d.ts +29 -0
  357. package/dist/executor/mcp-resolver.d.ts.map +1 -0
  358. package/dist/executor/mcp-resolver.js +80 -0
  359. package/dist/executor/mcp-resolver.js.map +1 -0
  360. package/dist/executor/permissions.d.ts +11 -0
  361. package/dist/executor/permissions.d.ts.map +1 -0
  362. package/dist/executor/permissions.js +32 -0
  363. package/dist/executor/permissions.js.map +1 -0
  364. package/dist/executor/profiles.d.ts +5 -0
  365. package/dist/executor/profiles.d.ts.map +1 -0
  366. package/dist/executor/profiles.js +35 -0
  367. package/dist/executor/profiles.js.map +1 -0
  368. package/dist/executor/prompt/assembler.d.ts +10 -0
  369. package/dist/executor/prompt/assembler.d.ts.map +1 -0
  370. package/dist/executor/prompt/assembler.js +28 -0
  371. package/dist/executor/prompt/assembler.js.map +1 -0
  372. package/dist/executor/prompt/index.d.ts +5 -0
  373. package/dist/executor/prompt/index.d.ts.map +1 -0
  374. package/dist/executor/prompt/index.js +5 -0
  375. package/dist/executor/prompt/index.js.map +1 -0
  376. package/dist/executor/prompt/sanitizer.d.ts +25 -0
  377. package/dist/executor/prompt/sanitizer.d.ts.map +1 -0
  378. package/dist/executor/prompt/sanitizer.js +81 -0
  379. package/dist/executor/prompt/sanitizer.js.map +1 -0
  380. package/dist/executor/prompt/schema-mapper.d.ts +7 -0
  381. package/dist/executor/prompt/schema-mapper.d.ts.map +1 -0
  382. package/dist/executor/prompt/schema-mapper.js +59 -0
  383. package/dist/executor/prompt/schema-mapper.js.map +1 -0
  384. package/dist/executor/prompt/templates.d.ts +31 -0
  385. package/dist/executor/prompt/templates.d.ts.map +1 -0
  386. package/dist/executor/prompt/templates.js +283 -0
  387. package/dist/executor/prompt/templates.js.map +1 -0
  388. package/dist/executor/ralph.d.ts +19 -0
  389. package/dist/executor/ralph.d.ts.map +1 -0
  390. package/dist/executor/ralph.js +112 -0
  391. package/dist/executor/ralph.js.map +1 -0
  392. package/dist/executor/test-quality.d.ts +117 -0
  393. package/dist/executor/test-quality.d.ts.map +1 -0
  394. package/dist/executor/test-quality.js +261 -0
  395. package/dist/executor/test-quality.js.map +1 -0
  396. package/dist/executor/validate.d.ts +15 -0
  397. package/dist/executor/validate.d.ts.map +1 -0
  398. package/dist/executor/validate.js +124 -0
  399. package/dist/executor/validate.js.map +1 -0
  400. package/dist/index.d.ts +29 -0
  401. package/dist/index.d.ts.map +1 -0
  402. package/dist/index.js +26 -0
  403. package/dist/index.js.map +1 -0
  404. package/dist/license.d.ts +18 -0
  405. package/dist/license.d.ts.map +1 -0
  406. package/dist/license.js +44 -0
  407. package/dist/license.js.map +1 -0
  408. package/dist/logger.d.ts +43 -0
  409. package/dist/logger.d.ts.map +1 -0
  410. package/dist/logger.js +91 -0
  411. package/dist/logger.js.map +1 -0
  412. package/dist/notifier/composite.d.ts +13 -0
  413. package/dist/notifier/composite.d.ts.map +1 -0
  414. package/dist/notifier/composite.js +28 -0
  415. package/dist/notifier/composite.js.map +1 -0
  416. package/dist/notifier/discord.d.ts +14 -0
  417. package/dist/notifier/discord.d.ts.map +1 -0
  418. package/dist/notifier/discord.js +105 -0
  419. package/dist/notifier/discord.js.map +1 -0
  420. package/dist/notifier/index.d.ts +6 -0
  421. package/dist/notifier/index.d.ts.map +1 -0
  422. package/dist/notifier/index.js +6 -0
  423. package/dist/notifier/index.js.map +1 -0
  424. package/dist/notifier/linear.d.ts +28 -0
  425. package/dist/notifier/linear.d.ts.map +1 -0
  426. package/dist/notifier/linear.js +138 -0
  427. package/dist/notifier/linear.js.map +1 -0
  428. package/dist/notifier/slack-alerts.d.ts +62 -0
  429. package/dist/notifier/slack-alerts.d.ts.map +1 -0
  430. package/dist/notifier/slack-alerts.js +184 -0
  431. package/dist/notifier/slack-alerts.js.map +1 -0
  432. package/dist/notifier/slack.d.ts +14 -0
  433. package/dist/notifier/slack.d.ts.map +1 -0
  434. package/dist/notifier/slack.js +146 -0
  435. package/dist/notifier/slack.js.map +1 -0
  436. package/dist/pipeline/automerge.d.ts +44 -0
  437. package/dist/pipeline/automerge.d.ts.map +1 -0
  438. package/dist/pipeline/automerge.js +135 -0
  439. package/dist/pipeline/automerge.js.map +1 -0
  440. package/dist/pipeline/config.d.ts +5 -0
  441. package/dist/pipeline/config.d.ts.map +1 -0
  442. package/dist/pipeline/config.js +68 -0
  443. package/dist/pipeline/config.js.map +1 -0
  444. package/dist/pipeline/distributed-lock.d.ts +50 -0
  445. package/dist/pipeline/distributed-lock.d.ts.map +1 -0
  446. package/dist/pipeline/distributed-lock.js +114 -0
  447. package/dist/pipeline/distributed-lock.js.map +1 -0
  448. package/dist/pipeline/error-classifier.d.ts +9 -0
  449. package/dist/pipeline/error-classifier.d.ts.map +1 -0
  450. package/dist/pipeline/error-classifier.js +25 -0
  451. package/dist/pipeline/error-classifier.js.map +1 -0
  452. package/dist/pipeline/index.d.ts +9 -0
  453. package/dist/pipeline/index.d.ts.map +1 -0
  454. package/dist/pipeline/index.js +9 -0
  455. package/dist/pipeline/index.js.map +1 -0
  456. package/dist/pipeline/pr-description.d.ts +35 -0
  457. package/dist/pipeline/pr-description.d.ts.map +1 -0
  458. package/dist/pipeline/pr-description.js +52 -0
  459. package/dist/pipeline/pr-description.js.map +1 -0
  460. package/dist/pipeline/queue.d.ts +7 -0
  461. package/dist/pipeline/queue.d.ts.map +1 -0
  462. package/dist/pipeline/queue.js +39 -0
  463. package/dist/pipeline/queue.js.map +1 -0
  464. package/dist/pipeline/router.d.ts +6 -0
  465. package/dist/pipeline/router.d.ts.map +1 -0
  466. package/dist/pipeline/router.js +19 -0
  467. package/dist/pipeline/router.js.map +1 -0
  468. package/dist/pipeline/runner.d.ts +142 -0
  469. package/dist/pipeline/runner.d.ts.map +1 -0
  470. package/dist/pipeline/runner.js +1848 -0
  471. package/dist/pipeline/runner.js.map +1 -0
  472. package/dist/pm/actions/approval-helpers.d.ts +11 -0
  473. package/dist/pm/actions/approval-helpers.d.ts.map +1 -0
  474. package/dist/pm/actions/approval-helpers.js +34 -0
  475. package/dist/pm/actions/approval-helpers.js.map +1 -0
  476. package/dist/pm/actions/cancel.d.ts +11 -0
  477. package/dist/pm/actions/cancel.d.ts.map +1 -0
  478. package/dist/pm/actions/cancel.js +68 -0
  479. package/dist/pm/actions/cancel.js.map +1 -0
  480. package/dist/pm/actions/deprioritize.d.ts +12 -0
  481. package/dist/pm/actions/deprioritize.d.ts.map +1 -0
  482. package/dist/pm/actions/deprioritize.js +55 -0
  483. package/dist/pm/actions/deprioritize.js.map +1 -0
  484. package/dist/pm/actions/promote.d.ts +11 -0
  485. package/dist/pm/actions/promote.d.ts.map +1 -0
  486. package/dist/pm/actions/promote.js +78 -0
  487. package/dist/pm/actions/promote.js.map +1 -0
  488. package/dist/pm/actions/recover-stuck.d.ts +42 -0
  489. package/dist/pm/actions/recover-stuck.d.ts.map +1 -0
  490. package/dist/pm/actions/recover-stuck.js +143 -0
  491. package/dist/pm/actions/recover-stuck.js.map +1 -0
  492. package/dist/pm/actions/recover.d.ts +18 -0
  493. package/dist/pm/actions/recover.d.ts.map +1 -0
  494. package/dist/pm/actions/recover.js +56 -0
  495. package/dist/pm/actions/recover.js.map +1 -0
  496. package/dist/pm/actions/resolve-approvals.d.ts +17 -0
  497. package/dist/pm/actions/resolve-approvals.d.ts.map +1 -0
  498. package/dist/pm/actions/resolve-approvals.js +92 -0
  499. package/dist/pm/actions/resolve-approvals.js.map +1 -0
  500. package/dist/pm/actions/start-todo.d.ts +28 -0
  501. package/dist/pm/actions/start-todo.d.ts.map +1 -0
  502. package/dist/pm/actions/start-todo.js +117 -0
  503. package/dist/pm/actions/start-todo.js.map +1 -0
  504. package/dist/pm/actions/triage.d.ts +13 -0
  505. package/dist/pm/actions/triage.d.ts.map +1 -0
  506. package/dist/pm/actions/triage.js +109 -0
  507. package/dist/pm/actions/triage.js.map +1 -0
  508. package/dist/pm/budget.d.ts +9 -0
  509. package/dist/pm/budget.d.ts.map +1 -0
  510. package/dist/pm/budget.js +62 -0
  511. package/dist/pm/budget.js.map +1 -0
  512. package/dist/pm/call-claude.d.ts +3 -0
  513. package/dist/pm/call-claude.d.ts.map +1 -0
  514. package/dist/pm/call-claude.js +37 -0
  515. package/dist/pm/call-claude.js.map +1 -0
  516. package/dist/pm/conflict-detector.d.ts +42 -0
  517. package/dist/pm/conflict-detector.d.ts.map +1 -0
  518. package/dist/pm/conflict-detector.js +116 -0
  519. package/dist/pm/conflict-detector.js.map +1 -0
  520. package/dist/pm/conflict.d.ts +20 -0
  521. package/dist/pm/conflict.d.ts.map +1 -0
  522. package/dist/pm/conflict.js +63 -0
  523. package/dist/pm/conflict.js.map +1 -0
  524. package/dist/pm/coordination.d.ts +50 -0
  525. package/dist/pm/coordination.d.ts.map +1 -0
  526. package/dist/pm/coordination.js +163 -0
  527. package/dist/pm/coordination.js.map +1 -0
  528. package/dist/pm/linear-helpers.d.ts +2 -0
  529. package/dist/pm/linear-helpers.d.ts.map +1 -0
  530. package/dist/pm/linear-helpers.js +16 -0
  531. package/dist/pm/linear-helpers.js.map +1 -0
  532. package/dist/pm/scheduler.d.ts +47 -0
  533. package/dist/pm/scheduler.d.ts.map +1 -0
  534. package/dist/pm/scheduler.js +346 -0
  535. package/dist/pm/scheduler.js.map +1 -0
  536. package/dist/pm/slack-helpers.d.ts +2 -0
  537. package/dist/pm/slack-helpers.d.ts.map +1 -0
  538. package/dist/pm/slack-helpers.js +24 -0
  539. package/dist/pm/slack-helpers.js.map +1 -0
  540. package/dist/pm/slack-interface.d.ts +133 -0
  541. package/dist/pm/slack-interface.d.ts.map +1 -0
  542. package/dist/pm/slack-interface.js +641 -0
  543. package/dist/pm/slack-interface.js.map +1 -0
  544. package/dist/pm/slack.d.ts +18 -0
  545. package/dist/pm/slack.d.ts.map +1 -0
  546. package/dist/pm/slack.js +144 -0
  547. package/dist/pm/slack.js.map +1 -0
  548. package/dist/pm/types.d.ts +99 -0
  549. package/dist/pm/types.d.ts.map +1 -0
  550. package/dist/pm/types.js +17 -0
  551. package/dist/pm/types.js.map +1 -0
  552. package/dist/repo/config.d.ts +35 -0
  553. package/dist/repo/config.d.ts.map +1 -0
  554. package/dist/repo/config.js +72 -0
  555. package/dist/repo/config.js.map +1 -0
  556. package/dist/repo/devcontainer.d.ts +33 -0
  557. package/dist/repo/devcontainer.d.ts.map +1 -0
  558. package/dist/repo/devcontainer.js +90 -0
  559. package/dist/repo/devcontainer.js.map +1 -0
  560. package/dist/repo/git.d.ts +185 -0
  561. package/dist/repo/git.d.ts.map +1 -0
  562. package/dist/repo/git.js +586 -0
  563. package/dist/repo/git.js.map +1 -0
  564. package/dist/repo/github.d.ts +56 -0
  565. package/dist/repo/github.d.ts.map +1 -0
  566. package/dist/repo/github.js +164 -0
  567. package/dist/repo/github.js.map +1 -0
  568. package/dist/repo/gitlab.d.ts +47 -0
  569. package/dist/repo/gitlab.d.ts.map +1 -0
  570. package/dist/repo/gitlab.js +91 -0
  571. package/dist/repo/gitlab.js.map +1 -0
  572. package/dist/repo/index.d.ts +7 -0
  573. package/dist/repo/index.d.ts.map +1 -0
  574. package/dist/repo/index.js +5 -0
  575. package/dist/repo/index.js.map +1 -0
  576. package/dist/repo/tech-stack.d.ts +13 -0
  577. package/dist/repo/tech-stack.d.ts.map +1 -0
  578. package/dist/repo/tech-stack.js +112 -0
  579. package/dist/repo/tech-stack.js.map +1 -0
  580. package/dist/security/index.d.ts +3 -0
  581. package/dist/security/index.d.ts.map +1 -0
  582. package/dist/security/index.js +3 -0
  583. package/dist/security/index.js.map +1 -0
  584. package/dist/security/review-checklist.d.ts +9 -0
  585. package/dist/security/review-checklist.d.ts.map +1 -0
  586. package/dist/security/review-checklist.js +46 -0
  587. package/dist/security/review-checklist.js.map +1 -0
  588. package/dist/security/sandbox.d.ts +7 -0
  589. package/dist/security/sandbox.d.ts.map +1 -0
  590. package/dist/security/sandbox.js +31 -0
  591. package/dist/security/sandbox.js.map +1 -0
  592. package/dist/server.d.ts +48 -0
  593. package/dist/server.d.ts.map +1 -0
  594. package/dist/server.js +90 -0
  595. package/dist/server.js.map +1 -0
  596. package/dist/types.d.ts +1230 -0
  597. package/dist/types.d.ts.map +1 -0
  598. package/dist/types.js +225 -0
  599. package/dist/types.js.map +1 -0
  600. package/dist/webhook/github-handler.d.ts +39 -0
  601. package/dist/webhook/github-handler.d.ts.map +1 -0
  602. package/dist/webhook/github-handler.js +439 -0
  603. package/dist/webhook/github-handler.js.map +1 -0
  604. package/dist/webhook/handler.d.ts +16 -0
  605. package/dist/webhook/handler.d.ts.map +1 -0
  606. package/dist/webhook/handler.js +171 -0
  607. package/dist/webhook/handler.js.map +1 -0
  608. package/dist/webhook/index.d.ts +5 -0
  609. package/dist/webhook/index.d.ts.map +1 -0
  610. package/dist/webhook/index.js +5 -0
  611. package/dist/webhook/index.js.map +1 -0
  612. package/dist/webhook/parser.d.ts +18 -0
  613. package/dist/webhook/parser.d.ts.map +1 -0
  614. package/dist/webhook/parser.js +30 -0
  615. package/dist/webhook/parser.js.map +1 -0
  616. package/dist/webhook/signature.d.ts +2 -0
  617. package/dist/webhook/signature.d.ts.map +1 -0
  618. package/dist/webhook/signature.js +14 -0
  619. package/dist/webhook/signature.js.map +1 -0
  620. package/package.json +40 -0
@@ -0,0 +1,286 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { createHmac } from "crypto";
3
+ import { createWebhookHandler } from "../webhook/handler.js";
4
+ import { createDb } from "../db/client.js";
5
+ // Helper to generate a valid HMAC signature
6
+ function sign(body, secret) {
7
+ return createHmac("sha256", secret).update(body).digest("hex");
8
+ }
9
+ // ---------------------------------------------------------------------------
10
+ // Fixtures
11
+ // ---------------------------------------------------------------------------
12
+ const SECRET = "whsec_test_secret";
13
+ const pipelineConfig = {
14
+ name: "auto-implement",
15
+ stages: ["triage", "implement", "test", "review"],
16
+ retry: { maxAttempts: 1, strategy: "fix-and-retry" },
17
+ review: { requiredApprovals: 1 },
18
+ prStrategy: "draft",
19
+ };
20
+ const repoConfig = {
21
+ url: "https://github.com/org/repo",
22
+ defaultBranch: "main",
23
+ testCommand: "npm test",
24
+ buildCommand: "npm run build",
25
+ };
26
+ function stateChangePayload(stateName, labels = ["auto-implement"]) {
27
+ return {
28
+ action: "update",
29
+ type: "Issue",
30
+ data: {
31
+ id: "issue-uuid-123",
32
+ identifier: "LIN-42",
33
+ title: "Add user search",
34
+ description: "Implement search functionality",
35
+ priority: 2,
36
+ state: { id: "state-uuid", name: stateName },
37
+ teamId: "team-frontend",
38
+ labels: labels.map((name) => ({ name })),
39
+ },
40
+ updatedFrom: { stateId: "old-state-uuid" },
41
+ };
42
+ }
43
+ function commentPayload() {
44
+ return {
45
+ action: "create",
46
+ type: "Comment",
47
+ data: { id: "comment-123", body: "Added a comment" },
48
+ };
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Mock PipelineRunner
52
+ // ---------------------------------------------------------------------------
53
+ function createMockRunner() {
54
+ return {
55
+ start: vi.fn().mockResolvedValue(undefined),
56
+ resume: vi.fn().mockResolvedValue(undefined),
57
+ pause: vi.fn().mockResolvedValue(undefined),
58
+ abort: vi.fn().mockResolvedValue(undefined),
59
+ isActive: vi.fn().mockReturnValue(false),
60
+ };
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Test helpers
64
+ // ---------------------------------------------------------------------------
65
+ function buildConfig(runner, overrides) {
66
+ return {
67
+ webhookSecret: SECRET,
68
+ runner: runner,
69
+ pipelineConfigs: { "auto-implement": pipelineConfig },
70
+ repoConfigs: { "team-frontend": repoConfig },
71
+ ...overrides,
72
+ };
73
+ }
74
+ async function postWebhook(app, body, secret = SECRET) {
75
+ const rawBody = JSON.stringify(body);
76
+ const sig = sign(rawBody, secret);
77
+ return app.request("/webhooks/linear", {
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ "Linear-Signature": sig,
82
+ },
83
+ body: rawBody,
84
+ });
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Tests
88
+ // ---------------------------------------------------------------------------
89
+ describe("createWebhookHandler", () => {
90
+ let runner;
91
+ beforeEach(() => {
92
+ vi.restoreAllMocks();
93
+ runner = createMockRunner();
94
+ });
95
+ it("returns 401 for invalid signature", async () => {
96
+ const app = createWebhookHandler(buildConfig(runner));
97
+ const rawBody = JSON.stringify(stateChangePayload("Todo"));
98
+ const res = await app.request("/webhooks/linear", {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "Linear-Signature": "invalid-sig",
103
+ },
104
+ body: rawBody,
105
+ });
106
+ expect(res.status).toBe(401);
107
+ const json = await res.json();
108
+ expect(json.error).toBe("Invalid signature");
109
+ });
110
+ it("returns 200 for valid signature with state change and dispatches start", async () => {
111
+ const app = createWebhookHandler(buildConfig(runner));
112
+ const res = await postWebhook(app, stateChangePayload("Todo"));
113
+ expect(res.status).toBe(200);
114
+ const json = await res.json();
115
+ expect(json.ok).toBe(true);
116
+ expect(json.action).toBe("start");
117
+ expect(json.runQueued).toBe(true);
118
+ expect(runner.start).toHaveBeenCalledTimes(1);
119
+ });
120
+ it("returns 200 for comment webhook (ignored)", async () => {
121
+ const app = createWebhookHandler(buildConfig(runner));
122
+ const res = await postWebhook(app, commentPayload());
123
+ expect(res.status).toBe(200);
124
+ const json = await res.json();
125
+ expect(json.ok).toBe(true);
126
+ expect(runner.start).not.toHaveBeenCalled();
127
+ });
128
+ it("returns 200 for unknown state (ignored)", async () => {
129
+ const app = createWebhookHandler(buildConfig(runner));
130
+ const res = await postWebhook(app, stateChangePayload("In Progress"));
131
+ expect(res.status).toBe(200);
132
+ const json = await res.json();
133
+ expect(json.ok).toBe(true);
134
+ expect(runner.start).not.toHaveBeenCalled();
135
+ });
136
+ it("deduplicates same webhook within 30s window", async () => {
137
+ const app = createWebhookHandler(buildConfig(runner));
138
+ const payload = stateChangePayload("Todo");
139
+ // First request should dispatch
140
+ const res1 = await postWebhook(app, payload);
141
+ const json1 = await res1.json();
142
+ expect(json1.action).toBe("start");
143
+ expect(runner.start).toHaveBeenCalledTimes(1);
144
+ // Second identical request should be deduplicated
145
+ const res2 = await postWebhook(app, payload);
146
+ const json2 = await res2.json();
147
+ expect(json2.deduplicated).toBe(true);
148
+ expect(runner.start).toHaveBeenCalledTimes(1); // Still 1
149
+ });
150
+ it("returns message when no pipeline matches labels", async () => {
151
+ const app = createWebhookHandler(buildConfig(runner));
152
+ const res = await postWebhook(app, stateChangePayload("Todo", ["unknown-label"]));
153
+ expect(res.status).toBe(200);
154
+ const json = await res.json();
155
+ expect(json.message).toBe("No pipeline config for labels");
156
+ expect(runner.start).not.toHaveBeenCalled();
157
+ });
158
+ it("returns 422 when no repo mapping found", async () => {
159
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
160
+ const config = buildConfig(runner, {
161
+ repoConfigs: {}, // No repo configs
162
+ });
163
+ const app = createWebhookHandler(config);
164
+ const res = await postWebhook(app, stateChangePayload("Todo"));
165
+ expect(res.status).toBe(422);
166
+ const json = await res.json();
167
+ expect(json.error).toBe("No repo mapping for team/project");
168
+ expect(json.teamId).toBe("team-frontend");
169
+ expect(runner.start).not.toHaveBeenCalled();
170
+ consoleSpy.mockRestore();
171
+ });
172
+ it("uses per-team triggerMap from repoConfig when present", async () => {
173
+ // Team uses "Ready for Dev" instead of default "Todo"
174
+ const customRepoConfig = {
175
+ ...repoConfig,
176
+ triggerMap: { start: "Ready for Dev", resume: "Approved", pause: "On Hold", abort: "Won't Fix" },
177
+ };
178
+ const app = createWebhookHandler(buildConfig(runner, { repoConfigs: { "team-frontend": customRepoConfig } }));
179
+ // Default "Todo" state should NOT trigger start with custom map
180
+ const resTodo = await postWebhook(app, stateChangePayload("Todo"));
181
+ const jsonTodo = await resTodo.json();
182
+ expect(jsonTodo.action).toBeUndefined();
183
+ expect(runner.start).not.toHaveBeenCalled();
184
+ // Custom "Ready for Dev" state should trigger start
185
+ const resCustom = await postWebhook(app, stateChangePayload("Ready for Dev"));
186
+ const jsonCustom = await resCustom.json();
187
+ expect(jsonCustom.action).toBe("start");
188
+ expect(runner.start).toHaveBeenCalledTimes(1);
189
+ });
190
+ it("falls back to global triggerMap when repoConfig has none", async () => {
191
+ // Global override: use "Backlog" as start state
192
+ const app = createWebhookHandler(buildConfig(runner, {
193
+ triggerMap: { start: "Backlog", resume: "Approved", pause: "Blocked", abort: "Canceled" },
194
+ }));
195
+ // Default "Todo" should NOT trigger start
196
+ const resTodo = await postWebhook(app, stateChangePayload("Todo"));
197
+ const jsonTodo = await resTodo.json();
198
+ expect(jsonTodo.action).toBeUndefined();
199
+ // Global "Backlog" should trigger start
200
+ const resBacklog = await postWebhook(app, stateChangePayload("Backlog"));
201
+ const jsonBacklog = await resBacklog.json();
202
+ expect(jsonBacklog.action).toBe("start");
203
+ expect(runner.start).toHaveBeenCalledTimes(1);
204
+ });
205
+ // -------------------------------------------------------------------------
206
+ // DB-backed dedup: restart-survival and multi-instance scenarios
207
+ // -------------------------------------------------------------------------
208
+ describe("DB-backed dedup", () => {
209
+ it("deduplicates within the same handler instance using DB", async () => {
210
+ const db = await createDb({ connectionString: ":memory:" });
211
+ const app = createWebhookHandler(buildConfig(runner, { db }));
212
+ const payload = stateChangePayload("Todo");
213
+ const res1 = await postWebhook(app, payload);
214
+ const json1 = await res1.json();
215
+ expect(json1.action).toBe("start");
216
+ expect(runner.start).toHaveBeenCalledTimes(1);
217
+ const res2 = await postWebhook(app, payload);
218
+ const json2 = await res2.json();
219
+ expect(json2.deduplicated).toBe(true);
220
+ expect(runner.start).toHaveBeenCalledTimes(1);
221
+ });
222
+ it("survives restart — second handler instance sees dedup state from DB", async () => {
223
+ // Use a shared in-memory SQLite DB (same connection shared across both handlers)
224
+ const db = await createDb({ connectionString: ":memory:" });
225
+ // First "process": handler 1 receives the webhook and stores dedup entry in DB
226
+ const app1 = createWebhookHandler(buildConfig(runner, { db }));
227
+ const payload = stateChangePayload("Todo");
228
+ const res1 = await postWebhook(app1, payload);
229
+ const json1 = await res1.json();
230
+ expect(json1.action).toBe("start");
231
+ expect(runner.start).toHaveBeenCalledTimes(1);
232
+ // Simulate "restart" — create a brand-new handler (new in-memory DedupSet would be empty,
233
+ // but DB-backed dedup still has the entry from the first handler)
234
+ const runner2 = createMockRunner();
235
+ const app2 = createWebhookHandler(buildConfig(runner2, { db }));
236
+ const res2 = await postWebhook(app2, payload);
237
+ const json2 = await res2.json();
238
+ expect(json2.deduplicated).toBe(true);
239
+ expect(runner2.start).not.toHaveBeenCalled();
240
+ });
241
+ it("provides correct dedup across two concurrent instances sharing the same DB", async () => {
242
+ // Both instances share the same DB connection (simulates multi-instance with shared Postgres)
243
+ const db = await createDb({ connectionString: ":memory:" });
244
+ const runner2 = createMockRunner();
245
+ const app1 = createWebhookHandler(buildConfig(runner, { db }));
246
+ const app2 = createWebhookHandler(buildConfig(runner2, { db }));
247
+ const payload = stateChangePayload("Todo");
248
+ // Instance 1 processes the webhook
249
+ const res1 = await postWebhook(app1, payload);
250
+ const json1 = await res1.json();
251
+ expect(json1.action).toBe("start");
252
+ expect(runner.start).toHaveBeenCalledTimes(1);
253
+ // Instance 2 receives the same webhook — should be deduplicated via shared DB
254
+ const res2 = await postWebhook(app2, payload);
255
+ const json2 = await res2.json();
256
+ expect(json2.deduplicated).toBe(true);
257
+ expect(runner2.start).not.toHaveBeenCalled();
258
+ });
259
+ it("falls back to in-memory dedup when no DB is configured", async () => {
260
+ // No db option — uses MemoryDedupBackend (existing behavior preserved)
261
+ const app = createWebhookHandler(buildConfig(runner));
262
+ const payload = stateChangePayload("Todo");
263
+ const res1 = await postWebhook(app, payload);
264
+ const json1 = await res1.json();
265
+ expect(json1.action).toBe("start");
266
+ const res2 = await postWebhook(app, payload);
267
+ const json2 = await res2.json();
268
+ expect(json2.deduplicated).toBe(true);
269
+ });
270
+ it("DB dedup cleanup removes expired entries", async () => {
271
+ const db = await createDb({ connectionString: ":memory:" });
272
+ const app = createWebhookHandler(buildConfig(runner, { db }));
273
+ const payload = stateChangePayload("Todo");
274
+ const res1 = await postWebhook(app, payload);
275
+ const json1 = await res1.json();
276
+ expect(json1.action).toBe("start");
277
+ expect(runner.start).toHaveBeenCalledTimes(1);
278
+ // Within the same 30s window, dedup should still work
279
+ const res2 = await postWebhook(app, payload);
280
+ const json2 = await res2.json();
281
+ expect(json2.deduplicated).toBe(true);
282
+ expect(runner.start).toHaveBeenCalledTimes(1);
283
+ });
284
+ });
285
+ });
286
+ //# sourceMappingURL=webhook-handler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-handler.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-handler.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhook.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhook.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createHmac } from "crypto";
3
+ import { verifyLinearSignature } from "../webhook/signature.js";
4
+ import { parseStateChange } from "../webhook/parser.js";
5
+ import stateChangePayload from "./fixtures/webhook-state-change.json" with { type: "json" };
6
+ import commentPayload from "./fixtures/webhook-comment.json" with { type: "json" };
7
+ // Helper to generate a valid HMAC signature
8
+ function sign(body, secret) {
9
+ return createHmac("sha256", secret).update(body).digest("hex");
10
+ }
11
+ // ---------------------------------------------------------------------------
12
+ // verifyLinearSignature
13
+ // ---------------------------------------------------------------------------
14
+ describe("verifyLinearSignature", () => {
15
+ const secret = "whsec_test_secret";
16
+ const body = JSON.stringify({ type: "Issue", action: "update" });
17
+ it("returns true for valid signature", () => {
18
+ const sig = sign(body, secret);
19
+ expect(verifyLinearSignature(body, sig, secret)).toBe(true);
20
+ });
21
+ it("returns false for invalid signature", () => {
22
+ expect(verifyLinearSignature(body, "bad-signature", secret)).toBe(false);
23
+ });
24
+ it("returns false for wrong secret", () => {
25
+ const sig = sign(body, "wrong-secret");
26
+ expect(verifyLinearSignature(body, sig, secret)).toBe(false);
27
+ });
28
+ });
29
+ // ---------------------------------------------------------------------------
30
+ // parseStateChange
31
+ // ---------------------------------------------------------------------------
32
+ describe("parseStateChange", () => {
33
+ it("extracts issue data from state change webhook", () => {
34
+ const result = parseStateChange(stateChangePayload);
35
+ expect(result).not.toBeNull();
36
+ expect(result.issue.id).toBe("issue-uuid-123");
37
+ expect(result.issue.identifier).toBe("LIN-42");
38
+ expect(result.issue.title).toBe("Add user search");
39
+ expect(result.issue.description).toBe("Implement search functionality");
40
+ expect(result.issue.priority).toBe(2);
41
+ expect(result.issue.teamId).toBe("team-frontend");
42
+ expect(result.issue.labels).toEqual([{ name: "auto-implement" }]);
43
+ expect(result.newState).toBe("Todo");
44
+ expect(result.previousState).toBeNull();
45
+ });
46
+ it("returns null for comment webhooks", () => {
47
+ expect(parseStateChange(commentPayload)).toBeNull();
48
+ });
49
+ it("returns null for non-update actions", () => {
50
+ const payload = { ...stateChangePayload, action: "create" };
51
+ expect(parseStateChange(payload)).toBeNull();
52
+ });
53
+ it("returns null when no stateId in updatedFrom", () => {
54
+ const payload = { ...stateChangePayload, updatedFrom: {} };
55
+ expect(parseStateChange(payload)).toBeNull();
56
+ });
57
+ });
58
+ //# sourceMappingURL=webhook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { type BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
+ import { drizzle as drizzlePg } from "drizzle-orm/postgres-js";
3
+ import { type SQL } from "drizzle-orm";
4
+ import * as schema from "./schema.js";
5
+ declare const fullSchema: typeof schema;
6
+ export type SqliteDb = BetterSQLite3Database<typeof fullSchema>;
7
+ /** Postgres DB typed against the unified schema via any to avoid pg-vs-sqlite column type mismatch. */
8
+ export type PgDb = ReturnType<typeof drizzlePg> & {
9
+ __driver: "postgres";
10
+ };
11
+ export type Db = SqliteDb | PgDb;
12
+ /**
13
+ * Escape-hatch type used when passing the Db union to Drizzle query
14
+ * builders that expect a single concrete driver type. Both SQLite and
15
+ * Postgres schemas have identical column structures, so the generated
16
+ * SQL is correct for either driver at runtime.
17
+ */
18
+ export type AnyDb = any;
19
+ /**
20
+ * Generates CREATE TABLE statements for both SQLite and Postgres from a single
21
+ * template, substituting driver-specific types for timestamps and booleans.
22
+ *
23
+ * This is the single source of truth for table DDL.
24
+ * Both drivers use this function — no separate DDL blocks.
25
+ */
26
+ export declare function getCreateTablesDDL(driver: "sqlite" | "postgres"): string;
27
+ /** Generate SQLite ALTER TABLE statements from the unified migration list. */
28
+ export declare function getMigrateSqlite(): string[];
29
+ /** Generate a Postgres DO $$ ... $$ migration block from the unified migration list. */
30
+ export declare function getMigratePostgres(): string;
31
+ export interface CreateDbOptions {
32
+ /** Auto-detected from connectionString if not provided. */
33
+ driver?: "sqlite" | "postgres";
34
+ /** File path / ":memory:" for SQLite, or connection URL for Postgres. */
35
+ connectionString: string;
36
+ }
37
+ export declare function createDb(options: CreateDbOptions): Promise<Db>;
38
+ /** Check if a Db instance is backed by Postgres. */
39
+ export declare function isPostgres(db: Db): boolean;
40
+ /**
41
+ * Returns a SQL expression that formats a crossTimestamp column as 'YYYY-MM-DD'.
42
+ *
43
+ * - SQLite: `date(col, 'unixepoch')` — column is INTEGER epoch seconds
44
+ * - Postgres: `to_char(col, 'YYYY-MM-DD')` — column is TIMESTAMPTZ
45
+ */
46
+ export declare function sqlDateGroup(db: Db, col: any): SQL<string>;
47
+ /**
48
+ * Returns a SQL expression that filters rows where a crossTimestamp column
49
+ * is at or after `days` days ago.
50
+ *
51
+ * - SQLite: `col >= unixepoch('now', '-N days')` — column is INTEGER epoch seconds
52
+ * - Postgres: `col >= now() - interval 'N days'` — column is TIMESTAMPTZ
53
+ */
54
+ export declare function sqlDaysAgoFilter(db: Db, col: any, days: number): SQL;
55
+ export {};
56
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAO,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAItC,QAAA,MAAM,UAAU,eAAS,CAAC;AAE1B,MAAM,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,UAAU,CAAC,CAAC;AAChE,uGAAuG;AACvG,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,GAAG;IAAE,QAAQ,EAAE,UAAU,CAAA;CAAE,CAAC;AAC3E,MAAM,MAAM,EAAE,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC;AAqCxB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAwFxE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAK3C;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,IAAI,MAAM,CAS3C;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC/B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAeD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,EAAE,CAAC,CA4BpE;AAED,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAI1D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAIpE"}
@@ -0,0 +1,201 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
3
+ import { drizzle as drizzlePg } from "drizzle-orm/postgres-js";
4
+ import { sql } from "drizzle-orm";
5
+ import postgres from "postgres";
6
+ import * as schema from "./schema.js";
7
+ import { _setSchemaDriver } from "./schema.js";
8
+ import { runMigrationsPostgres } from "./migrator.js";
9
+ const fullSchema = schema;
10
+ /**
11
+ * Single unified migration list.
12
+ * SQLite and Postgres migration statements are auto-generated from this list.
13
+ * To add a new column to an existing table: add ONE entry here.
14
+ */
15
+ const MIGRATION_COLUMNS = [
16
+ // BEC-87: auto-recover transient failures
17
+ { table: "pipeline_runs", column: "retry_count", sqliteType: "INTEGER NOT NULL DEFAULT 0", pgType: "INTEGER NOT NULL DEFAULT 0" },
18
+ // BEC-84: review-feedback support
19
+ { table: "pipeline_runs", column: "run_type", sqliteType: "TEXT NOT NULL DEFAULT 'standard'", pgType: "TEXT NOT NULL DEFAULT 'standard'" },
20
+ { table: "pipeline_runs", column: "parent_run_id", sqliteType: "TEXT", pgType: "TEXT" },
21
+ { table: "pipeline_runs", column: "feedback_context", sqliteType: "TEXT", pgType: "TEXT" },
22
+ // BEC-95: auto-merge audit log
23
+ { table: "pipeline_runs", column: "auto_merged", sqliteType: "INTEGER", pgType: "BOOLEAN" },
24
+ { table: "pipeline_runs", column: "auto_merge_reason", sqliteType: "TEXT", pgType: "TEXT" },
25
+ // BEC-94: auto-commit quality metric
26
+ { table: "pipeline_runs", column: "auto_committed", sqliteType: "INTEGER", pgType: "BOOLEAN" },
27
+ ];
28
+ /**
29
+ * Generates CREATE TABLE statements for both SQLite and Postgres from a single
30
+ * template, substituting driver-specific types for timestamps and booleans.
31
+ *
32
+ * This is the single source of truth for table DDL.
33
+ * Both drivers use this function — no separate DDL blocks.
34
+ */
35
+ export function getCreateTablesDDL(driver) {
36
+ const ts = driver === "postgres" ? "TIMESTAMPTZ" : "INTEGER";
37
+ const now = driver === "postgres" ? "now()" : "unixepoch()";
38
+ const bool = driver === "postgres" ? "BOOLEAN" : "INTEGER";
39
+ return `
40
+ CREATE TABLE IF NOT EXISTS pipeline_runs (
41
+ id TEXT PRIMARY KEY,
42
+ issue_id TEXT NOT NULL,
43
+ issue_title TEXT NOT NULL,
44
+ pipeline_key TEXT NOT NULL,
45
+ repo_url TEXT NOT NULL,
46
+ branch TEXT,
47
+ status TEXT NOT NULL,
48
+ started_at ${ts} NOT NULL DEFAULT (${now}),
49
+ completed_at ${ts},
50
+ pr_url TEXT,
51
+ total_input_tokens INTEGER NOT NULL DEFAULT 0,
52
+ total_output_tokens INTEGER NOT NULL DEFAULT 0,
53
+ error_message TEXT,
54
+ current_stage_index INTEGER,
55
+ resume_payload TEXT,
56
+ retry_count INTEGER NOT NULL DEFAULT 0,
57
+ run_type TEXT NOT NULL DEFAULT 'standard',
58
+ parent_run_id TEXT,
59
+ feedback_context TEXT,
60
+ auto_merged ${bool},
61
+ auto_merge_reason TEXT,
62
+ auto_committed ${bool}
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS stage_runs (
66
+ id TEXT PRIMARY KEY,
67
+ pipeline_run_id TEXT NOT NULL REFERENCES pipeline_runs(id),
68
+ stage TEXT NOT NULL,
69
+ status TEXT NOT NULL,
70
+ started_at ${ts} NOT NULL DEFAULT (${now}),
71
+ completed_at ${ts},
72
+ input_tokens INTEGER NOT NULL DEFAULT 0,
73
+ output_tokens INTEGER NOT NULL DEFAULT 0,
74
+ turns INTEGER NOT NULL DEFAULT 0,
75
+ handoff_artifact TEXT,
76
+ error_message TEXT
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS agent_logs (
80
+ id TEXT PRIMARY KEY,
81
+ stage_run_id TEXT NOT NULL REFERENCES stage_runs(id),
82
+ timestamp ${ts} NOT NULL DEFAULT (${now}),
83
+ type TEXT NOT NULL,
84
+ content TEXT NOT NULL
85
+ );
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_stage_runs_pipeline_run_id ON stage_runs(pipeline_run_id);
88
+ CREATE INDEX IF NOT EXISTS idx_agent_logs_stage_run_id ON agent_logs(stage_run_id);
89
+ CREATE INDEX IF NOT EXISTS idx_pipeline_runs_issue_id ON pipeline_runs(issue_id);
90
+ CREATE INDEX IF NOT EXISTS idx_pipeline_runs_status ON pipeline_runs(status);
91
+
92
+ CREATE TABLE IF NOT EXISTS pm_approvals (
93
+ id TEXT PRIMARY KEY,
94
+ issue_id TEXT NOT NULL,
95
+ action TEXT NOT NULL,
96
+ reason TEXT NOT NULL,
97
+ slack_message_ts TEXT NOT NULL,
98
+ status TEXT NOT NULL,
99
+ created_at ${ts} NOT NULL DEFAULT (${now}),
100
+ resolved_at ${ts}
101
+ );
102
+
103
+ CREATE INDEX IF NOT EXISTS idx_pm_approvals_status ON pm_approvals(status);
104
+
105
+ CREATE TABLE IF NOT EXISTS active_work (
106
+ id TEXT PRIMARY KEY,
107
+ run_id TEXT NOT NULL UNIQUE,
108
+ issue_id TEXT NOT NULL,
109
+ stage TEXT NOT NULL,
110
+ files_modified TEXT,
111
+ started_at ${ts} NOT NULL DEFAULT (${now}),
112
+ updated_at ${ts} NOT NULL DEFAULT (${now})
113
+ );
114
+
115
+ CREATE INDEX IF NOT EXISTS idx_active_work_issue_id ON active_work(issue_id);
116
+
117
+ CREATE TABLE IF NOT EXISTS webhook_dedup (
118
+ id TEXT PRIMARY KEY,
119
+ expires_at ${ts} NOT NULL
120
+ );
121
+ `;
122
+ }
123
+ /** Generate SQLite ALTER TABLE statements from the unified migration list. */
124
+ export function getMigrateSqlite() {
125
+ return MIGRATION_COLUMNS.map(({ table, column, sqliteType }) => `ALTER TABLE ${table} ADD COLUMN ${column} ${sqliteType}`);
126
+ }
127
+ /** Generate a Postgres DO $$ ... $$ migration block from the unified migration list. */
128
+ export function getMigratePostgres() {
129
+ const checks = MIGRATION_COLUMNS.map(({ table, column, pgType }) => ` IF NOT EXISTS (SELECT 1 FROM information_schema.columns\n` +
130
+ ` WHERE table_name = '${table}' AND column_name = '${column}') THEN\n` +
131
+ ` ALTER TABLE ${table} ADD COLUMN ${column} ${pgType};\n` +
132
+ ` END IF;`).join("\n");
133
+ return `DO $$\nBEGIN\n${checks}\nEND $$;`;
134
+ }
135
+ function detectDriver(connectionString) {
136
+ if (connectionString.startsWith("postgres://") ||
137
+ connectionString.startsWith("postgresql://")) {
138
+ return "postgres";
139
+ }
140
+ return "sqlite";
141
+ }
142
+ /** Tag used to identify the driver at runtime. */
143
+ const DB_DRIVER_TAG = Symbol("dbDriver");
144
+ export async function createDb(options) {
145
+ const driver = options.driver ?? detectDriver(options.connectionString);
146
+ if (driver === "postgres") {
147
+ // Set the schema driver so crossTimestamp.toDriver() serialises Dates as
148
+ // ISO-8601 strings, which Postgres accepts for TIMESTAMPTZ columns.
149
+ _setSchemaDriver("postgres");
150
+ const client = postgres(options.connectionString);
151
+ await client.unsafe(getCreateTablesDDL("postgres"));
152
+ await client.unsafe(getMigratePostgres());
153
+ await runMigrationsPostgres(client);
154
+ const db = drizzlePg(client, { schema: fullSchema });
155
+ db[DB_DRIVER_TAG] = "postgres";
156
+ return db;
157
+ }
158
+ // SQLite: serialise Dates as epoch-second integers (default).
159
+ _setSchemaDriver("sqlite");
160
+ const sqlite = new Database(options.connectionString);
161
+ sqlite.pragma("journal_mode = WAL");
162
+ sqlite.pragma("foreign_keys = ON");
163
+ sqlite.exec(getCreateTablesDDL("sqlite"));
164
+ for (const stmt of getMigrateSqlite()) {
165
+ try {
166
+ sqlite.exec(stmt);
167
+ }
168
+ catch { /* column already exists */ }
169
+ }
170
+ const db = drizzleSqlite(sqlite, { schema: fullSchema });
171
+ db[DB_DRIVER_TAG] = "sqlite";
172
+ return db;
173
+ }
174
+ /** Check if a Db instance is backed by Postgres. */
175
+ export function isPostgres(db) {
176
+ return db[DB_DRIVER_TAG] === "postgres";
177
+ }
178
+ /**
179
+ * Returns a SQL expression that formats a crossTimestamp column as 'YYYY-MM-DD'.
180
+ *
181
+ * - SQLite: `date(col, 'unixepoch')` — column is INTEGER epoch seconds
182
+ * - Postgres: `to_char(col, 'YYYY-MM-DD')` — column is TIMESTAMPTZ
183
+ */
184
+ export function sqlDateGroup(db, col) {
185
+ return isPostgres(db)
186
+ ? sql `to_char(${col}, 'YYYY-MM-DD')`
187
+ : sql `date(${col}, 'unixepoch')`;
188
+ }
189
+ /**
190
+ * Returns a SQL expression that filters rows where a crossTimestamp column
191
+ * is at or after `days` days ago.
192
+ *
193
+ * - SQLite: `col >= unixepoch('now', '-N days')` — column is INTEGER epoch seconds
194
+ * - Postgres: `col >= now() - interval 'N days'` — column is TIMESTAMPTZ
195
+ */
196
+ export function sqlDaysAgoFilter(db, col, days) {
197
+ return isPostgres(db)
198
+ ? sql `${col} >= now() - interval '${sql.raw(String(days))} days'`
199
+ : sql `${col} >= unixepoch('now', '-${sql.raw(String(days))} days')`;
200
+ }
201
+ //# sourceMappingURL=client.js.map