@bevel-software/platform-core-backend 0.26.0 → 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 (613) hide show
  1. package/agent-guide/access-control.md +17 -0
  2. package/agent-guide/conventions.md +8 -0
  3. package/agent-guide/html-views.md +83 -0
  4. package/agent-guide/new-knowledge-base.md +24 -0
  5. package/agent-guide/tool-manuals.md +10 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.d.ts.map +1 -1
  8. package/dist/assets.js +26 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/core/catalog-revision.d.ts +19 -1
  11. package/dist/core/catalog-revision.d.ts.map +1 -1
  12. package/dist/core/catalog-revision.js +14 -3
  13. package/dist/core/catalog-revision.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts +5 -0
  15. package/dist/core/create-core-server.d.ts.map +1 -1
  16. package/dist/core/create-core-server.js +54 -3
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +26 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +100 -8
  21. package/dist/core/create-core-services.js.map +1 -1
  22. package/dist/core/lifecycle.d.ts +16 -1
  23. package/dist/core/lifecycle.d.ts.map +1 -1
  24. package/dist/core/lifecycle.js +18 -1
  25. package/dist/core/lifecycle.js.map +1 -1
  26. package/dist/core-config.d.ts +21 -0
  27. package/dist/core-config.d.ts.map +1 -1
  28. package/dist/core-config.js +22 -1
  29. package/dist/core-config.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/access/access-control.interface.d.ts +8 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +1 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +11 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/directory-sync-bot.js +1 -1
  41. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  42. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  43. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  44. package/dist/modules/agent-guide/agent-guide.js +8 -0
  45. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  46. package/dist/modules/agent-guide/index.d.ts +1 -1
  47. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  48. package/dist/modules/agent-guide/index.js +1 -1
  49. package/dist/modules/agent-guide/index.js.map +1 -1
  50. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  51. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/compose.js +19 -2
  53. package/dist/modules/agent-instructions/compose.js.map +1 -1
  54. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  55. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  56. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  57. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  58. package/dist/modules/audit/agent-audit.service.js +43 -2
  59. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  60. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  61. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  62. package/dist/modules/audit/agent-connection.routes.js +38 -0
  63. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  64. package/dist/modules/audit/audit.contract.d.ts +24 -0
  65. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  66. package/dist/modules/audit/audit.contract.js.map +1 -1
  67. package/dist/modules/audit/index.d.ts +1 -0
  68. package/dist/modules/audit/index.d.ts.map +1 -1
  69. package/dist/modules/audit/index.js +1 -0
  70. package/dist/modules/audit/index.js.map +1 -1
  71. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  72. package/dist/modules/auth/account.routes.js +10 -3
  73. package/dist/modules/auth/account.routes.js.map +1 -1
  74. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  75. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  76. package/dist/modules/auth/auth.middleware.js +1 -1
  77. package/dist/modules/auth/auth.middleware.js.map +1 -1
  78. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  79. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  80. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  81. package/dist/modules/database/core-schema.d.ts +96 -0
  82. package/dist/modules/database/core-schema.d.ts.map +1 -1
  83. package/dist/modules/database/core-schema.js +32 -3
  84. package/dist/modules/database/core-schema.js.map +1 -1
  85. package/dist/modules/embed/account-link.service.d.ts +42 -0
  86. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  87. package/dist/modules/embed/account-link.service.js +50 -0
  88. package/dist/modules/embed/account-link.service.js.map +1 -0
  89. package/dist/modules/embed/embed-link.d.ts +58 -0
  90. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  91. package/dist/modules/embed/embed-link.js +160 -0
  92. package/dist/modules/embed/embed-link.js.map +1 -0
  93. package/dist/modules/embed/embed.errors.d.ts +30 -0
  94. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  95. package/dist/modules/embed/embed.errors.js +42 -0
  96. package/dist/modules/embed/embed.errors.js.map +1 -0
  97. package/dist/modules/embed/embed.interface.d.ts +204 -0
  98. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  99. package/dist/modules/embed/embed.interface.js +2 -0
  100. package/dist/modules/embed/embed.interface.js.map +1 -0
  101. package/dist/modules/embed/embed.routes.d.ts +59 -0
  102. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  103. package/dist/modules/embed/embed.routes.js +343 -0
  104. package/dist/modules/embed/embed.routes.js.map +1 -0
  105. package/dist/modules/embed/embed.service.d.ts +169 -0
  106. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  107. package/dist/modules/embed/embed.service.js +627 -0
  108. package/dist/modules/embed/embed.service.js.map +1 -0
  109. package/dist/modules/embed/embed.tools.d.ts +51 -0
  110. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  111. package/dist/modules/embed/embed.tools.js +170 -0
  112. package/dist/modules/embed/embed.tools.js.map +1 -0
  113. package/dist/modules/embed/mcp-app.d.ts +64 -0
  114. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  115. package/dist/modules/embed/mcp-app.js +147 -0
  116. package/dist/modules/embed/mcp-app.js.map +1 -0
  117. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  118. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  119. package/dist/modules/embed/mcp-app.routes.js +36 -0
  120. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  121. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  122. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  123. package/dist/modules/github-app/github-app.connection.js +34 -3
  124. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  125. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  126. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  127. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  128. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  129. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  130. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  131. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  132. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  133. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  134. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  135. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  136. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  137. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  138. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  139. package/dist/modules/mcp/mcp.service.js +64 -8
  140. package/dist/modules/mcp/mcp.service.js.map +1 -1
  141. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  142. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  143. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  144. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  145. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  146. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  147. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  148. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  149. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  150. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  151. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  152. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  153. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  154. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  155. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  156. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  157. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  158. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  159. package/dist/modules/onboarding/starter-packs.js +156 -0
  160. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  161. package/dist/modules/plugins/index.d.ts +1 -1
  162. package/dist/modules/plugins/index.d.ts.map +1 -1
  163. package/dist/modules/plugins/index.js +1 -1
  164. package/dist/modules/plugins/index.js.map +1 -1
  165. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  166. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  167. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  168. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  169. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  170. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  171. package/dist/modules/plugins/plugins.routes.js +31 -0
  172. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  173. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  174. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  175. package/dist/modules/settings/deployment-settings.service.js +134 -1
  176. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  177. package/dist/modules/settings/repository-source.d.ts +1 -0
  178. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  179. package/dist/modules/settings/repository-source.js +3 -3
  180. package/dist/modules/settings/repository-source.js.map +1 -1
  181. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  182. package/dist/modules/skills/skills.tools.js +50 -32
  183. package/dist/modules/skills/skills.tools.js.map +1 -1
  184. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  185. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  186. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  187. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  188. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  189. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  190. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  191. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  192. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  193. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  194. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  195. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  196. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  197. package/dist/modules/tool-helpers/index.d.ts +28 -1
  198. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  199. package/dist/modules/tool-helpers/index.js +28 -1
  200. package/dist/modules/tool-helpers/index.js.map +1 -1
  201. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  202. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  203. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  204. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  205. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  206. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  207. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  208. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  209. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  210. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  211. package/dist/modules/tool-helpers/tool-def.js +70 -6
  212. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  213. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  214. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  215. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  216. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  217. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  218. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  219. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  220. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  221. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  222. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  223. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  224. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  225. package/dist/modules/tool-registry/description-length.js +27 -5
  226. package/dist/modules/tool-registry/description-length.js.map +1 -1
  227. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  228. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  229. package/dist/modules/tool-registry/guide-first.js +9 -1
  230. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  231. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  232. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  233. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  234. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  235. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  236. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  237. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  238. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  239. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  240. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  241. package/dist/modules/workflow/file-lock.service.js +62 -5
  242. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  243. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  244. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  245. package/dist/modules/workflow/git/git.service.js +172 -36
  246. package/dist/modules/workflow/git/git.service.js.map +1 -1
  247. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  248. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  249. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  250. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  251. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  252. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  253. package/dist/modules/workflow/pending-commits.service.js +25 -1
  254. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  255. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  256. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  257. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  258. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  259. package/dist/modules/workflow/recovery-bot.js +1 -1
  260. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  261. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  263. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  264. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  265. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  266. package/dist/modules/workflow/sanitize-error.js +45 -0
  267. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  268. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  269. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  270. package/dist/modules/workflow/workflow.service.js +805 -106
  271. package/dist/modules/workflow/workflow.service.js.map +1 -1
  272. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  273. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  274. package/dist/modules/workspace/agent-download.builder.js +238 -0
  275. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  276. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  277. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  278. package/dist/modules/workspace/agent-download.routes.js +156 -0
  279. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  280. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  281. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  282. package/dist/modules/workspace/agent-download.store.js +423 -0
  283. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  284. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  285. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  286. package/dist/modules/workspace/agent-upload.store.js +16 -6
  287. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  288. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  289. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  290. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  291. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  292. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  293. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  294. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  295. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  296. package/dist/modules/workspace/first-run.d.ts +111 -0
  297. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  298. package/dist/modules/workspace/first-run.js +181 -0
  299. package/dist/modules/workspace/first-run.js.map +1 -0
  300. package/dist/modules/workspace/move-links.d.ts +92 -0
  301. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  302. package/dist/modules/workspace/move-links.js +173 -0
  303. package/dist/modules/workspace/move-links.js.map +1 -0
  304. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  305. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  306. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  307. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  308. package/dist/modules/workspace/workspace.routes.js +63 -22
  309. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  310. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  311. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  312. package/dist/modules/workspace/workspace.service.js +197 -24
  313. package/dist/modules/workspace/workspace.service.js.map +1 -1
  314. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  315. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  316. package/dist/modules/workspace/workspace.tools.js +443 -65
  317. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  318. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  319. package/dist/modules/write-access/write-access.js +9 -0
  320. package/dist/modules/write-access/write-access.js.map +1 -1
  321. package/dist/shared/domain-errors.d.ts +73 -1
  322. package/dist/shared/domain-errors.d.ts.map +1 -1
  323. package/dist/shared/domain-errors.js +86 -12
  324. package/dist/shared/domain-errors.js.map +1 -1
  325. package/dist/shared/git.contract.d.ts +10 -1
  326. package/dist/shared/git.contract.d.ts.map +1 -1
  327. package/dist/shared/git.contract.js.map +1 -1
  328. package/dist/shared/utf8-text.d.ts +15 -0
  329. package/dist/shared/utf8-text.d.ts.map +1 -0
  330. package/dist/shared/utf8-text.js +20 -0
  331. package/dist/shared/utf8-text.js.map +1 -0
  332. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  333. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  334. package/dist/tenancy/static-tenant-source.js +9 -2
  335. package/dist/tenancy/static-tenant-source.js.map +1 -1
  336. package/mcp-app/page.html +349 -0
  337. package/migrations/0017_atlassian_account_links.sql +37 -0
  338. package/migrations/meta/0017_snapshot.json +2396 -0
  339. package/migrations/meta/_journal.json +8 -1
  340. package/package.json +5 -3
  341. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  342. package/src/assets.ts +28 -2
  343. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  344. package/src/core/__tests__/gated-boot.test.ts +1 -0
  345. package/src/core/__tests__/lifecycle.test.ts +14 -0
  346. package/src/core/catalog-revision.ts +15 -2
  347. package/src/core/create-core-server.ts +67 -3
  348. package/src/core/create-core-services.ts +140 -6
  349. package/src/core/lifecycle.ts +28 -2
  350. package/src/core-config.ts +24 -1
  351. package/src/index.ts +2 -2
  352. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  353. package/src/modules/access/access-control.interface.ts +13 -0
  354. package/src/modules/access/access-control.service.ts +16 -0
  355. package/src/modules/access/directory-sync-bot.ts +1 -1
  356. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  357. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  358. package/src/modules/agent-guide/agent-guide.ts +9 -0
  359. package/src/modules/agent-guide/index.ts +1 -0
  360. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  361. package/src/modules/agent-instructions/compose.ts +17 -2
  362. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  363. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  364. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  365. package/src/modules/audit/agent-audit.service.ts +58 -3
  366. package/src/modules/audit/agent-connection.routes.ts +56 -0
  367. package/src/modules/audit/audit.contract.ts +26 -0
  368. package/src/modules/audit/index.ts +1 -0
  369. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  370. package/src/modules/auth/account.routes.ts +10 -3
  371. package/src/modules/auth/auth.middleware.ts +1 -1
  372. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  373. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  374. package/src/modules/database/core-schema.ts +33 -3
  375. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  376. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  377. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  378. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  379. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  380. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  381. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  382. package/src/modules/embed/account-link.service.ts +76 -0
  383. package/src/modules/embed/embed-link.ts +180 -0
  384. package/src/modules/embed/embed.errors.ts +43 -0
  385. package/src/modules/embed/embed.interface.ts +203 -0
  386. package/src/modules/embed/embed.routes.ts +363 -0
  387. package/src/modules/embed/embed.service.ts +705 -0
  388. package/src/modules/embed/embed.tools.ts +206 -0
  389. package/src/modules/embed/mcp-app.routes.ts +41 -0
  390. package/src/modules/embed/mcp-app.ts +173 -0
  391. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  392. package/src/modules/github-app/github-app.connection.ts +38 -4
  393. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  394. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  395. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  396. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  397. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  398. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  399. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  400. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  401. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  402. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  403. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  404. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  405. package/src/modules/mcp/mcp.service.ts +81 -12
  406. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  407. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  408. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  409. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  410. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  411. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  412. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  413. package/src/modules/onboarding/starter-packs.ts +182 -0
  414. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  415. package/src/modules/plugins/index.ts +1 -0
  416. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  417. package/src/modules/plugins/plugins.routes.ts +29 -0
  418. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  419. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  420. package/src/modules/settings/deployment-settings.service.ts +131 -1
  421. package/src/modules/settings/repository-source.ts +4 -4
  422. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  423. package/src/modules/skills/skills.tools.ts +55 -35
  424. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  425. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  426. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  427. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  428. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  429. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  430. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  431. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  432. package/src/modules/tool-helpers/index.ts +34 -1
  433. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  434. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  435. package/src/modules/tool-helpers/tool-def.ts +132 -7
  436. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  437. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  438. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  439. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  440. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  441. package/src/modules/tool-registry/description-length.ts +34 -7
  442. package/src/modules/tool-registry/guide-first.ts +8 -1
  443. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  444. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  445. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  446. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  447. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  448. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  449. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  450. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  451. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  452. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  453. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  454. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  455. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  457. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  458. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  459. package/src/modules/workflow/file-lock.service.ts +67 -5
  460. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  461. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  462. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  463. package/src/modules/workflow/git/git.service.ts +176 -38
  464. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  465. package/src/modules/workflow/pending-commits.service.ts +29 -1
  466. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  467. package/src/modules/workflow/recovery-bot.ts +1 -1
  468. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  469. package/src/modules/workflow/sanitize-error.ts +50 -0
  470. package/src/modules/workflow/workflow.service.ts +879 -112
  471. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  472. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  473. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  474. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  475. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  476. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  477. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  478. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  479. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  480. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  481. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  482. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  483. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  484. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  485. package/src/modules/workspace/agent-download.builder.ts +308 -0
  486. package/src/modules/workspace/agent-download.routes.ts +183 -0
  487. package/src/modules/workspace/agent-download.store.ts +533 -0
  488. package/src/modules/workspace/agent-upload.store.ts +23 -7
  489. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  490. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  491. package/src/modules/workspace/first-run.ts +222 -0
  492. package/src/modules/workspace/move-links.ts +237 -0
  493. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  494. package/src/modules/workspace/workspace.routes.ts +64 -22
  495. package/src/modules/workspace/workspace.service.ts +202 -25
  496. package/src/modules/workspace/workspace.tools.ts +492 -63
  497. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  498. package/src/modules/write-access/write-access.ts +9 -0
  499. package/src/shared/domain-errors.ts +108 -11
  500. package/src/shared/git.contract.ts +8 -1
  501. package/src/shared/utf8-text.ts +21 -0
  502. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  503. package/src/tenancy/static-tenant-source.ts +10 -2
  504. package/starter-packs/README.md +103 -0
  505. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  506. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  507. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  508. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  509. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  510. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  511. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  512. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  513. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  514. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  515. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  516. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  517. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  518. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  519. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  520. package/starter-packs/engineering/pack.yaml +11 -0
  521. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  522. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  523. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  524. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  525. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  526. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  527. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  528. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  529. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  530. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  531. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  532. package/starter-packs/general/pack.yaml +10 -0
  533. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  534. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  535. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  536. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  537. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  538. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  539. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  540. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  541. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  542. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  543. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  544. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  545. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  546. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  547. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  548. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  549. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  550. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  551. package/starter-packs/operations/pack.yaml +12 -0
  552. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  553. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  554. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  555. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  556. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  557. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  558. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  559. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  560. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  561. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  562. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  563. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  564. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  565. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  566. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  567. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  568. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  569. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  570. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  571. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  572. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  573. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  574. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  575. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  576. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  577. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  578. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  579. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  580. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  581. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  582. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  583. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  584. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  585. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  586. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  587. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  588. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  589. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  590. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  591. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  592. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  593. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  594. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  595. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  596. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  597. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  598. package/starter-packs/sales/pack.yaml +13 -0
  599. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  600. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  601. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  602. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  603. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  604. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  605. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  606. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  607. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  608. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  609. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  610. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  611. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  612. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  613. package/starter-packs/support/pack.yaml +12 -0
