@wascartech/devbot 0.0.0-stage → 2.1.1

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 (376) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +638 -2
  3. package/dist/access-grant-store.js +99 -0
  4. package/dist/access-grant-store.js.map +1 -0
  5. package/dist/access-resolve.js +11 -0
  6. package/dist/access-resolve.js.map +1 -0
  7. package/dist/access-rule-store.js +99 -0
  8. package/dist/access-rule-store.js.map +1 -0
  9. package/dist/admin/access-routes.js +73 -0
  10. package/dist/admin/access-routes.js.map +1 -0
  11. package/dist/admin/access-rule-routes.js +107 -0
  12. package/dist/admin/access-rule-routes.js.map +1 -0
  13. package/dist/admin/custom-repos-routes.js +93 -0
  14. package/dist/admin/custom-repos-routes.js.map +1 -0
  15. package/dist/admin/env-routes.js +74 -0
  16. package/dist/admin/env-routes.js.map +1 -0
  17. package/dist/admin/global-settings-routes.js +254 -0
  18. package/dist/admin/global-settings-routes.js.map +1 -0
  19. package/dist/admin/mcp-cleanup.js +12 -0
  20. package/dist/admin/mcp-cleanup.js.map +1 -0
  21. package/dist/admin/mcp-routes.js +169 -0
  22. package/dist/admin/mcp-routes.js.map +1 -0
  23. package/dist/admin/query-params.js +80 -0
  24. package/dist/admin/query-params.js.map +1 -0
  25. package/dist/admin/routes.js +150 -0
  26. package/dist/admin/routes.js.map +1 -0
  27. package/dist/admin/run-history-routes.js +44 -0
  28. package/dist/admin/run-history-routes.js.map +1 -0
  29. package/dist/admin/run-routes.js +99 -0
  30. package/dist/admin/run-routes.js.map +1 -0
  31. package/dist/admin/settings-routes.js +119 -0
  32. package/dist/admin/settings-routes.js.map +1 -0
  33. package/dist/admin/stats-routes.js +51 -0
  34. package/dist/admin/stats-routes.js.map +1 -0
  35. package/dist/admin/task-routes.js +213 -0
  36. package/dist/admin/task-routes.js.map +1 -0
  37. package/dist/admin/test-routes.js +38 -0
  38. package/dist/admin/test-routes.js.map +1 -0
  39. package/dist/admin/volume-routes.js +82 -0
  40. package/dist/admin/volume-routes.js.map +1 -0
  41. package/dist/admin/webhook-routes.js +174 -0
  42. package/dist/admin/webhook-routes.js.map +1 -0
  43. package/dist/agents.js +48 -0
  44. package/dist/agents.js.map +1 -0
  45. package/dist/assignee-value.js +6 -0
  46. package/dist/assignee-value.js.map +1 -0
  47. package/dist/attachments.js +76 -0
  48. package/dist/attachments.js.map +1 -0
  49. package/dist/auto-commands.js +88 -0
  50. package/dist/auto-commands.js.map +1 -0
  51. package/dist/auto-features.js +32 -0
  52. package/dist/auto-features.js.map +1 -0
  53. package/dist/base-branch-store.js +40 -0
  54. package/dist/base-branch-store.js.map +1 -0
  55. package/dist/base-branches.js +9 -0
  56. package/dist/base-branches.js.map +1 -0
  57. package/dist/build-workflow.js +116 -0
  58. package/dist/build-workflow.js.map +1 -0
  59. package/dist/change-request-lifecycle.js +16 -0
  60. package/dist/change-request-lifecycle.js.map +1 -0
  61. package/dist/change-request-store.js +72 -0
  62. package/dist/change-request-store.js.map +1 -0
  63. package/dist/ci-failure.js +80 -0
  64. package/dist/ci-failure.js.map +1 -0
  65. package/dist/cli.js +3 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/config.js +17 -0
  68. package/dist/config.js.map +1 -0
  69. package/dist/conflict-fix.js +178 -0
  70. package/dist/conflict-fix.js.map +1 -0
  71. package/dist/cookie.js +70 -0
  72. package/dist/cookie.js.map +1 -0
  73. package/dist/crypto-util.js +40 -0
  74. package/dist/crypto-util.js.map +1 -0
  75. package/dist/custom-repos-store.js +90 -0
  76. package/dist/custom-repos-store.js.map +1 -0
  77. package/dist/custom-repos-sync.js +118 -0
  78. package/dist/custom-repos-sync.js.map +1 -0
  79. package/dist/db.js +12 -0
  80. package/dist/db.js.map +1 -0
  81. package/dist/devbot-scripts.js +18 -0
  82. package/dist/devbot-scripts.js.map +1 -0
  83. package/dist/devcontainer.js +150 -0
  84. package/dist/devcontainer.js.map +1 -0
  85. package/dist/env-config-store.js +132 -0
  86. package/dist/env-config-store.js.map +1 -0
  87. package/dist/error-log-store.js +193 -0
  88. package/dist/error-log-store.js.map +1 -0
  89. package/dist/git.js +605 -0
  90. package/dist/git.js.map +1 -0
  91. package/dist/global-queue.js +127 -0
  92. package/dist/global-queue.js.map +1 -0
  93. package/dist/global-settings-store.js +307 -0
  94. package/dist/global-settings-store.js.map +1 -0
  95. package/dist/help.js +41 -0
  96. package/dist/help.js.map +1 -0
  97. package/dist/http-csp.js +15 -0
  98. package/dist/http-csp.js.map +1 -0
  99. package/dist/http-util.js +40 -0
  100. package/dist/http-util.js.map +1 -0
  101. package/dist/idempotency.js +50 -0
  102. package/dist/idempotency.js.map +1 -0
  103. package/dist/lifecycle.js +234 -0
  104. package/dist/lifecycle.js.map +1 -0
  105. package/dist/logger.js +33 -0
  106. package/dist/logger.js.map +1 -0
  107. package/dist/lsp-servers.js +81 -0
  108. package/dist/lsp-servers.js.map +1 -0
  109. package/dist/mcp/auth.js +62 -0
  110. package/dist/mcp/auth.js.map +1 -0
  111. package/dist/mcp/effective-mcp.js +42 -0
  112. package/dist/mcp/effective-mcp.js.map +1 -0
  113. package/dist/mcp/oauth-refresh.js +138 -0
  114. package/dist/mcp/oauth-refresh.js.map +1 -0
  115. package/dist/mcp/peer-sync.js +109 -0
  116. package/dist/mcp/peer-sync.js.map +1 -0
  117. package/dist/mcp/registry.js +54 -0
  118. package/dist/mcp/registry.js.map +1 -0
  119. package/dist/mcp/runner-bridge.js +93 -0
  120. package/dist/mcp/runner-bridge.js.map +1 -0
  121. package/dist/mcp/server.js +173 -0
  122. package/dist/mcp/server.js.map +1 -0
  123. package/dist/mcp/tools/_helpers.js +88 -0
  124. package/dist/mcp/tools/_helpers.js.map +1 -0
  125. package/dist/mcp/tools/attachments.js +117 -0
  126. package/dist/mcp/tools/attachments.js.map +1 -0
  127. package/dist/mcp/tools/cicd.js +105 -0
  128. package/dist/mcp/tools/cicd.js.map +1 -0
  129. package/dist/mcp/tools/discovery.js +34 -0
  130. package/dist/mcp/tools/discovery.js.map +1 -0
  131. package/dist/mcp/tools/issues.js +56 -0
  132. package/dist/mcp/tools/issues.js.map +1 -0
  133. package/dist/mcp/tools/merge_requests.js +67 -0
  134. package/dist/mcp/tools/merge_requests.js.map +1 -0
  135. package/dist/mcp/tools/metadata.js +127 -0
  136. package/dist/mcp/tools/metadata.js.map +1 -0
  137. package/dist/mcp/tools/peer_sync.js +43 -0
  138. package/dist/mcp/tools/peer_sync.js.map +1 -0
  139. package/dist/mcp/tools/repository.js +178 -0
  140. package/dist/mcp/tools/repository.js.map +1 -0
  141. package/dist/mcp/tools/work_tracking.js +125 -0
  142. package/dist/mcp/tools/work_tracking.js.map +1 -0
  143. package/dist/mcp/tools/workitems.js +30 -0
  144. package/dist/mcp/tools/workitems.js.map +1 -0
  145. package/dist/mcp/volume-files.js +187 -0
  146. package/dist/mcp/volume-files.js.map +1 -0
  147. package/dist/mcp-auth-store.js +134 -0
  148. package/dist/mcp-auth-store.js.map +1 -0
  149. package/dist/mcp-config-store.js +83 -0
  150. package/dist/mcp-config-store.js.map +1 -0
  151. package/dist/mention.js +512 -0
  152. package/dist/mention.js.map +1 -0
  153. package/dist/migrate.js +438 -0
  154. package/dist/migrate.js.map +1 -0
  155. package/dist/mr-assignee.js +71 -0
  156. package/dist/mr-assignee.js.map +1 -0
  157. package/dist/mr-content.js +90 -0
  158. package/dist/mr-content.js.map +1 -0
  159. package/dist/oauth-routes.js +54 -0
  160. package/dist/oauth-routes.js.map +1 -0
  161. package/dist/onboarding-allowlist.js +32 -0
  162. package/dist/onboarding-allowlist.js.map +1 -0
  163. package/dist/opencode.js +291 -0
  164. package/dist/opencode.js.map +1 -0
  165. package/dist/outbound-webhook-store.js +229 -0
  166. package/dist/outbound-webhook-store.js.map +1 -0
  167. package/dist/outbound-webhook.js +314 -0
  168. package/dist/outbound-webhook.js.map +1 -0
  169. package/dist/path-sandbox.js +200 -0
  170. package/dist/path-sandbox.js.map +1 -0
  171. package/dist/platform/git-auth.js +34 -0
  172. package/dist/platform/git-auth.js.map +1 -0
  173. package/dist/platform/gitealike/attachments.js +62 -0
  174. package/dist/platform/gitealike/attachments.js.map +1 -0
  175. package/dist/platform/gitealike/base.js +935 -0
  176. package/dist/platform/gitealike/base.js.map +1 -0
  177. package/dist/platform/gitealike/forgejo.js +16 -0
  178. package/dist/platform/gitealike/forgejo.js.map +1 -0
  179. package/dist/platform/gitealike/gitea.js +16 -0
  180. package/dist/platform/gitealike/gitea.js.map +1 -0
  181. package/dist/platform/gitealike/http.js +152 -0
  182. package/dist/platform/gitealike/http.js.map +1 -0
  183. package/dist/platform/gitealike/schemas.js +284 -0
  184. package/dist/platform/gitealike/schemas.js.map +1 -0
  185. package/dist/platform/github/attachments.js +52 -0
  186. package/dist/platform/github/attachments.js.map +1 -0
  187. package/dist/platform/github/http.js +153 -0
  188. package/dist/platform/github/http.js.map +1 -0
  189. package/dist/platform/github/index.js +936 -0
  190. package/dist/platform/github/index.js.map +1 -0
  191. package/dist/platform/github/schemas.js +308 -0
  192. package/dist/platform/github/schemas.js.map +1 -0
  193. package/dist/platform/github/zip.js +85 -0
  194. package/dist/platform/github/zip.js.map +1 -0
  195. package/dist/platform/gitlab/admin.js +105 -0
  196. package/dist/platform/gitlab/admin.js.map +1 -0
  197. package/dist/platform/gitlab/api.js +229 -0
  198. package/dist/platform/gitlab/api.js.map +1 -0
  199. package/dist/platform/gitlab/attachments.js +79 -0
  200. package/dist/platform/gitlab/attachments.js.map +1 -0
  201. package/dist/platform/gitlab/http.js +165 -0
  202. package/dist/platform/gitlab/http.js.map +1 -0
  203. package/dist/platform/gitlab/index.js +630 -0
  204. package/dist/platform/gitlab/index.js.map +1 -0
  205. package/dist/platform/gitlab/oauth.js +58 -0
  206. package/dist/platform/gitlab/oauth.js.map +1 -0
  207. package/dist/platform/gitlab/read.js +182 -0
  208. package/dist/platform/gitlab/read.js.map +1 -0
  209. package/dist/platform/gitlab/schemas-read.js +274 -0
  210. package/dist/platform/gitlab/schemas-read.js.map +1 -0
  211. package/dist/platform/gitlab/schemas.js +251 -0
  212. package/dist/platform/gitlab/schemas.js.map +1 -0
  213. package/dist/platform/gitlab/signature.js +52 -0
  214. package/dist/platform/gitlab/signature.js.map +1 -0
  215. package/dist/platform/index.js +38 -0
  216. package/dist/platform/index.js.map +1 -0
  217. package/dist/platform/port.js +26 -0
  218. package/dist/platform/port.js.map +1 -0
  219. package/dist/platform/types.js +80 -0
  220. package/dist/platform/types.js.map +1 -0
  221. package/dist/podman.js +119 -0
  222. package/dist/podman.js.map +1 -0
  223. package/dist/project-sync.js +69 -0
  224. package/dist/project-sync.js.map +1 -0
  225. package/dist/prompt.js +47 -0
  226. package/dist/prompt.js.map +1 -0
  227. package/dist/queue-store.js +236 -0
  228. package/dist/queue-store.js.map +1 -0
  229. package/dist/queue.js +129 -0
  230. package/dist/queue.js.map +1 -0
  231. package/dist/redact.js +23 -0
  232. package/dist/redact.js.map +1 -0
  233. package/dist/run-files.js +297 -0
  234. package/dist/run-files.js.map +1 -0
  235. package/dist/run-filters.js +54 -0
  236. package/dist/run-filters.js.map +1 -0
  237. package/dist/run-history-store.js +229 -0
  238. package/dist/run-history-store.js.map +1 -0
  239. package/dist/run-log-sink.js +270 -0
  240. package/dist/run-log-sink.js.map +1 -0
  241. package/dist/run-log.js +97 -0
  242. package/dist/run-log.js.map +1 -0
  243. package/dist/run-usage.js +29 -0
  244. package/dist/run-usage.js.map +1 -0
  245. package/dist/run-view.js +112 -0
  246. package/dist/run-view.js.map +1 -0
  247. package/dist/runner.js +1094 -0
  248. package/dist/runner.js.map +1 -0
  249. package/dist/runtime-apply.js +26 -0
  250. package/dist/runtime-apply.js.map +1 -0
  251. package/dist/runtime-settings.js +71 -0
  252. package/dist/runtime-settings.js.map +1 -0
  253. package/dist/runtime-state.js +19 -0
  254. package/dist/runtime-state.js.map +1 -0
  255. package/dist/server.js +119 -0
  256. package/dist/server.js.map +1 -0
  257. package/dist/settings-store.js +320 -0
  258. package/dist/settings-store.js.map +1 -0
  259. package/dist/status.js +121 -0
  260. package/dist/status.js.map +1 -0
  261. package/dist/stop.js +56 -0
  262. package/dist/stop.js.map +1 -0
  263. package/dist/sweeps.js +87 -0
  264. package/dist/sweeps.js.map +1 -0
  265. package/dist/task-log.js +279 -0
  266. package/dist/task-log.js.map +1 -0
  267. package/dist/task-view.js +62 -0
  268. package/dist/task-view.js.map +1 -0
  269. package/dist/token-store.js +68 -0
  270. package/dist/token-store.js.map +1 -0
  271. package/dist/types/access.js +74 -0
  272. package/dist/types/access.js.map +1 -0
  273. package/dist/types/custom-repos.js +28 -0
  274. package/dist/types/custom-repos.js.map +1 -0
  275. package/dist/types/env-config.js +25 -0
  276. package/dist/types/env-config.js.map +1 -0
  277. package/dist/types/env.js +190 -0
  278. package/dist/types/env.js.map +1 -0
  279. package/dist/types/global-settings.js +178 -0
  280. package/dist/types/global-settings.js.map +1 -0
  281. package/dist/types/lsp-servers.js +145 -0
  282. package/dist/types/lsp-servers.js.map +1 -0
  283. package/dist/types/mcp-config.js +77 -0
  284. package/dist/types/mcp-config.js.map +1 -0
  285. package/dist/types/opencode.js +59 -0
  286. package/dist/types/opencode.js.map +1 -0
  287. package/dist/types/outbound-webhook.js +56 -0
  288. package/dist/types/outbound-webhook.js.map +1 -0
  289. package/dist/types/project-settings.js +114 -0
  290. package/dist/types/project-settings.js.map +1 -0
  291. package/dist/types/provider-registry.js +37 -0
  292. package/dist/types/provider-registry.js.map +1 -0
  293. package/dist/types/runtime-settings.js +78 -0
  294. package/dist/types/runtime-settings.js.map +1 -0
  295. package/dist/usage-stats.js +159 -0
  296. package/dist/usage-stats.js.map +1 -0
  297. package/dist/version.js +19 -0
  298. package/dist/version.js.map +1 -0
  299. package/dist/volume-gc.js +193 -0
  300. package/dist/volume-gc.js.map +1 -0
  301. package/dist/volume-manage.js +191 -0
  302. package/dist/volume-manage.js.map +1 -0
  303. package/dist/volume-store.js +122 -0
  304. package/dist/volume-store.js.map +1 -0
  305. package/dist/webhook.js +1106 -0
  306. package/dist/webhook.js.map +1 -0
  307. package/dist/work-item-auto-store.js +75 -0
  308. package/dist/work-item-auto-store.js.map +1 -0
  309. package/dist/work-item-branch-store.js +37 -0
  310. package/dist/work-item-branch-store.js.map +1 -0
  311. package/migrations/0001_initial.sql +59 -0
  312. package/migrations/0002_queue_status.sql +15 -0
  313. package/migrations/0003_work_item_base_branch.sql +14 -0
  314. package/migrations/0004_project_settings.sql +15 -0
  315. package/migrations/0005_global_settings.sql +34 -0
  316. package/migrations/0006_model_variants.sql +8 -0
  317. package/migrations/0007_container_error_logs.sql +21 -0
  318. package/migrations/0008_project_access_grants.sql +16 -0
  319. package/migrations/0009_drop_imported_legacy.sql +4 -0
  320. package/migrations/0010_container_error_log_model.sql +6 -0
  321. package/migrations/0011_container_error_log_title.sql +5 -0
  322. package/migrations/0012_pending_task_cancel.sql +6 -0
  323. package/migrations/0013_builder_image.sql +6 -0
  324. package/migrations/0014_build_workflow.sql +7 -0
  325. package/migrations/0015_access_rules.sql +24 -0
  326. package/migrations/0016_trigger_policy.sql +17 -0
  327. package/migrations/0017_attachment_upload.sql +8 -0
  328. package/migrations/0018_work_item_branch.sql +11 -0
  329. package/migrations/0019_auto_push_mr.sql +18 -0
  330. package/migrations/0020_outbound_webhooks.sql +47 -0
  331. package/migrations/0021_runtime_settings.sql +12 -0
  332. package/migrations/0022_auto_resolve_conflicts.sql +17 -0
  333. package/migrations/0023_ci_fail_auto_report.sql +27 -0
  334. package/migrations/0024_work_item_auto_override.sql +22 -0
  335. package/migrations/0025_run_history.sql +37 -0
  336. package/migrations/0026_run_history_task_seq.sql +6 -0
  337. package/migrations/0027_custom_repos.sql +11 -0
  338. package/migrations/0028_run_usage.sql +14 -0
  339. package/migrations/0029_drop_ocr_langs.sql +6 -0
  340. package/migrations/0030_mr_assignee.sql +9 -0
  341. package/migrations/0031_lsp_servers.sql +14 -0
  342. package/opencode-config/managed/devbot-agent-policy.md +61 -0
  343. package/opencode-config/managed/opencode.json +77 -0
  344. package/opencode-config/opencode.json +211 -0
  345. package/opencode-config/plugins/devbot-path-guard.js +224 -0
  346. package/package.json +158 -4
  347. package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
  348. package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
  349. package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
  350. package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
  351. package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
  352. package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
  353. package/public/assets/PageGrid-CBROIU87.js +1 -0
  354. package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
  355. package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
  356. package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
  357. package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
  358. package/public/assets/RunsPage-CRnnXU4P.js +1 -0
  359. package/public/assets/Select-Bvx32Diz.js +1 -0
  360. package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
  361. package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
  362. package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
  363. package/public/assets/UsagePage-BLMDEsv-.js +332 -0
  364. package/public/assets/UsagePage-DodEeAjB.css +1 -0
  365. package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
  366. package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
  367. package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
  368. package/public/assets/index-BMbMibyb.js +4 -0
  369. package/public/assets/index-C5RBBrPj.js +30 -0
  370. package/public/assets/index-CgX29UgC.css +1 -0
  371. package/public/assets/usage-format-jsb31_O8.js +1 -0
  372. package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
  373. package/public/assets/useRouteTab-DfwrcMic.js +1 -0
  374. package/public/favicon.ico +0 -0
  375. package/public/favicon.png +0 -0
  376. package/public/index.html +15 -0
