@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
@@ -15,9 +15,11 @@ import { ToolError, type ToolContext } from '../../tool-helpers/tool.contract.js
15
15
  import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
16
16
  import { registerWorkspaceTools } from '../workspace.tools.js';
17
17
  import { sharedFileRules, sharedFileRulesSection } from '../../agent-instructions/shared-file-rules.js';
18
+ import { composeAgentGuide } from '../../agent-guide/agent-guide.js';
18
19
  import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
19
20
  import { RoutineWritePolicyService } from '../routine-write-policy.js';
20
21
  import { UuidSessionSink, type ISessionSink } from '../session-sink.js';
22
+ import { FIRST_RUN_SECTION_ID, STARTER_GUIDE_FILE, firstRunNote, type FirstRunStarterSource } from '../first-run.js';
21
23
  import { WorkflowHooks, type AgentOperationContext } from '../../workflow/workflow-hooks.js';
22
24
  import { SESSION_ID_DESCRIPTION, ToolDescriptionNotes } from '../agent-access.gate.js';
23
25
  import { SpillStore } from '../spill-store.js';
@@ -27,13 +29,16 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
27
29
  import { AccessControlService } from '../../access/access-control.service.js';
28
30
  import { NodeFs } from '../../kb-fs/node-fs.js';
29
31
  import type { WorkspaceService } from '../workspace.service.js';
