@bevel-software/platform-core-backend 0.25.2 → 0.27.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 (733) hide show
  1. package/agent-guide/access-control.md +251 -0
  2. package/agent-guide/conventions.md +35 -0
  3. package/agent-guide/directory-structure.md +145 -0
  4. package/agent-guide/finding-things.md +7 -0
  5. package/agent-guide/html-views.md +83 -0
  6. package/agent-guide/introduction.md +27 -0
  7. package/agent-guide/new-knowledge-base.md +24 -0
  8. package/agent-guide/skills.md +47 -0
  9. package/agent-guide/tool-manuals.md +227 -0
  10. package/agent-guide/where-a-new-file-goes.md +36 -0
  11. package/dist/assets.d.ts +26 -0
  12. package/dist/assets.d.ts.map +1 -1
  13. package/dist/assets.js +35 -2
  14. package/dist/assets.js.map +1 -1
  15. package/dist/core/catalog-revision.d.ts +19 -1
  16. package/dist/core/catalog-revision.d.ts.map +1 -1
  17. package/dist/core/catalog-revision.js +14 -3
  18. package/dist/core/catalog-revision.js.map +1 -1
  19. package/dist/core/core-ports.d.ts +11 -0
  20. package/dist/core/core-ports.d.ts.map +1 -1
  21. package/dist/core/core-ports.js.map +1 -1
  22. package/dist/core/create-core-server.d.ts +5 -0
  23. package/dist/core/create-core-server.d.ts.map +1 -1
  24. package/dist/core/create-core-server.js +65 -3
  25. package/dist/core/create-core-server.js.map +1 -1
  26. package/dist/core/create-core-services.d.ts +35 -0
  27. package/dist/core/create-core-services.d.ts.map +1 -1
  28. package/dist/core/create-core-services.js +114 -12
  29. package/dist/core/create-core-services.js.map +1 -1
  30. package/dist/core/lifecycle.d.ts +16 -1
  31. package/dist/core/lifecycle.d.ts.map +1 -1
  32. package/dist/core/lifecycle.js +18 -1
  33. package/dist/core/lifecycle.js.map +1 -1
  34. package/dist/core-config.d.ts +21 -0
  35. package/dist/core-config.d.ts.map +1 -1
  36. package/dist/core-config.js +22 -1
  37. package/dist/core-config.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/modules/access/access-control.interface.d.ts +17 -0
  43. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  44. package/dist/modules/access/access-control.service.d.ts +2 -0
  45. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-control.service.js +27 -0
  47. package/dist/modules/access/access-control.service.js.map +1 -1
  48. package/dist/modules/access/directory-sync-bot.js +1 -1
  49. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  50. package/dist/modules/agent-guide/agent-guide.d.ts +145 -0
  51. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
  52. package/dist/modules/agent-guide/agent-guide.js +199 -0
  53. package/dist/modules/agent-guide/agent-guide.js.map +1 -0
  54. package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
  55. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
  56. package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
  57. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
  58. package/dist/modules/agent-guide/index.d.ts +4 -0
  59. package/dist/modules/agent-guide/index.d.ts.map +1 -0
  60. package/dist/modules/agent-guide/index.js +4 -0
  61. package/dist/modules/agent-guide/index.js.map +1 -0
  62. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
  63. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  64. package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
  65. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  66. package/dist/modules/agent-instructions/compose.d.ts +14 -6
  67. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  68. package/dist/modules/agent-instructions/compose.js +28 -8
  69. package/dist/modules/agent-instructions/compose.js.map +1 -1
  70. package/dist/modules/agent-instructions/index.d.ts +1 -1
  71. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  72. package/dist/modules/agent-instructions/index.js +1 -1
  73. package/dist/modules/agent-instructions/index.js.map +1 -1
  74. package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
  75. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  76. package/dist/modules/agent-instructions/shared-file-rules.js +51 -104
  77. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  78. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  79. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  80. package/dist/modules/audit/agent-audit.service.js +43 -2
  81. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  82. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  83. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  84. package/dist/modules/audit/agent-connection.routes.js +38 -0
  85. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  86. package/dist/modules/audit/audit.contract.d.ts +24 -0
  87. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  88. package/dist/modules/audit/audit.contract.js.map +1 -1
  89. package/dist/modules/audit/index.d.ts +1 -0
  90. package/dist/modules/audit/index.d.ts.map +1 -1
  91. package/dist/modules/audit/index.js +1 -0
  92. package/dist/modules/audit/index.js.map +1 -1
  93. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  94. package/dist/modules/auth/account.routes.js +10 -3
  95. package/dist/modules/auth/account.routes.js.map +1 -1
  96. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  97. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  98. package/dist/modules/auth/auth.middleware.js +1 -1
  99. package/dist/modules/auth/auth.middleware.js.map +1 -1
  100. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  101. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  102. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  103. package/dist/modules/database/core-schema.d.ts +96 -0
  104. package/dist/modules/database/core-schema.d.ts.map +1 -1
  105. package/dist/modules/database/core-schema.js +32 -3
  106. package/dist/modules/database/core-schema.js.map +1 -1
  107. package/dist/modules/embed/account-link.service.d.ts +42 -0
  108. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  109. package/dist/modules/embed/account-link.service.js +50 -0
  110. package/dist/modules/embed/account-link.service.js.map +1 -0
  111. package/dist/modules/embed/embed-link.d.ts +58 -0
  112. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  113. package/dist/modules/embed/embed-link.js +160 -0
  114. package/dist/modules/embed/embed-link.js.map +1 -0
  115. package/dist/modules/embed/embed.errors.d.ts +30 -0
  116. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  117. package/dist/modules/embed/embed.errors.js +42 -0
  118. package/dist/modules/embed/embed.errors.js.map +1 -0
  119. package/dist/modules/embed/embed.interface.d.ts +204 -0
  120. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  121. package/dist/modules/embed/embed.interface.js +2 -0
  122. package/dist/modules/embed/embed.interface.js.map +1 -0
  123. package/dist/modules/embed/embed.routes.d.ts +59 -0
  124. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  125. package/dist/modules/embed/embed.routes.js +343 -0
  126. package/dist/modules/embed/embed.routes.js.map +1 -0
  127. package/dist/modules/embed/embed.service.d.ts +169 -0
  128. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  129. package/dist/modules/embed/embed.service.js +627 -0
  130. package/dist/modules/embed/embed.service.js.map +1 -0
  131. package/dist/modules/embed/embed.tools.d.ts +51 -0
  132. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  133. package/dist/modules/embed/embed.tools.js +170 -0
  134. package/dist/modules/embed/embed.tools.js.map +1 -0
  135. package/dist/modules/embed/mcp-app.d.ts +64 -0
  136. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  137. package/dist/modules/embed/mcp-app.js +147 -0
  138. package/dist/modules/embed/mcp-app.js.map +1 -0
  139. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  140. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  141. package/dist/modules/embed/mcp-app.routes.js +36 -0
  142. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  143. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  144. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  145. package/dist/modules/github-app/github-app.connection.js +34 -3
  146. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  147. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  148. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  149. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  150. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  151. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  152. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  153. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  154. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  155. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  156. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  158. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +57 -3
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +177 -24
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
  168. package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
  169. package/dist/modules/mcp/tool-schema-guard.js +171 -0
  170. package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
  171. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  172. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  173. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  174. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  175. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  176. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  177. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  178. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  179. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  180. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  181. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  182. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  183. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  184. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  185. package/dist/modules/onboarding/starter-packs.js +156 -0
  186. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  187. package/dist/modules/plugins/index.d.ts +1 -1
  188. package/dist/modules/plugins/index.d.ts.map +1 -1
  189. package/dist/modules/plugins/index.js +1 -1
  190. package/dist/modules/plugins/index.js.map +1 -1
  191. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  192. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  193. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  194. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  195. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  196. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  197. package/dist/modules/plugins/plugins.routes.js +31 -0
  198. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  199. package/dist/modules/plugins/plugins.tools.d.ts +36 -2
  200. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
  201. package/dist/modules/plugins/plugins.tools.js +71 -14
  202. package/dist/modules/plugins/plugins.tools.js.map +1 -1
  203. package/dist/modules/settings/deployment-settings.service.d.ts +34 -7
  204. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  205. package/dist/modules/settings/deployment-settings.service.js +148 -54
  206. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  207. package/dist/modules/settings/repository-source.d.ts +1 -0
  208. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  209. package/dist/modules/settings/repository-source.js +3 -3
  210. package/dist/modules/settings/repository-source.js.map +1 -1
  211. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  212. package/dist/modules/settings/setup.routes.js +3 -6
  213. package/dist/modules/settings/setup.routes.js.map +1 -1
  214. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  215. package/dist/modules/skills/skills.tools.js +103 -43
  216. package/dist/modules/skills/skills.tools.js.map +1 -1
  217. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  218. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  219. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  220. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  221. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  222. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  223. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  224. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  225. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  226. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  227. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  228. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  229. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  230. package/dist/modules/tool-helpers/index.d.ts +28 -1
  231. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  232. package/dist/modules/tool-helpers/index.js +28 -1
  233. package/dist/modules/tool-helpers/index.js.map +1 -1
  234. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  235. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  236. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  237. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  238. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  239. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  240. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  241. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  242. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  243. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  244. package/dist/modules/tool-helpers/tool-def.js +70 -6
  245. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  246. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  247. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  248. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  249. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  250. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  251. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  253. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
  254. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  255. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  256. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
  257. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  258. package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
  259. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  260. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
  261. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  262. package/dist/modules/tool-manuals/tool-manuals.tools.js +77 -38
  263. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  264. package/dist/modules/tool-registry/description-length.d.ts +20 -16
  265. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  266. package/dist/modules/tool-registry/description-length.js +50 -30
  267. package/dist/modules/tool-registry/description-length.js.map +1 -1
  268. package/dist/modules/tool-registry/guide-first.d.ts +27 -0
  269. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
  270. package/dist/modules/tool-registry/guide-first.js +40 -0
  271. package/dist/modules/tool-registry/guide-first.js.map +1 -0
  272. package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
  273. package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
  274. package/dist/modules/tool-registry/tool-registry.js +9 -2
  275. package/dist/modules/tool-registry/tool-registry.js.map +1 -1
  276. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
  277. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
  278. package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
  279. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
  280. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +126 -0
  281. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
  282. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +611 -0
  283. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
  284. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
  285. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
  286. package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
  287. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
  288. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  289. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  290. package/dist/modules/workflow/agent-tools/workflow.tools.js +133 -37
  291. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  292. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  293. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  294. package/dist/modules/workflow/file-lock.service.js +62 -5
  295. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  296. package/dist/modules/workflow/git/git.service.d.ts +282 -14
  297. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  298. package/dist/modules/workflow/git/git.service.js +627 -126
  299. package/dist/modules/workflow/git/git.service.js.map +1 -1
  300. package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
  301. package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
  302. package/dist/modules/workflow/git/merge-commit.js +89 -0
  303. package/dist/modules/workflow/git/merge-commit.js.map +1 -0
  304. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  305. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  306. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  307. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  308. package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
  309. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  310. package/dist/modules/workflow/git/pull-request.service.js +332 -37
  311. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  312. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  313. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  314. package/dist/modules/workflow/pending-commits.service.js +25 -1
  315. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  316. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  317. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  318. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  319. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  320. package/dist/modules/workflow/recovery-bot.js +1 -1
  321. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  322. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
  323. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  324. package/dist/modules/workflow/review-workflow/review-workflow.service.js +180 -13
  325. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  326. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  327. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  328. package/dist/modules/workflow/sanitize-error.js +45 -0
  329. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  330. package/dist/modules/workflow/workflow.routes.d.ts +6 -2
  331. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  332. package/dist/modules/workflow/workflow.routes.js +7 -2
  333. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  334. package/dist/modules/workflow/workflow.service.d.ts +187 -15
  335. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  336. package/dist/modules/workflow/workflow.service.js +808 -106
  337. package/dist/modules/workflow/workflow.service.js.map +1 -1
  338. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  339. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  340. package/dist/modules/workspace/agent-download.builder.js +238 -0
  341. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  342. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  343. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  344. package/dist/modules/workspace/agent-download.routes.js +156 -0
  345. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  346. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  347. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  348. package/dist/modules/workspace/agent-download.store.js +423 -0
  349. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  350. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  351. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  352. package/dist/modules/workspace/agent-upload.store.js +16 -6
  353. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  354. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  355. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  356. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  357. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  358. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  359. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  360. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  361. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  362. package/dist/modules/workspace/first-run.d.ts +111 -0
  363. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  364. package/dist/modules/workspace/first-run.js +181 -0
  365. package/dist/modules/workspace/first-run.js.map +1 -0
  366. package/dist/modules/workspace/move-links.d.ts +92 -0
  367. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  368. package/dist/modules/workspace/move-links.js +173 -0
  369. package/dist/modules/workspace/move-links.js.map +1 -0
  370. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  371. package/dist/modules/workspace/startup/steps/seed-tree.js +24 -39
  372. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  373. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
  374. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  375. package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
  376. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  377. package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
  378. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  379. package/dist/modules/workspace/startup/steps/template-source.js +5 -3
  380. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  381. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  382. package/dist/modules/workspace/workspace.routes.js +63 -22
  383. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  384. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  385. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  386. package/dist/modules/workspace/workspace.service.js +197 -24
  387. package/dist/modules/workspace/workspace.service.js.map +1 -1
  388. package/dist/modules/workspace/workspace.tools.d.ts +68 -1
  389. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  390. package/dist/modules/workspace/workspace.tools.js +642 -71
  391. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  392. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  393. package/dist/modules/write-access/write-access.js +9 -0
  394. package/dist/modules/write-access/write-access.js.map +1 -1
  395. package/dist/shared/domain-errors.d.ts +84 -1
  396. package/dist/shared/domain-errors.d.ts.map +1 -1
  397. package/dist/shared/domain-errors.js +100 -12
  398. package/dist/shared/domain-errors.js.map +1 -1
  399. package/dist/shared/git.contract.d.ts +10 -1
  400. package/dist/shared/git.contract.d.ts.map +1 -1
  401. package/dist/shared/git.contract.js.map +1 -1
  402. package/dist/shared/hidden-tools.d.ts +44 -0
  403. package/dist/shared/hidden-tools.d.ts.map +1 -0
  404. package/dist/shared/hidden-tools.js +13 -0
  405. package/dist/shared/hidden-tools.js.map +1 -0
  406. package/dist/shared/utf8-text.d.ts +15 -0
  407. package/dist/shared/utf8-text.d.ts.map +1 -0
  408. package/dist/shared/utf8-text.js +20 -0
  409. package/dist/shared/utf8-text.js.map +1 -0
  410. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  411. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  412. package/dist/tenancy/static-tenant-source.js +9 -2
  413. package/dist/tenancy/static-tenant-source.js.map +1 -1
  414. package/kb-template/.bevelignore +0 -5
  415. package/mcp-app/page.html +349 -0
  416. package/migrations/0017_atlassian_account_links.sql +37 -0
  417. package/migrations/meta/0017_snapshot.json +2396 -0
  418. package/migrations/meta/_journal.json +8 -1
  419. package/package.json +6 -3
  420. package/src/__tests__/kb-layout-config.test.ts +10 -100
  421. package/src/__tests__/packaged-assets-ship.test.ts +66 -0
  422. package/src/assets.ts +38 -2
  423. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  424. package/src/core/__tests__/gated-boot.test.ts +1 -0
  425. package/src/core/__tests__/lifecycle.test.ts +14 -0
  426. package/src/core/catalog-revision.ts +15 -2
  427. package/src/core/core-ports.ts +11 -0
  428. package/src/core/create-core-server.ts +78 -3
  429. package/src/core/create-core-services.ts +168 -10
  430. package/src/core/lifecycle.ts +28 -2
  431. package/src/core-config.ts +24 -1
  432. package/src/index.ts +2 -2
  433. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
  434. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
  435. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  436. package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
  437. package/src/modules/access/access-control.interface.ts +28 -0
  438. package/src/modules/access/access-control.service.ts +37 -0
  439. package/src/modules/access/directory-sync-bot.ts +1 -1
  440. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +386 -0
  441. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +207 -0
  442. package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
  443. package/src/modules/agent-guide/agent-guide.ts +300 -0
  444. package/src/modules/agent-guide/index.ts +22 -0
  445. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +39 -118
  446. package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
  447. package/src/modules/agent-instructions/compose.ts +26 -8
  448. package/src/modules/agent-instructions/index.ts +0 -3
  449. package/src/modules/agent-instructions/shared-file-rules.ts +50 -112
  450. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  451. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  452. package/src/modules/audit/agent-audit.service.ts +58 -3
  453. package/src/modules/audit/agent-connection.routes.ts +56 -0
  454. package/src/modules/audit/audit.contract.ts +26 -0
  455. package/src/modules/audit/index.ts +1 -0
  456. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  457. package/src/modules/auth/account.routes.ts +10 -3
  458. package/src/modules/auth/auth.middleware.ts +1 -1
  459. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  460. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  461. package/src/modules/database/core-schema.ts +33 -3
  462. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  463. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  464. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  465. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  466. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  467. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  468. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  469. package/src/modules/embed/account-link.service.ts +76 -0
  470. package/src/modules/embed/embed-link.ts +180 -0
  471. package/src/modules/embed/embed.errors.ts +43 -0
  472. package/src/modules/embed/embed.interface.ts +203 -0
  473. package/src/modules/embed/embed.routes.ts +363 -0
  474. package/src/modules/embed/embed.service.ts +705 -0
  475. package/src/modules/embed/embed.tools.ts +206 -0
  476. package/src/modules/embed/mcp-app.routes.ts +41 -0
  477. package/src/modules/embed/mcp-app.ts +173 -0
  478. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  479. package/src/modules/github-app/github-app.connection.ts +38 -4
  480. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  481. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  482. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  483. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  484. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  485. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  486. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
  487. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  488. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  489. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +252 -0
  490. package/src/modules/mcp/__tests__/mcp.service.test.ts +42 -24
  491. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  492. package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
  493. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  494. package/src/modules/mcp/mcp.service.ts +218 -31
  495. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  496. package/src/modules/mcp/tool-schema-guard.ts +196 -0
  497. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  498. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  499. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  500. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  501. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  502. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  503. package/src/modules/onboarding/starter-packs.ts +182 -0
  504. package/src/modules/plugins/__tests__/plugins.tools.test.ts +177 -4
  505. package/src/modules/plugins/index.ts +1 -0
  506. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  507. package/src/modules/plugins/plugins.routes.ts +29 -0
  508. package/src/modules/plugins/plugins.tools.ts +75 -15
  509. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +176 -56
  510. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  511. package/src/modules/settings/deployment-settings.service.ts +144 -55
  512. package/src/modules/settings/repository-source.ts +4 -4
  513. package/src/modules/settings/setup.routes.ts +3 -6
  514. package/src/modules/skills/__tests__/skills.tools.description.test.ts +95 -0
  515. package/src/modules/skills/skills.tools.ts +112 -46
  516. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  517. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  518. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  519. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  520. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  521. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  522. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  523. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  524. package/src/modules/tool-helpers/index.ts +34 -1
  525. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  526. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  527. package/src/modules/tool-helpers/tool-def.ts +132 -7
  528. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  529. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  530. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
  531. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
  532. package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
  533. package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
  534. package/src/modules/tool-manuals/tool-manuals.tools.ts +86 -38
  535. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +173 -0
  536. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +81 -66
  537. package/src/modules/tool-registry/description-length.ts +57 -32
  538. package/src/modules/tool-registry/guide-first.ts +41 -0
  539. package/src/modules/tool-registry/tool-registry.ts +9 -2
  540. package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
  541. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  542. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  543. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  544. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  545. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  546. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  547. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  548. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  549. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  550. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  551. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  552. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  553. package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
  554. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1554 -0
  555. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +150 -4
  556. package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
  557. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +759 -0
  558. package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
  559. package/src/modules/workflow/agent-tools/workflow.tools.ts +137 -35
  560. package/src/modules/workflow/file-lock.service.ts +67 -5
  561. package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
  562. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
  563. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  564. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
  565. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  566. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  567. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
  568. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
  569. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
  570. package/src/modules/workflow/git/git.service.ts +714 -133
  571. package/src/modules/workflow/git/merge-commit.ts +88 -0
  572. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  573. package/src/modules/workflow/git/pull-request.service.ts +380 -54
  574. package/src/modules/workflow/pending-commits.service.ts +29 -1
  575. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  576. package/src/modules/workflow/recovery-bot.ts +1 -1
  577. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
  578. package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
  579. package/src/modules/workflow/review-workflow/review-workflow.service.ts +191 -12
  580. package/src/modules/workflow/sanitize-error.ts +50 -0
  581. package/src/modules/workflow/workflow.routes.ts +7 -2
  582. package/src/modules/workflow/workflow.service.ts +886 -112
  583. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  584. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
  585. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  586. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  587. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  588. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  589. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  590. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  591. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  592. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  593. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  594. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
  595. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  596. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  597. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
  598. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  599. package/src/modules/workspace/__tests__/workspace.tools.test.ts +664 -34
  600. package/src/modules/workspace/agent-download.builder.ts +308 -0
  601. package/src/modules/workspace/agent-download.routes.ts +183 -0
  602. package/src/modules/workspace/agent-download.store.ts +533 -0
  603. package/src/modules/workspace/agent-upload.store.ts +23 -7
  604. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  605. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  606. package/src/modules/workspace/first-run.ts +222 -0
  607. package/src/modules/workspace/move-links.ts +237 -0
  608. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
  609. package/src/modules/workspace/startup/steps/seed-tree.ts +23 -38
  610. package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
  611. package/src/modules/workspace/startup/steps/template-source.ts +5 -3
  612. package/src/modules/workspace/workspace.routes.ts +64 -22
  613. package/src/modules/workspace/workspace.service.ts +202 -25
  614. package/src/modules/workspace/workspace.tools.ts +707 -68
  615. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  616. package/src/modules/write-access/write-access.ts +9 -0
  617. package/src/shared/domain-errors.ts +123 -11
  618. package/src/shared/git.contract.ts +8 -1
  619. package/src/shared/hidden-tools.ts +45 -0
  620. package/src/shared/utf8-text.ts +21 -0
  621. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  622. package/src/tenancy/static-tenant-source.ts +10 -2
  623. package/starter-packs/README.md +103 -0
  624. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  625. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  626. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  627. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  628. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  629. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  630. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  631. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  632. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  633. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  634. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  635. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  636. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  637. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  638. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  639. package/starter-packs/engineering/pack.yaml +11 -0
  640. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  641. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  642. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  643. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  644. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  645. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  646. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  647. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  648. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  649. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  650. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  651. package/starter-packs/general/pack.yaml +10 -0
  652. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  653. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  654. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  655. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  656. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  657. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  658. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  659. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  660. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  661. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  662. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  663. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  664. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  665. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  666. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  667. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  668. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  669. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  670. package/starter-packs/operations/pack.yaml +12 -0
  671. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  672. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  673. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  674. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  675. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  676. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  677. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  678. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  679. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  680. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  681. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  682. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  683. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  684. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  685. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  686. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  687. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  688. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  689. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  690. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  691. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  692. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  693. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  694. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  695. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  696. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  697. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  698. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  699. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  700. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  701. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  702. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  703. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  704. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  705. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  706. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  707. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  708. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  709. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  710. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  711. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  712. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  713. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  714. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  715. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  716. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  717. package/starter-packs/sales/pack.yaml +13 -0
  718. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  719. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  720. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  721. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  722. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  723. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  724. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  725. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  726. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  727. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  728. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  729. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  730. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  731. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  732. package/starter-packs/support/pack.yaml +12 -0
  733. package/kb-template/AGENTS.md +0 -730