@@ -0,0 +1,76 @@
1
+ // Forge-free attachment helpers shared by every driver.
2
+ //
3
+ // Discovery (which note body snippets are attachments) and fetching (which
4
+ // forge endpoint serves them) are forge-specific and live in each driver:
5
+ // src/platform/gitlab/attachments.ts
6
+ // src/platform/gitealike/attachments.ts
7
+ // src/platform/github/attachments.ts
8
+ // This module owns only the forge-neutral primitives: the markdown-scanning
9
+ // regexes used to find references, code-span stripping, URL decoding, and the
10
+ // filename→MIME table used when staging an outbound attachment.
11
+ //
12
+ // DevBot no longer extracts or OCRs attachment content host-side. Attachments
13
+ // are downloaded whole into the run volume by the agent (the
14
+ // `download_work_item_attachment` MCP tool) and handled autonomously; image/PDF
15
+ // OCR is available to the agent inside the builder image.
16
+ // ---- Shared markdown-scanning primitives -----------------------------------
17
+ // Drivers strip code spans before matching so a ref inside a code sample is
18
+ // not treated as an actual attachment.
19
+ export const FENCED_CODE = /```[\s\S]*?```/g;
20
+ export const INLINE_CODE = /`[^`\n]*`/g;
21
+ export const MD_LINK = /!?\[([^\]]*)\]\(([^)]+)\)/g;
22
+ export function stripCodeSpans(body) {
23
+ return body.replace(FENCED_CODE, "").replace(INLINE_CODE, "");
24
+ }
25
+ export function safeDecode(s) {
26
+ try {
27
+ return decodeURIComponent(s);
28
+ }
29
+ catch {
30
+ return s;
31
+ }
32
+ }
33
+ // ---- Filename → MIME -------------------------------------------------------
34
+ // Inline MIME table (no npm dep). Maps filename extension → MIME used when
35
+ // staging an outbound attachment so the forge stores a useful Content-Type.
36
+ const EXT_TO_MIME = {
37
+ txt: "text/plain", log: "text/plain", md: "text/markdown",
38
+ csv: "text/csv", tsv: "text/tab-separated-values",
39
+ json: "application/json", xml: "application/xml", yaml: "text/yaml",
40
+ yml: "text/yaml", toml: "application/toml", ini: "text/plain",
41
+ diff: "text/x-diff", patch: "text/x-diff",
42
+ sh: "text/x-shellscript", bash: "text/x-shellscript",
43
+ sql: "application/sql", html: "text/html", htm: "text/html",
44
+ css: "text/css", js: "text/javascript", ts: "text/typescript",
45
+ py: "text/x-python", rb: "text/x-ruby", go: "text/x-go",
46
+ rs: "text/x-rust", java: "text/x-java", c: "text/x-c", h: "text/x-c",
47
+ cpp: "text/x-c++", hpp: "text/x-c++", cs: "text/x-csharp",
48
+ php: "text/x-php", pl: "text/x-perl", lua: "text/x-lua",
49
+ env: "text/plain", conf: "text/plain", cfg: "text/plain",
50
+ gitignore: "text/plain", dockerfile: "text/plain",
51
+ // images
52
+ png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
53
+ gif: "image/gif", webp: "image/webp", bmp: "image/bmp",
54
+ tif: "image/tiff", tiff: "image/tiff",
55
+ // pdf
56
+ pdf: "application/pdf",
57
+ };
58
+ /** Extension-based MIME lookup for a filename (no forge, no IO). Used when
59
+ * staging an outbound attachment so the forge stores a useful Content-Type. */
60
+ export function mimeForFilename(filename) {
61
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
62
+ return EXT_TO_MIME[ext] ?? "application/octet-stream";
63
+ }
64
+ /** Turn a forge-supplied filename into a safe staging basename. Strips any
65
+ * directory components and keeps only `[A-Za-z0-9._-]`, so a forged name like
66
+ * `../../etc/passwd` or `a/b` can never escape the staging dir. Falls back to
67
+ * `fallback` when nothing usable remains. */
68
+ export function safeDownloadFilename(filename, fallback = "attachment") {
69
+ const base = filename.split(/[/\\]/).pop() ?? "";
70
+ const cleaned = base.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, "");
71
+ const trimmed = cleaned.slice(0, 128);
72
+ if (!trimmed || trimmed === "." || trimmed === "..")
73
+ return fallback;
74
+ return trimmed;
75
+ }
76
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { config } from "./config.js";
2
+ import { log } from "./logger.js";
3
+ import { getPlatform } from "./platform/index.js";
4
+ import { isOnboardingAllowed } from "./onboarding-allowlist.js";
5
+ import { resolveAutoFeature } from "./settings-store.js";
6
+ import { AUTO_FEATURES, autoFeatureMeta } from "./auto-features.js";
7
+ import { clearAutoOverride, getAutoOverride, setAutoOverride, } from "./work-item-auto-store.js";
8
+ // Contextual auto-* command handler (work item #58). Applies /auto-push,
9
+ // /auto-mr, /auto-resolve-conflicts, /auto-ci-report (and /auto, /ask) to this
10
+ // work item's override rows and posts one aggregated reply note.
11
+ //
12
+ // Authorization is enforced by the general trigger gate in webhook.ts before
13
+ // this is called. The one extra rule: enabling a feature the project resolves
14
+ // OFF requires the actor to be a DevBot admin (allowlisted in ONBOARDING_USERS,
15
+ // fail-closed). Disabling is always allowed for an authorized subject — it is
16
+ // the safe direction.
17
+ function projectResolved(projectId, feature) {
18
+ return resolveAutoFeature(projectId, feature);
19
+ }
20
+ function isAdmin(username) {
21
+ return isOnboardingAllowed(username, config.ONBOARDING_USERS);
22
+ }
23
+ // Apply one command to one feature and return the reply line.
24
+ function applyOne(projectId, workItemId, feature, action, admin) {
25
+ const label = autoFeatureMeta(feature).label;
26
+ const projectOn = projectResolved(projectId, feature);
27
+ const override = getAutoOverride(workItemId, feature);
28
+ if (action === "query") {
29
+ if (override === "off")
30
+ return `⚙️ \`${label}\` — disabled for this work item.`;
31
+ if (override === "on")
32
+ return `⚙️ \`${label}\` — enabled for this work item (project default is off).`;
33
+ return projectOn ? `⚙️ \`${label}\` — enabled.` : `⚙️ \`${label}\` — not enabled on this project.`;
34
+ }
35
+ if (action === "off") {
36
+ setAutoOverride(workItemId, feature, "off");
37
+ return `✅ \`${label}\` disabled for this work item.`;
38
+ }
39
+ // action === "on"
40
+ if (projectOn) {
41
+ const cleared = clearAutoOverride(workItemId, feature);
42
+ return cleared
43
+ ? `✅ \`${label}\` re-enabled for this work item.`
44
+ : `ℹ️ \`${label}\` is already enabled for this work item.`;
45
+ }
46
+ if (admin) {
47
+ setAutoOverride(workItemId, feature, "on");
48
+ return `✅ \`${label}\` enabled for this work item.`;
49
+ }
50
+ return `🚫 \`${label}\` is not enabled on this project — only a DevBot admin can enable it for this work item.`;
51
+ }
52
+ // Expand the ordered command list into (feature, action) pairs. "all" fans out
53
+ // to every feature; later commands for the same feature still win because they
54
+ // are applied in order and the reply line is keyed per feature (last wins).
55
+ function expand(commands) {
56
+ const out = [];
57
+ for (const c of commands) {
58
+ if (c.feature === "all") {
59
+ for (const f of AUTO_FEATURES)
60
+ out.push({ feature: f, action: c.action });
61
+ }
62
+ else {
63
+ out.push({ feature: c.feature, action: c.action });
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ export async function handleAutoCommands(params) {
69
+ const { commands } = params;
70
+ if (commands.length === 0)
71
+ return;
72
+ const admin = isAdmin(params.actorUsername);
73
+ // Apply in order; the last command touching a feature determines its line.
74
+ const lines = new Map();
75
+ for (const { feature, action } of expand(commands)) {
76
+ lines.set(feature, applyOne(params.projectId, params.workItemId, feature, action, admin));
77
+ }
78
+ const body = [...lines.values()].join("\n");
79
+ if (!body)
80
+ return;
81
+ try {
82
+ await getPlatform().workItems.postErrorReply(params.projectId, params.workItemNumber, params.triggeringNoteId, body);
83
+ }
84
+ catch (e) {
85
+ log.warn("auto-command reply failed", { workItemId: params.workItemId, err: e });
86
+ }
87
+ }
88
+ //# sourceMappingURL=auto-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-commands.js","sourceRoot":"","sources":["../src/auto-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ // Metadata for the four auto-* features (work item #58). Pure: no DB, no
2
+ // config import, so both the parser (mention.ts) and the store
3
+ // (work-item-auto-store.ts) can share one source of truth for the neutral
4
+ // feature key, the settings DB key, the command token, and the display label.
5
+ export const AUTO_FEATURES = ["autoPush", "autoMr", "autoResolveConflicts", "ciFailAutoReport"];
6
+ const META = {
7
+ autoPush: { key: "autoPush", dbKey: "auto_push", token: "/auto-push", label: "auto-push" },
8
+ autoMr: { key: "autoMr", dbKey: "auto_mr", token: "/auto-mr", label: "auto-mr" },
9
+ autoResolveConflicts: {
10
+ key: "autoResolveConflicts",
11
+ dbKey: "auto_resolve_conflicts",
12
+ token: "/auto-resolve-conflicts",
13
+ label: "auto-resolve-conflicts",
14
+ },
15
+ ciFailAutoReport: {
16
+ key: "ciFailAutoReport",
17
+ dbKey: "ci_fail_auto_report",
18
+ token: "/auto-ci-report",
19
+ label: "auto-ci-report",
20
+ },
21
+ };
22
+ export function autoFeatureMeta(key) {
23
+ return META[key];
24
+ }
25
+ export function isAutoFeature(value) {
26
+ return AUTO_FEATURES.includes(value);
27
+ }
28
+ export function autoFeatureByToken(token) {
29
+ const t = token.toLowerCase();
30
+ return AUTO_FEATURES.find((k) => META[k].token === t);
31
+ }
32
+ //# sourceMappingURL=auto-features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-features.js","sourceRoot":"","sources":["../src/auto-features.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,+DAA+D;AAC/D,0EAA0E;AAC1E,8EAA8E;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,sBAAsB,EAAE,kBAAkB,CAAU,CAAC;AAczG,MAAM,IAAI,GAAyC;IACjD,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE;IAC1F,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE;IAChF,oBAAoB,EAAE;QACpB,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,wBAAwB;QAC/B,KAAK,EAAE,yBAAyB;QAChC,KAAK,EAAE,wBAAwB;KAChC;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,kBAAkB;QACvB,KAAK,EAAE,qBAAqB;QAC5B,KAAK,EAAE,iBAAiB;QACxB,KAAK,EAAE,gBAAgB;KACxB;CACF,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,GAAgB;IAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAQ,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAC9B,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { db } from "./db.js";
2
+ // Per-work-item base/target branch chosen via the /base <branch> mention token.
3
+ // Belt-and-braces: the table is also created by migrations/0003_work_item_base_branch.sql
4
+ // once the migration framework (wi-3, MR !3) lands. This init runs in
5
+ // lifecycle.ts bootstrap() alongside the other init*Table() calls so the table
6
+ // exists even before migrate() is wired up. Idempotent (IF NOT EXISTS).
7
+ export function initBaseBranchTable() {
8
+ db.exec(`
9
+ CREATE TABLE IF NOT EXISTS work_item_base_branch (
10
+ work_item_id INTEGER PRIMARY KEY,
11
+ base_branch TEXT NOT NULL,
12
+ created_at INTEGER NOT NULL
13
+ )
14
+ `);
15
+ }
16
+ // First-write-wins: only sets the base branch if no row exists yet for this work
17
+ // item. Subsequent /base mentions (after the first run) do NOT overwrite —
18
+ // runner.ts gates the /base-ignored note on volume existence, and the store
19
+ // enforces the same first-wins invariant here so a post-reset re-mention can
20
+ // set a fresh value while a same-session re-mention cannot. INSERT OR IGNORE
21
+ // is the atomic first-write-wins primitive; the row is the single source of
22
+ // truth for mrTarget across all subsequent runs.
23
+ export function setBaseBranchIfUnset(workItemId, branch) {
24
+ db.prepare(`
25
+ INSERT OR IGNORE INTO work_item_base_branch (work_item_id, base_branch, created_at)
26
+ VALUES (?, ?, ?)
27
+ `).run(workItemId, branch, Math.floor(Date.now() / 1000));
28
+ }
29
+ export function getBaseBranch(workItemId) {
30
+ const row = db
31
+ .prepare("SELECT base_branch FROM work_item_base_branch WHERE work_item_id = ?")
32
+ .get(workItemId);
33
+ return row ? row.base_branch : null;
34
+ }
35
+ // Cleared by /reset (agents.ts handleResetNote) and work-item close (webhook.ts
36
+ // close path) so a post-reset re-mention with /base sets a fresh value.
37
+ export function deleteBaseBranch(workItemId) {
38
+ db.prepare("DELETE FROM work_item_base_branch WHERE work_item_id = ?").run(workItemId);
39
+ }
40
+ //# sourceMappingURL=base-branch-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-branch-store.js","sourceRoot":"","sources":["../src/base-branch-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,gFAAgF;AAChF,0FAA0F;AAC1F,wEAAwE;AACxE,iFAAiF;AACjF,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB;IACjC,EAAE,CAAC,IAAI,CAAC;;;;;;GAMP,CAAC,CAAC;AACL,CAAC;AAED,iFAAiF;AACjF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,mDAAmD;AACnD,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,MAAc;IACrE,EAAE,CAAC,OAAO,CAAC;;;GAGV,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,sEAAsE,CAAC;SAC/E,GAAG,CAAC,UAAU,CAAwC,CAAC;IAC1D,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,CAAC;AAED,gFAAgF;AAChF,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,9 @@
1
+ // Parse a comma-separated branch list into walk order: dedupe + trim + filter
2
+ // empties, preserving the stored order so callers try the top/first entry first
3
+ // (highest priority). Single-sourced so git.ts (clone slot 2) and runner.ts
4
+ // (mrTarget) share one definition of the walk, and so per-project overrides
5
+ // parse identically to the global default.
6
+ export function parseBaseBranches(raw) {
7
+ return [...new Set(raw.split(",").map((s) => s.trim()).filter(Boolean))];
8
+ }
9
+ //# sourceMappingURL=base-branches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-branches.js","sourceRoot":"","sources":["../src/base-branches.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,2CAA2C;AAC3C,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,116 @@
1
+ // Build-mode prompt scaffolding, split into two user-editable templates:
2
+ //
3
+ // - workflowTemplate — branch checkout/rebase + commit prose, ending with a
4
+ // `{{mrBlock}}` reference (auto-appended when omitted).
5
+ // - mrDescriptionTemplate — the MR/PR description body, rendered INSIDE the
6
+ // fixed ```mr scaffold.
7
+ //
8
+ // The ```mr fence, the `title:`/`description:` keys, and the "must be last"
9
+ // rule are DevBot-owned and never editable: they are the machine-parsed
10
+ // contract (src/mr-content.ts). Users edit prose + description structure only.
11
+ //
12
+ // Placeholder policy: validate-on-save rejects unknown `{{...}}` tokens (the
13
+ // zod schemas call findUnknownPlaceholders), no literal escape. The empty
14
+ // string means "use the built-in default", so an upgrade reproduces today's
15
+ // prompt byte-for-byte until an admin edits a field.
16
+ export const WORKFLOW_PLACEHOLDERS = ["iid", "branch", "mrTarget", "mrBlock"];
17
+ export const MR_DESCRIPTION_PLACEHOLDERS = ["iid", "branch", "mrTarget"];
18
+ // The built-in workflow body (everything after the fixed `\n\n---\n\n`
19
+ // separator that runner.ts adds). Reproduces the pre-feature hardcoded text
20
+ // exactly; placeholders were substituted for the per-run values.
21
+ export const DEFAULT_BUILD_WORKFLOW_TEMPLATE = `Before you start, make sure you are on the work item branch \`{{branch}}\`:
22
+ - DevBot has already placed your checkout on \`{{branch}}\`. Verify with \`git rev-parse --abbrev-ref HEAD\` — it must show \`{{branch}}\`.
23
+ - Once on it, rebase onto the latest remote: \`git rebase origin/{{branch}}\` (DevBot already fetched origin for this run; skip the rebase if \`origin/{{branch}}\` doesn't exist — fresh branch).
24
+ - If HEAD is somehow different (recovery only): run \`git checkout {{branch}}\` if it exists locally, else \`git checkout -b {{branch}} origin/{{branch}}\` if it exists remotely, else \`git checkout -B {{branch}} {{mrTarget}}\` to start the work branch from the MR target.
25
+ - If a rebase hits conflicts, stop and report them in your reply — do not force anything.
26
+
27
+ When finished, commit your work:
28
+ - Stage every change with \`git add -A\`.
29
+ - Create a **single** git commit using Conventional Commits format: \`type(scope): imperative summary (#{{iid}})\`
30
+ where \`type\` is one of feat, fix, refactor, docs, test, chore, perf, style, build, ci and \`scope\` is the touched module (omit \`scope\` if the change spans multiple modules).
31
+ - Add a body paragraph explaining *what* and *why* when the change is non-trivial.
32
+ - **Do not push** — devbot pushes the branch after you finish.
33
+
34
+ After your commit, summarize the WHOLE branch for the MR (not just this run's commit):
35
+ - Run \`git log origin/{{mrTarget}}..HEAD --format='- %s'\` to list every commit on this branch since it diverged from {{mrTarget}}.
36
+ - Read each commit's subject and body with \`git log origin/{{mrTarget}}..HEAD\` (no --format) for the full bodies.
37
+ - Write the MR content block from that full picture as structured Markdown:
38
+
39
+ {{mrBlock}}
40
+
41
+ DevBot parses the block and strips it from the note posted to the work item; the description body (everything after \`description:\`) is passed through to the forge verbatim as the MR/PR description, which renders Markdown. The block must be the last thing in your reply. Re-runs replace the MR/PR description, so always summarize the full branch, not just the latest commit. The closing \`Resolves #{{iid}}\` line lets the forge auto-close the work item when the merge/pull request merges — keep it as the last line.`;
42
+ // The built-in MR description body, injected after the fixed `description:`
43
+ // key. Matches the pre-feature scaffold's body exactly.
44
+ export const DEFAULT_MR_DESCRIPTION_TEMPLATE = `## Summary
45
+ <1-3 sentences: what the branch as a whole changes>
46
+
47
+ ## Changes
48
+ - <commit subject 1>
49
+ - <commit subject 2>
50
+ - …list every commit from the git log above (use the same subjects)
51
+
52
+ ## Rationale
53
+ <1-2 sentences on why, if the change is non-trivial; omit this whole section if the change is self-evident>
54
+
55
+ Resolves #{{iid}}`;
56
+ // All `{{name}}` tokens present in a template, deduped, in first-seen order.
57
+ export function extractPlaceholders(template) {
58
+ const out = [];
59
+ const seen = new Set();
60
+ for (const m of template.matchAll(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g)) {
61
+ const name = m[1];
62
+ if (!seen.has(name)) {
63
+ seen.add(name);
64
+ out.push(name);
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ // Placeholder names used in `template` that are not in `allowed`. Empty array
70
+ // means valid. Used by the zod schemas (save-time validation).
71
+ export function findUnknownPlaceholders(template, allowed) {
72
+ const ok = new Set(allowed);
73
+ return extractPlaceholders(template).filter((name) => !ok.has(name));
74
+ }
75
+ function fill(template, values) {
76
+ let out = template;
77
+ for (const [name, value] of Object.entries(values)) {
78
+ // Function replacer so `$` sequences in user content are never treated as
79
+ // String.replace patterns.
80
+ out = out.replaceAll(`{{${name}}}`, () => value);
81
+ }
82
+ return out;
83
+ }
84
+ // The fixed ```mr scaffold wrapping the rendered description. DevBot-owned.
85
+ function mrBlock(workItemNumber, description) {
86
+ return [
87
+ "```mr",
88
+ `title: <Conventional Commit summary of the branch's overall change, end with (#${workItemNumber})>`,
89
+ "description:",
90
+ description,
91
+ "```",
92
+ ].join("\n");
93
+ }
94
+ // Render the build-mode workflow body. runner.ts prepends the fixed separator.
95
+ // When `workflow` omits `{{mrBlock}}`, the scaffold is appended so MR parsing
96
+ // can never silently degrade.
97
+ export function renderBuildWorkflow(input) {
98
+ const workflowTpl = input.workflow?.trim() ? input.workflow : DEFAULT_BUILD_WORKFLOW_TEMPLATE;
99
+ const descTpl = input.mrDescription?.trim()
100
+ ? input.mrDescription
101
+ : DEFAULT_MR_DESCRIPTION_TEMPLATE;
102
+ const description = fill(descTpl, {
103
+ iid: String(input.workItemNumber),
104
+ branch: input.branch,
105
+ mrTarget: input.mrTarget,
106
+ });
107
+ const block = mrBlock(input.workItemNumber, description);
108
+ const rendered = fill(workflowTpl, {
109
+ iid: String(input.workItemNumber),
110
+ branch: input.branch,
111
+ mrTarget: input.mrTarget,
112
+ mrBlock: block,
113
+ });
114
+ return workflowTpl.includes("{{mrBlock}}") ? rendered : `${rendered}\n\n${block}`;
115
+ }
116
+ //# sourceMappingURL=build-workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-workflow.js","sourceRoot":"","sources":["../src/build-workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export function decideChangeRequestAction(mrs) {
2
+ const sorted = [...mrs].sort((a, b) => b.number - a.number);
3
+ const open = sorted.find((m) => m.state === "open");
4
+ if (open)
5
+ return { kind: "update", number: open.number };
6
+ return { kind: "create", priorState: sorted[0]?.state ?? null };
7
+ }
8
+ // The open merge request associated with a branch, highest number first (a
9
+ // forge can briefly return more than one). Used by the standalone
10
+ // /fix-conflicts task to resolve against the MR's *current* target branch,
11
+ // so a retargeted MR is fixed against the right base. Pure + testable.
12
+ export function openChangeRequest(mrs) {
13
+ const open = [...mrs].sort((a, b) => b.number - a.number).find((m) => m.state === "open");
14
+ return open ?? null;
15
+ }
16
+ //# sourceMappingURL=change-request-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-request-lifecycle.js","sourceRoot":"","sources":["../src/change-request-lifecycle.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,yBAAyB,CAAC,GAA2B;IACnE,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IACpD,IAAI,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACzD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;AAClE,CAAC;AAED,2EAA2E;AAC3E,oEAAoE;AACpE,6EAA6E;AAC7E,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,GAA2B;IAC3D,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IAC1F,OAAO,IAAI,IAAI,IAAI,CAAC;AACtB,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { db } from "./db.js";
2
+ import { log } from "./logger.js";
3
+ // Durable registry of merge/pull requests DevBot created (work item #57). The
4
+ // CI-failure handler needs to answer "is this failed pipeline on an MR DevBot
5
+ // opened, and for which work item?" — the forge webhook alone cannot (a GitLab
6
+ // Pipeline Hook carries no MR author, and DevBot never persisted the MR iid).
7
+ // Rows are written when the runner CREATES an MR; an MR DevBot merely updated
8
+ // is not registered (it was not created by DevBot).
9
+ //
10
+ // last_pipeline_id / last_pipeline_sha implement dedup: a pipeline is reported
11
+ // to the work item once. A redelivered webhook (or a re-fired workflow_run) sees
12
+ // the stored id and is ignored. Same DB file (devbot.db, 0600).
13
+ export function initChangeRequestTable() {
14
+ db.exec(`
15
+ CREATE TABLE IF NOT EXISTS change_requests (
16
+ project_id INTEGER NOT NULL,
17
+ source_branch TEXT NOT NULL,
18
+ cr_number INTEGER NOT NULL,
19
+ work_item_id INTEGER NOT NULL,
20
+ work_item_number INTEGER NOT NULL,
21
+ last_pipeline_id INTEGER,
22
+ last_pipeline_sha TEXT,
23
+ last_reported_at INTEGER,
24
+ created_at INTEGER NOT NULL,
25
+ PRIMARY KEY (project_id, source_branch)
26
+ )
27
+ `);
28
+ db.exec("CREATE INDEX IF NOT EXISTS idx_change_requests_work_item ON change_requests(work_item_id)");
29
+ }
30
+ // Register an MR DevBot just created. Upsert keeps created_at and any dedup
31
+ // state on a re-create of the same branch (e.g. branch reused after /reset).
32
+ export function recordChangeRequest(input) {
33
+ const now = Math.floor(Date.now() / 1000);
34
+ db.prepare(`INSERT INTO change_requests
35
+ (project_id, source_branch, cr_number, work_item_id, work_item_number, created_at)
36
+ VALUES (?, ?, ?, ?, ?, ?)
37
+ ON CONFLICT(project_id, source_branch) DO UPDATE SET
38
+ cr_number = excluded.cr_number,
39
+ work_item_id = excluded.work_item_id,
40
+ work_item_number = excluded.work_item_number`).run(input.projectId, input.sourceBranch, input.crNumber, input.workItemId, input.workItemNumber, now);
41
+ }
42
+ export function getChangeRequestBySourceBranch(projectId, sourceBranch) {
43
+ const row = db
44
+ .prepare("SELECT project_id, source_branch, cr_number, work_item_id, work_item_number, last_pipeline_id, last_pipeline_sha, last_reported_at, created_at FROM change_requests WHERE project_id = ? AND source_branch = ?")
45
+ .get(projectId, sourceBranch);
46
+ return row ?? null;
47
+ }
48
+ // Mark a pipeline as reported so redeliveries (same pipeline id or sha) are
49
+ // ignored. Returns false when the row no longer exists.
50
+ export function markCiReported(input) {
51
+ const now = Math.floor(Date.now() / 1000);
52
+ const r = db
53
+ .prepare(`UPDATE change_requests
54
+ SET last_pipeline_id = ?, last_pipeline_sha = ?, last_reported_at = ?
55
+ WHERE project_id = ? AND source_branch = ?`)
56
+ .run(input.pipelineId, input.sha, now, input.projectId, input.sourceBranch);
57
+ return r.changes > 0;
58
+ }
59
+ // Test-only cleanup hook (the table is tiny and rows persist in production).
60
+ export function deleteChangeRequest(projectId, sourceBranch) {
61
+ db.prepare("DELETE FROM change_requests WHERE project_id = ? AND source_branch = ?").run(projectId, sourceBranch);
62
+ }
63
+ // Test-only: wipe all rows.
64
+ export function deleteAllChangeRequests() {
65
+ try {
66
+ db.prepare("DELETE FROM change_requests").run();
67
+ }
68
+ catch (e) {
69
+ log.debug("deleteAllChangeRequests skipped", { err: e });
70
+ }
71
+ }
72
+ //# sourceMappingURL=change-request-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-request-store.js","sourceRoot":"","sources":["../src/change-request-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,oDAAoD;AACpD,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,gEAAgE;AAEhE,MAAM,UAAU,sBAAsB;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;GAaP,CAAC,CAAC;IACH,EAAE,CAAC,IAAI,CAAC,2FAA2F,CAAC,CAAC;AACvG,CAAC;AAcD,4EAA4E;AAC5E,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,KAMnC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,EAAE,CAAC,OAAO,CACR;;;;;;oDAMgD,CACjD,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;AAC1G,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,SAAiB,EAAE,YAAoB;IACpF,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN,gNAAgN,CACjN;SACA,GAAG,CAAC,SAAS,EAAE,YAAY,CAAiC,CAAC;IAChE,OAAO,GAAG,IAAI,IAAI,CAAC;AACrB,CAAC;AAED,4EAA4E;AAC5E,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,KAK9B;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CACN;;kDAE4C,CAC7C;SACA,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC9E,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,YAAoB;IACzE,EAAE,CAAC,OAAO,CAAC,wEAAwE,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AACpH,CAAC;AAED,4BAA4B;AAC5B,MAAM,UAAU,uBAAuB;IACrC,IAAI,CAAC;QACH,EAAE,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC,GAAG,EAAE,CAAC;IAClD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { getPlatform } from "./platform/index.js";
2
+ import { log } from "./logger.js";
3
+ // Build the CI-failure context block injected into a plan run's prompt. Kept
4
+ // free of the runner's git/volume machinery so it can be unit-tested with a
5
+ // stub platform. Logs are fetched at run time (fresher and larger than a
6
+ // webhook can carry); each job log is tailed — the failure is at the end, so
7
+ // the engine drops the head exactly as the feature requires.
8
+ //
9
+ // Every step degrades gracefully: a forge error yields a section noting the
10
+ // gap rather than aborting the whole run, because the agent can still reason
11
+ // from the work item and job names.
12
+ // Per-job cap (tail) and a total budget across all fetched jobs. The totals are
13
+ // deliberately modest: this block is context, not the task itself.
14
+ export const CI_LOG_PER_JOB_BYTES = 50_000;
15
+ export const CI_LOG_TOTAL_BYTES = 100_000;
16
+ export const CI_MAX_JOBS = 3;
17
+ function fmtJob(job) {
18
+ return job.webUrl ? `- \`${job.name}\` (${job.status}) — ${job.webUrl}` : `- \`${job.name}\` (${job.status})`;
19
+ }
20
+ export async function buildCiFailureSection(projectId, ctx, injectedCi) {
21
+ const platform = injectedCi ? { ci: injectedCi } : getPlatform();
22
+ const header = [
23
+ "## CI failure on the DevBot merge/pull request",
24
+ "",
25
+ `Pipeline \`${ctx.pipelineId}\` failed on \`${ctx.ref}\`${ctx.sha ? ` @ \`${ctx.sha.slice(0, 8)}\`` : ""}${ctx.changeRequestNumber ? ` (MR !${ctx.changeRequestNumber})` : ""}.`,
26
+ "Triage the failure and propose a concrete fix. CI job logs (tails) are included below.",
27
+ ].join("\n");
28
+ let jobs = [];
29
+ try {
30
+ const listed = await platform.ci.listPipelineJobs(projectId, ctx.pipelineId, { outcome: "failed" });
31
+ jobs = listed.map((j) => ({
32
+ id: j.id,
33
+ name: j.name,
34
+ status: j.status,
35
+ ...(j.webUrl ? { webUrl: j.webUrl } : {}),
36
+ }));
37
+ }
38
+ catch (e) {
39
+ log.warn("ci-failure job list failed", { projectId, pipelineId: ctx.pipelineId, err: e });
40
+ return {
41
+ section: `${header}\n\n_Could not list the pipeline's jobs: ${e instanceof Error ? e.message : String(e)}._`,
42
+ jobCount: 0,
43
+ truncated: false,
44
+ };
45
+ }
46
+ if (jobs.length === 0) {
47
+ return { section: `${header}\n\n_No failed jobs were reported for this pipeline._`, jobCount: 0, truncated: false };
48
+ }
49
+ const selected = jobs.slice(0, CI_MAX_JOBS);
50
+ const omittedJobs = jobs.slice(CI_MAX_JOBS);
51
+ const parts = [header, "", "### Failed jobs", ...selected.map(fmtJob)];
52
+ if (omittedJobs.length > 0) {
53
+ parts.push(`_(${omittedJobs.length} more failed job(s) omitted.)_`);
54
+ }
55
+ let budget = CI_LOG_TOTAL_BYTES;
56
+ let anyTruncated = false;
57
+ for (const job of selected) {
58
+ if (budget <= 0)
59
+ break;
60
+ const cap = Math.min(CI_LOG_PER_JOB_BYTES, budget);
61
+ try {
62
+ const logContent = await platform.ci.getJobLog(projectId, job.id, cap);
63
+ budget -= logContent.content.length;
64
+ if (logContent.truncated)
65
+ anyTruncated = true;
66
+ parts.push("", `### Log: ${job.name}`, "```", logContent.content.trimEnd(), "```");
67
+ }
68
+ catch (e) {
69
+ parts.push("", `_Could not fetch the log for \`${job.name}\`: ${e instanceof Error ? e.message : String(e)}._`);
70
+ }
71
+ }
72
+ if (budget <= 0 && selected.length > 0)
73
+ anyTruncated = true;
74
+ return {
75
+ section: parts.join("\n"),
76
+ jobCount: selected.length,
77
+ truncated: anyTruncated,
78
+ };
79
+ }
80
+ //# sourceMappingURL=ci-failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-failure.js","sourceRoot":"","sources":["../src/ci-failure.ts"],"names":[],"mappings":"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"}
package/dist/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "./server.js";
3
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,aAAa,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,17 @@
1
+ import { EnvSchema } from "./types/env.js";
2
+ function loadEnv() {
3
+ const result = EnvSchema.safeParse(process.env);
4
+ if (!result.success) {
5
+ const formatted = result.error.issues
6
+ .map((i) => ` ${i.path.join(".")}: ${i.message}`)
7
+ .join("\n");
8
+ throw new Error(`Invalid environment configuration:\n${formatted}`);
9
+ }
10
+ return result.data;
11
+ }
12
+ export const config = loadEnv();
13
+ // Base-branch parsing lives in ./base-branches.ts so it can be imported by
14
+ // stores without pulling in config validation. Re-exported here for callers
15
+ // that historically imported it from config.
16
+ export { parseBaseBranches } from "./base-branches.js";
17
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,gBAAgB,CAAC;AAErD,SAAS,OAAO;IACd,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aACjD,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,uCAAuC,SAAS,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAQ,OAAO,EAAE,CAAC;AAGrC,2EAA2E;AAC3E,4EAA4E;AAC5E,6CAA6C;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}