30
- import { isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
32
+ import { DEFAULT_KB_LAYOUT, isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
31
33
  import { assertValidBranchName } from '../../kb-fs/branch-name.js';
32
34
  import { normalizeWorkspacePath } from '../../kb-fs/repo-path.js';
33
35
  import { GIT_INTERNALS_MESSAGE, PathNotFoundError } from '../../../shared/domain-errors.js';
34
36
  import { AccessDeniedError } from '../../access-model/access-errors.js';
35
37
  import { proposalTitleFor } from '../write-denial.js';
36
38
  import { NOT_FOUND_NEXT_STEP } from '../not-found.js';
39
+ import { compileCheck, exampleArguments } from '@bevel-software/platform-mcp-core';
40
+ import { routeToolSchemas } from '../../tool-helpers/route-tool-schemas.js';
41
+ import { TOOL_DESCRIPTION_CAP, clientVisibleLength } from '../../tool-registry/description-length.js';
37
42
 
38
43
  const KB_DIR = 'knowledge-base';
39
44
 
@@ -316,7 +321,9 @@ const postRaw = (url: string, body: unknown = {}) =>
316
321
  * POST a well-formed call. Every KB tool requires `branch` — a call that names
317
322
  * none is refused at the mount with 400 `branch-required` — so this names one
318
323
  * unless the test already did. A test ABOUT the missing input uses `postRaw`,
319
- * so the thing under test is never papered over by the helper.
324
+ * so the thing under test is never papered over by the helper. So does
325
+ * `start_session`, which declares no arguments at all and forbids extras: its
326
+ * route refuses a `branch` as an argument it does not have.
320
327
  */
321
328
  const post = (url: string, body: unknown = {}) =>
322
329
  postRaw(
@@ -328,6 +335,7 @@ const post = (url: string, body: unknown = {}) =>
328
335
 
329
336
  beforeEach(() => {
330
337
  /* fresh per test via start() */
338
+ guideText = 'THE PLATFORM GUIDE\n';
331
339
  });
332
340
  afterEach(async () => {
333
341
  if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
@@ -966,6 +974,8 @@ describe('write modes and per-path outcomes', () => {
966
974
  for (const res of [
967
975
  await writeFile(base, { path: `${KB_DIR}/a.md`, content: 'x', mode: 'replace' }),
968
976
  await writeFiles(base, { files: [{ path: `${KB_DIR}/a.md`, content: 'x' }], mode: 'replace' }),
977
+ // An empty batch is no way round it: the mode is judged before the batch is.
978
+ await writeFiles(base, { files: [], mode: 'bogus' }),
969
979
  ]) {
970
980
  expect(res.status).toBe(400);
971
981
  const body = (await res.json()) as { error: string; code: string };
@@ -1122,6 +1132,15 @@ describe("the agent guide at the guide's name", () => {
1122
1132
  expect((await read(base, GUIDE, { offset: 4, limit: 8 })).content).toBe('PLATFORM');
1123
1133
  });
1124
1134
 
1135
+ it('answers with the platform\'s composed guide, HTML views section and all', async () => {
1136
+ guideText = await composeAgentGuide(DEFAULT_KB_LAYOUT);
1137
+ const base = await start();
1138
+ const { content } = await read(base, 'AGENTS.md');
1139
+ expect(content).toBe(guideText);
1140
+ expect(content).toContain('## HTML views');
1141
+ expect(content).toContain('**A bare fragment scrolls the page.**');
1142
+ });
1143
+
1125
1144
  it("puts the knowledge base's own AGENTS.md first, then the separator, then the guide", async () => {
1126
1145
  guideText = 'THE PLATFORM GUIDE\n';
1127
1146
  const base = await start();
@@ -1936,6 +1955,52 @@ describe('office documents and PDFs', () => {
1936
1955
  expect(external.find((t) => t.name === 'start_session')!.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `)).toBe(true);
1937
1956
  });
1938
1957
 
1958
+ /**
1959
+ * The call example at the top of every description is generated from the
1960
+ * tool's input schema, and the same schema is what the argument check reads.
1961
+ * If the two could disagree, the platform would publish an example its own
1962
+ * check refuses — so every declared tool is called with its own example here.
1963
+ */
1964
+ it('every declared tool can be called with its own generated example', async () => {
1965
+ await start();
1966
+ const tools = await toolRegistry.listInternal();
1967
+ // The whole family this harness declares, the four the scenarios name included.
1968
+ for (const name of ['read_file', 'write_file', 'list_files', 'grep']) {
1969
+ expect(tools.some((t) => t.name === name), name).toBe(true);
1970
+ }
1971
+ expect(tools.length).toBeGreaterThan(12);
1972
+ for (const def of tools) {
1973
+ const compiled = compileCheck(def.inputs);
1974
+ expect(compiled.checkable, `${def.name}: ${compiled.checkable ? '' : compiled.reason}`).toBe(true);
1975
+ // Narrowed by hand: the assertion above already failed the test if not.
1976
+ if (!compiled.checkable) throw new Error(compiled.reason);
1977
+ expect(compiled.check(exampleArguments(def.inputs)), def.name).toEqual([]);
1978
+ // And the FLAT schema, which is what the tool's own route checks the
1979
+ // call against: the two must agree, or a call the example produced would
1980
+ // be refused one layer in.
1981
+ const flat = routeToolSchemas(def.name)?.flat;
1982
+ expect(flat, def.name).toBeDefined();
1983
+ const flatCheck = compileCheck(flat);
1984
+ expect(flatCheck.checkable, `${def.name} (flat): ${flatCheck.checkable ? '' : flatCheck.reason}`).toBe(true);
1985
+ if (!flatCheck.checkable) throw new Error(flatCheck.reason);
1986
+ expect(flatCheck.check(exampleArguments(flat)), `${def.name} (flat)`).toEqual([]);
1987
+ }
1988
+ });
1989
+
1990
+ it('every description, call example and purpose prefix included, stays inside the cap a client shows', async () => {
1991
+ await start();
1992
+ const tools = await toolRegistry.listInternal();
1993
+ for (const def of tools) {
1994
+ // Measured as a CLIENT receives it: the call line, the purpose prefix at
1995
+ // its own cap (the four knowledge-base tools carry one), and the
1996
+ // description — the three things that ride one tool's entry.
1997
+ const received = clientVisibleLength(def);
1998
+ expect(received, `${def.name} is ${received} characters (cap ${TOOL_DESCRIPTION_CAP})`).toBeLessThanOrEqual(
1999
+ TOOL_DESCRIPTION_CAP,
2000
+ );
2001
+ }
2002
+ });
2003
+
1939
2004
  describe('binary capability contract: a text file, a document, an image and a zip', () => {
1940
2005
  const PNG = Buffer.from(
1941
2006
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
@@ -2201,6 +2266,15 @@ describe('start_session', () => {
2201
2266
  // `UuidSessionSink`, because "did fifty first calls collide?" is a question
2202
2267
  // only the real minting can answer.
2203
2268
  sink?: ISessionSink,
2269
+ // The default branch's workspace directory, for the tests about the
2270
+ // `firstRun` note. Without one the workspace service is a bare stand-in
2271
+ // the note's check cannot use, which is what every other test here wants:
2272
+ // the call answers with the id alone.
2273
+ defaultWorkspaceDir?: string,
2274
+ // The starter pack the knowledge base was filled from, for the note.
2275
+ starterPacks?: FirstRunStarterSource,
2276
+ // Who may read what: everything, unless a test about the note's gate says otherwise.
2277
+ access: IAccessControl = allowAll,
2204
2278
  ): Promise<string> {
2205
2279
  created = [];
2206
2280
  const registry = new ToolRegistry();
@@ -2209,7 +2283,12 @@ describe('start_session', () => {
2209
2283
  scope: auth.scope,
2210
2284
  source: auth.source,
2211
2285
  abortSignal: signal,
2212
- workspaceService: {} as never,
2286
+ workspaceService: (defaultWorkspaceDir
2287
+ ? {
2288
+ hasBootstrappedWorkspace: async () => true,
2289
+ getWorkspacePath: async () => defaultWorkspaceDir,
2290
+ }
2291
+ : {}) as never,
2213
2292
  workflowService: {} as never,
2214
2293
  events: {} as never,
2215
2294
  getFilesystem: async () => ({}) as never,
@@ -2232,10 +2311,17 @@ describe('start_session', () => {
2232
2311
  const router = express.Router();
2233
2312
  registerWorkspaceTools(
2234
2313
  registry, router, auth, toolHandler,
2235
- new SpillStore(join(tmpdir(), 'bevel-test-spills')), new DocExtractService(join(tmpdir(), 'bevel-test-doc-extract')), allowAll, testKbContext({ kbDirName: KB_DIR }),
2314
+ new SpillStore(join(tmpdir(), 'bevel-test-spills')), new DocExtractService(join(tmpdir(), 'bevel-test-doc-extract')), access, testKbContext({ kbDirName: KB_DIR }),
2236
2315
  { recoveryBotEmail: RECOVERY_BOT, hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
2237
2316
  new RoutineWritePolicyService(),
2238
2317
  sink ?? fakeSessionSink,
2318
+ undefined,
2319
+ undefined,
2320
+ undefined,
2321
+ undefined,
2322
+ undefined /* downloads */,
2323
+ starterPacks,
2324
+ new NodeFs(),
2239
2325
  );
2240
2326
  app.use('/api', router);
2241
2327
  server = await new Promise<HttpServer>((r) => {
@@ -2251,13 +2337,13 @@ describe('start_session', () => {
2251
2337
 
2252
2338
  it('returns the sink-minted id as sessionId', async () => {
2253
2339
  const base = await startSessionApp();
2254
- const res = (await (await post(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2340
+ const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2255
2341
  expect(res.sessionId).toBe('thread-xyz');
2256
2342
  });
2257
2343
 
2258
2344
  it('mints the session for the authenticated user', async () => {
2259
2345
  const base = await startSessionApp();
2260
- await post(`${base}/api/agent/tools/start_session`);
2346
+ await postRaw(`${base}/api/agent/tools/start_session`);
2261
2347
  expect(created).toHaveLength(1);
2262
2348
  expect(created[0].userId).toBe('user-42');
2263
2349
  expect(created[0].startedAt).toBeInstanceOf(Date);
@@ -2268,7 +2354,7 @@ describe('start_session', () => {
2268
2354
  // loopback token resolves to source 'external' at the verifier (see
2269
2355
  // tool-auth), so it is admitted here like any external agent.
2270
2356
  const base = await startSessionApp('internal');
2271
- const res = await post(`${base}/api/agent/tools/start_session`);
2357
+ const res = await postRaw(`${base}/api/agent/tools/start_session`);
2272
2358
  expect(res.status).toBe(403);
2273
2359
  expect(created).toHaveLength(0);
2274
2360
  });
@@ -2292,7 +2378,7 @@ describe('start_session', () => {
2292
2378
  it('answers every one of them with a session id of its own', async () => {
2293
2379
  const base = await startSessionApp('external', new UuidSessionSink());
2294
2380
 
2295
- const responses = await Promise.all(Array.from({ length: 50 }, () => post(`${base}/api/agent/tools/start_session`)));
2381
+ const responses = await Promise.all(Array.from({ length: 50 }, () => postRaw(`${base}/api/agent/tools/start_session`)));
2296
2382
  const bodies = (await Promise.all(responses.map((r) => r.json()))) as Array<{ sessionId?: string }>;
2297
2383
 
2298
2384
  expect(responses.map((r) => r.status)).toEqual(Array.from({ length: 50 }, () => 200));
@@ -2325,13 +2411,13 @@ describe('start_session', () => {
2325
2411
  const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {});
2326
2412
  let failed: Awaited<ReturnType<typeof post>>;
2327
2413
  try {
2328
- failed = await post(`${base}/api/agent/tools/start_session`);
2414
+ failed = await postRaw(`${base}/api/agent/tools/start_session`);
2329
2415
  } finally {
2330
2416
  errorLog.mockRestore();
2331
2417
  }
2332
2418
  expect(failed.status).toBeGreaterThanOrEqual(500);
2333
2419
 
2334
- const retried = (await (await post(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2420
+ const retried = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2335
2421
  expect(retried.sessionId).toBe('session-2');
2336
2422
  expect(calls).toBe(2);
2337
2423
  });
@@ -2343,8 +2429,8 @@ describe('start_session', () => {
2343
2429
  // to other tools, and the spare is simply never mentioned again.
2344
2430
  const base = await startSessionApp('external', new UuidSessionSink());
2345
2431
 
2346
- const first = (await (await post(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2347
- const retry = (await (await post(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2432
+ const first = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2433
+ const retry = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2348
2434
 
2349
2435
  expect(retry.sessionId).not.toBe(first.sessionId);
2350
2436
  expect(first.sessionId).toBeTruthy();
@@ -2372,6 +2458,141 @@ describe('start_session', () => {
2372
2458
  expect(description).toMatch(/retry/i);
2373
2459
  expect(description).toMatch(/created nothing/i);
2374
2460
  expect(description).toMatch(/harmless/i);
2461
+ // And that the answer may carry a note to act on, so an agent reading
2462
+ // only the catalog knows the field is not noise.
2463
+ expect(description).toContain('`firstRun`');
2464
+ });
2465
+
2466
+ /**
2467
+ * "The agent is the onboarding guide": on a knowledge base nobody has
2468
+ * written in yet, the first call of a conversation says so, and the note
2469
+ * stops on its own once a page exists.
2470
+ */
2471
+ describe('the firstRun note', () => {
2472
+ let wsDir = '';
2473
+ const knowledge = () => join(wsDir, KB_DIR, 'KnowledgeBase');
2474
+
2475
+ beforeEach(async () => {
2476
+ wsDir = await mkdtemp(join(tmpdir(), 'bevel-first-run-'));
2477
+ await mkdir(knowledge(), { recursive: true });
2478
+ await writeFile(join(knowledge(), STARTER_GUIDE_FILE), '# How to get started\n');
2479
+ });
2480
+
2481
+ afterEach(async () => {
2482
+ await rm(wsDir, { recursive: true, force: true });
2483
+ });
2484
+
2485
+ const startSession = async () => {
2486
+ const base = await startSessionApp('external', undefined, wsDir);
2487
+ // `postRaw`: the tool takes no arguments, and `post` adds a branch.
2488
+ return (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string; firstRun?: string };
2489
+ };
2490
+
2491
+ it('is there while the knowledge folder holds only the starter guide', async () => {
2492
+ // Folder placeholders and access rules are not pages either.
2493
+ await mkdir(join(knowledge(), 'Empty'), { recursive: true });
2494
+ await writeFile(join(knowledge(), 'Empty', '.gitkeep'), '');
2495
+ await writeFile(join(knowledge(), 'access.md'), '# Access\n');
2496
+
2497
+ const res = await startSession();
2498
+
2499
+ expect(res.sessionId).toBe('thread-xyz');
2500
+ expect(res.firstRun).toBe(firstRunNote(`${KB_DIR}/KnowledgeBase`));
2501
+ expect(res.firstRun).toContain(`\`${FIRST_RUN_SECTION_ID}\``);
2502
+ });
2503
+
2504
+ it('is gone once another page exists beside the starter guide', async () => {
2505
+ await writeFile(join(knowledge(), 'Glossary.md'), '# Glossary\n');
2506
+
2507
+ const res = await startSession();
2508
+
2509
+ expect(res.sessionId).toBe('thread-xyz');
2510
+ expect(res).not.toHaveProperty('firstRun');
2511
+ });
2512
+
2513
+ it('is gone once another page exists in a folder', async () => {
2514
+ await mkdir(join(knowledge(), 'Company'), { recursive: true });
2515
+ await writeFile(join(knowledge(), 'Company', 'About.md'), '# About us\n');
2516
+
2517
+ const res = await startSession();
2518
+
2519
+ expect(res.sessionId).toBe('thread-xyz');
2520
+ expect(res).not.toHaveProperty('firstRun');
2521
+ });
2522
+
2523
+ it("after a starter pack, stays while the pack's pages are untouched and names its suggestions", async () => {
2524
+ await writeFile(join(knowledge(), 'Customers.md'), '# Customers\n');
2525
+ const starter: FirstRunStarterSource = {
2526
+ firstRunStarter: async () => ({
2527
+ name: 'Sales',
2528
+ suggestedPages: ['Customers', 'Pricing'],
2529
+ pages: new Map([['Customers.md', '# Customers\n']]),
2530
+ }),
2531
+ };
2532
+ const base = await startSessionApp('external', undefined, wsDir, starter);
2533
+ const first = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
2534
+ expect(first.firstRun).toBe(
2535
+ firstRunNote(`${KB_DIR}/KnowledgeBase`, { name: 'Sales', suggestedPages: ['Customers', 'Pricing'] }),
2536
+ );
2537
+
2538
+ await writeFile(join(knowledge(), 'Customers.md'), '# Customers\n\nAcme.\n');
2539
+ const second = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
2540
+ expect(second).not.toHaveProperty('firstRun');
2541
+ });
2542
+
2543
+ /**
2544
+ * The note is a read: it says what the knowledge folder holds. A caller
2545
+ * who may not read the folder gets the id alone, and a pack page the
2546
+ * caller may not read is judged as anybody's page, so the note never
2547
+ * tells them it is still a placeholder.
2548
+ */
2549
+ it('is withheld from a caller who may not read the knowledge folder', async () => {
2550
+ const closed = { ...allowAll, canRead: async () => false } as unknown as IAccessControl;
2551
+ const base = await startSessionApp('external', undefined, wsDir, undefined, closed);
2552
+ const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
2553
+ expect(res).toEqual({ sessionId: 'thread-xyz' });
2554
+ });
2555
+
2556
+ it('judges the folder as the caller may see it: a page they may not read leaves the note standing', async () => {
2557
+ await mkdir(join(knowledge(), 'Leadership'), { recursive: true });
2558
+ await writeFile(join(knowledge(), 'Leadership', 'Plan.md'), '# Plan\n');
2559
+ const planClosed = {
2560
+ ...allowAll,
2561
+ canReadBatch: async (_w: string, _u: string, paths: string[]) =>
2562
+ new Map(paths.map((p) => [p, !p.endsWith('Plan.md')])),
2563
+ } as unknown as IAccessControl;
2564
+ const base = await startSessionApp('external', undefined, wsDir, undefined, planClosed);
2565
+ const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
2566
+ expect(res.firstRun).toBe(firstRunNote(`${KB_DIR}/KnowledgeBase`));
2567
+ });
2568
+
2569
+ it('says nothing of a pack whose page the caller may not read, in the checkout or gone from it', async () => {
2570
+ // Absent from the checkout, so the folder reads as new — and the note
2571
+ // would still name the pack and its suggestions.
2572
+ const starter: FirstRunStarterSource = {
2573
+ firstRunStarter: async () => ({
2574
+ name: 'Sales',
2575
+ suggestedPages: ['Customers'],
2576
+ pages: new Map([['Customers.md', '# Customers\n']]),
2577
+ }),
2578
+ };
2579
+ const pageClosed = {
2580
+ ...allowAll,
2581
+ canReadBatch: async (_w: string, _u: string, paths: string[]) =>
2582
+ new Map(paths.map((p) => [p, !p.endsWith('Customers.md')])),
2583
+ } as unknown as IAccessControl;
2584
+ const base = await startSessionApp('external', undefined, wsDir, starter, pageClosed);
2585
+ const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
2586
+ expect(res).toEqual({ sessionId: 'thread-xyz' });
2587
+ });
2588
+
2589
+ it('never costs the session id: a workspace it cannot read answers with the id alone', async () => {
2590
+ await rm(wsDir, { recursive: true, force: true });
2591
+
2592
+ const res = await startSession();
2593
+
2594
+ expect(res).toEqual({ sessionId: 'thread-xyz' });
2595
+ });
2375
2596
  });
2376
2597
  });
2377
2598
 
@@ -2945,6 +3166,108 @@ describe('preflight for moves and deletes', () => {
2945
3166
  });
2946
3167
 
2947
3168
  describe('move_file', () => {
3169
+ describe('rewrites links', () => {
3170
+ /** A node at Sales/deal.md, linked from a page beside it, a read-only page, and a page the caller cannot read. */
3171
+ async function linked(): Promise<string> {
3172
+ const base = await seeded();
3173
+ await fs.writeFile(KB('NodeTypes/Deal.md'), '# Deal\n');
3174
+ await fs.writeFile(KB('Sales/deal.md'), '---\nnodeType: "[Deal](../NodeTypes/Deal.md)"\nid: deal-a\n---\n\n# Deal\nSee [policy](../HR/policy.md).\n');
3175
+ await fs.writeFile(KB('Sales/index.md'), `[d](deal.md "Deal") [r](/${KB_DIR}/Sales/deal.md#terms) [id](deal-a)\n\n\`deal.md\`\n`);
3176
+ await fs.writeFile(KB('Locked/refs.md'), '[d](../Sales/deal.md)\n');
3177
+ await fs.writeFile(KB('Secret/refs.md'), '[d](../Sales/deal.md)\n');
3178
+ await fs.writeFile(KB('Sales/transcripts/01.md'), '[d](../deal.md)\n');
3179
+ return base;
3180
+ }
3181
+ const args = { src: KB('Sales/deal.md'), dest: KB('Sales/2026/deal.md') };
3182
+
3183
+ it('by default: links in and out are rewritten, the dry run answering the plan the move carries out', async () => {
3184
+ const base = await linked();
3185
+ const dry = await call(base, 'move_file', { ...args, dryRun: true });
3186
+ expect(dry.status).toBe(200);
3187
+ expect(dry.body.links).toEqual({
3188
+ filesEdited: 2,
3189
+ linksRewritten: 4,
3190
+ edits: [
3191
+ { path: KB('Sales/2026/deal.md'), from: '../NodeTypes/Deal.md', to: '../../NodeTypes/Deal.md' },
3192
+ { path: KB('Sales/2026/deal.md'), from: '../HR/policy.md', to: '../../HR/policy.md' },
3193
+ { path: KB('Sales/index.md'), from: 'deal.md', to: '2026/deal.md' },
3194
+ { path: KB('Sales/index.md'), from: `/${KB_DIR}/Sales/deal.md#terms`, to: `/${KB_DIR}/Sales/2026/deal.md#terms` },
3195
+ ],
3196
+ notRewritten: [{ path: KB('Locked/refs.md'), reason: 'no write access', links: ['../Sales/deal.md'] }],
3197
+ unsearched: 'Links in files you cannot read were not searched, and may still point at the old path.',
3198
+ });
3199
+ // Nothing about the unreadable page leaks.
3200
+ expect(JSON.stringify(dry.body)).not.toContain('Secret');
3201
+ expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toContain('[d](deal.md "Deal")');
3202
+
3203
+ const run = await call(base, 'move_file', args);
3204
+ expect(run.status).toBe(200);
3205
+ expect(run.body).toMatchObject({ moved: true });
3206
+ expect(run.body.links).toEqual(dry.body.links);
3207
+ expect(await fs.readFile(KB('Sales/2026/deal.md'), { encoding: 'utf8' })).toBe(
3208
+ '---\nnodeType: "[Deal](../../NodeTypes/Deal.md)"\nid: deal-a\n---\n\n# Deal\nSee [policy](../../HR/policy.md).\n',
3209
+ );
3210
+ expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toBe(
3211
+ `[d](2026/deal.md "Deal") [r](/${KB_DIR}/Sales/2026/deal.md#terms) [id](deal-a)\n\n\`deal.md\`\n`,
3212
+ );
3213
+ // The read-only page, the unreadable page and the transcript keep the old path.
3214
+ expect(await fs.readFile(KB('Locked/refs.md'), { encoding: 'utf8' })).toBe('[d](../Sales/deal.md)\n');
3215
+ expect(await fs.readFile(KB('Secret/refs.md'), { encoding: 'utf8' })).toBe('[d](../Sales/deal.md)\n');
3216
+ expect(await fs.readFile(KB('Sales/transcripts/01.md'), { encoding: 'utf8' })).toBe('[d](../deal.md)\n');
3217
+ });
3218
+
3219
+ it('`rewriteLinks: false` moves exactly as before: no link touched, no `links` block', async () => {
3220
+ const base = await linked();
3221
+ const dry = await call(base, 'move_file', { ...args, dryRun: true, rewriteLinks: false });
3222
+ expect(dry.body.links).toBeUndefined();
3223
+ const run = await call(base, 'move_file', { ...args, rewriteLinks: false });
3224
+ expect(run.body).toMatchObject({ moved: true });
3225
+ expect(run.body.links).toBeUndefined();
3226
+ expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toContain('[d](deal.md "Deal")');
3227
+ expect(await fs.readFile(KB('Sales/2026/deal.md'), { encoding: 'utf8' })).toContain('../NodeTypes/Deal.md');
3228
+ });
3229
+
3230
+ it('the pages the answer names reach the read hook, the edited ones the write hook, and a page merely searched neither', async () => {
3231
+ const base = await linked();
3232
+ const reads: string[] = [];
3233
+ const writes: string[] = [];
3234
+ hooks.onAgentRead(async (op) => {
3235
+ reads.push(op.wsPath ?? '');
3236
+ });
3237
+ hooks.onPreWrite(async (op) => {
3238
+ writes.push(op.wsPath ?? '');
3239
+ });
3240
+ expect((await call(base, 'move_file', args)).body).toMatchObject({ moved: true });
3241
+ // `Locked/refs.md` is named (left for want of write access, its links
3242
+ // listed), so the read hook hears of it; the unreadable page and the
3243
+ // transcript are never named, so it never hears of them.
3244
+ expect(reads.sort()).toEqual([KB('Locked/refs.md'), KB('Sales/deal.md'), KB('Sales/index.md')]);
3245
+ expect([...new Set(writes)].sort()).toEqual([KB('Sales/2026/deal.md'), KB('Sales/deal.md'), KB('Sales/index.md')]);
3246
+ });
3247
+
3248
+ it('a move that would edit more than 200 files is refused, and nothing changes', async () => {
3249
+ const base = await seeded();
3250
+ for (let i = 0; i < 201; i++) await fs.writeFile(KB(`Sales/pages/p${i}.md`), '[d](../deal.md)\n');
3251
+ const dry = await call(base, 'move_file', { ...args, dryRun: true });
3252
+ expect(dry.body).toMatchObject({ allowed: false });
3253
+ expect(dry.body.reason).toMatch(/201 files.*rewriteLinks: false/s);
3254
+ const run = await call(base, 'move_file', args);
3255
+ expect(run.status).toBe(400);
3256
+ expect(JSON.stringify(run.body)).toContain('rewriteLinks: false');
3257
+ expect(await exists(args.src)).toBe(true);
3258
+ expect(await fs.readFile(KB('Sales/pages/p0.md'), { encoding: 'utf8' })).toBe('[d](../deal.md)\n');
3259
+ });
3260
+
3261
+ it('the description states the default, the opt-out, and that prose and code are not changed', async () => {
3262
+ await start();
3263
+ const def = (await toolRegistry.listInternal()).find((t) => t.name === 'move_file');
3264
+ const description = typeof def?.description === 'string' ? def.description : '';
3265
+ expect(description).toMatch(/rewritten by default/);
3266
+ expect(description).toContain('`rewriteLinks: false`');
3267
+ expect(description).toMatch(/plain prose or code are never changed/);
3268
+ });
3269
+ });
3270
+
2948
3271
  it('a same-access move: the dry run changes nothing, the real call runs without confirm', async () => {
2949
3272
  const base = await seeded();
2950
3273
  const args = { src: KB('Sales/deal.md'), dest: KB('Sales/2026/deal.md') };
@@ -3303,6 +3626,80 @@ describe('preflight for moves and deletes', () => {
3303
3626
  expect(run.status).toBe(403);
3304
3627
  expect(run.body).toMatchObject({ kind: 'write-denied', path: KB('Sales/deal.md'), canPropose: true });
3305
3628
  });
3629
+
3630
+ // A nested access.md is deleted the way a person deletes it in the app:
3631
+ // by whoever may write it, through the same one-file delete (the locking
3632
+ // filesystem's, which commits and pushes as the caller) as any file.
3633
+ it('a nested access.md is deleted by a caller who may write it, like any file', async () => {
3634
+ const base = await seeded();
3635
+ await fs.writeFile(KB('Sales/access.md'), '---\nread: Admin\n---\n');
3636
+ expect((await call(base, 'file_stat', { path: KB('Sales/access.md') })).body).toMatchObject({
3637
+ managed: true, movable: false, deletable: true,
3638
+ });
3639
+ const deleted: string[] = [];
3640
+ const deleteFile = fs.deleteFile.bind(fs);
3641
+ fs.deleteFile = async (p: string) => { deleted.push(p); return deleteFile(p); };
3642
+
3643
+ const run = await call(base, 'delete_file', { path: KB('Sales/access.md') });
3644
+
3645
+ expect(run.status).toBe(200);
3646
+ expect(run.body).toEqual({ path: KB('Sales/access.md'), deleted: true });
3647
+ expect(deleted).toEqual([KB('Sales/access.md')]);
3648
+ expect(await exists(KB('Sales/access.md'))).toBe(false);
3649
+ expect(await exists(KB('Sales/deal.md'))).toBe(true);
3650
+ });
3651
+
3652
+ it('a nested access.md the caller may not write is the ordinary write refusal, and stays', async () => {
3653
+ const base = await seeded();
3654
+ await fs.writeFile(KB('Locked/access.md'), '---\nread: everyone\n---\n');
3655
+ expect((await call(base, 'file_stat', { path: KB('Locked/access.md') })).body).toMatchObject({
3656
+ managed: true, deletable: false,
3657
+ });
3658
+
3659
+ const run = await call(base, 'delete_file', { path: KB('Locked/access.md') });
3660
+
3661
+ expect(run.status).toBe(403);
3662
+ expect(run.body).toMatchObject({ kind: 'write-denied', path: KB('Locked/access.md'), canPropose: true });
3663
+ expect(JSON.stringify(run.body)).not.toContain('platform file');
3664
+ expect(await exists(KB('Locked/access.md'))).toBe(true);
3665
+ });
3666
+
3667
+ it('the root access.md and roles.yaml are the repository\'s own: refused for everyone, and they stay', async () => {
3668
+ const base = await seeded();
3669
+ await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
3670
+ for (const name of ['access.md', 'roles.yaml']) {
3671
+ // The caller here may write and own everything: the refusal is not about who asks.
3672
+ expect((await call(base, 'file_stat', { path: KB(name) })).body).toMatchObject({
3673
+ managed: true, movable: false, deletable: false,
3674
+ access: { read: true, write: true, download: true, owner: true },
3675
+ });
3676
+ const run = await call(base, 'delete_file', { path: KB(name) });
3677
+ expect(run.status).toBe(400);
3678
+ expect(run.body.error).toBe(`${name} is the repository's own file and cannot be deleted.`);
3679
+ expect(await exists(KB(name))).toBe(true);
3680
+ }
3681
+ });
3682
+
3683
+ it('a nested roles.yaml is content, and .bevelignore stays refused as before', async () => {
3684
+ const base = await seeded();
3685
+ await fs.writeFile(KB('Sales/roles.yaml'), 'content');
3686
+ await fs.writeFile(KB('Sales/.bevelignore'), '*.tmp\n');
3687
+ expect((await call(base, 'delete_file', { path: KB('Sales/roles.yaml') })).status).toBe(200);
3688
+ const ignore = await call(base, 'delete_file', { path: KB('Sales/.bevelignore') });
3689
+ expect(ignore.status).toBe(400);
3690
+ expect(ignore.body.error).toBe('.bevelignore is a platform file and cannot be deleted through the agent tools.');
3691
+ expect((await call(base, 'file_stat', { path: KB('Sales/.bevelignore') })).body).toMatchObject({ deletable: false });
3692
+ });
3693
+
3694
+ it('delete_folder still refuses the repository root, which keeps its access.md', async () => {
3695
+ const base = await seeded();
3696
+ for (const args of [{ dryRun: true }, { confirm: true }]) {
3697
+ const run = await call(base, 'delete_folder', { path: KB_DIR, ...args });
3698
+ expect(run.body.allowed === false || run.status === 400).toBe(true);
3699
+ expect(JSON.stringify(run.body)).toContain('The repository root is a platform folder and cannot be moved or deleted.');
3700
+ }
3701
+ expect(await exists(KB('access.md'))).toBe(true);
3702
+ });
3306
3703
  });
3307
3704
 
3308
3705
  describe('what counts as the platform\'s own, and what a move or delete may reach', () => {