@@ -0,0 +1,407 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ import { PgDialect } from 'drizzle-orm/pg-core';
4
+ import type { SQL } from 'drizzle-orm';
5
+ import { ReviewWorkflowService } from '../review-workflow.service.js';
6
+ import { changeRequests, prMergeLog } from '../../../database/schema.js';
7
+ import type { Database } from '../../../database/connection.js';
8
+ import type { WorkspaceService } from '../../../workspace/workspace.service.js';
9
+ import type { GitService } from '../../git/git.service.js';
10
+ import type { AppliedMergeResult, AuthUser, FileApprovalState } from '@bevel-software/platform-shared';
11
+ import type { IAccessControl } from '../../../access/access-control.interface.js';
12
+
13
+ /**
14
+ * What `mergePr` records as a change request's `merged_sha`, which is the only
15
+ * thing an applied request's files can later be read from.
16
+ *
17
+ * Two shas come back from the merge and they are not interchangeable: `sha` is
18
+ * the state the target is left at, `mergeCommit` is the commit this request
19
+ * owns. Recording the first publishes another request's files under this
20
+ * request's number (cubic P1 on #347); dropping the second when a retry finds
21
+ * nothing left to merge makes an already-merged request permanently fileless
22
+ * (cubic P2 on #347). The row must take `mergeCommit`, exactly.
23
+ */
24
+
25
+ const USER: AuthUser = { id: 'u-alice', email: 'alice@bevel.software', name: 'Alice' };
26
+ const BASE = 'current-company-state';
27
+ const HEAD_SHA = 'head-sha-1';
28
+
29
+ const CR_ROW = {
30
+ id: 'cr-1',
31
+ number: 12,
32
+ sourceBranch: 'alice/add',
33
+ targetBranch: BASE,
34
+ title: 'Add a note',
35
+ body: '',
36
+ authorEmail: 'alice@bevel.software',
37
+ authorName: 'Alice',
38
+ state: 'open',
39
+ };
40
+
41
+ /**
42
+ * One approved file, which is all the gate needs to let the merge through: an
43
+ * empty approval set is itself a hard block ("no file changes to approve"), so
44
+ * the "nothing to merge" cases below are about git finding the change already on
45
+ * the target, never about a request with no files.
46
+ */
47
+ const APPROVED: FileApprovalState = {
48
+ path: 'Knowledge/Ops/note.md',
49
+ eligibleApprovers: { roles: ['Ops'], users: [] },
50
+ approvedBy: [
51
+ {
52
+ name: 'Olga',
53
+ email: 'olga@bevel.software',
54
+ approvedAt: '2026-10-02T10:00:00Z',
55
+ isStale: false,
56
+ isSelfApproval: false,
57
+ },
58
+ ],
59
+ eligibilityResolved: true,
60
+ isApproved: true,
61
+ inMergeGate: true,
62
+ viewerCanApprove: false,
63
+ };
64
+
65
+ /**
66
+ * Every write the merge performs, in order, with the table it targeted and
67
+ * whether it was an insert or an update — the ORDER is part of what one of the
68
+ * tests below pins.
69
+ */
70
+ type Captured = { kind: 'insert' | 'update'; table: unknown; values: Record<string, unknown> };
71
+
72
+ /**
73
+ * What the row says, and whether the CAS wins, are PARAMETERS, not constants.
74
+ * `mergePr` and `finalizeAlreadyApplied` both re-validate the lifecycle against
75
+ * the row rather than against the caller's `state` argument, and a stub that
76
+ * always answers `open` and always wins leaves those paths unreachable — a
77
+ * regression that dropped one would still pass (cubic P3 on #347).
78
+ *
79
+ * `rows` is consumed in order and its last entry repeats, so a test can say
80
+ * what the row looked like when the attempt started and what it says when the
81
+ * lost CAS is explained.
82
+ */
83
+ function makeDb(
84
+ captured: Captured[],
85
+ {
86
+ rows = [CR_ROW],
87
+ casWon = true,
88
+ begunLog = true,
89
+ }: { rows?: Record<string, unknown>[]; casWon?: boolean; begunLog?: boolean } = {},
90
+ ): Database {
91
+ let crReads = 0;
92
+ const thenable = (data: unknown) => {
93
+ const p = Promise.resolve(data) as Promise<unknown> & { limit: () => Promise<unknown> };
94
+ p.limit = () => Promise.resolve(data);
95
+ return p;
96
+ };
97
+ /**
98
+ * What the CAS compares against: the row's state at the moment of the
99
+ * update. `casWon` says the row is still open then; a lost CAS means it is
100
+ * what the LAST row says (the terminal state the lost CAS is explained by).
101
+ */
102
+ const stateAtCas = casWon ? 'open' : String(rows[rows.length - 1]?.state ?? 'merged');
103
+ return {
104
+ select: () => ({
105
+ from: (table: unknown) => ({
106
+ where: () => {
107
+ // The merge log begun by the attempt that pushed and crashed —
108
+ // `succeeded: false`, no error — is what makes a retry look for its
109
+ // commit at all.
110
+ if (table === prMergeLog) return thenable(begunLog ? [{ id: 'merge-log-0' }] : []);
111
+ if (table !== changeRequests) return thenable([]);
112
+ const row = rows[Math.min(crReads, rows.length - 1)];
113
+ crReads += 1;
114
+ return thenable([row]);
115
+ },
116
+ }),
117
+ }),
118
+ insert: (table: unknown) => ({
119
+ values: (values: Record<string, unknown>) => {
120
+ captured.push({ kind: 'insert', table, values });
121
+ return { returning: async () => [{ id: 'merge-log-1' }] };
122
+ },
123
+ }),
124
+ update: (table: unknown) => ({
125
+ set: (values: Record<string, unknown>) => {
126
+ captured.push({ kind: 'update', table, values });
127
+ return {
128
+ where: (predicate: SQL) => {
129
+ const t = thenable(undefined) as unknown as Promise<unknown> & {
130
+ returning: () => Promise<unknown>;
131
+ };
132
+ // The CAS is answered FROM THE WHERE CLAUSE, not from a flag: the
133
+ // update of the change request row lands only if the row is in
134
+ // the state the predicate asks for. A predicate that no longer
135
+ // guards on `state = 'open'` would therefore land on a terminal
136
+ // row here — which is the race this test file exists to pin, so
137
+ // dropping the guard fails the lost-CAS tests below instead of
138
+ // passing them by a stub that answered from `casWon` alone.
139
+ const { sql, params } = new PgDialect().sqlToQuery(predicate);
140
+ const guardsOpen = /"state"/.test(sql) && params.includes('open');
141
+ const lands = table !== changeRequests || !guardsOpen || stateAtCas === 'open';
142
+ t.returning = () => Promise.resolve(lands ? [{ id: CR_ROW.id }] : []);
143
+ return t;
144
+ },
145
+ };
146
+ },
147
+ }),
148
+ } as unknown as Database;
149
+ }
150
+
151
+ function makeService(mergeResult: AppliedMergeResult) {
152
+ const captured: Captured[] = [];
153
+ // Typed by its signature rather than its body, so the recorded call keeps the
154
+ // arguments the assertion below reads out of it.
155
+ const mergeChangeRequest = vi.fn<(...args: unknown[]) => Promise<AppliedMergeResult>>(
156
+ async () => mergeResult,
157
+ );
158
+ const workspace = {
159
+ ensureRemotesFetched: vi.fn(async () => undefined),
160
+ getOrCreateForBranch: vi.fn(async () => ({ id: 'ws-base' })),
161
+ } as unknown as WorkspaceService;
162
+ const svc = new ReviewWorkflowService(
163
+ makeDb(captured),
164
+ {} as unknown as IAccessControl,
165
+ workspace,
166
+ { mergeChangeRequest } as unknown as GitService,
167
+ );
168
+ const merge = () =>
169
+ svc.mergePr(CR_ROW.number, USER, HEAD_SHA, [APPROVED], 'open', CR_ROW.title, BASE, 'ws-1');
170
+ /** The values written to the change request row (not the merge log). */
171
+ const crUpdate = () => captured.find((c) => c.kind === 'update' && c.table === changeRequests)?.values;
172
+ return { merge, crUpdate, mergeChangeRequest };
173
+ }
174
+
175
+ describe('mergePr — which commit the row records', () => {
176
+ it('records the merge commit this request owns, not the state the target is left at', async () => {
177
+ const { merge, crUpdate } = makeService({
178
+ kind: 'merged',
179
+ sha: 'target-tip-which-is-another-requests-merge',
180
+ mergeCommit: 'this-requests-own-merge',
181
+ });
182
+
183
+ const result = await merge();
184
+
185
+ expect(crUpdate()).toMatchObject({ state: 'merged', mergedSha: 'this-requests-own-merge' });
186
+ // The caller still hears the state the target is at, which is what "merged"
187
+ // means for the request even when it is not a commit of its own.
188
+ expect(result.sha).toBe('target-tip-which-is-another-requests-merge');
189
+ });
190
+
191
+ it('records nothing when the request owns no merge commit', async () => {
192
+ // The genuinely empty request: the target already contained its change, and
193
+ // no attempt of its own ever wrote a commit. It has no file list to lose, and
194
+ // the target tip it is merged at belongs to somebody else.
195
+ const { merge, crUpdate } = makeService({
196
+ kind: 'merged',
197
+ sha: 'target-tip-which-is-another-requests-merge',
198
+ mergeCommit: null,
199
+ });
200
+
201
+ await merge();
202
+
203
+ expect(crUpdate()).toMatchObject({ state: 'merged', mergedSha: null });
204
+ });
205
+
206
+ it('names the request to the merge, so a retry can recognise its own commit', async () => {
207
+ // Without the number, a merge that finds nothing to merge cannot tell an
208
+ // empty request from one whose merge commit a previous attempt pushed before
209
+ // the row update failed — and would record null for both.
210
+ const { merge, mergeChangeRequest } = makeService({
211
+ kind: 'merged',
212
+ sha: 'tip',
213
+ mergeCommit: 'own',
214
+ });
215
+
216
+ await merge();
217
+
218
+ expect(mergeChangeRequest).toHaveBeenCalledTimes(1);
219
+ const opts = mergeChangeRequest.mock.calls[0][5] as { appliedChangeNumber?: number };
220
+ expect(opts.appliedChangeNumber).toBe(CR_ROW.number);
221
+ });
222
+ });
223
+
224
+ /**
225
+ * The retry the row-recording fix is FOR, reached the way production reaches it.
226
+ *
227
+ * When an attempt pushes the merge commit and then fails to write the row, the
228
+ * request stays open with its change already on the target — so it differs from
229
+ * the target by nothing, its file list is empty, and the approval gate refuses
230
+ * the next attempt ("no file changes to approve") before any merge runs. The
231
+ * recovery inside `mergeChangeRequest` is therefore never reached from the app,
232
+ * and the request stays fileless and un-appliable for good. The gate has to ask
233
+ * the ownership question itself.
234
+ */
235
+ function makeStuckService(
236
+ own: string | null,
237
+ // The two states a merge is decided against, kept APART because the service
238
+ // treats them apart: `gateState` is the caller's (what drove the gate it
239
+ // clicked), `rowState` is the authoritative row's. A stale caller passing
240
+ // 'open' for a row that is already terminal is exactly what the row-level
241
+ // guard is for.
242
+ {
243
+ gateState = 'open',
244
+ rowState = 'open',
245
+ casWon = true,
246
+ // What the row says when the lost CAS is explained — a cancel closes a
247
+ // request just as a merge does, and the audit line must not guess.
248
+ stateAfterCas = 'merged',
249
+ // Whether an earlier attempt left a begun, never-completed merge-log row —
250
+ // the only evidence that there may be a pushed commit to recover.
251
+ begunLog = true,
252
+ }: {
253
+ gateState?: string;
254
+ rowState?: string;
255
+ casWon?: boolean;
256
+ stateAfterCas?: string;
257
+ begunLog?: boolean;
258
+ } = {},
259
+ ) {
260
+ const rows = [{ ...CR_ROW, state: rowState }, { ...CR_ROW, state: stateAfterCas }];
261
+ const captured: Captured[] = [];
262
+ const mergeChangeRequest = vi.fn<(...args: unknown[]) => Promise<AppliedMergeResult>>(async () => ({
263
+ kind: 'merged',
264
+ sha: 'should-not-be-reached',
265
+ mergeCommit: null,
266
+ }));
267
+ const appliedMergeCommitOnTarget = vi.fn(async () => own);
268
+ const workspace = {
269
+ getOrCreateForBranch: vi.fn(async () => ({ id: 'ws-base' })),
270
+ } as unknown as WorkspaceService;
271
+ const svc = new ReviewWorkflowService(
272
+ makeDb(captured, { rows, casWon, begunLog }),
273
+ {} as unknown as IAccessControl,
274
+ workspace,
275
+ { mergeChangeRequest, appliedMergeCommitOnTarget } as unknown as GitService,
276
+ );
277
+ // NO approvals: an already-applied request has no file to approve, which is
278
+ // exactly what makes it indistinguishable from an empty one at the gate.
279
+ const merge = () =>
280
+ svc.mergePr(CR_ROW.number, USER, HEAD_SHA, [], gateState as 'open', CR_ROW.title, BASE, 'ws-1');
281
+ const crUpdate = () => captured.find((c) => c.kind === 'update' && c.table === changeRequests)?.values;
282
+ return { merge, crUpdate, captured, mergeChangeRequest, appliedMergeCommitOnTarget, workspace };
283
+ }
284
+
285
+ describe('mergePr — a request whose merge commit was pushed but never recorded', () => {
286
+ it('asks git only when the database shows an attempt that began and never completed', async () => {
287
+ // A genuinely empty request — nothing on the target, no attempt behind it
288
+ // — is refused as it always was, and the refusal touches no clone and no
289
+ // remote: the probe that fetches the target runs only behind the one
290
+ // piece of evidence a crashed attempt leaves, its begun merge-log row.
291
+ const { merge, appliedMergeCommitOnTarget, workspace } = makeStuckService('a-commit', { begunLog: false });
292
+ await expect(merge()).rejects.toThrow(/no file changes to approve/i);
293
+ expect(appliedMergeCommitOnTarget).not.toHaveBeenCalled();
294
+ expect((workspace as unknown as { getOrCreateForBranch: ReturnType<typeof vi.fn> }).getOrCreateForBranch).not.toHaveBeenCalled();
295
+ });
296
+
297
+ it('hands the stored title to the probe, so a merge commit in the old message format is recognised', async () => {
298
+ const { merge, appliedMergeCommitOnTarget } = makeStuckService('a-commit');
299
+ await merge();
300
+ expect(appliedMergeCommitOnTarget).toHaveBeenCalledWith('ws-base', BASE, CR_ROW.number, CR_ROW.title);
301
+ });
302
+
303
+ it('records that commit instead of refusing, and merges nothing', async () => {
304
+ const { merge, crUpdate, mergeChangeRequest, appliedMergeCommitOnTarget } =
305
+ makeStuckService('the-commit-the-first-attempt-pushed');
306
+
307
+ const result = await merge();
308
+
309
+ expect(crUpdate()).toMatchObject({
310
+ state: 'merged',
311
+ mergedSha: 'the-commit-the-first-attempt-pushed',
312
+ });
313
+ expect(result.sha).toBe('the-commit-the-first-attempt-pushed');
314
+ // Asked of the TARGET branch's own workspace, for this request's number.
315
+ expect(appliedMergeCommitOnTarget).toHaveBeenCalledWith('ws-base', BASE, CR_ROW.number, CR_ROW.title);
316
+ // Nothing was merged, committed or pushed: the merge already happened.
317
+ expect(mergeChangeRequest).not.toHaveBeenCalled();
318
+ });
319
+
320
+ it('still refuses a request that owns no merge commit', async () => {
321
+ // The genuinely empty request. The hard block is the right answer for it,
322
+ // and the probe is what tells the two apart.
323
+ const { merge, crUpdate, mergeChangeRequest } = makeStuckService(null);
324
+
325
+ await expect(merge()).rejects.toThrow('no file changes to approve');
326
+
327
+ expect(crUpdate()).toBeUndefined();
328
+ expect(mergeChangeRequest).not.toHaveBeenCalled();
329
+ });
330
+
331
+ it('does not go looking when the hard block is the request\'s state', async () => {
332
+ // A closed or merged request earns its own refusal, and must keep it: the
333
+ // recovery is only ever about the empty-file-set block.
334
+ const { merge, appliedMergeCommitOnTarget, crUpdate } = makeStuckService('a-commit', { gateState: 'closed', rowState: 'closed' });
335
+
336
+ await expect(merge()).rejects.toThrow('This pull request is closed.');
337
+
338
+ expect(appliedMergeCommitOnTarget).not.toHaveBeenCalled();
339
+ expect(crUpdate()).toBeUndefined();
340
+ });
341
+
342
+ it('refuses when the AUTHORITATIVE row is already terminal, whatever the caller passed', async () => {
343
+ // The caller's gate inputs were resolved before a concurrent apply landed,
344
+ // so it still says `open` while the row says `merged`. The recovery runs
345
+ // ahead of `mergePr`'s own lifecycle re-check, so its own row-state guard is
346
+ // the only thing between a stale click and a second finalization.
347
+ const { merge, crUpdate, appliedMergeCommitOnTarget } = makeStuckService('a-commit', {
348
+ gateState: 'open',
349
+ rowState: 'merged',
350
+ });
351
+
352
+ await expect(merge()).rejects.toThrow('no file changes to approve');
353
+
354
+ expect(appliedMergeCommitOnTarget).not.toHaveBeenCalled();
355
+ expect(crUpdate()).toBeUndefined();
356
+ });
357
+
358
+ it('logs the attempt before it finalizes the row, and completes the log after', async () => {
359
+ // Order matters: a log write that failed after the CAS would leave a request
360
+ // recorded as merged with no attempt behind it, and the caller erroring on a
361
+ // request that IS merged. The attempt goes in first, carrying the commit it
362
+ // is about, and is completed once the row is won.
363
+ const { merge, captured } = makeStuckService('the-commit-the-first-attempt-pushed');
364
+
365
+ await merge();
366
+
367
+ const inserted = captured.find((c) => c.kind === 'insert');
368
+ expect(inserted?.values).toMatchObject({ succeeded: false, mergeMethod: 'merge' });
369
+ expect(String(inserted?.values.error)).toContain('the-commit-the-first-attempt-pushed');
370
+ // The log's completion comes AFTER the change request row is finalized.
371
+ const order = captured.map((c) => (c.kind === 'insert' ? 'log-insert' : c.table === changeRequests ? 'cr' : 'log-update'));
372
+ expect(order).toEqual(['log-insert', 'cr', 'log-update']);
373
+ expect(captured[2].values).toMatchObject({ succeeded: true });
374
+ });
375
+ it('names the state the row actually ended in when the CAS is lost to a CANCEL', async () => {
376
+ // A cancel closes a request exactly as a merge does, and both lose this CAS.
377
+ // Filing a cancellation as "a concurrent merge won" sends whoever reads
378
+ // pr_merge_log afterwards to the wrong cause (cubic P2 on #347).
379
+ const { merge, captured } = makeStuckService('a-commit', {
380
+ casWon: false,
381
+ stateAfterCas: 'closed',
382
+ });
383
+
384
+ await expect(merge()).rejects.toThrow('This change request is closed.');
385
+
386
+ const logUpdate = captured.filter((c) => c.kind === 'update' && c.table !== changeRequests).pop();
387
+ expect(logUpdate?.values).toMatchObject({ succeeded: false });
388
+ expect(String(logUpdate?.values.error)).toBe(
389
+ 'Change request was no longer open (closed); this attempt did not finalize it.',
390
+ );
391
+ expect(String(logUpdate?.values.error)).not.toContain('concurrent');
392
+ });
393
+
394
+ it('says merged, and refuses as merged, when the CAS is lost to another merge', async () => {
395
+ const { merge, captured } = makeStuckService('a-commit', {
396
+ casWon: false,
397
+ stateAfterCas: 'merged',
398
+ });
399
+
400
+ await expect(merge()).rejects.toThrow('This change request has already been merged.');
401
+
402
+ const logUpdate = captured.filter((c) => c.kind === 'update' && c.table !== changeRequests).pop();
403
+ expect(String(logUpdate?.values.error)).toBe(
404
+ 'Change request was no longer open (merged); this attempt did not finalize it.',
405
+ );
406
+ });
407
+ });
@@ -1,4 +1,4 @@
1
- import { and, asc, eq } from 'drizzle-orm';
1
+ import { and, asc, eq, isNull } from 'drizzle-orm';
2
2
  import { logger } from '../../../shared/logging.js';
