lastlight 0.1.8 → 0.2.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 (478) hide show
  1. package/README.md +265 -118
  2. package/agent-context/rules.md +62 -9
  3. package/agent-context/security.md +69 -0
  4. package/config/default.yaml +89 -0
  5. package/deploy/.env.production.example +20 -2
  6. package/deploy/entrypoint.sh +16 -17
  7. package/deploy/native/README.md +160 -0
  8. package/deploy/native/install.sh +110 -0
  9. package/deploy/native/lastlight.env.example +95 -0
  10. package/deploy/native/lastlight.service +50 -0
  11. package/deploy/sandbox-entrypoint.sh +59 -42
  12. package/dist/admin/auth.d.ts +1 -1
  13. package/dist/admin/auth.js +3 -1
  14. package/dist/admin/auth.js.map +1 -1
  15. package/dist/admin/chat-session-reader.d.ts +11 -14
  16. package/dist/admin/chat-session-reader.js +27 -51
  17. package/dist/admin/chat-session-reader.js.map +1 -1
  18. package/dist/admin/docker.d.ts +37 -0
  19. package/dist/admin/docker.js +86 -1
  20. package/dist/admin/docker.js.map +1 -1
  21. package/dist/admin/index.js +38 -3
  22. package/dist/admin/index.js.map +1 -1
  23. package/dist/admin/log-search.test.d.ts +1 -0
  24. package/dist/admin/log-search.test.js +78 -0
  25. package/dist/admin/log-search.test.js.map +1 -0
  26. package/dist/admin/routes.d.ts +52 -3
  27. package/dist/admin/routes.js +780 -63
  28. package/dist/admin/routes.js.map +1 -1
  29. package/dist/admin/routes.test.js +680 -14
  30. package/dist/admin/routes.test.js.map +1 -1
  31. package/dist/admin/server-logs.test.d.ts +1 -0
  32. package/dist/admin/server-logs.test.js +58 -0
  33. package/dist/admin/server-logs.test.js.map +1 -0
  34. package/dist/admin/sessions.d.ts +26 -39
  35. package/dist/admin/sessions.js +86 -310
  36. package/dist/admin/sessions.js.map +1 -1
  37. package/dist/admin/sessions.test.d.ts +1 -0
  38. package/dist/admin/sessions.test.js +78 -0
  39. package/dist/admin/sessions.test.js.map +1 -0
  40. package/dist/admin/version.d.ts +15 -0
  41. package/dist/admin/version.js +73 -0
  42. package/dist/admin/version.js.map +1 -0
  43. package/dist/admin/version.test.d.ts +1 -0
  44. package/dist/admin/version.test.js +71 -0
  45. package/dist/admin/version.test.js.map +1 -0
  46. package/dist/cli-config.d.ts +55 -0
  47. package/dist/cli-config.js +119 -0
  48. package/dist/cli-config.js.map +1 -0
  49. package/dist/cli-config.test.d.ts +1 -0
  50. package/dist/cli-config.test.js +92 -0
  51. package/dist/cli-config.test.js.map +1 -0
  52. package/dist/cli-format.d.ts +19 -0
  53. package/dist/cli-format.js +107 -0
  54. package/dist/cli-format.js.map +1 -0
  55. package/dist/cli-server.d.ts +73 -0
  56. package/dist/cli-server.js +347 -0
  57. package/dist/cli-server.js.map +1 -0
  58. package/dist/cli-server.test.d.ts +1 -0
  59. package/dist/cli-server.test.js +38 -0
  60. package/dist/cli-server.test.js.map +1 -0
  61. package/dist/cli-timeline.d.ts +35 -0
  62. package/dist/cli-timeline.js +373 -0
  63. package/dist/cli-timeline.js.map +1 -0
  64. package/dist/cli-timeline.test.d.ts +1 -0
  65. package/dist/cli-timeline.test.js +104 -0
  66. package/dist/cli-timeline.test.js.map +1 -0
  67. package/dist/cli.d.ts +0 -19
  68. package/dist/cli.js +910 -194
  69. package/dist/cli.js.map +1 -1
  70. package/dist/config-overlay.test.d.ts +1 -0
  71. package/dist/config-overlay.test.js +112 -0
  72. package/dist/config-overlay.test.js.map +1 -0
  73. package/dist/config-resolve.d.ts +28 -0
  74. package/dist/config-resolve.js +47 -0
  75. package/dist/config-resolve.js.map +1 -0
  76. package/dist/config-resolve.test.d.ts +1 -0
  77. package/dist/config-resolve.test.js +68 -0
  78. package/dist/config-resolve.test.js.map +1 -0
  79. package/dist/config.d.ts +62 -49
  80. package/dist/config.js +452 -76
  81. package/dist/config.js.map +1 -1
  82. package/dist/config.test.js +201 -32
  83. package/dist/config.test.js.map +1 -1
  84. package/dist/connectors/github-webhook.js +83 -5
  85. package/dist/connectors/github-webhook.js.map +1 -1
  86. package/dist/connectors/github-webhook.test.d.ts +1 -0
  87. package/dist/connectors/github-webhook.test.js +156 -0
  88. package/dist/connectors/github-webhook.test.js.map +1 -0
  89. package/dist/connectors/messaging/session-manager.d.ts +18 -0
  90. package/dist/connectors/messaging/session-manager.js +83 -2
  91. package/dist/connectors/messaging/session-manager.js.map +1 -1
  92. package/dist/connectors/messaging/session-manager.test.d.ts +1 -0
  93. package/dist/connectors/messaging/session-manager.test.js +144 -0
  94. package/dist/connectors/messaging/session-manager.test.js.map +1 -0
  95. package/dist/connectors/slack/connector.d.ts +9 -0
  96. package/dist/connectors/slack/connector.js +15 -0
  97. package/dist/connectors/slack/connector.js.map +1 -1
  98. package/dist/connectors/slack/mrkdwn.js +81 -0
  99. package/dist/connectors/slack/mrkdwn.js.map +1 -1
  100. package/dist/connectors/slack/mrkdwn.test.js +49 -0
  101. package/dist/connectors/slack/mrkdwn.test.js.map +1 -1
  102. package/dist/connectors/types.d.ts +1 -1
  103. package/dist/cron/fanout.d.ts +33 -0
  104. package/dist/cron/fanout.js +55 -0
  105. package/dist/cron/fanout.js.map +1 -0
  106. package/dist/cron/fanout.test.d.ts +1 -0
  107. package/dist/cron/fanout.test.js +73 -0
  108. package/dist/cron/fanout.test.js.map +1 -0
  109. package/dist/cron/jobs.d.ts +5 -0
  110. package/dist/cron/jobs.js +10 -3
  111. package/dist/cron/jobs.js.map +1 -1
  112. package/dist/cron/scheduler.d.ts +12 -0
  113. package/dist/cron/scheduler.js +27 -1
  114. package/dist/cron/scheduler.js.map +1 -1
  115. package/dist/engine/agent-executor.d.ts +158 -0
  116. package/dist/engine/agent-executor.js +1140 -0
  117. package/dist/engine/agent-executor.js.map +1 -0
  118. package/dist/engine/agent-executor.test.d.ts +1 -0
  119. package/dist/engine/agent-executor.test.js +395 -0
  120. package/dist/engine/agent-executor.test.js.map +1 -0
  121. package/dist/engine/chat-runner.d.ts +123 -0
  122. package/dist/engine/chat-runner.js +379 -0
  123. package/dist/engine/chat-runner.js.map +1 -0
  124. package/dist/engine/chat-runner.test.d.ts +1 -0
  125. package/dist/engine/chat-runner.test.js +104 -0
  126. package/dist/engine/chat-runner.test.js.map +1 -0
  127. package/dist/engine/chat-skills.d.ts +45 -0
  128. package/dist/engine/chat-skills.js +184 -0
  129. package/dist/engine/chat-skills.js.map +1 -0
  130. package/dist/engine/chat-skills.test.d.ts +1 -0
  131. package/dist/engine/chat-skills.test.js +20 -0
  132. package/dist/engine/chat-skills.test.js.map +1 -0
  133. package/dist/engine/chat.d.ts +20 -23
  134. package/dist/engine/chat.js +251 -155
  135. package/dist/engine/chat.js.map +1 -1
  136. package/dist/engine/chat.test.d.ts +1 -0
  137. package/dist/engine/chat.test.js +42 -0
  138. package/dist/engine/chat.test.js.map +1 -0
  139. package/dist/engine/classifier.d.ts +35 -2
  140. package/dist/engine/classifier.js +170 -31
  141. package/dist/engine/classifier.js.map +1 -1
  142. package/dist/engine/classifier.test.d.ts +1 -0
  143. package/dist/engine/classifier.test.js +115 -0
  144. package/dist/engine/classifier.test.js.map +1 -0
  145. package/dist/engine/dispatcher.d.ts +60 -0
  146. package/dist/engine/dispatcher.js +601 -0
  147. package/dist/engine/dispatcher.js.map +1 -0
  148. package/dist/engine/dispatcher.test.d.ts +1 -0
  149. package/dist/engine/dispatcher.test.js +511 -0
  150. package/dist/engine/dispatcher.test.js.map +1 -0
  151. package/dist/engine/event-shim.d.ts +117 -0
  152. package/dist/engine/event-shim.js +435 -0
  153. package/dist/engine/event-shim.js.map +1 -0
  154. package/dist/engine/event-shim.test.d.ts +1 -0
  155. package/dist/engine/event-shim.test.js +194 -0
  156. package/dist/engine/event-shim.test.js.map +1 -0
  157. package/dist/engine/git-auth.d.ts +13 -17
  158. package/dist/engine/git-auth.js +99 -42
  159. package/dist/engine/git-auth.js.map +1 -1
  160. package/dist/engine/git-auth.test.d.ts +1 -0
  161. package/dist/engine/git-auth.test.js +117 -0
  162. package/dist/engine/git-auth.test.js.map +1 -0
  163. package/dist/engine/github-app-client.d.ts +7 -0
  164. package/dist/engine/github-app-client.js +16 -0
  165. package/dist/engine/github-app-client.js.map +1 -0
  166. package/dist/engine/github-app-client.test.d.ts +1 -0
  167. package/dist/engine/github-app-client.test.js +44 -0
  168. package/dist/engine/github-app-client.test.js.map +1 -0
  169. package/dist/engine/github-tools.d.ts +12 -0
  170. package/dist/engine/github-tools.js +261 -0
  171. package/dist/engine/github-tools.js.map +1 -0
  172. package/dist/engine/github.d.ts +1027 -43
  173. package/dist/engine/github.js +153 -14
  174. package/dist/engine/github.js.map +1 -1
  175. package/dist/engine/llm.d.ts +47 -0
  176. package/dist/engine/llm.js +221 -0
  177. package/dist/engine/llm.js.map +1 -0
  178. package/dist/engine/llm.test.d.ts +1 -0
  179. package/dist/engine/llm.test.js +189 -0
  180. package/dist/engine/llm.test.js.map +1 -0
  181. package/dist/engine/profiles.d.ts +249 -0
  182. package/dist/engine/profiles.js +42 -0
  183. package/dist/engine/profiles.js.map +1 -0
  184. package/dist/engine/router.d.ts +12 -6
  185. package/dist/engine/router.js +339 -81
  186. package/dist/engine/router.js.map +1 -1
  187. package/dist/engine/router.test.js +428 -78
  188. package/dist/engine/router.test.js.map +1 -1
  189. package/dist/engine/screen.d.ts +41 -0
  190. package/dist/engine/screen.js +93 -0
  191. package/dist/engine/screen.js.map +1 -0
  192. package/dist/engine/screen.test.d.ts +1 -0
  193. package/dist/engine/screen.test.js +82 -0
  194. package/dist/engine/screen.test.js.map +1 -0
  195. package/dist/index.js +423 -437
  196. package/dist/index.js.map +1 -1
  197. package/dist/managed-repos.d.ts +7 -9
  198. package/dist/managed-repos.js +12 -15
  199. package/dist/managed-repos.js.map +1 -1
  200. package/dist/managed-repos.test.js +24 -19
  201. package/dist/managed-repos.test.js.map +1 -1
  202. package/dist/notify/index.d.ts +15 -0
  203. package/dist/notify/index.js +6 -0
  204. package/dist/notify/index.js.map +1 -0
  205. package/dist/notify/model.d.ts +57 -0
  206. package/dist/notify/model.js +89 -0
  207. package/dist/notify/model.js.map +1 -0
  208. package/dist/notify/model.test.d.ts +1 -0
  209. package/dist/notify/model.test.js +109 -0
  210. package/dist/notify/model.test.js.map +1 -0
  211. package/dist/notify/notifier.d.ts +17 -0
  212. package/dist/notify/notifier.js +87 -0
  213. package/dist/notify/notifier.js.map +1 -0
  214. package/dist/notify/notifier.test.d.ts +1 -0
  215. package/dist/notify/notifier.test.js +100 -0
  216. package/dist/notify/notifier.test.js.map +1 -0
  217. package/dist/notify/render.d.ts +21 -0
  218. package/dist/notify/render.js +59 -0
  219. package/dist/notify/render.js.map +1 -0
  220. package/dist/notify/render.test.d.ts +1 -0
  221. package/dist/notify/render.test.js +65 -0
  222. package/dist/notify/render.test.js.map +1 -0
  223. package/dist/notify/transports/github.d.ts +26 -0
  224. package/dist/notify/transports/github.js +26 -0
  225. package/dist/notify/transports/github.js.map +1 -0
  226. package/dist/notify/transports/slack.d.ts +26 -0
  227. package/dist/notify/transports/slack.js +28 -0
  228. package/dist/notify/transports/slack.js.map +1 -0
  229. package/dist/notify/transports.test.d.ts +1 -0
  230. package/dist/notify/transports.test.js +71 -0
  231. package/dist/notify/transports.test.js.map +1 -0
  232. package/dist/notify/types.d.ts +95 -0
  233. package/dist/notify/types.js +16 -0
  234. package/dist/notify/types.js.map +1 -0
  235. package/dist/sandbox/docker-compose.test.d.ts +1 -0
  236. package/dist/sandbox/docker-compose.test.js +128 -0
  237. package/dist/sandbox/docker-compose.test.js.map +1 -0
  238. package/dist/sandbox/docker.d.ts +97 -9
  239. package/dist/sandbox/docker.js +192 -41
  240. package/dist/sandbox/docker.js.map +1 -1
  241. package/dist/sandbox/docker.test.d.ts +1 -0
  242. package/dist/sandbox/docker.test.js +137 -0
  243. package/dist/sandbox/docker.test.js.map +1 -0
  244. package/dist/sandbox/egress-allowlist.d.ts +69 -0
  245. package/dist/sandbox/egress-allowlist.js +164 -0
  246. package/dist/sandbox/egress-allowlist.js.map +1 -0
  247. package/dist/sandbox/egress-allowlist.test.d.ts +1 -0
  248. package/dist/sandbox/egress-allowlist.test.js +65 -0
  249. package/dist/sandbox/egress-allowlist.test.js.map +1 -0
  250. package/dist/sandbox/egress-firewall-config.d.ts +127 -0
  251. package/dist/sandbox/egress-firewall-config.js +434 -0
  252. package/dist/sandbox/egress-firewall-config.js.map +1 -0
  253. package/dist/sandbox/egress-firewall-config.test.d.ts +1 -0
  254. package/dist/sandbox/egress-firewall-config.test.js +244 -0
  255. package/dist/sandbox/egress-firewall-config.test.js.map +1 -0
  256. package/dist/sandbox/images.d.ts +23 -0
  257. package/dist/sandbox/images.js +55 -0
  258. package/dist/sandbox/images.js.map +1 -0
  259. package/dist/sandbox/index.d.ts +82 -3
  260. package/dist/sandbox/index.js +223 -93
  261. package/dist/sandbox/index.js.map +1 -1
  262. package/dist/sandbox/index.test.d.ts +1 -0
  263. package/dist/sandbox/index.test.js +157 -0
  264. package/dist/sandbox/index.test.js.map +1 -0
  265. package/dist/session-log.d.ts +63 -0
  266. package/dist/session-log.js +290 -0
  267. package/dist/session-log.js.map +1 -0
  268. package/dist/session-log.test.d.ts +1 -0
  269. package/dist/session-log.test.js +85 -0
  270. package/dist/session-log.test.js.map +1 -0
  271. package/dist/setup.d.ts +31 -12
  272. package/dist/setup.js +516 -174
  273. package/dist/setup.js.map +1 -1
  274. package/dist/setup.test.js +138 -6
  275. package/dist/setup.test.js.map +1 -1
  276. package/dist/state/approval-store.d.ts +86 -0
  277. package/dist/state/approval-store.js +140 -0
  278. package/dist/state/approval-store.js.map +1 -0
  279. package/dist/state/build-assets.d.ts +50 -0
  280. package/dist/state/build-assets.js +154 -0
  281. package/dist/state/build-assets.js.map +1 -0
  282. package/dist/state/build-assets.test.d.ts +1 -0
  283. package/dist/state/build-assets.test.js +106 -0
  284. package/dist/state/build-assets.test.js.map +1 -0
  285. package/dist/state/db.d.ts +65 -333
  286. package/dist/state/db.js +112 -835
  287. package/dist/state/db.js.map +1 -1
  288. package/dist/state/db.test.js +127 -91
  289. package/dist/state/db.test.js.map +1 -1
  290. package/dist/state/execution-store.d.ts +257 -0
  291. package/dist/state/execution-store.js +527 -0
  292. package/dist/state/execution-store.js.map +1 -0
  293. package/dist/state/migrate.d.ts +15 -0
  294. package/dist/state/migrate.js +175 -0
  295. package/dist/state/migrate.js.map +1 -0
  296. package/dist/state/workflow-run-store.d.ts +195 -0
  297. package/dist/state/workflow-run-store.js +363 -0
  298. package/dist/state/workflow-run-store.js.map +1 -0
  299. package/dist/state/workflow-run-store.test.d.ts +1 -0
  300. package/dist/state/workflow-run-store.test.js +264 -0
  301. package/dist/state/workflow-run-store.test.js.map +1 -0
  302. package/dist/telemetry/index.d.ts +38 -0
  303. package/dist/telemetry/index.js +253 -0
  304. package/dist/telemetry/index.js.map +1 -0
  305. package/dist/telemetry/index.test.d.ts +1 -0
  306. package/dist/telemetry/index.test.js +73 -0
  307. package/dist/telemetry/index.test.js.map +1 -0
  308. package/dist/telemetry/pi-events.d.ts +12 -0
  309. package/dist/telemetry/pi-events.js +134 -0
  310. package/dist/telemetry/pi-events.js.map +1 -0
  311. package/dist/telemetry/pi-events.test.d.ts +1 -0
  312. package/dist/telemetry/pi-events.test.js +69 -0
  313. package/dist/telemetry/pi-events.test.js.map +1 -0
  314. package/dist/workflows/dag.d.ts +13 -1
  315. package/dist/workflows/dag.js +7 -3
  316. package/dist/workflows/dag.js.map +1 -1
  317. package/dist/workflows/dag.test.js +22 -0
  318. package/dist/workflows/dag.test.js.map +1 -1
  319. package/dist/workflows/golden-build.test.d.ts +1 -0
  320. package/dist/workflows/golden-build.test.js +45 -0
  321. package/dist/workflows/golden-build.test.js.map +1 -0
  322. package/dist/workflows/loader-overlay.test.d.ts +1 -0
  323. package/dist/workflows/loader-overlay.test.js +101 -0
  324. package/dist/workflows/loader-overlay.test.js.map +1 -0
  325. package/dist/workflows/loader.d.ts +36 -11
  326. package/dist/workflows/loader.js +311 -87
  327. package/dist/workflows/loader.js.map +1 -1
  328. package/dist/workflows/loader.test.js +114 -10
  329. package/dist/workflows/loader.test.js.map +1 -1
  330. package/dist/workflows/loop-eval.js +2 -0
  331. package/dist/workflows/loop-eval.js.map +1 -1
  332. package/dist/workflows/loop-eval.test.js +14 -0
  333. package/dist/workflows/loop-eval.test.js.map +1 -1
  334. package/dist/workflows/phase-executor.d.ts +145 -0
  335. package/dist/workflows/phase-executor.js +691 -0
  336. package/dist/workflows/phase-executor.js.map +1 -0
  337. package/dist/workflows/phase-executor.test.d.ts +1 -0
  338. package/dist/workflows/phase-executor.test.js +434 -0
  339. package/dist/workflows/phase-executor.test.js.map +1 -0
  340. package/dist/workflows/phase-ref.d.ts +44 -0
  341. package/dist/workflows/phase-ref.js +78 -0
  342. package/dist/workflows/phase-ref.js.map +1 -0
  343. package/dist/workflows/phase-ref.test.d.ts +1 -0
  344. package/dist/workflows/phase-ref.test.js +24 -0
  345. package/dist/workflows/phase-ref.test.js.map +1 -0
  346. package/dist/workflows/resume.d.ts +5 -11
  347. package/dist/workflows/resume.js +88 -7
  348. package/dist/workflows/resume.js.map +1 -1
  349. package/dist/workflows/runner.d.ts +34 -27
  350. package/dist/workflows/runner.js +252 -930
  351. package/dist/workflows/runner.js.map +1 -1
  352. package/dist/workflows/runner.test.js +346 -35
  353. package/dist/workflows/runner.test.js.map +1 -1
  354. package/dist/workflows/schema.d.ts +52 -10
  355. package/dist/workflows/schema.js +147 -7
  356. package/dist/workflows/schema.js.map +1 -1
  357. package/dist/workflows/simple.d.ts +67 -3
  358. package/dist/workflows/simple.js +248 -87
  359. package/dist/workflows/simple.js.map +1 -1
  360. package/dist/workflows/simple.test.d.ts +1 -0
  361. package/dist/workflows/simple.test.js +107 -0
  362. package/dist/workflows/simple.test.js.map +1 -0
  363. package/dist/workflows/templates.d.ts +29 -0
  364. package/dist/workflows/templates.js +42 -4
  365. package/dist/workflows/templates.js.map +1 -1
  366. package/dist/workflows/templates.test.js +103 -0
  367. package/dist/workflows/templates.test.js.map +1 -1
  368. package/dist/workflows/triggers.d.ts +21 -0
  369. package/dist/workflows/triggers.js +44 -0
  370. package/dist/workflows/triggers.js.map +1 -0
  371. package/dist/workflows/verdict.d.ts +19 -0
  372. package/dist/workflows/verdict.js +30 -0
  373. package/dist/workflows/verdict.js.map +1 -0
  374. package/dist/workflows/verdict.test.d.ts +1 -0
  375. package/dist/workflows/verdict.test.js +44 -0
  376. package/dist/workflows/verdict.test.js.map +1 -0
  377. package/dist/worktree/manager.d.ts +1 -1
  378. package/dist/worktree/manager.js +14 -14
  379. package/dist/worktree/manager.js.map +1 -1
  380. package/dist/worktree/manager.test.d.ts +1 -0
  381. package/dist/worktree/manager.test.js +87 -0
  382. package/dist/worktree/manager.test.js.map +1 -0
  383. package/docker-compose.yml +206 -5
  384. package/package.json +19 -7
  385. package/sandbox.Dockerfile +97 -18
  386. package/skills/README.md +40 -0
  387. package/skills/browser-qa/SKILL.md +193 -0
  388. package/skills/browser-qa/scripts/agent-browser.mjs +380 -0
  389. package/skills/building/SKILL.md +95 -0
  390. package/skills/chat/SKILL.md +30 -11
  391. package/skills/code-review/SKILL.md +59 -0
  392. package/skills/debug-production/SKILL.md +108 -0
  393. package/skills/demo/SKILL.md +194 -0
  394. package/skills/demo/scripts/compose-demo.sh +205 -0
  395. package/skills/issue-answer/SKILL.md +90 -0
  396. package/skills/issue-comment/SKILL.md +38 -24
  397. package/skills/issue-triage/SKILL.md +103 -37
  398. package/skills/issue-triage/references/AGENT-BRIEF.md +61 -0
  399. package/skills/pr-comment/SKILL.md +64 -0
  400. package/skills/pr-review/SKILL.md +65 -49
  401. package/skills/qa-test/SKILL.md +97 -0
  402. package/skills/repo-health/SKILL.md +48 -45
  403. package/skills/security-feedback/SKILL.md +173 -0
  404. package/skills/security-feedback/references/templates.md +77 -0
  405. package/skills/security-review/SKILL.md +213 -0
  406. package/skills/security-review/references/issue-format.md +302 -0
  407. package/skills/verify/SKILL.md +118 -0
  408. package/workflows/answer.yaml +37 -0
  409. package/workflows/build.yaml +32 -22
  410. package/workflows/cron-security.yaml +6 -0
  411. package/workflows/demo.yaml +55 -0
  412. package/workflows/explore.yaml +9 -0
  413. package/workflows/pr-comment.yaml +16 -0
  414. package/workflows/pr-fix.yaml +3 -0
  415. package/workflows/pr-review.yaml +4 -1
  416. package/workflows/prompts/answer.md +67 -0
  417. package/workflows/prompts/architect.md +24 -7
  418. package/workflows/prompts/demo.md +100 -0
  419. package/workflows/prompts/executor.md +29 -20
  420. package/workflows/prompts/explore-ask.md +51 -22
  421. package/workflows/prompts/explore-publish.md +2 -2
  422. package/workflows/prompts/explore-read.md +24 -10
  423. package/workflows/prompts/explore-synthesize.md +15 -8
  424. package/workflows/prompts/fix.md +9 -10
  425. package/workflows/prompts/guardrails.md +26 -8
  426. package/workflows/prompts/pr-fix.md +6 -7
  427. package/workflows/prompts/pr.md +11 -10
  428. package/workflows/prompts/qa-browser.md +95 -0
  429. package/workflows/prompts/qa-synth.md +44 -0
  430. package/workflows/prompts/qa-test.md +60 -0
  431. package/workflows/prompts/re-reviewer.md +8 -9
  432. package/workflows/prompts/reviewer.md +10 -9
  433. package/workflows/prompts/verify-browser.md +93 -0
  434. package/workflows/prompts/verify-synth.md +44 -0
  435. package/workflows/prompts/verify.md +63 -0
  436. package/workflows/qa-test.yaml +87 -0
  437. package/workflows/security-feedback.yaml +25 -0
  438. package/workflows/security-review.yaml +29 -0
  439. package/workflows/verify.yaml +85 -0
  440. package/deploy/mcp-config.tmpl.json +0 -14
  441. package/dist/cron/rate-limits.d.ts +0 -26
  442. package/dist/cron/rate-limits.js +0 -193
  443. package/dist/cron/rate-limits.js.map +0 -1
  444. package/dist/engine/executor.d.ts +0 -75
  445. package/dist/engine/executor.js +0 -292
  446. package/dist/engine/executor.js.map +0 -1
  447. package/mcp-github-app/package.json +0 -18
  448. package/mcp-github-app/src/auth.js +0 -66
  449. package/mcp-github-app/src/github.js +0 -371
  450. package/mcp-github-app/src/index.js +0 -534
  451. package/skills/devops/webhook-subscriptions/SKILL.md +0 -180
  452. package/skills/github/DESCRIPTION.md +0 -3
  453. package/skills/github/codebase-inspection/SKILL.md +0 -115
  454. package/skills/github/github-auth/SKILL.md +0 -54
  455. package/skills/github/github-auth/scripts/gh-env.sh +0 -66
  456. package/skills/github/github-code-review/SKILL.md +0 -480
  457. package/skills/github/github-code-review/references/review-output-template.md +0 -74
  458. package/skills/github/github-issues/SKILL.md +0 -369
  459. package/skills/github/github-issues/templates/bug-report.md +0 -35
  460. package/skills/github/github-issues/templates/feature-request.md +0 -31
  461. package/skills/github/github-pr-workflow/SKILL.md +0 -395
  462. package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +0 -183
  463. package/skills/github/github-pr-workflow/references/conventional-commits.md +0 -71
  464. package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +0 -35
  465. package/skills/github/github-pr-workflow/templates/pr-body-feature.md +0 -33
  466. package/skills/github/github-repo-management/SKILL.md +0 -515
  467. package/skills/github/github-repo-management/references/github-api-cheatsheet.md +0 -161
  468. package/skills/github-orchestrator/SKILL.md +0 -103
  469. package/skills/mcp/DESCRIPTION.md +0 -3
  470. package/skills/mcp/mcporter/SKILL.md +0 -122
  471. package/skills/mcp/native-mcp/SKILL.md +0 -356
  472. package/skills/software-development/architect/SKILL.md +0 -154
  473. package/skills/software-development/assure-guardrails/SKILL.md +0 -239
  474. package/skills/software-development/plan/SKILL.md +0 -64
  475. package/skills/software-development/requesting-code-review/SKILL.md +0 -291
  476. package/skills/software-development/subagent-driven-development/SKILL.md +0 -433
  477. package/skills/software-development/systematic-debugging/SKILL.md +0 -366
  478. package/skills/software-development/test-driven-development/SKILL.md +0 -342
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Aggregate root for a workflow run's lifecycle. Owns the `workflow_runs`
3
+ * table (including the `phase_history` JSON column) and the named, atomic
4
+ * transitions a run moves through: create → pause-for-approval →
5
+ * resume/finish, plus gate resolution and the socratic reply append.
6
+ *
7
+ * Carved out of the old `StateDb` god-class (issue #97). The deepening: every
8
+ * multi-step mutation that previously lived as an un-guarded read-modify-write
9
+ * chain in `index.ts` / `admin/routes.ts` / `phase-executor.ts` is now a
10
+ * single named operation wrapped in ONE `better-sqlite3` transaction — a throw
11
+ * partway through rolls the whole thing back, so a run can never be left
12
+ * marked `running` with its gate unresolved (the hazard the issue calls out).
13
+ *
14
+ * Constructed with an injected {@link ApprovalStore}: an approval has no life
15
+ * independent of its run, so the cross-table ops call the approval store's
16
+ * single-table writes from inside this store's transaction. Both stores MUST
17
+ * be built from the same `Database` — better-sqlite3 transactions are
18
+ * per-connection.
19
+ *
20
+ * The long-running re-dispatch (`dispatchWorkflow`, which spawns a Docker
21
+ * sandbox for minutes) is deliberately NOT part of any transaction here —
22
+ * better-sqlite3 transactions are synchronous. The atomic boundary is the DB
23
+ * mutations only; the caller dispatches after the transaction commits.
24
+ */
25
+ export class WorkflowRunStore {
26
+ db;
27
+ approvals;
28
+ constructor(db, deps) {
29
+ this.db = db;
30
+ this.approvals = deps.approvals;
31
+ }
32
+ // ── Plain single-mutation operations ───────────────────────────
33
+ /** Create a new workflow run record */
34
+ createRun(run) {
35
+ const now = new Date().toISOString();
36
+ this.db.prepare(`
37
+ INSERT INTO workflow_runs (id, workflow_name, trigger_id, repo, issue_number, current_phase, phase_history, status, context, scratch, started_at, updated_at)
38
+ VALUES (?, ?, ?, ?, ?, ?, '[]', ?, ?, ?, ?, ?)
39
+ `).run(run.id, run.workflowName, run.triggerId, run.repo ?? null, run.issueNumber ?? null, run.currentPhase, run.status, run.context ? JSON.stringify(run.context) : null, run.scratch ? JSON.stringify(run.scratch) : null, run.startedAt, now);
40
+ }
41
+ /**
42
+ * Top-level merge of `patch` into the workflow run's scratch state.
43
+ * Loop iterations can append to `scratch.socratic.qa` without clobbering
44
+ * other keys.
45
+ *
46
+ * Throws if `patch` is not JSON-serializable (e.g. it contains a BigInt) —
47
+ * which is exactly how the atomic ops prove their rollback: a poison patch
48
+ * makes `JSON.stringify` throw mid-transaction.
49
+ */
50
+ mergeScratch(id, patch) {
51
+ const now = new Date().toISOString();
52
+ const row = this.db
53
+ .prepare(`SELECT scratch FROM workflow_runs WHERE id = ?`)
54
+ .get(id);
55
+ if (!row)
56
+ return;
57
+ const current = row.scratch ? JSON.parse(row.scratch) : {};
58
+ const merged = { ...current, ...patch };
59
+ // Stringify BEFORE the UPDATE so a non-serializable patch throws without
60
+ // having mutated the row — keeps the op all-or-nothing even outside a txn.
61
+ const serialized = JSON.stringify(merged);
62
+ this.db
63
+ .prepare(`UPDATE workflow_runs SET scratch = ?, updated_at = ? WHERE id = ?`)
64
+ .run(serialized, now, id);
65
+ }
66
+ /**
67
+ * Update the current phase and append to phase history. This is the single
68
+ * seam through which all phase-history writes flow (issue #97 defers the
69
+ * full `phase_history`/`executions` reconciliation to a follow-up, but
70
+ * routes every write through here so that future change touches one method
71
+ * instead of ~10 call sites).
72
+ */
73
+ appendPhase(id, phase, entry) {
74
+ const now = new Date().toISOString();
75
+ const row = this.db.prepare(`SELECT phase_history FROM workflow_runs WHERE id = ?`).get(id);
76
+ if (!row)
77
+ return;
78
+ const history = JSON.parse(row.phase_history);
79
+ history.push(entry);
80
+ this.db.prepare(`
81
+ UPDATE workflow_runs SET current_phase = ?, phase_history = ?, updated_at = ? WHERE id = ?
82
+ `).run(phase, JSON.stringify(history), now, id);
83
+ }
84
+ /** Get a single workflow run by ID */
85
+ getRun(id) {
86
+ const row = this.db.prepare(`SELECT * FROM workflow_runs WHERE id = ?`).get(id);
87
+ return row ? this.deserialize(row) : null;
88
+ }
89
+ /** Find the most recent active (running or paused) workflow run for a trigger */
90
+ getByTrigger(triggerId) {
91
+ const row = this.db.prepare(`
92
+ SELECT * FROM workflow_runs
93
+ WHERE trigger_id = ? AND status IN ('running', 'paused')
94
+ ORDER BY started_at DESC
95
+ LIMIT 1
96
+ `).get(triggerId);
97
+ return row ? this.deserialize(row) : null;
98
+ }
99
+ /** List all active (running or paused) workflow runs */
100
+ listActive() {
101
+ const rows = this.db.prepare(`
102
+ SELECT * FROM workflow_runs WHERE status IN ('running', 'paused') ORDER BY started_at DESC
103
+ `).all();
104
+ return rows.map((r) => this.deserialize(r));
105
+ }
106
+ /** List recent workflow runs, ordered by start time descending */
107
+ listRecent(limit = 20) {
108
+ const rows = this.db.prepare(`
109
+ SELECT * FROM workflow_runs ORDER BY started_at DESC LIMIT ?
110
+ `).all(limit);
111
+ return rows.map((r) => this.deserialize(r));
112
+ }
113
+ /**
114
+ * List workflow runs with pagination + filters. Returns the page slice
115
+ * AND the post-filter total so the dashboard knows how many remain.
116
+ *
117
+ * - sinceIso filters `started_at >= sinceIso` (used for the header date range).
118
+ * - workflowName filters by exact `workflow_name`.
119
+ * - statuses filters to one of the workflow statuses (`running`, `paused`,
120
+ * `succeeded`, `failed`, `cancelled`). Used for the dashboard's "live"
121
+ * filter, which maps to ['running','paused'].
122
+ */
123
+ list(opts = {}) {
124
+ const limit = opts.limit ?? 20;
125
+ const offset = opts.offset ?? 0;
126
+ const where = [];
127
+ const params = [];
128
+ if (opts.sinceIso) {
129
+ where.push("started_at >= ?");
130
+ params.push(opts.sinceIso);
131
+ }
132
+ if (opts.workflowName) {
133
+ where.push("workflow_name = ?");
134
+ params.push(opts.workflowName);
135
+ }
136
+ if (opts.statuses && opts.statuses.length > 0) {
137
+ where.push(`status IN (${opts.statuses.map(() => "?").join(",")})`);
138
+ params.push(...opts.statuses);
139
+ }
140
+ const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
141
+ const total = this.db
142
+ .prepare(`SELECT COUNT(*) as c FROM workflow_runs ${whereClause}`)
143
+ .get(...params).c;
144
+ // Explicit column list — the heavy JSON blobs (`context`, `scratch`)
145
+ // can each be many MB on long-running build runs and
146
+ // the dashboard list view doesn't read them. Returning them turned a
147
+ // 20-row page into a 14MB payload. The single-row endpoint
148
+ // (`getRun`) still uses `SELECT *` so the detail panel keeps
149
+ // the full row when the user picks one.
150
+ const rows = this.db
151
+ .prepare(`SELECT
152
+ id, workflow_name, trigger_id, repo, issue_number,
153
+ current_phase, phase_history, status,
154
+ restart_count, started_at, updated_at, finished_at
155
+ FROM workflow_runs
156
+ ${whereClause}
157
+ ORDER BY started_at DESC
158
+ LIMIT ? OFFSET ?`)
159
+ .all(...params, limit, offset);
160
+ return {
161
+ runs: rows.map((r) => this.deserialize(r)),
162
+ total,
163
+ };
164
+ }
165
+ /** Distinct workflow_name values, sorted alphabetically. */
166
+ distinctNames() {
167
+ const rows = this.db
168
+ .prepare(`SELECT DISTINCT workflow_name FROM workflow_runs ORDER BY workflow_name ASC`)
169
+ .all();
170
+ return rows.map((r) => r.workflow_name);
171
+ }
172
+ /**
173
+ * Mark a workflow run as finished. Plain status flip when called with just
174
+ * `{ error }`; when a `terminalMarker` is supplied (the runner's
175
+ * `on_success.set_phase`), the phase-history append and the status flip
176
+ * happen in ONE transaction so the dashboard never sees the terminal phase
177
+ * without the finished status, or vice versa.
178
+ */
179
+ finishRun(id, status, opts = {}) {
180
+ const apply = () => {
181
+ if (opts.terminalMarker) {
182
+ this.appendPhase(id, opts.terminalMarker.phase, {
183
+ phase: opts.terminalMarker.phase,
184
+ timestamp: new Date().toISOString(),
185
+ success: true,
186
+ summary: opts.terminalMarker.summary,
187
+ });
188
+ }
189
+ this.flipFinished(id, status, opts.error);
190
+ };
191
+ if (opts.terminalMarker) {
192
+ this.db.transaction(apply)();
193
+ }
194
+ else {
195
+ apply();
196
+ }
197
+ }
198
+ flipFinished(id, status, error) {
199
+ const now = new Date().toISOString();
200
+ this.db.prepare(`
201
+ UPDATE workflow_runs SET status = ?, finished_at = ?, updated_at = ?, context = CASE
202
+ WHEN ? IS NOT NULL THEN json_patch(COALESCE(context, '{}'), json_object('error', ?))
203
+ ELSE context
204
+ END WHERE id = ?
205
+ `).run(status, now, now, error ?? null, error ?? null, id);
206
+ }
207
+ /** Cancel a workflow run */
208
+ cancelRun(id) {
209
+ const now = new Date().toISOString();
210
+ this.db.prepare(`
211
+ UPDATE workflow_runs SET status = 'cancelled', updated_at = ?, finished_at = ? WHERE id = ?
212
+ `).run(now, now, id);
213
+ }
214
+ /** Pause a workflow run (waiting for approval) */
215
+ setPaused(id) {
216
+ const now = new Date().toISOString();
217
+ this.db.prepare(`
218
+ UPDATE workflow_runs SET status = 'paused', updated_at = ? WHERE id = ?
219
+ `).run(now, id);
220
+ }
221
+ /** Resume a paused workflow run (set back to running) */
222
+ setRunning(id) {
223
+ const now = new Date().toISOString();
224
+ this.db.prepare(`
225
+ UPDATE workflow_runs SET status = 'running', updated_at = ? WHERE id = ?
226
+ `).run(now, id);
227
+ }
228
+ /**
229
+ * Increment the restart counter and return the new value. Used by
230
+ * `resumeOrphanedWorkflows` to enforce a retry budget so a run that
231
+ * crashes the host (agent OOM, etc.) eventually self-terminates instead
232
+ * of being re-dispatched on every boot.
233
+ */
234
+ incrementRestartCount(id) {
235
+ const now = new Date().toISOString();
236
+ this.db.prepare(`
237
+ UPDATE workflow_runs
238
+ SET restart_count = COALESCE(restart_count, 0) + 1, updated_at = ?
239
+ WHERE id = ?
240
+ `).run(now, id);
241
+ const row = this.db.prepare(`SELECT restart_count FROM workflow_runs WHERE id = ?`)
242
+ .get(id);
243
+ return row?.restart_count ?? 0;
244
+ }
245
+ deserialize(row) {
246
+ return {
247
+ id: row.id,
248
+ workflowName: row.workflow_name,
249
+ triggerId: row.trigger_id,
250
+ repo: row.repo,
251
+ issueNumber: row.issue_number,
252
+ currentPhase: row.current_phase,
253
+ phaseHistory: JSON.parse(row.phase_history),
254
+ status: row.status,
255
+ context: row.context ? JSON.parse(row.context) : undefined,
256
+ scratch: row.scratch ? JSON.parse(row.scratch) : undefined,
257
+ restartCount: typeof row.restart_count === "number" ? row.restart_count : 0,
258
+ startedAt: row.started_at,
259
+ updatedAt: row.updated_at,
260
+ finishedAt: row.finished_at,
261
+ };
262
+ }
263
+ // ── Named atomic lifecycle operations ──────────────────────────
264
+ //
265
+ // Each wraps ONE better-sqlite3 transaction. The long-running re-dispatch
266
+ // (`dispatchWorkflow`) stays OUT of these — the caller dispatches after the
267
+ // transaction commits.
268
+ /**
269
+ * Resolve a socratic reply gate and resume the run, atomically: the reply
270
+ * gate flips `pending → approved` (recording the reply text), the caller's
271
+ * scratch patch merges in, and the run goes back to `running` — all in one
272
+ * transaction. A throw anywhere rolls the whole thing back, so the run can
273
+ * never be left `running` with its gate unresolved or its reply lost (the
274
+ * hazard issue #97 calls out).
275
+ *
276
+ * The reply-gate resolve is guarded by `kind = 'reply' AND status = 'pending'`
277
+ * (see {@link ApprovalStore.resolveReplyGate}); if it changes zero rows —
278
+ * because a racing reply already resolved the gate — this throws and the
279
+ * transaction aborts, which is the double-reply concurrency guard.
280
+ *
281
+ * The caller (`index.ts`) computes the explore-domain `scratch.socratic.qa`
282
+ * patch and passes it in; the generic store stays ignorant of that shape.
283
+ */
284
+ /**
285
+ * Pause a run for an approval gate, atomically: create the pending approval,
286
+ * optionally merge a scratch patch (loops persist their iteration state
287
+ * here), append the `waiting_approval` phase marker, and flip the run to
288
+ * `paused` — all in one transaction. Covers the standard post-phase gate,
289
+ * the reviewer-loop gate, and the interactive generic-loop gate.
290
+ *
291
+ * The marker is appended BEFORE the approval insert so that an insert
292
+ * failure provably rolls back the phase-history write (the injected
293
+ * ApprovalStore participates in the same transaction); ordering is invisible
294
+ * outside the transaction.
295
+ */
296
+ pauseForApproval(runId, approval, marker, scratchPatch) {
297
+ this.db.transaction(() => {
298
+ this.appendPhase(runId, marker.phase, {
299
+ phase: marker.phase,
300
+ timestamp: new Date().toISOString(),
301
+ success: true,
302
+ summary: marker.summary,
303
+ });
304
+ if (scratchPatch)
305
+ this.mergeScratch(runId, scratchPatch);
306
+ this.approvals.create(approval);
307
+ this.setPaused(runId);
308
+ })();
309
+ }
310
+ /**
311
+ * Approve a gate and resume its run, atomically. Reads the approval to find
312
+ * its run, records the `approved` response, and flips the run back to
313
+ * `running` in one transaction; returns the run so the caller can dispatch.
314
+ * Used by the GitHub/Slack path (`index.ts`), which validates the dispatch
315
+ * target BEFORE calling this so the flip to `running` is always followed by a
316
+ * dispatch. The dashboard path does NOT use this — it can't prove a dispatch
317
+ * will follow before responding, so it records the approval and lets
318
+ * `resumeWorkflow` flip the run only as part of an actual dispatch.
319
+ */
320
+ resolveGateAndResume(approvalId, responder) {
321
+ return this.db.transaction(() => {
322
+ const approval = this.approvals.getById(approvalId);
323
+ if (!approval)
324
+ throw new Error(`approval ${approvalId} not found`);
325
+ const changed = this.approvals.respond(approvalId, "approved", responder);
326
+ if (changed !== 1) {
327
+ throw new Error(`approval ${approvalId} is not pending (already resolved?)`);
328
+ }
329
+ this.setRunning(approval.workflowRunId);
330
+ return this.getRun(approval.workflowRunId);
331
+ })();
332
+ }
333
+ /**
334
+ * Reject a gate and fail its run, atomically: record the `rejected` response
335
+ * (with reason) and flip the run to `failed` in one transaction. Returns the
336
+ * run (or null if the approval's run was already gone).
337
+ */
338
+ resolveGateAndFail(approvalId, responder, reason) {
339
+ return this.db.transaction(() => {
340
+ const approval = this.approvals.getById(approvalId);
341
+ if (!approval)
342
+ throw new Error(`approval ${approvalId} not found`);
343
+ const changed = this.approvals.respond(approvalId, "rejected", responder, reason);
344
+ if (changed !== 1) {
345
+ throw new Error(`approval ${approvalId} is not pending (already resolved?)`);
346
+ }
347
+ this.flipFinished(approval.workflowRunId, "failed", `Rejected: ${reason || "no reason given"}`);
348
+ return this.getRun(approval.workflowRunId);
349
+ })();
350
+ }
351
+ resolveReplyGateAndResume(runId, approvalId, replyText, responder, scratchPatch) {
352
+ return this.db.transaction(() => {
353
+ const changed = this.approvals.resolveReplyGate(approvalId, replyText, responder);
354
+ if (changed !== 1) {
355
+ throw new Error(`reply gate ${approvalId} is not pending (already resolved?)`);
356
+ }
357
+ this.mergeScratch(runId, scratchPatch);
358
+ this.setRunning(runId);
359
+ return this.getRun(runId);
360
+ })();
361
+ }
362
+ }
363
+ //# sourceMappingURL=workflow-run-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-run-store.js","sourceRoot":"","sources":["../../src/state/workflow-run-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,264 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import Database from "better-sqlite3";
3
+ import { randomUUID } from "crypto";
4
+ import { StateDb } from "./db.js";
5
+ import { migrate } from "./migrate.js";
6
+ import { ApprovalStore } from "./approval-store.js";
7
+ import { WorkflowRunStore } from "./workflow-run-store.js";
8
+ let db;
9
+ beforeEach(() => {
10
+ db = new StateDb(":memory:");
11
+ });
12
+ afterEach(() => {
13
+ db.close();
14
+ });
15
+ /** Create a fresh run and return its id. */
16
+ function makeRun(overrides = {}) {
17
+ const id = randomUUID();
18
+ db.runs.createRun({
19
+ id,
20
+ workflowName: "explore",
21
+ triggerId: `slack:${id}`,
22
+ currentPhase: "socratic",
23
+ status: "running",
24
+ startedAt: new Date().toISOString(),
25
+ ...overrides,
26
+ });
27
+ return id;
28
+ }
29
+ describe("pauseForApproval", () => {
30
+ it("creates the approval, appends the marker, merges scratch, and pauses — in one step", () => {
31
+ const runId = makeRun();
32
+ const approvalId = randomUUID();
33
+ db.runs.pauseForApproval(runId, {
34
+ id: approvalId,
35
+ workflowRunId: runId,
36
+ gate: "post_architect",
37
+ summary: "Plan ready",
38
+ kind: "approve",
39
+ artifact: "architect-plan.md",
40
+ createdAt: new Date().toISOString(),
41
+ }, { phase: "waiting_approval", summary: "Waiting for approval: post_architect" }, { socratic: { iteration: 2 } });
42
+ const run = db.runs.getRun(runId);
43
+ expect(run.status).toBe("paused");
44
+ expect(run.currentPhase).toBe("waiting_approval");
45
+ expect(run.phaseHistory.at(-1).phase).toBe("waiting_approval");
46
+ expect(run.scratch).toEqual({ socratic: { iteration: 2 } });
47
+ const approval = db.approvals.getById(approvalId);
48
+ expect(approval.status).toBe("pending");
49
+ expect(approval.gate).toBe("post_architect");
50
+ expect(approval.artifact).toBe("architect-plan.md");
51
+ });
52
+ it("rolls back the phase-history append when the approval store throws (injected collaborator)", () => {
53
+ // Build a store stack on a raw shared connection so we can inject a fake
54
+ // ApprovalStore whose create() throws as the last txn step.
55
+ const raw = new Database(":memory:");
56
+ migrate(raw);
57
+ const realApprovals = new ApprovalStore(raw);
58
+ const seed = new WorkflowRunStore(raw, { approvals: realApprovals });
59
+ const runId = randomUUID();
60
+ seed.createRun({
61
+ id: runId,
62
+ workflowName: "build",
63
+ triggerId: "owner/repo#1",
64
+ currentPhase: "architect",
65
+ status: "running",
66
+ startedAt: new Date().toISOString(),
67
+ });
68
+ const throwingApprovals = {
69
+ create() {
70
+ throw new Error("approval insert failed");
71
+ },
72
+ };
73
+ const runs = new WorkflowRunStore(raw, { approvals: throwingApprovals });
74
+ expect(() => runs.pauseForApproval(runId, {
75
+ id: randomUUID(),
76
+ workflowRunId: runId,
77
+ gate: "post_architect",
78
+ summary: "Plan ready",
79
+ createdAt: new Date().toISOString(),
80
+ }, { phase: "waiting_approval" })).toThrow("approval insert failed");
81
+ // The appendPhase that ran before the throwing collaborator must be rolled
82
+ // back: the run is still running with empty phase history.
83
+ const run = seed.getRun(runId);
84
+ expect(run.status).toBe("running");
85
+ expect(run.phaseHistory).toEqual([]);
86
+ raw.close();
87
+ });
88
+ });
89
+ describe("finishRun with a terminal marker", () => {
90
+ it("appends the on_success phase marker and flips status in one step", () => {
91
+ const runId = makeRun();
92
+ db.runs.finishRun(runId, "succeeded", { terminalMarker: { phase: "done", summary: "PR #5" } });
93
+ const run = db.runs.getRun(runId);
94
+ expect(run.status).toBe("succeeded");
95
+ expect(run.finishedAt).toBeTruthy();
96
+ expect(run.currentPhase).toBe("done");
97
+ const last = run.phaseHistory.at(-1);
98
+ expect(last.phase).toBe("done");
99
+ expect(last.summary).toBe("PR #5");
100
+ expect(last.success).toBe(true);
101
+ });
102
+ it("plain finish (no marker) flips status without touching phase history", () => {
103
+ const runId = makeRun();
104
+ db.runs.finishRun(runId, "failed", { error: "boom" });
105
+ const run = db.runs.getRun(runId);
106
+ expect(run.status).toBe("failed");
107
+ expect(run.phaseHistory).toEqual([]);
108
+ expect(run.context).toEqual({ error: "boom" });
109
+ });
110
+ });
111
+ describe("resolveGateAndResume — approve path", () => {
112
+ it("approves the gate and sets the run running, returning the run", () => {
113
+ const runId = makeRun({ status: "paused" });
114
+ const approvalId = randomUUID();
115
+ db.approvals.create({
116
+ id: approvalId,
117
+ workflowRunId: runId,
118
+ gate: "post_architect",
119
+ summary: "Plan ready",
120
+ createdAt: new Date().toISOString(),
121
+ });
122
+ const run = db.runs.resolveGateAndResume(approvalId, "bob");
123
+ expect(run.id).toBe(runId);
124
+ expect(run.status).toBe("running");
125
+ const approval = db.approvals.getById(approvalId);
126
+ expect(approval.status).toBe("approved");
127
+ expect(approval.respondedBy).toBe("bob");
128
+ });
129
+ it("throws on an unknown approval id", () => {
130
+ expect(() => db.runs.resolveGateAndResume("no-such-approval", "bob")).toThrow();
131
+ });
132
+ it("does not resume a stale approval that was already resolved", () => {
133
+ const runId = makeRun({ status: "paused" });
134
+ const approvalId = randomUUID();
135
+ db.approvals.create({
136
+ id: approvalId,
137
+ workflowRunId: runId,
138
+ gate: "post_architect",
139
+ summary: "Plan ready",
140
+ createdAt: new Date().toISOString(),
141
+ });
142
+ db.runs.resolveGateAndFail(approvalId, "carol", "plan incomplete");
143
+ expect(() => db.runs.resolveGateAndResume(approvalId, "bob")).toThrow("not pending");
144
+ expect(db.runs.getRun(runId).status).toBe("failed");
145
+ expect(db.approvals.getById(approvalId).status).toBe("rejected");
146
+ });
147
+ });
148
+ describe("resolveGateAndFail — reject path", () => {
149
+ it("does not fail a stale approval that was already approved", () => {
150
+ const runId = makeRun({ status: "paused" });
151
+ const approvalId = randomUUID();
152
+ db.approvals.create({
153
+ id: approvalId,
154
+ workflowRunId: runId,
155
+ gate: "post_architect",
156
+ summary: "Plan ready",
157
+ createdAt: new Date().toISOString(),
158
+ });
159
+ db.runs.resolveGateAndResume(approvalId, "bob");
160
+ expect(() => db.runs.resolveGateAndFail(approvalId, "carol", "too late")).toThrow("not pending");
161
+ expect(db.runs.getRun(runId).status).toBe("running");
162
+ expect(db.approvals.getById(approvalId).status).toBe("approved");
163
+ });
164
+ it("rejects the gate and fails the run", () => {
165
+ const runId = makeRun({ status: "paused" });
166
+ const approvalId = randomUUID();
167
+ db.approvals.create({
168
+ id: approvalId,
169
+ workflowRunId: runId,
170
+ gate: "post_architect",
171
+ summary: "Plan ready",
172
+ createdAt: new Date().toISOString(),
173
+ });
174
+ const run = db.runs.resolveGateAndFail(approvalId, "carol", "plan incomplete");
175
+ expect(run.id).toBe(runId);
176
+ expect(run.status).toBe("failed");
177
+ const approval = db.approvals.getById(approvalId);
178
+ expect(approval.status).toBe("rejected");
179
+ expect(approval.respondedBy).toBe("carol");
180
+ expect(approval.response).toBe("plan incomplete");
181
+ expect(db.runs.getRun(runId).context).toEqual({ error: "Rejected: plan incomplete" });
182
+ });
183
+ it("records a fallback error annotation when rejection has no reason", () => {
184
+ const runId = makeRun({ status: "paused" });
185
+ const approvalId = randomUUID();
186
+ db.approvals.create({
187
+ id: approvalId,
188
+ workflowRunId: runId,
189
+ gate: "post_architect",
190
+ summary: "Plan ready",
191
+ createdAt: new Date().toISOString(),
192
+ });
193
+ const run = db.runs.resolveGateAndFail(approvalId, "carol");
194
+ expect(run.status).toBe("failed");
195
+ expect(run.context).toEqual({ error: "Rejected: no reason given" });
196
+ });
197
+ });
198
+ describe("resolveReplyGateAndResume — the socratic explore-reply atomic op", () => {
199
+ it("resolves the reply gate, merges scratch, and sets the run running in one step", () => {
200
+ const runId = makeRun({ status: "paused" });
201
+ const approvalId = randomUUID();
202
+ db.approvals.create({
203
+ id: approvalId,
204
+ workflowRunId: runId,
205
+ gate: "socratic_1",
206
+ summary: "What problem are we solving?",
207
+ kind: "reply",
208
+ createdAt: new Date().toISOString(),
209
+ });
210
+ const scratchPatch = { socratic: { qa: [{ question: "Q1", answer: "A1" }] } };
211
+ const run = db.runs.resolveReplyGateAndResume(runId, approvalId, "A1", "alice", scratchPatch);
212
+ // Returns the resumed run
213
+ expect(run).not.toBeNull();
214
+ expect(run.id).toBe(runId);
215
+ expect(run.status).toBe("running");
216
+ // Gate resolved with the reply text recorded
217
+ const approval = db.approvals.getById(approvalId);
218
+ expect(approval.status).toBe("approved");
219
+ expect(approval.response).toBe("A1");
220
+ expect(approval.respondedBy).toBe("alice");
221
+ // Scratch merged
222
+ const reloaded = db.runs.getRun(runId);
223
+ expect(reloaded.status).toBe("running");
224
+ expect(reloaded.scratch).toEqual(scratchPatch);
225
+ });
226
+ it("double-reply concurrency guard: a second resolve throws and leaves the run paused", () => {
227
+ const runId = makeRun({ status: "paused" });
228
+ const approvalId = randomUUID();
229
+ db.approvals.create({
230
+ id: approvalId,
231
+ workflowRunId: runId,
232
+ gate: "socratic_1",
233
+ summary: "Q",
234
+ kind: "reply",
235
+ createdAt: new Date().toISOString(),
236
+ });
237
+ db.runs.resolveReplyGateAndResume(runId, approvalId, "first", "alice", {});
238
+ db.runs.setPaused(runId); // pretend it paused again on the next iteration
239
+ // A racing second reply against the already-resolved gate must not resume.
240
+ expect(() => db.runs.resolveReplyGateAndResume(runId, approvalId, "second", "bob", {})).toThrow();
241
+ expect(db.runs.getRun(runId).status).toBe("paused");
242
+ });
243
+ it("rolls back the gate resolution when the scratch patch is not serializable", () => {
244
+ const runId = makeRun({ status: "paused" });
245
+ const approvalId = randomUUID();
246
+ db.approvals.create({
247
+ id: approvalId,
248
+ workflowRunId: runId,
249
+ gate: "socratic_1",
250
+ summary: "Q",
251
+ kind: "reply",
252
+ createdAt: new Date().toISOString(),
253
+ });
254
+ // A BigInt makes JSON.stringify throw mid-transaction (natural poison input).
255
+ const poison = { socratic: { n: BigInt(1) } };
256
+ expect(() => db.runs.resolveReplyGateAndResume(runId, approvalId, "A1", "alice", poison)).toThrow();
257
+ // Nothing applied: gate still pending, run still paused, scratch untouched.
258
+ expect(db.approvals.getById(approvalId).status).toBe("pending");
259
+ const reloaded = db.runs.getRun(runId);
260
+ expect(reloaded.status).toBe("paused");
261
+ expect(reloaded.scratch).toBeUndefined();
262
+ });
263
+ });
264
+ //# sourceMappingURL=workflow-run-store.test.js.map