@@ -0,0 +1,261 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { MOVE_LINK_EDIT_CAP, UNSEARCHED_SENTENCE, planMoveLinks, type MoveLinksInput } from '../move-links.js';
3
+
4
+ /**
5
+ * The plan `move_file` builds for its links: which pages it edits, which it
6
+ * names and leaves, and what it never discloses.
7
+ */
8
+
9
+ const KB = 'knowledge-base';
10
+
11
+ function inputOf(
12
+ files: Record<string, string>,
13
+ over: Partial<MoveLinksInput> = {},
14
+ ): MoveLinksInput & { reads: string[] } {
15
+ const reads: string[] = [];
16
+ return {
17
+ src: `${KB}/Old`,
18
+ dest: `${KB}/New/Old`,
19
+ branch: 'main',
20
+ kbDirName: KB,
21
+ allFiles: Object.keys(files),
22
+ canRead: async (paths) => new Map(paths.map((p) => [p, true])),
23
+ writeBlocked: async () => [],
24
+ readText: async (p) => {
25
+ reads.push(p);
26
+ return files[p];
27
+ },
28
+ readRefused: async () => false,
29
+ writeRefusal: async () => null,
30
+ reads,
31
+ ...over,
32
+ };
33
+ }
34
+
35
+ describe('planMoveLinks', () => {
36
+ it('edits inbound pages and moved pages, reporting each edit at the file’s new path', async () => {
37
+ const files = {
38
+ [`${KB}/Old/One.md`]: '---\nnodeType: "[T](../NodeTypes/Task.md)"\n---\n[two](Two.md)\n',
39
+ [`${KB}/Old/Two.md`]: 'no links\n',
40
+ [`${KB}/Index.md`]: '[one](Old/One.md) and [elsewhere](Other.md)\n',
41
+ [`${KB}/Unrelated.md`]: '[x](Other.md)\n',
42
+ [`${KB}/Old/pic.png`]: 'binary',
43
+ };
44
+ const input = inputOf(files);
45
+ const plan = await planMoveLinks(input);
46
+ expect(plan.report).toEqual({
47
+ filesEdited: 2,
48
+ linksRewritten: 2,
49
+ edits: [
50
+ { path: `${KB}/Index.md`, from: 'Old/One.md', to: 'New/Old/One.md' },
51
+ { path: `${KB}/New/Old/One.md`, from: '../NodeTypes/Task.md', to: '../../NodeTypes/Task.md' },
52
+ ],
53
+ notRewritten: [],
54
+ });
55
+ expect(plan.edits.map((e) => [e.lockAt, e.path])).toEqual([
56
+ [`${KB}/Index.md`, `${KB}/Index.md`],
57
+ [`${KB}/Old/One.md`, `${KB}/New/Old/One.md`],
58
+ ]);
59
+ // Binary files are never read.
60
+ expect(input.reads).not.toContain(`${KB}/Old/pic.png`);
61
+ });
62
+
63
+ it('a readable page the caller may not change is named with its links, and not edited', async () => {
64
+ const files = {
65
+ [`${KB}/Old/One.md`]: 'x\n',
66
+ [`${KB}/Locked/Page.md`]: '[one](../Old/One.md)\n',
67
+ };
68
+ const plan = await planMoveLinks(inputOf(files, { writeBlocked: async (paths) => paths.filter((p) => p.includes('Locked')) }));
69
+ expect(plan.edits).toEqual([]);
70
+ expect(plan.report.notRewritten).toEqual([
71
+ { path: `${KB}/Locked/Page.md`, reason: 'no write access', links: ['../Old/One.md'] },
72
+ ]);
73
+ });
74
+
75
+ it('a page the caller cannot read is never opened, named or counted — one sentence instead', async () => {
76
+ const files = {
77
+ [`${KB}/Old/One.md`]: 'x\n',
78
+ [`${KB}/Secret/A.md`]: '[one](../Old/One.md)\n',
79
+ [`${KB}/Secret/B.md`]: '[one](../Old/One.md)\n',
80
+ };
81
+ const input = inputOf(files, {
82
+ canRead: async (paths) => new Map(paths.map((p) => [p, !p.includes('Secret')])),
83
+ });
84
+ const plan = await planMoveLinks(input);
85
+ expect(input.reads.some((p) => p.includes('Secret'))).toBe(false);
86
+ expect(plan.report.unsearched).toBe(UNSEARCHED_SENTENCE);
87
+ expect(JSON.stringify(plan.report)).not.toMatch(/Secret|\b2\b/);
88
+ expect(plan.report.filesEdited).toBe(0);
89
+ });
90
+
91
+ it('transcripts/ and probes/ folders are never searched', async () => {
92
+ const files = {
93
+ [`${KB}/Old/One.md`]: 'x\n',
94
+ [`${KB}/T/transcripts/01.md`]: '[one](../../Old/One.md)\n',
95
+ [`${KB}/T/probes/p.md`]: '[one](../../Old/One.md)\n',
96
+ };
97
+ const input = inputOf(files);
98
+ const plan = await planMoveLinks(input);
99
+ expect(plan.report.filesEdited).toBe(0);
100
+ expect(input.reads.filter((p) => /transcripts|probes/.test(p))).toEqual([]);
101
+ });
102
+
103
+ it('an HTML page is reported, not rewritten', async () => {
104
+ const files = {
105
+ [`${KB}/Old/One.md`]: 'x\n',
106
+ [`${KB}/Board.html`]: '<a href="Old/One.md">one</a>',
107
+ };
108
+ const plan = await planMoveLinks(inputOf(files));
109
+ expect(plan.edits).toEqual([]);
110
+ expect(plan.report.notRewritten).toEqual([{ path: `${KB}/Board.html`, reason: 'html page', links: ['Old/One.md'] }]);
111
+ });
112
+
113
+ it('raw HTML inside a markdown page is reported, not rewritten — beside the markdown links that are', async () => {
114
+ const files = {
115
+ [`${KB}/Old/One.md`]: 'x\n',
116
+ [`${KB}/Old/pic.png`]: '',
117
+ // One markdown link, which is rewritten, and one raw `<img>`, which is not.
118
+ [`${KB}/Gallery.md`]: '[one](Old/One.md)\n\n<img src="Old/pic.png" alt="pic">\n',
119
+ // A page whose raw HTML points elsewhere is not named.
120
+ [`${KB}/Other.md`]: '<a href="Elsewhere.md">x</a> mentions Old in prose\n',
121
+ };
122
+ const plan = await planMoveLinks(inputOf(files));
123
+ expect(plan.edits.map((e) => e.path)).toEqual([`${KB}/Gallery.md`]);
124
+ expect(plan.edits[0].content).toBe('[one](New/Old/One.md)\n\n<img src="Old/pic.png" alt="pic">\n');
125
+ expect(plan.report.notRewritten).toEqual([
126
+ { path: `${KB}/Gallery.md`, reason: 'html in markdown', links: ['Old/pic.png'] },
127
+ ]);
128
+ });
129
+
130
+ it('raw HTML inside code or escaped — a fence, an indented block, a code span, a `\\<` — is an example, not reported', async () => {
131
+ const files = {
132
+ [`${KB}/Old/One.md`]: 'x\n',
133
+ [`${KB}/Howto.md`]:
134
+ 'Write it like `<a href="Old/One.md">` or \\<a href="Old/One.md"> or:\n\n```html\n<a href="Old/One.md">one</a>\n```\n\n' +
135
+ ' <img src="Old/One.md">\n\nAnd a live one: <a href="Old/One.md">one</a>\n',
136
+ };
137
+ const plan = await planMoveLinks(inputOf(files));
138
+ expect(plan.edits).toEqual([]);
139
+ expect(plan.report.notRewritten).toEqual([
140
+ { path: `${KB}/Howto.md`, reason: 'html in markdown', links: ['Old/One.md'] },
141
+ ]);
142
+ });
143
+
144
+ it(`more than ${MOVE_LINK_EDIT_CAP} edited files is refused, saying how to split or switch off`, async () => {
145
+ const files: Record<string, string> = { [`${KB}/Old/One.md`]: 'x\n' };
146
+ for (let i = 0; i <= MOVE_LINK_EDIT_CAP; i++) files[`${KB}/Pages/P${i}.md`] = '[one](../Old/One.md)\n';
147
+ const writeRefusal = vi.fn(async () => null);
148
+ const plan = await planMoveLinks(inputOf(files, { writeRefusal }));
149
+ expect(plan.overCap).toMatch(/201 files.*rewriteLinks: false/s);
150
+ expect(plan.overCap).toMatch(/subfolder/);
151
+ expect(plan.edits).toEqual([]);
152
+ // Nothing is edited, so the write hook hears of nothing.
153
+ expect(writeRefusal).not.toHaveBeenCalled();
154
+ // Exactly at the cap is allowed.
155
+ delete files[`${KB}/Pages/P0.md`];
156
+ expect((await planMoveLinks(inputOf(files))).overCap).toBeUndefined();
157
+ });
158
+
159
+ it('only edited files reach the hooks; a refusal leaves that file named and unedited', async () => {
160
+ const files = {
161
+ [`${KB}/Old/One.md`]: '[t](../T.md)\n',
162
+ [`${KB}/A.md`]: '[one](Old/One.md)\n',
163
+ [`${KB}/B.md`]: '[one](Old/One.md)\n',
164
+ [`${KB}/Searched.md`]: 'mentions Old but links nowhere\n',
165
+ };
166
+ const readRefused = vi.fn(async () => false);
167
+ const writeRefusal = vi.fn(async (_lockAt: string, path: string) =>
168
+ path.endsWith('B.md') ? 'refused: not in this session' : null);
169
+ const plan = await planMoveLinks(inputOf(files, { readRefused, writeRefusal }));
170
+ // The pages the answer names, and not the one merely searched.
171
+ expect(readRefused.mock.calls.map((c) => c[0]).sort()).toEqual([`${KB}/A.md`, `${KB}/B.md`, `${KB}/Old/One.md`]);
172
+ expect(writeRefusal.mock.calls.map((c) => c[0]).sort()).toEqual([`${KB}/A.md`, `${KB}/B.md`, `${KB}/Old/One.md`]);
173
+ expect(plan.edits.map((e) => e.path)).toEqual([`${KB}/A.md`, `${KB}/New/Old/One.md`]);
174
+ expect(plan.report.notRewritten).toEqual([{ path: `${KB}/B.md`, reason: 'refused: not in this session', links: ['Old/One.md'] }]);
175
+ });
176
+
177
+ it('a page the read hook refuses is treated as unreadable: never named, one sentence instead', async () => {
178
+ const files = {
179
+ [`${KB}/Old/One.md`]: 'x\n',
180
+ [`${KB}/Hidden.md`]: '[one](Old/One.md) [secret](Secret/Target.md)\n',
181
+ };
182
+ const plan = await planMoveLinks(inputOf(files, { readRefused: async () => true }));
183
+ expect(plan.edits).toEqual([]);
184
+ expect(plan.report.notRewritten).toEqual([]);
185
+ expect(plan.report.unsearched).toBe(UNSEARCHED_SENTENCE);
186
+ expect(JSON.stringify(plan.report)).not.toMatch(/Hidden|Secret|read denied/);
187
+ });
188
+
189
+ it('a page the read hook refuses is not named for its raw HTML, nor as one the caller may not change', async () => {
190
+ // Every way a page gets NAMED passes the same gate: an HTML page, raw
191
+ // HTML in a markdown page, and a page left unedited for want of write
192
+ // access are all pages the answer would quote links from.
193
+ const files = {
194
+ [`${KB}/Old/One.md`]: 'x\n',
195
+ [`${KB}/Board.html`]: '<a href="Old/One.md">one</a>',
196
+ [`${KB}/Gallery.md`]: '<img src="Old/One.md">\n',
197
+ [`${KB}/Locked.md`]: '[one](Old/One.md)\n',
198
+ [`${KB}/Shown.md`]: '[one](Old/One.md)\n',
199
+ };
200
+ const readRefused = vi.fn(async (path: string) => path !== `${KB}/Shown.md`);
201
+ const plan = await planMoveLinks(
202
+ inputOf(files, { readRefused, writeBlocked: async (paths) => paths.filter((p) => p.endsWith('Locked.md')) }),
203
+ );
204
+ expect(plan.edits.map((e) => e.path)).toEqual([`${KB}/Shown.md`]);
205
+ expect(plan.report.notRewritten).toEqual([]);
206
+ expect(plan.report.unsearched).toBe(UNSEARCHED_SENTENCE);
207
+ expect(JSON.stringify(plan.report)).not.toMatch(/Board|Gallery|Locked/);
208
+ // Asked once per page named, never for one merely searched — the moved
209
+ // file itself links nowhere, so it is not named and not asked about.
210
+ expect(readRefused.mock.calls.map((c) => c[0]).sort()).toEqual([
211
+ `${KB}/Board.html`,
212
+ `${KB}/Gallery.md`,
213
+ `${KB}/Locked.md`,
214
+ `${KB}/Shown.md`,
215
+ ]);
216
+ });
217
+
218
+ it('a link spelling the moved name with percent-encoding or escapes is still found', async () => {
219
+ const files = {
220
+ [`${KB}/My Old (1)/One.md`]: 'x\n',
221
+ [`${KB}/Lower.md`]: '[one](My%20Old%20%281%29/One.md)\n',
222
+ [`${KB}/Partial.md`]: '[one](My%20Old%20(1)/One.md)\n',
223
+ [`${KB}/Escaped.md`]: '[one](My%20Old%20\\(1\\)/One.md)\n',
224
+ [`${KB}/Mixed.md`]: '[one](%4dy%20Old%20%281%29/One.md)\n',
225
+ };
226
+ const plan = await planMoveLinks(inputOf(files, { src: `${KB}/My Old (1)`, dest: `${KB}/New/My Old (1)` }));
227
+ expect(plan.edits.map((e) => e.path).sort()).toEqual([`${KB}/Escaped.md`, `${KB}/Lower.md`, `${KB}/Mixed.md`, `${KB}/Partial.md`]);
228
+ });
229
+
230
+ it('a page gone between the listing and its read is skipped; one that cannot be opened marks the search incomplete', async () => {
231
+ const files = {
232
+ [`${KB}/Old/One.md`]: 'x\n',
233
+ [`${KB}/Gone.md`]: '[one](Old/One.md)\n',
234
+ [`${KB}/Index.md`]: '[one](Old/One.md)\n',
235
+ };
236
+ const failWith = (code: string) => async (p: string) => {
237
+ if (p.endsWith('Gone.md')) throw Object.assign(new Error(code), { code });
238
+ return files[p as keyof typeof files];
239
+ };
240
+ const gone = await planMoveLinks(inputOf(files, { readText: failWith('ENOENT') }));
241
+ expect(gone.edits.map((e) => e.path)).toEqual([`${KB}/Index.md`]);
242
+ expect(gone.report.unsearched).toBeUndefined();
243
+ // Its folder became a file: gone all the same.
244
+ const folderGone = await planMoveLinks(inputOf(files, { readText: failWith('ENOTDIR') }));
245
+ expect(folderGone.edits.map((e) => e.path)).toEqual([`${KB}/Index.md`]);
246
+ expect(folderGone.report.unsearched).toBeUndefined();
247
+ const denied = await planMoveLinks(inputOf(files, { readText: failWith('EACCES') }));
248
+ expect(denied.edits.map((e) => e.path)).toEqual([`${KB}/Index.md`]);
249
+ expect(denied.report.unsearched).toBe(UNSEARCHED_SENTENCE);
250
+ expect(JSON.stringify(denied.report)).not.toMatch(/Gone/);
251
+ });
252
+
253
+ it('lists at most 100 edits while counting all of them', async () => {
254
+ const files: Record<string, string> = { [`${KB}/Old/One.md`]: 'x\n' };
255
+ for (let i = 0; i < 60; i++) files[`${KB}/Pages/P${i}.md`] = '[a](../Old/One.md) [b](../Old/One.md#x)\n';
256
+ const plan = await planMoveLinks(inputOf(files));
257
+ expect(plan.report.filesEdited).toBe(60);
258
+ expect(plan.report.linksRewritten).toBe(120);
259
+ expect(plan.report.edits).toHaveLength(100);
260
+ });
261
+ });
@@ -0,0 +1,219 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import express from 'express';
6
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
7
+ import { LocalFilesystem } from '@mastra/core/workspace';
8
+ import { testKbContext } from '../../../__tests__/kb-context.js';
9
+ import { ToolRegistry } from '../../tool-registry/tool-registry.js';
10
+ import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
11
+ import { routeToolSchemas } from '../../tool-helpers/route-tool-schemas.js';
12
+ import type { ToolContext } from '../../tool-helpers/tool.contract.js';
13
+ import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
14
+ import { registerWorkspaceTools } from '../workspace.tools.js';
15
+ import { RoutineWritePolicyService } from '../routine-write-policy.js';
16
+ import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
17
+ import { ToolDescriptionNotes } from '../agent-access.gate.js';
18
+ import { SpillStore } from '../spill-store.js';
19
+ import { DocExtractService } from '../file-readers/doc-extract.service.js';
20
+ import { AccessControlService } from '../../access/access-control.service.js';
21
+ import { NodeFs } from '../../kb-fs/node-fs.js';
22
+ import { BRANCH_REQUIRED_MESSAGE } from '../../../shared/domain-errors.js';
23
+ import { callLine, compileCheck, exampleArguments } from '@bevel-software/platform-mcp-core';
24
+
25
+ /**
26
+ * The REST route a script, a runner or a CI job calls with a connection key —
27
+ * `POST /api/agent/tools/<name>` — driven against the REAL workspace tools.
28
+ *
29
+ * This is the surface the first implementation left unprotected: `write_file`
30
+ * without `content` answered a 500 `TypeError`, `move_file` without `dest` the
31
+ * same, and `grep` without `pattern` answered 200 having matched every file in
32
+ * the knowledge base. The three calls the ticket names are here, with their own
33
+ * schemas and their own route handlers, so the refusal under test is the one a
34
+ * caller really gets.
35
+ */
36
+
37
+ const KB = 'knowledge-base';
38
+ const BRANCH = 'main';
39
+
40
+ const TREE: Record<string, string> = {
41
+ 'roles.yaml': 'roles:\n Admin:\n - author@x.io\n',
42
+ 'access.md': '---\nread:\n - everyone\nwrite:\n - everyone\n---\n',
43
+ 'KnowledgeBase/Note.md': '# Note\n\nthe needle is here\n',
44
+ 'KnowledgeBase/Other.md': '# Other\n\nthe needle is here too\n',
45
+ };
46
+
47
+ let root = '';
48
+ let docCache = '';
49
+ let base = '';
50
+ let server: HttpServer;
51
+
52
+ beforeAll(async () => {
53
+ root = await mkdtemp(join(tmpdir(), 'rest-tool-arguments-'));
54
+ for (const [rel, text] of Object.entries(TREE)) {
55
+ const abs = join(root, KB, rel);
56
+ await mkdir(dirname(abs), { recursive: true });
57
+ await writeFile(abs, text);
58
+ }
59
+ const workspaceService = {
60
+ getWorkspacePath: async () => root,
61
+ ensureRemotesFetched: async () => undefined,
62
+ } as unknown as Parameters<typeof AccessControlService.prototype.constructor>[0];
63
+ const access = new AccessControlService(workspaceService as never, KB, new NodeFs());
64
+ const fs = new LocalFilesystem({ basePath: root, contained: true });
65
+ const resolve = async (auth: ToolAuth, signal: AbortSignal): Promise<ToolContext> =>
66
+ ({
67
+ user: { id: 'u', email: 'author@x.io', name: 'Author' },
68
+ scope: auth.scope,
69
+ source: auth.source,
70
+ abortSignal: signal,
71
+ workspaceService: workspaceService as never,
72
+ workflowService: {} as never,
73
+ events: {} as never,
74
+ getFilesystem: async () => fs,
75
+ }) as unknown as ToolContext;
76
+ const app = express();
77
+ app.use(express.json());
78
+ const router = express.Router();
79
+ docCache = await mkdtemp(join(tmpdir(), 'rest-tool-arguments-doc-'));
80
+ registerWorkspaceTools(
81
+ new ToolRegistry(),
82
+ router,
83
+ (req, _res, next) => {
84
+ req.toolAuth = { source: 'external', userId: 'u', scope: 'write' };
85
+ next();
86
+ },
87
+ createToolHandlerFactory(resolve),
88
+ new SpillStore(join(tmpdir(), 'bevel-test-spills')),
89
+ new DocExtractService(docCache),
90
+ access,
91
+ testKbContext({ kbDirName: KB }),
92
+ { recoveryBotEmail: 'recovery-bot@bevel.local', hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
93
+ new RoutineWritePolicyService(),
94
+ {} as never,
95
+ );
96
+ app.use('/api', router);
97
+ server = await new Promise<HttpServer>((r) => {
98
+ const s = app.listen(0, () => r(s));
99
+ });
100
+ base = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
101
+ });
102
+
103
+ afterAll(async () => {
104
+ await new Promise<void>((r) => server.close(() => r()));
105
+ await rm(root, { recursive: true, force: true });
106
+ await rm(docCache, { recursive: true, force: true });
107
+ });
108
+
109
+ interface Answered {
110
+ status: number;
111
+ body: { error?: string; kind?: string; matches?: unknown[]; truncated?: boolean };
112
+ }
113
+
114
+ /** One call on the REST route, exactly as a script with a connection key makes it. */
115
+ async function callTool(tool: string, body: unknown): Promise<Answered> {
116
+ const res = await fetch(`${base}/api/agent/tools/${tool}`, {
117
+ method: 'POST',
118
+ headers: { 'content-type': 'application/json', authorization: 'Bearer connection-key' },
119
+ body: JSON.stringify(body),
120
+ });
121
+ return { status: res.status, body: (await res.json()) as Answered['body'] };
122
+ }
123
+
124
+ const exists = (rel: string) =>
125
+ stat(join(root, rel))
126
+ .then(() => true)
127
+ .catch(() => false);
128
+
129
+ describe('POST /api/agent/tools/<name> checks the arguments against the tool', () => {
130
+ it('refuses write_file without `content` — the 400 with the interface, not a 500, and writes nothing', async () => {
131
+ const { status, body } = await callTool('write_file', {
132
+ branch: BRANCH,
133
+ path: `${KB}/KnowledgeBase/New.md`,
134
+ sessionId: 's-1',
135
+ });
136
+ expect(status).toBe(400);
137
+ expect(body.kind).toBe('arguments-do-not-match');
138
+ expect(body.error).toContain('The arguments do not match the "write_file" tool.');
139
+ expect(body.error).toContain('"content" is required, and was not given.');
140
+ expect(body.error).toContain('Interface of "write_file":');
141
+ expect(body.error).toContain('content (string, required)');
142
+ expect(body.error).toContain('path (string, required)');
143
+ expect(body.error?.split('\n').pop()).toBe(
144
+ callLine('KNOWLEDGE_BASE.write_file', routeToolSchemas('write_file')!.wire),
145
+ );
146
+ // The call ran nothing: no file was created.
147
+ expect(await exists(`${KB}/KnowledgeBase/New.md`)).toBe(false);
148
+ });
149
+
150
+ it('refuses move_file without `dest`, and moves nothing', async () => {
151
+ const { status, body } = await callTool('move_file', {
152
+ branch: BRANCH,
153
+ src: `${KB}/KnowledgeBase/Note.md`,
154
+ });
155
+ expect(status).toBe(400);
156
+ expect(body.kind).toBe('arguments-do-not-match');
157
+ expect(body.error).toContain('"dest" is required, and was not given.');
158
+ expect(body.error).toContain('dest (string, required)');
159
+ expect(await readFile(join(root, KB, 'KnowledgeBase/Note.md'), 'utf8')).toContain('the needle is here');
160
+ });
161
+
162
+ it('refuses grep without `pattern`, and searches nothing', async () => {
163
+ const { status, body } = await callTool('grep', { branch: BRANCH });
164
+ expect(status).toBe(400);
165
+ expect(body.kind).toBe('arguments-do-not-match');
166
+ expect(body.error).toContain('"pattern" is required, and was not given.');
167
+ expect(body.error).toContain('pattern (string, required) — JavaScript regular expression.');
168
+ // Not a 200 that matched every file, which is what this surface answered
169
+ // before the check lived in the route.
170
+ expect(body.matches).toBeUndefined();
171
+ });
172
+
173
+ it('names an argument of the wrong type, and one the tool does not have', async () => {
174
+ const wrongType = await callTool('grep', { branch: BRANCH, pattern: 'needle', max_results: 'ten' });
175
+ expect(wrongType.status).toBe(400);
176
+ expect(wrongType.body.error).toContain('"max_results" must be integer, but string was given.');
177
+ const unknown = await callTool('grep', { branch: BRANCH, pattern: 'needle', recursive: true });
178
+ expect(unknown.status).toBe(400);
179
+ expect(unknown.body.error).toContain('"recursive" is not an argument of this tool.');
180
+ });
181
+
182
+ it('keeps the refusal that names a missing branch, word for word', async () => {
183
+ const { status, body } = await callTool('read_file', { path: `${KB}/KnowledgeBase/Note.md` });
184
+ expect(status).toBe(400);
185
+ expect(body.kind).toBe('branch-required');
186
+ expect(body.error).toBe(BRANCH_REQUIRED_MESSAGE);
187
+ });
188
+
189
+ it('passes a matching call on with exactly the arguments that were sent', async () => {
190
+ const { status, body } = await callTool('grep', {
191
+ branch: BRANCH,
192
+ pattern: 'needle',
193
+ path: `${KB}/KnowledgeBase`,
194
+ max_results: 1,
195
+ });
196
+ expect(status).toBe(200);
197
+ // `max_results: 1` was honoured, so the argument reached the tool as sent —
198
+ // the check neither dropped it nor rewrote it.
199
+ expect(body.matches).toHaveLength(1);
200
+ expect(body.truncated).toBe(true);
201
+ });
202
+
203
+ it('every mounted tool can be called with its own generated example', async () => {
204
+ const names = ['read_file', 'write_file', 'move_file', 'grep', 'list_files'];
205
+ for (const name of names) {
206
+ const schemas = routeToolSchemas(name);
207
+ expect(schemas, name).toBeDefined();
208
+ // The example an agent is told to copy is generated from the WIRE schema;
209
+ // what its route checks is the FLAT one. Both must accept it, or the
210
+ // platform publishes an example its own check refuses.
211
+ const flat = compileCheck(schemas!.flat);
212
+ expect(flat.checkable, name).toBe(true);
213
+ if (flat.checkable) expect(flat.check(exampleArguments(schemas!.flat)), name).toEqual([]);
214
+ const wire = compileCheck(schemas!.wire);
215
+ expect(wire.checkable, name).toBe(true);
216
+ if (wire.checkable) expect(wire.check(exampleArguments(schemas!.wire)), name).toEqual([]);
217
+ }
218
+ });
219
+ });
@@ -70,6 +70,9 @@ async function makeHarness(): Promise<Harness> {
70
70
  asked.push(p);
71
71
  return true;
72
72
  },
73
+ // Each file of a zipped folder is judged too; this suite is about where
74
+ // the FOLDER is placed, so the files all pass.
75
+ canDownloadBatch: async (_w: string, _e: string, paths: string[]) => new Map(paths.map((p) => [p, true])),
73
76
  } as unknown as IAccessControl;