3
3
 
4
4
  const log = logger('review-workflow');
@@ -18,6 +18,7 @@ import { changeRequests, prComments, prFileApprovals, prMergeLog, users } from '
18
18
  import { AccessUnreadableError } from '../../access-model/access-errors.js';
19
19
  import type { IAccessControl } from '../../access/access-control.interface.js';
20
20
  import type { GitService } from '../git/git.service.js';
21
+ import { mergeCommitSubject } from '../git/merge-commit.js';
21
22
  import { redactSecret } from '../../../shared/redact-secret.js';
22
23
  import {
23
24
  ChangeRequestConflictsError,
@@ -44,6 +45,14 @@ import {
44
45
  // trail — squashing would collapse them into one. A merge commit preserves every
45
46
  // commit on the branch.
46
47
  const MERGE_METHOD = 'merge' as const;
48
+
49
+ /**
50
+ * The hard block an empty file set earns — named, because two places have to
51
+ * agree on it: the gate that raises it, and `finalizeAlreadyApplied`, which
52
+ * recognises it as the one refusal that can mean "already applied" rather than
53
+ * "nothing to apply".
54
+ */
55
+ const NOTHING_TO_APPROVE = 'This pull request has no file changes to approve.';
47
56
  const EVERYONE_CANONICAL = 'everyone';
48
57
 
49
58
  /**
@@ -239,7 +248,7 @@ function evaluateGateParts(input: MergeGateInput): { hardReasons: string[]; warn
239
248
  // mergeable either — a PR that touches no files shouldn't be opened in
240
249
  // the first place, let alone merged.
241
250
  if (input.approvals.length === 0 && input.state === 'open') {
242
- reasons.push('This pull request has no file changes to approve.');
251
+ reasons.push(NOTHING_TO_APPROVE);
243
252
  }
244
253
 
245
254
  // Ownership enforcement binds every file with an eligible approver,
@@ -469,7 +478,8 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
469
478
  if (batch) viewerCanApproveByPath = batch;
470
479
  } catch (err) {
471
480
  if (err instanceof AccessUnreadableError) throw err;
472
- log.warn(`canWriteBatchAtRef failed for PR #${prNumber} viewer=${viewerEmail}:`, { err });
481
+ // The viewer is not named: log lines carry no email addresses.
482
+ log.warn(`canWriteBatchAtRef failed for PR #${prNumber} (for the viewer):`, { err });
473
483
  }
474
484
  }
475
485
  }
@@ -671,7 +681,17 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
671
681
  // opted into bypass; with bypass, the bypassed warnings get inlined in the
672
682
  // merge commit body so git history captures the decision.
673
683
  const gate = evaluateGateParts({ prNumber, state, approvals });
674
- if (gate.hardReasons.length > 0) throw new MergeBlockedError(gate.hardReasons);
684
+ if (gate.hardReasons.length > 0) {
685
+ // One of those reasons can mean the opposite of what it says. "No file
686
+ // changes to approve" is also what an ALREADY APPLIED request looks like:
687
+ // its change is on the target, so it differs from it by nothing. That is
688
+ // the state a request is left in when an attempt pushed its merge commit
689
+ // and then failed to record the row — and refusing the retry here is what
690
+ // kept the recovery inside the merge from ever running (cubic P2 on #347).
691
+ const recovered = await this.finalizeAlreadyApplied(prNumber, user, headSha, gate.hardReasons);
692
+ if (recovered) return recovered;
693
+ throw new MergeBlockedError(gate.hardReasons);
694
+ }
675
695
  if (gate.warnings.length > 0 && !opts.bypass) {
676
696
  throw new MergeBlockedError(gate.warnings);
677
697
  }
@@ -730,7 +750,9 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
730
750
  // Attribution lives on the merge commit itself (authored as the human
731
751
  // triggerer). When bypass is used, the bypassed warnings are appended so the
732
752
  // decision survives in git history — no separate audit table needed.
733
- const subject = `${prTitle} (#${prNumber})`;
753
+ // One place builds this, one place reads it back — `merge-commit.ts` says
754
+ // why an applied request's reader has to verify the subject at all.
755
+ const subject = mergeCommitSubject(prTitle, prNumber);
734
756
  const bypassFooter =
735
757
  opts.bypass && gate.warnings.length > 0
736
758
  ? `\n\nApproval requirements bypassed:\n${gate.warnings.map((w) => `- ${w}`).join('\n')}`
@@ -750,6 +772,10 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
750
772
  cr.targetBranch,
751
773
  { subject, body },
752
774
  user,
775
+ // Named so a merge that finds nothing to merge can tell an empty request
776
+ // apart from one a previous attempt already merged and failed to record
777
+ // (see `ownMergeCommitOn`).
778
+ { appliedChangeNumber: prNumber, appliedChangeTitle: cr.title },
753
779
  );
754
780
  } catch (err) {
755
781
  const redacted = redactTokens(err instanceof Error ? err.message : String(err), this.git.credentials?.token());
@@ -780,16 +806,35 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
780
806
  const completedAt = new Date();
781
807
  const [updatedCr] = await this.db
782
808
  .update(changeRequests)
783
- .set({ state: 'merged', mergedSha: mergeResult.sha, closedAt: completedAt, updatedAt: completedAt })
809
+ .set({
810
+ state: 'merged',
811
+ // Only the commit this request OWNS, never `sha`. With nothing to merge
812
+ // `sha` is the target tip — usually another request's merge commit — and
813
+ // recording it here would let this request be read back with that other
814
+ // request's files under its number (cubic P1 on #347).
815
+ //
816
+ // `mergeCommit` is also what makes a retry after a failed finalization
817
+ // idempotent: the merge commit a previous attempt pushed is found and
818
+ // recorded instead of being dropped as "nothing was merged" (cubic P2 on
819
+ // #347). It is null only when the request has no merge commit at all,
820
+ // and such a request has no file list to lose.
821
+ mergedSha: mergeResult.mergeCommit,
822
+ closedAt: completedAt,
823
+ updatedAt: completedAt,
824
+ })
784
825
  .where(and(eq(changeRequests.id, cr.id), eq(changeRequests.state, 'open')))
785
826
  .returning({ id: changeRequests.id });
786
827
  if (!updatedCr) {
787
- // A concurrent merge won the CAS between our lifecycle re-check and here.
788
- // Our own git merge was a harmless idempotent no-op, but this attempt did
789
- // NOT finalize the CR. Finalize this log row with an explanatory error and
790
- // a completedAt so it doesn't linger as a phantom `succeeded=false,
791
- // error=null` entry that a "failed merges" audit query would misread.
792
- const raceError = 'Change request was merged by a concurrent request; this attempt did not finalize it.';
828
+ // Something else moved the row out of `open` between our lifecycle
829
+ // re-check and here. Our own git merge was a harmless idempotent no-op,
830
+ // but this attempt did NOT finalize the CR. Finalize this log row with an
831
+ // explanatory error and a completedAt so it doesn't linger as a phantom
832
+ // `succeeded=false, error=null` entry that a "failed merges" audit query
833
+ // would misread — and say only what the lost CAS proves, which is that the
834
+ // row was no longer open: a cancel closes it too, and naming a concurrent
835
+ // MERGE would file that cancellation under the wrong cause (cubic P2 on
836
+ // #347, raised against the copy of this line in `finalizeAlreadyApplied`).
837
+ const raceError = 'Change request was no longer open; this attempt did not finalize it.';
793
838
  await this.db
794
839
  .update(prMergeLog)
795
840
  .set({ succeeded: false, completedAt, error: raceError })
@@ -808,6 +853,140 @@ export class ReviewWorkflowService implements IReviewWorkflowService {
808
853
  };
809
854
  }
810
855
 
856
+ /**
857
+ * Record a change request that is ALREADY applied — its merge commit is on the
858
+ * target — and that the gate was about to refuse for having nothing to merge.
859
+ * Null when the request owns no such commit, which is every other reason an
860
+ * approval set can be empty.
861
+ *
862
+ * The case this exists for: an attempt pushed the merge commit and then failed
863
+ * to write the row (a transient database fault in between). The request stays
864
+ * `open` while its change is on the target, so the diff that produces its file
865
+ * list — and with it every approval the gate reads — is empty. The retry is
866
+ * then refused BEFORE the merge runs, which is where the recovery inside
867
+ * `mergeChangeRequest` sits, so the request can never be finalized: it is
868
+ * fileless, author-only, and un-appliable for good, with the commit holding
869
+ * its files sitting on the target unreferenced (cubic P2 on #347).
870
+ *
871
+ * Nothing is merged, committed or pushed here. The only write is the row that
872
+ * should have been written by the attempt that pushed, and the merge-log entry
873
+ * that says this caller finalized it.
874
+ *
875
+ * Narrow on purpose:
876
+ * • exactly ONE hard reason, and it is the empty-file-set one. A request that
877
+ * is already `merged` or `closed` has its own reason and must keep it.
878
+ * • the row must still be `open`, read here and re-checked by the CAS below.
879
+ * • the commit must be the request's OWN, by the same subject predicate the
880
+ * reader verifies with (`mergeCommitSubjectNames`), so this records only
881
+ * what `appliedChangeShas` will accept. No branch name a user can choose
882
+ * can produce that subject — `assertValidBranchName` forbids both the space
883
+ * and the `#` the agent `merge_branch` subject would need.
884
+ *
885
+ * Who may trigger it needs no extra check: the app resolves the request's
886
+ * detail for the caller first and refuses when it answers nothing, and a
887
+ * request with no readable file list is visible to its author alone. So the
888
+ * author is the one who can finalize their own stuck request.
889
+ */
890
+ private async finalizeAlreadyApplied(
891
+ prNumber: number,
892
+ user: AuthUser,
893
+ headSha: string,
894
+ hardReasons: string[],
895
+ ): Promise<MergePrResult | null> {
896
+ if (hardReasons.length !== 1 || hardReasons[0] !== NOTHING_TO_APPROVE) return null;
897
+
898
+ const [cr] = await this.db
899
+ .select()
900
+ .from(changeRequests)
901
+ .where(eq(changeRequests.number, prNumber))
902
+ .limit(1);
903
+ if (!cr || cr.state !== 'open' || !cr.targetBranch) return null;
904
+
905
+ // The cheap question first, of the database alone: did an attempt on this
906
+ // request push and then crash before recording? Such an attempt leaves its
907
+ // log row begun and never completed — `succeeded: false` with no error,
908
+ // the shape no finished attempt has. Without one there is nothing to
909
+ // recover, and a genuinely empty request is refused here as it always
910
+ // was, without a clone being created or a remote fetched on its behalf.
911
+ const [begun] = await this.db
912
+ .select({ id: prMergeLog.id })
913
+ .from(prMergeLog)
914
+ .where(and(eq(prMergeLog.prNumber, prNumber), eq(prMergeLog.succeeded, false), isNull(prMergeLog.error)))
915
+ .limit(1);
916
+ if (!begun) return null;
917
+
918
+ const baseWorkspace = await this.workspaceService.getOrCreateForBranch(cr.targetBranch);
919
+ const own = await this.git.appliedMergeCommitOnTarget(baseWorkspace.id, cr.targetBranch, prNumber, cr.title);
920
+ if (!own) return null;
921
+
922
+ // The attempt is logged BEFORE the row is finalized, in the same order the
923
+ // merge path logs its own: a log write that fails must not leave a request
924
+ // recorded as merged with no attempt behind it, and failing here fails
925
+ // before anything is committed, so the retry is clean (cubic P2 on #347).
926
+ const triggeredByEmail = canonicalEmail(user.email);
927
+ const [logRow] = await this.db
928
+ .insert(prMergeLog)
929
+ .values({
930
+ prNumber,
931
+ triggeredByEmail,
932
+ triggeredByEmailBidx: triggeredByEmail,
933
+ triggeredByName: user.name,
934
+ headShaAtMerge: headSha,
935
+ mergeMethod: MERGE_METHOD,
936
+ succeeded: false,
937
+ // Not an error — the one thing a reader of this row has to know is that
938
+ // no merge was performed under it, because it had already been
939
+ // performed. Written at the start so it survives a crash in between.
940
+ error: `Finalizing an earlier attempt's merge commit ${own}; nothing is merged by this one.`,
941
+ })
942
+ .returning({ id: prMergeLog.id });
943
+
944
+ // Same CAS as the merge path: a concurrent finalization wins and we say so
945
+ // rather than overwriting its terminal state.
946
+ const completedAt = new Date();
947
+ const [updated] = await this.db
948
+ .update(changeRequests)
949
+ .set({ state: 'merged', mergedSha: own, closedAt: completedAt, updatedAt: completedAt })
950
+ .where(and(eq(changeRequests.id, cr.id), eq(changeRequests.state, 'open')))
951
+ .returning({ id: changeRequests.id });
952
+ if (!updated) {
953
+ // The CAS loses to ANY terminal transition, not just a merge: `cancelPr`
954
+ // closes a request the same way. So the row is re-read and the reason it
955
+ // actually carries is what goes into the audit line and into the refusal
956
+ // — a cancellation filed as "a concurrent merge won" sends whoever reads
957
+ // pr_merge_log after the fact to the wrong cause (cubic P2 on #347).
958
+ const [current] = await this.db
959
+ .select({ state: changeRequests.state })
960
+ .from(changeRequests)
961
+ .where(eq(changeRequests.id, cr.id))
962
+ .limit(1);
963
+ const raceError =
964
+ `Change request was no longer open (${current?.state ?? 'row missing'}); this attempt did not finalize it.`;
965
+ await this.db
966
+ .update(prMergeLog)
967
+ .set({ succeeded: false, completedAt, error: raceError })
968
+ .where(eq(prMergeLog.id, logRow.id));
969
+ throw new MergeBlockedError([
970
+ current?.state === 'merged'
971
+ ? 'This change request has already been merged.'
972
+ : 'This change request is closed.',
973
+ ]);
974
+ }
975
+ await this.db
976
+ .update(prMergeLog)
977
+ .set({
978
+ succeeded: true,
979
+ completedAt,
980
+ error: `Finalized an earlier attempt's merge commit ${own}; nothing was merged by this one.`,
981
+ })
982
+ .where(eq(prMergeLog.id, logRow.id));
983
+ log.warn(
984
+ `change request #${prNumber} was already merged as ${own} but its row was never updated; recorded it now.`,
985
+ );
986
+
987
+ return { prNumber, sha: own, mergedAt: completedAt.toISOString() };
988
+ }
989
+
811
990
  async cancelPr(
812
991
  prNumber: number,
813
992
  user: AuthUser,