74
77
 
75
78
  const locked: string[] = [];
@@ -15,6 +15,7 @@ import type { IAdminAccessService } from '../../admin/admin.interface.js';
15
15
  import { createWorkspaceRoutes } from '../workspace.routes.js';
16
16
  import type { ICreatorAccess } from '../../access-model/creator.js';
17
17
  import type { WorkspaceService } from '../workspace.service.js';
18
+ import { AccessDeniedError } from '../../access-model/access-errors.js';
18
19
 
19
20
  const stubCreatorAccess: ICreatorAccess = {
20
21
  planForCreate: async () => null,
@@ -46,6 +47,8 @@ interface Harness {
46
47
  repoDir: string;
47
48
  /** Exposed so a test can assert the PATH the route handed the service. */
48
49
  deleteFileMock: ReturnType<typeof vi.fn>;
50
+ /** Exposed so a test can make the write gate inside it refuse. */
51
+ acquireLockMock: ReturnType<typeof vi.fn>;
49
52
  }
50
53
 
51
54
  async function makeHarness(): Promise<Harness> {
@@ -125,6 +128,7 @@ async function makeHarness(): Promise<Harness> {
125
128
  workspaceDir,
126
129
  repoDir: path.join(workspaceDir, KB),
127
130
  deleteFileMock: workspaceServiceMock.deleteFile as unknown as ReturnType<typeof vi.fn>,
131
+ acquireLockMock: workflowServiceMock.acquireLock as unknown as ReturnType<typeof vi.fn>,
128
132
  };
129
133
  }
130
134
 
@@ -218,3 +222,101 @@ describe('DELETE /workspace/:id/file — one file identity', () => {
218
222
  }
219
223
  });
220
224
  });
225
+
226
+ /**
227
+ * The root's `access.md` and `roles.yaml` govern the whole repository, so the
228
+ * app's delete route refuses them — with the sentence `delete_file` and the
229
+ * explorer use — before a lock is taken. A nested `access.md` is a file like
230
+ * any other here: the lock gate decides whether the caller may write it.
231
+ */
232
+ describe('DELETE /workspace/:id/file — the repository\'s own files', () => {
233
+ let h: Harness | null = null;
234
+ afterEach(async () => {
235
+ if (h) {
236
+ await closeServer(h.server);
237
+ await fs.rm(h.workspaceDir, { recursive: true, force: true });
238
+ }
239
+ h = null;
240
+ });
241
+
242
+ const del = (base: string, p: string) =>
243
+ fetch(`${base}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent(p)}`, { method: 'DELETE' });
244
+
245
+ it.each(['access.md', 'roles.yaml'])('refuses the root %s with one sentence and leaves it', async (name) => {
246
+ h = await makeHarness();
247
+ await fs.mkdir(h.repoDir, { recursive: true });
248
+ await fs.writeFile(path.join(h.repoDir, name), 'x', 'utf-8');
249
+
250
+ const res = await del(h.baseUrl, name);
251
+
252
+ expect(res.status).toBe(409);
253
+ expect(await res.json()).toEqual({ error: `${name} is the repository's own file and cannot be deleted.` });
254
+ expect(h.deleteFileMock).not.toHaveBeenCalled();
255
+ expect(await exists(path.join(h.repoDir, name))).toBe(true);
256
+ });
257
+
258
+ it('deletes a nested access.md like any file, and a nested roles.yaml too', async () => {
259
+ h = await makeHarness();
260
+ await fs.mkdir(path.join(h.repoDir, 'Team'), { recursive: true });
261
+ await fs.writeFile(path.join(h.repoDir, 'Team/access.md'), '---\nread: Admin\n---\n', 'utf-8');
262
+ await fs.writeFile(path.join(h.repoDir, 'Team/roles.yaml'), 'content', 'utf-8');
263
+ // Not emptied by these deletes, so no placeholder needs keeping.
264
+ await fs.writeFile(path.join(h.repoDir, 'Team/notes.md'), 'n', 'utf-8');
265
+
266
+ for (const p of ['Team/access.md', 'Team/roles.yaml']) {
267
+ const res = await del(h.baseUrl, p);
268
+ expect(res.status).toBe(200);
269
+ expect(h.deleteFileMock).toHaveBeenCalledWith(WORKSPACE_ID, `${KB}/${p}`);
270
+ expect(await exists(path.join(h.repoDir, p))).toBe(false);
271
+ }
272
+ });
273
+
274
+ it('refuses a nested access.md with the ordinary write refusal when the caller may not write it', async () => {
275
+ h = await makeHarness();
276
+ await fs.mkdir(path.join(h.repoDir, 'Team'), { recursive: true });
277
+ await fs.writeFile(path.join(h.repoDir, 'Team/access.md'), '---\nwrite: Admin\n---\n', 'utf-8');
278
+ // The write gate lives inside `acquireLock`; it refuses as it does for any file.
279
+ const refusal = new AccessDeniedError({ path: 'Team/access.md', eligibleRoles: ['Admin'], eligibleUsers: [] });
280
+ h.acquireLockMock.mockRejectedValueOnce(refusal);
281
+
282
+ const res = await del(h.baseUrl, 'Team/access.md');
283
+
284
+ expect(res.status).toBe(403);
285
+ const body = (await res.json()) as { error: string };
286
+ expect(body.error).toBe(refusal.message);
287
+ expect(body.error).not.toContain('platform file');
288
+ expect(body.error).not.toContain("repository's own file");
289
+ expect(h.deleteFileMock).not.toHaveBeenCalled();
290
+ expect(await exists(path.join(h.repoDir, 'Team/access.md'))).toBe(true);
291
+ });
292
+
293
+ it('refuses a reserved top-level folder as delete_folder does, and leaves what it holds', async () => {
294
+ h = await makeHarness();
295
+ await fs.mkdir(path.join(h.repoDir, 'KnowledgeBase/Product'), { recursive: true });
296
+ await fs.writeFile(path.join(h.repoDir, 'KnowledgeBase/Product/Plan.md'), 'p', 'utf-8');
297
+
298
+ const res = await del(h.baseUrl, `${KB}/KnowledgeBase`);
299
+
300
+ expect(res.status).toBe(409);
301
+ expect(await res.json()).toEqual({ error: 'KnowledgeBase/ is a platform folder and cannot be moved or deleted.' });
302
+ expect(h.deleteFileMock).not.toHaveBeenCalled();
303
+ expect(await exists(path.join(h.repoDir, 'KnowledgeBase/Product/Plan.md'))).toBe(true);
304
+ });
305
+
306
+ it.each([KB, `${KB}/`])('refuses the repository root as a folder (%j) and leaves its own files', async (p) => {
307
+ h = await makeHarness();
308
+ await fs.mkdir(path.join(h.repoDir, 'Team'), { recursive: true });
309
+ await fs.writeFile(path.join(h.repoDir, 'access.md'), 'x', 'utf-8');
310
+ await fs.writeFile(path.join(h.repoDir, 'roles.yaml'), 'x', 'utf-8');
311
+ await fs.writeFile(path.join(h.repoDir, 'Team/notes.md'), 'n', 'utf-8');
312
+
313
+ const res = await del(h.baseUrl, p);
314
+
315
+ expect(res.status).toBe(409);
316
+ expect(await res.json()).toEqual({ error: 'The repository root is a platform folder and cannot be moved or deleted.' });
317
+ expect(h.deleteFileMock).not.toHaveBeenCalled();
318
+ for (const f of ['access.md', 'roles.yaml', 'Team/notes.md']) {
319
+ expect(await exists(path.join(h.repoDir, f))).toBe(true);
320
+ }
321
+ });
322
+ });