@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,222 @@
1
+ /**
2
+ * The "first run" note `start_session` adds on a knowledge base nobody has
3
+ * written in yet.
4
+ *
5
+ * Someone who has just set up Hexis and connected their agent has an empty
6
+ * knowledge base and an agent that could fill it. The agent cannot know the
7
+ * first from a static instruction — the same text reaches a knowledge base of
8
+ * ten thousand pages — so the signal is computed when the session starts:
9
+ * while the knowledge folder of the default branch holds nothing but the
10
+ * starter guide the template seeds, `start_session` answers with a short note
11
+ * telling the agent to offer to draft the first pages, and the guide's
12
+ * {@link FIRST_RUN_SECTION_ID} section says how. Once a page of anyone's
13
+ * exists, the note stops on its own: there is no flag to clear.
14
+ *
15
+ * The question is asked AS THE CALLER, gated like a read. The note goes only
16
+ * to someone who may read the knowledge folder, and it judges the folder as
17
+ * they may see it: a page they may not read is passed over as if it were not
18
+ * there (`mayRead` in {@link knowledgeFolderIsNew}), so a note that does or
19
+ * does not come tells them nothing about pages beyond them. The answer names
20
+ * no file but the starter guide, which every knowledge base is seeded with,
21
+ * and a chosen starter pack's pages — and when one of those is beyond the
22
+ * caller, no note at all, since a list with a gap would say where the gap is.
23
+ *
24
+ * A STARTER PACK does not end it. The pages a pack adds are tasks with a
25
+ * heading and a line or two — "ask your agent to draft this" — so while one
26
+ * still holds exactly what the pack wrote it is not a page anyone wrote, and
27
+ * the note goes on, naming the pages the pack suggests drafting first. The
28
+ * first edit to any of them makes the knowledge base established.
29
+ */
30
+
31
+ import nodeFs from 'node:fs/promises';
32
+ import { join } from 'node:path';
33
+ import type { ITreeWalker } from '../../shared/fs.contract.js';
34
+ import { NEW_KNOWLEDGE_BASE_SECTION_ID } from '../agent-guide/index.js';
35
+ import { BevelIgnoreStack } from '../kb-fs/bevel-ignore.js';
36
+
37
+ /**
38
+ * The starter guide the template seeds at the top of the knowledge folder
39
+ * (`kb-template/KnowledgeBase/How to get started.md`). The one page that does
40
+ * not make a knowledge base established. A test holds this to the template, so
41
+ * renaming one without the other fails there.
42
+ */
43
+ export const STARTER_GUIDE_FILE = 'How to get started.md';
44
+
45
+ /**
46
+ * The guide section the note points at (`agent-guide/new-knowledge-base.md`):
47
+ * the guide's own id, so the note cannot name a section the guide renamed.
48
+ */
49
+ export const FIRST_RUN_SECTION_ID = NEW_KNOWLEDGE_BASE_SECTION_ID;
50
+
51
+ /**
52
+ * The starter pack a knowledge base was filled from, as far as the first-run
53
+ * check cares: what the team chose, the pages it suggests drafting, and the
54
+ * pages it wrote — by path below the knowledge folder, with the text each was
55
+ * written with.
56
+ */
57
+ export interface FirstRunStarter {
58
+ name: string;
59
+ suggestedPages: readonly string[];
60
+ pages: ReadonlyMap<string, string>;
61
+ }
62
+
63
+ /** Where `start_session` asks which starter pack was chosen (see `modules/onboarding`). */
64
+ export interface FirstRunStarterSource {
65
+ firstRunStarter(): Promise<FirstRunStarter | null>;
66
+ }
67
+
68
+ /**
69
+ * Files that are configuration, not content, wherever they sit: a folder's
70
+ * access rules. Dot-entries (the folder placeholder `.gitkeep`, `.bevelignore`,
71
+ * an OS's `.DS_Store`) the knowledge-base walk never shows at all.
72
+ */
73
+ const NOT_CONTENT = new Set(['access.md']);
74
+
75
+ /**
76
+ * How many folders the check lists, the knowledge folder included, before it
77
+ * calls the knowledge base established. A knowledge base with this many
78
+ * folders and no page among them is not one an agent should greet as new, and
79
+ * `start_session` must stay cheap whatever the tree looks like.
80
+ */
81
+ export const FOLDER_BUDGET = 200;
82
+
83
+ /**
84
+ * Which of some pages (paths below the knowledge folder) the caller may
85
+ * read — `canReadBatch` for them, with the folder's prefix put on and taken
86
+ * off again.
87
+ */
88
+ export type MayRead = (relPaths: string[]) => Promise<ReadonlyMap<string, boolean>>;
89
+
90
+ /**
91
+ * Whether the knowledge folder `knowledgeDir` of the checkout at `repoRoot`
92
+ * holds nothing but the starter guide: no file in it or below it other than
93
+ * that guide at its top, folder placeholders, dot-files and access rules. A
94
+ * folder that is not there answers false: that is a knowledge base in a shape
95
+ * the template never made, and greeting it as new would be a guess.
96
+ *
97
+ * `starterPages` are a starter pack's pages (path below the folder → the text
98
+ * the pack wrote): one still holding exactly that text is not a page either.
99
+ *
100
+ * THE knowledge-base walk does the walking ({@link ITreeWalker.walkKb}, see
101
+ * `shared/fs.contract.ts`), the way the explorer's does: dot-entries never
102
+ * shown, a link or a socket never entered nor counted as a file, and
103
+ * `.bevelignore` honoured — the repository root's rules and every folder's on
104
+ * the way down — so what the explorer hides this check does not see either,
105
+ * and a knowledge folder those rules hide is not greeted. A folder that
106
+ * cannot be listed is the answer: not new, since it cannot say.
107
+ *
108
+ * With `mayRead`, the answer is the CALLER'S: a file they may not read is not
109
+ * theirs to know of, so it does not make the folder old for them. Every
110
+ * folder is entered, as the agent's own listing shows every folder and
111
+ * filters its files — a deeper rule may open a page below a folder its
112
+ * parent's rules close — and a folder's pages are judged in chunks of
113
+ * {@link JUDGE_CHUNK}; the first the caller may read ends the walk: not new.
114
+ * Without `mayRead` the first page ends it. Either way {@link FOLDER_BUDGET}
115
+ * folders end it too: that many and no page is not a new knowledge base.
116
+ */
117
+ export async function knowledgeFolderIsNew(
118
+ disk: ITreeWalker,
119
+ repoRoot: string,
120
+ knowledgeDir: string,
121
+ starterPages?: ReadonlyMap<string, string>,
122
+ mayRead?: MayRead,
123
+ ): Promise<boolean> {
124
+ let sawRoot = false;
125
+ let established = false;
126
+ let listed = 0;
127
+ /** Whether any of `pages` (paths below the knowledge folder) is one the caller may read. */
128
+ const anyReadable = async (pages: string[]): Promise<boolean> => {
129
+ const verdicts = await mayRead!(pages);
130
+ return pages.some((p) => verdicts.get(p) === true);
131
+ };
132
+ try {
133
+ // The rules in force above the knowledge folder — the repository root's
134
+ // file — which a walk starting below it would not see on its own.
135
+ const above = await BevelIgnoreStack.empty().extendedWith(repoRoot);
136
+ const dir = join(repoRoot, knowledgeDir);
137
+ if (above.isIgnored(dir, true)) return false;
138
+ await disk.walkKb(
139
+ dir,
140
+ [
141
+ {
142
+ async onDir(rel, entries, folder) {
143
+ if (rel === '') sawRoot = true;
144
+ if (++listed > FOLDER_BUDGET) {
145
+ established = true;
146
+ return;
147
+ }
148
+ let pages: string[] = [];
149
+ for (const entry of entries) {
150
+ if (!entry.isFile() || NOT_CONTENT.has(entry.name)) continue;
151
+ if (rel === '' && entry.name === STARTER_GUIDE_FILE) continue;
152
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
153
+ if (await isUntouchedStarterPage(join(folder.abs, entry.name), starterPages?.get(childRel))) continue;
154
+ // A page, with nobody to ask: something someone put there.
155
+ if (!mayRead) {
156
+ established = true;
157
+ return;
158
+ }
159
+ pages.push(childRel);
160
+ if (pages.length >= JUDGE_CHUNK) {
161
+ if (await anyReadable(pages)) {
162
+ established = true;
163
+ return;
164
+ }
165
+ pages = [];
166
+ }
167
+ }
168
+ if (pages.length > 0 && (await anyReadable(pages))) established = true;
169
+ },
170
+ },
171
+ ],
172
+ { ignore: above, until: () => established, unreadable: 'throw' },
173
+ );
174
+ } catch {
175
+ return false;
176
+ }
177
+ return sawRoot && !established;
178
+ }
179
+
180
+ /** How many of a folder's pages are judged in one ask of `mayRead`. */
181
+ const JUDGE_CHUNK = 200;
182
+
183
+ /**
184
+ * Whether the file at `file` still holds exactly `written`, the text a starter
185
+ * pack put there (line endings aside: a CRLF checkout of the same text is the
186
+ * same page). No `written` means no pack wrote it.
187
+ */
188
+ export async function isUntouchedStarterPage(file: string, written: string | undefined): Promise<boolean> {
189
+ if (written === undefined) return false;
190
+ const norm = (text: string) => text.replace(/\r\n?/g, '\n');
191
+ try {
192
+ return norm(await nodeFs.readFile(file, 'utf8')) === norm(written);
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * The note itself, as `start_session` returns it under `firstRun`. Complete on
200
+ * its own, for the agent that reads no further: what the state is, what to
201
+ * offer, and that the person's own request comes first.
202
+ */
203
+ export function firstRunNote(
204
+ knowledgeFolder: string,
205
+ starter?: Pick<FirstRunStarter, 'name' | 'suggestedPages'>,
206
+ ): string {
207
+ if (starter) {
208
+ const pages = starter.suggestedPages.length > 0 ? ` (${starter.suggestedPages.join(', ')})` : '';
209
+ return (
210
+ `This knowledge base is new: \`${knowledgeFolder}/\` holds the starter guide and the ${starter.name} starter pages, which are short placeholders to fill in. ` +
211
+ `Once in this conversation, offer to draft its first pages${pages} from the person's website, repository or a few sentences of theirs, for them to review. ` +
212
+ 'If they asked for something else, answer that first and make the offer in one line. ' +
213
+ `The guide's \`${FIRST_RUN_SECTION_ID}\` section says how.`
214
+ );
215
+ }
216
+ return (
217
+ `This knowledge base is new: \`${knowledgeFolder}/\` has no pages yet, the starter guide aside. ` +
218
+ "Once in this conversation, offer to draft its first pages (what the organisation does, its customers, its products, a glossary, how it works) from the person's website or a few sentences of theirs, for them to review. " +
219
+ 'If they asked for something else, answer that first and make the offer in one line. ' +
220
+ `The guide's \`${FIRST_RUN_SECTION_ID}\` section says how.`
221
+ );
222
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The link half of `move_file`: which pages a move edits, and what it reports.
3
+ *
4
+ * A move breaks two sets of links — those inside the moved files that point
5
+ * elsewhere, and those elsewhere that point at the moved files. The plan reads
6
+ * both through the shared grammar (`md-links` in platform-shared) and answers
7
+ * the edits to make plus the report the agent gets, the same for a dry run and
8
+ * for the move itself.
9
+ *
10
+ * Who sees what:
11
+ * - a file the caller cannot read is never opened; the report then carries
12
+ * one sentence saying such files may still point at the old path, with no
13
+ * name and no count;
14
+ * - a file the caller can read but may not change (a protected branch's
15
+ * write rules, or the write hook's refusal) is named with its links, and
16
+ * left;
17
+ * - a file the read hook refuses counts as one the caller cannot read: not
18
+ * named, only covered by the same sentence — the hook is asked for every
19
+ * file the answer would name, before it is named, and for no other;
20
+ * - an HTML page is named with its links, and left — and so is a markdown
21
+ * page for the raw HTML it carries (`<a href>`, `<img src>`), which the
22
+ * grammar reads but never rewrites.
23
+ */
24
+
25
+ import {
26
+ htmlLinksAffectedByMove,
27
+ rewriteMdLinks,
28
+ scanMarkdownHtmlLinks,
29
+ type MdLinkEdit,
30
+ } from '@bevel-software/platform-shared';
31
+
32
+ /** More edited files than this and the move is refused. */
33
+ export const MOVE_LINK_EDIT_CAP = 200;
34
+ /** How many edits the report lists. */
35
+ export const MOVE_LINK_REPORT_EDITS = 100;
36
+
37
+ /** The sentence about files the caller cannot read. */
38
+ export const UNSEARCHED_SENTENCE =
39
+ 'Links in files you cannot read were not searched, and may still point at the old path.';
40
+
41
+ /** A folder segment whose files are records of the past, never searched or rewritten. */
42
+ const UNSEARCHED_SEGMENTS = new Set(['transcripts', 'probes']);
43
+
44
+ export interface MoveLinksReport {
45
+ filesEdited: number;
46
+ linksRewritten: number;
47
+ /** The first {@link MOVE_LINK_REPORT_EDITS} edits; `path` is where the file sits after the move. */
48
+ edits: { path: string; from: string; to: string }[];
49
+ notRewritten: { path: string; reason: string; links: string[] }[];
50
+ unsearched?: string;
51
+ }
52
+
53
+ export interface PlannedEdit {
54
+ /** Where the file sits after the move. */
55
+ path: string;
56
+ /** Where it sits now. */
57
+ lockAt: string;
58
+ /** The bytes the edit was computed from — compared again under the lock. */
59
+ original: string;
60
+ content: string;
61
+ links: MdLinkEdit[];
62
+ }
63
+
64
+ export interface MoveLinksPlan {
65
+ edits: PlannedEdit[];
66
+ report: MoveLinksReport;
67
+ /** Set when the move would edit more than the cap; the move is refused with it. */
68
+ overCap?: string;
69
+ }
70
+
71
+ export interface MoveLinksInput {
72
+ src: string;
73
+ dest: string;
74
+ branch: string;
75
+ kbDirName: string;
76
+ /** Every file on the branch inside the clone folder, workspace-relative. */
77
+ allFiles: string[];
78
+ /** Read verdicts for workspace-relative paths. */
79
+ canRead: (paths: string[]) => Promise<Map<string, boolean>>;
80
+ /** The paths among these the caller may not change (protected-branch rules). */
81
+ writeBlocked: (paths: string[]) => Promise<string[]>;
82
+ readText: (path: string) => Promise<string>;
83
+ /**
84
+ * The deployment's read hook on one page, at its current path: true when
85
+ * it refuses. Asked for every page the answer would NAME — edited, or left
86
+ * with its links listed — and for no page merely searched: naming is the
87
+ * disclosure. A refused page is one the caller cannot read, covered by the
88
+ * one sentence and never named.
89
+ */
90
+ readRefused: (path: string) => Promise<boolean>;
91
+ /**
92
+ * The deployment's write hook on a page the move would edit, at its
93
+ * post-move path: why it may not be, or null. Asked for edited pages only.
94
+ */
95
+ writeRefusal: (lockAt: string, path: string) => Promise<string | null>;
96
+ }
97
+
98
+ /** Whether `path` lies in a `transcripts/` or `probes/` folder. */
99
+ export function isUnsearchedRecord(path: string): boolean {
100
+ return path.split('/').slice(0, -1).some((s) => UNSEARCHED_SEGMENTS.has(s));
101
+ }
102
+
103
+ const isMarkdown = (p: string) => /\.md$/i.test(p);
104
+ const isHtml = (p: string) => /\.html?$/i.test(p);
105
+
106
+ /**
107
+ * The search's cheap first cut: a file linking at `name` names it — raw, or
108
+ * spelled with markdown escapes or percent-encoding, which are undone here
109
+ * the way the resolver undoes them (any case, any subset of characters).
110
+ */
111
+ function mayMention(text: string, name: string): boolean {
112
+ if (text.includes(name)) return true;
113
+ const unescaped = text.replace(/\\([!-/:-@[-`{-~])/g, '$1');
114
+ const decoded = unescaped.replace(/(?:%[0-9a-f]{2})+/gi, (run) => {
115
+ try {
116
+ return decodeURIComponent(run);
117
+ } catch {
118
+ return run;
119
+ }
120
+ });
121
+ return decoded.includes(name);
122
+ }
123
+
124
+ export async function planMoveLinks(input: MoveLinksInput): Promise<MoveLinksPlan> {
125
+ const { src, dest, branch, kbDirName } = input;
126
+ const mapPath = (p: string): string | null =>
127
+ p === src ? dest : p.startsWith(`${src}/`) ? dest + p.slice(src.length) : null;
128
+ const srcName = src.slice(src.lastIndexOf('/') + 1);
129
+
130
+ const candidates = input.allFiles.filter((p) => (isMarkdown(p) || isHtml(p)) && !isUnsearchedRecord(p)).sort();
131
+ const readable = await input.canRead(candidates);
132
+ let unsearched = candidates.some((p) => readable.get(p) !== true);
133
+
134
+ const edits: PlannedEdit[] = [];
135
+ /** Pages the answer would name for raw HTML, held back until the read hook allows the page. */
136
+ const htmlReports: { lockAt: string; entry: MoveLinksReport['notRewritten'][number] }[] = [];
137
+ for (const oldPath of candidates) {
138
+ if (readable.get(oldPath) !== true) continue;
139
+ const newPath = mapPath(oldPath) ?? oldPath;
140
+ // The sidebar moves and deletes without this tool's locks, so a page can
141
+ // go between the listing and this read. A page gone has no links to fix;
142
+ // one that cannot be opened is one more the search did not cover. Gone is
143
+ // the disk's own definition of absence: ENOENT, and ENOTDIR for a page
144
+ // whose folder became a file in the meantime.
145
+ let text: string;
146
+ try {
147
+ text = await input.readText(oldPath);
148
+ } catch (err) {
149
+ const code = (err as NodeJS.ErrnoException | null)?.code;
150
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') unsearched = true;
151
+ continue;
152
+ }
153
+ // A file that stays put is only touched by a link that names the moved
154
+ // path, so a page that does not mention it is skipped unparsed.
155
+ if (newPath === oldPath && !mayMention(text, srcName)) continue;
156
+ const opts = { oldPath, newPath, mapPath, kbDirName, branch };
157
+ if (isHtml(oldPath)) {
158
+ const links = htmlLinksAffectedByMove(text, opts);
159
+ if (links.length > 0) htmlReports.push({ lockAt: oldPath, entry: { path: newPath, reason: 'html page', links } });
160
+ continue;
161
+ }
162
+ // Raw HTML inside a markdown page (`<img src>`, `<a href>`) renders like
163
+ // a link but is not one the grammar rewrites: named with those links and
164
+ // left, as an HTML page is, so a stale target is never silent. Only a
165
+ // live tag: one inside code or behind a `\<` escape is an example.
166
+ const html = htmlLinksAffectedByMove(text, opts, scanMarkdownHtmlLinks(text));
167
+ if (html.length > 0) {
168
+ htmlReports.push({ lockAt: oldPath, entry: { path: newPath, reason: 'html in markdown', links: html } });
169
+ }
170
+ const rewritten = rewriteMdLinks(text, opts);
171
+ if (rewritten.edits.length === 0) continue;
172
+ edits.push({ path: newPath, lockAt: oldPath, original: text, content: rewritten.text, links: rewritten.edits });
173
+ }
174
+
175
+ // ONE gate before anything is named: the read hook, asked once per page the
176
+ // answer would name, whether as an edit or as a page left with its links
177
+ // listed. A page it refuses is one the caller cannot read — covered by the
178
+ // one sentence, named nowhere, its links quoted nowhere. A page merely
179
+ // searched is never asked about: nothing of it reaches the answer.
180
+ const readAllowed = new Map<string, boolean>();
181
+ const mayName = async (lockAt: string): Promise<boolean> => {
182
+ let allowed = readAllowed.get(lockAt);
183
+ if (allowed === undefined) {
184
+ allowed = !(await input.readRefused(lockAt));
185
+ readAllowed.set(lockAt, allowed);
186
+ if (!allowed) unsearched = true;
187
+ }
188
+ return allowed;
189
+ };
190
+ const notRewritten: MoveLinksReport['notRewritten'] = [];
191
+ for (const r of htmlReports) if (await mayName(r.lockAt)) notRewritten.push(r.entry);
192
+ const named: PlannedEdit[] = [];
193
+ for (const e of edits) if (await mayName(e.lockAt)) named.push(e);
194
+
195
+ // Only a file that stays put can be one the caller may not change: every
196
+ // moved file was already judged writable at both ends by the move itself.
197
+ const blocked = new Set(await input.writeBlocked(named.filter((e) => e.path === e.lockAt).map((e) => e.path)));
198
+ const kept = named.filter((e) => {
199
+ if (!blocked.has(e.path)) return true;
200
+ notRewritten.push({ path: e.path, reason: 'no write access', links: e.links.map((l) => l.from) });
201
+ return false;
202
+ });
203
+
204
+ if (kept.length > MOVE_LINK_EDIT_CAP) {
205
+ return {
206
+ edits: [],
207
+ report: reportOf([], notRewritten, unsearched),
208
+ overCap:
209
+ `This move would edit links in ${kept.length} files, more than the ${MOVE_LINK_EDIT_CAP} one move may edit, so nothing was moved. ` +
210
+ 'Move fewer files at a time (a subfolder, then the rest), or pass `rewriteLinks: false` to move without rewriting links.',
211
+ };
212
+ }
213
+
214
+ // The write hook hears about edited files only.
215
+ const allowed: PlannedEdit[] = [];
216
+ for (const e of kept) {
217
+ const refusal = await input.writeRefusal(e.lockAt, e.path);
218
+ if (refusal === null) allowed.push(e);
219
+ else notRewritten.push({ path: e.path, reason: refusal, links: e.links.map((l) => l.from) });
220
+ }
221
+ return { edits: allowed, report: reportOf(allowed, notRewritten, unsearched) };
222
+ }
223
+
224
+ function reportOf(
225
+ edits: PlannedEdit[],
226
+ notRewritten: MoveLinksReport['notRewritten'],
227
+ unsearched: boolean,
228
+ ): MoveLinksReport {
229
+ const flat = edits.flatMap((e) => e.links.map((l) => ({ path: e.path, from: l.from, to: l.to })));
230
+ return {
231
+ filesEdited: edits.length,
232
+ linksRewritten: flat.length,
233
+ edits: flat.slice(0, MOVE_LINK_REPORT_EDITS),
234
+ notRewritten,
235
+ ...(unsearched ? { unsearched: UNSEARCHED_SENTENCE } : {}),
236
+ };
237
+ }
@@ -8,6 +8,7 @@ import { TEMPLATE_SOURCE_FALLBACKS, TemplateSource, renderTemplateText } from '.
8
8
  import { assertNotGitInternals, hasGitInternalsSegment } from '../../../../shared/git-internals.js';
9
9
  import { GitInternalsError } from '../../../../shared/domain-errors.js';
10
10
  import { isManagedGuide } from '../../../agent-guide/agent-guide.js';
11
+ import { utf8Text } from '../../../../shared/utf8-text.js';
11
12
 
12
13
  /**
13
14
  * The empty-remote seed builder the runner takes as `buildSeedTree`: the full
@@ -155,7 +156,7 @@ class KbSeedTree {
155
156
  // Judged the way the copy below judges: a binary under the name is
156
157
  // spotted from its first bytes and never read whole.
157
158
  const source = await this.templates.pathOf(name);
158
- const text = (await headHasNul(source)) ? null : asText(await fs.readFile(source));
159
+ const text = (await headHasNul(source)) ? null : utf8Text(await fs.readFile(source));
159
160
  if (text !== null && isManagedGuide(text)) return;
160
161
  }
161
162
  await this.copyTemplateFile(relDir ? path.join(relDir, name) : name, dest);
@@ -179,7 +180,7 @@ class KbSeedTree {
179
180
  // A binary is spotted from its first bytes (a NUL turns up early in any
180
181
  // real one) and streamed across without ever being read whole; only what
181
182
  // may be text is read in full, and the full decode is still the judge.
182
- const text = (await headHasNul(from)) ? null : asText(await fs.readFile(from));
183
+ const text = (await headHasNul(from)) ? null : utf8Text(await fs.readFile(from));
183
184
  if (text === null) {
184
185
  await fs.copyFile(from, to);
185
186
  } else {
@@ -204,16 +205,6 @@ async function headHasNul(file: string): Promise<boolean> {
204
205
  }
205
206
  }
206
207
 
207
- /** The bytes as text when they ARE text — strict UTF-8, BOM kept, no NUL — else null. */
208
- function asText(bytes: Buffer): string | null {
209
- if (bytes.includes(0)) return null;
210
- try {
211
- return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
212
- } catch {
213
- return null;
214
- }
215
- }
216
-
217
208
  /**
218
209
  * Whether `rel` under `templateRoot` resolves inside a git folder — the
219
210
  * template entry is a link, and following it would read the repository's own
@@ -13,9 +13,13 @@ import {
13
13
  canonicalRelativePath,
14
14
  folderPlaceholderPath,
15
15
  isPlatformFile,
16
+ isPlatformFolder,
16
17
  isPlatformRestoreShape,
18
+ isRepositoryOwnFile,
19
+ repositoryOwnFileDeleteRefusal,
17
20
  platformFileCreationRefusal,
18
21
  platformFileRefusal,
22
+ platformFolderRefusal,
19
23
  reservedRootDirNames,
20
24
  } from '@bevel-software/platform-shared';
21
25
  import { FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
@@ -39,6 +43,7 @@ import { removeEmptyDirs } from './empty-dirs.js';
39
43
  import '../auth/auth.middleware.js'; // Express Request augmentation
40
44
  import type { SkillSaveCheck } from './workspace.tools.js';
41
45
  import { MAX_UPLOAD_BYTES } from './upload-limits.js';
46
+ import { OCTET_STREAM, rawExtensionOf, rawMimeFor } from './file-readers/raw-mime.js';
42
47
 
43
48
  /**
44
49
  * One file identity from one request field, or `null` when the caller sent
@@ -571,8 +576,11 @@ export function createWorkspaceRoutes(
571
576
  res: express.Response,
572
577
  workspaceId: string,
573
578
  relativePath: string,
579
+ // A caller that has already resolved the user hands it over, so one
580
+ // request is not looked up twice.
581
+ resolvedUser?: { email: string },
574
582
  ): Promise<boolean> {
575
- const user = await requireUser(req, res);
583
+ const user = resolvedUser ?? (await requireUser(req, res));
576
584
  if (!user) return false;
577
585
  let allowed: boolean;
578
586
  try {
@@ -852,27 +860,15 @@ export function createWorkspaceRoutes(
852
860
  // its own weak ETag and answers a matching If-None-Match with a 304.
853
861
  res.setHeader('ETag', `"${at.blobId}"`);
854
862
  }
855
- const ext = filePath.slice(filePath.lastIndexOf('.')).toLowerCase();
856
- const mimeTypes: Record<string, string> = {
857
- '.png': 'image/png',
858
- '.jpg': 'image/jpeg',
859
- '.jpeg': 'image/jpeg',
860
- '.gif': 'image/gif',
861
- '.webp': 'image/webp',
862
- '.svg': 'image/svg+xml',
863
- '.bmp': 'image/bmp',
864
- '.ico': 'image/x-icon',
865
- '.pdf': 'application/pdf',
866
- '.docx':
867
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
868
- '.xlsx':
869
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
870
- };
863
+ // The type from the extension, the one table the embed's raw route
864
+ // serves from as well (`file-readers/raw-mime.ts`).
865
+ const ext = rawExtensionOf(filePath);
866
+ const inlineMime = rawMimeFor(filePath);
871
867
  // SVG is active web content (it can carry <script>), and it is active in
872
868
  // BOTH directions: a saved-to-disk SVG re-opened later runs its scripts
873
869
  // under the file:// origin, so a download is forced to octet-stream.
874
- const downloadMime = ext === '.svg' ? 'application/octet-stream' : (mimeTypes[ext] || 'application/octet-stream');
875
- res.setHeader('Content-Type', wantsDownload ? downloadMime : (mimeTypes[ext] || 'application/octet-stream'));
870
+ const downloadMime = ext === '.svg' ? OCTET_STREAM : inlineMime;
871
+ res.setHeader('Content-Type', wantsDownload ? downloadMime : inlineMime);
876
872
  // Block MIME-sniffing so a misdeclared file can't be promoted to
877
873
  // active content by the browser.
878
874
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -978,9 +974,33 @@ export function createWorkspaceRoutes(
978
974
  res.status(400).json({ error: 'download=1 is required for folder zip downloads' });
979
975
  return;
980
976
  }
981
- if (!(await requireDownloadPermission(req, res, id, folderPath))) return;
977
+ const user = await requireUser(req, res);
978
+ if (!user) return;
979
+ if (!(await requireDownloadPermission(req, res, id, folderPath, user))) return;
980
+ // `download` on the folder lets the caller ASK for the zip. What goes in
981
+ // it is judged file by file, through the two gates the per-file route
982
+ // runs in the same order: a file the caller may not read is left out, and
983
+ // so is one they may read but not download. Nothing in the answer names a
984
+ // left-out file; the count below covers only files the caller could
985
+ // already see in the tree, so it discloses nothing the tree does not.
986
+ let withheld = 0;
987
+ const include = async (paths: string[]): Promise<ReadonlySet<string>> => {
988
+ const readable = await accessControl.canReadBatch(id, user.email, paths);
989
+ const visible = paths.filter((p) => readable.get(p) === true);
990
+ const downloadable = await accessControl.canDownloadBatch(id, user.email, visible);
991
+ const kept = new Set(visible.filter((p) => downloadable.get(p) === true));
992
+ withheld = visible.length - kept.size;
993
+ return kept;
994
+ };
982
995
  try {
983
- const buffer = await workspaceService.createFolderZip(id, folderPath);
996
+ const buffer = await workspaceService.createFolderZip(id, folderPath, include);
997
+ res.setHeader('X-Withheld-Files', String(withheld));
998
+ // One caller's archive: which files it holds, and the count beside it,
999
+ // are that caller's verdicts. `private` keeps a shared cache from
1000
+ // handing it to the next caller; `no-store` because, unlike the single
1001
+ // file, nothing here can be revalidated (no ETag), so a copy is never
1002
+ // worth keeping.
1003
+ res.setHeader('Cache-Control', 'private, no-store');
984
1004
  // `|| 'folder'` covers the edge case where `folderPath` itself was
985
1005
  // a single bare slash (`/`) that survived the trim — the service
986
1006
  // would still reject it as path traversal, but the basename
@@ -999,7 +1019,11 @@ export function createWorkspaceRoutes(
999
1019
  res.status(413).json({ error: error.message });
1000
1020
  return;
1001
1021
  }
1002
- if (error instanceof PathTraversalError) {
1022
+ // A traversal refusal (`PathTraversalError`), or the access tree failing
1023
+ // to load while the entries were judged (`AccessConfigError`): both are
1024
+ // domain errors carrying their own status and payload, the same ones
1025
+ // the single-file gate answers with.
1026
+ if (error instanceof WorkflowDomainError) {
1003
1027
  sendError(res, error);
1004
1028
  return;
1005
1029
  }
@@ -1098,6 +1122,16 @@ export function createWorkspaceRoutes(
1098
1122
  // Not on disk — let workspaceService.deleteFile return its own 404.
1099
1123
  }
1100
1124
  if (stat?.isDirectory()) {
1125
+ // A platform folder — the repository root, whose sweep would take the
1126
+ // root's `access.md` and `roles.yaml` with it, or one of the reserved
1127
+ // top-level folders that hold a whole section of the knowledge base —
1128
+ // is refused here as `delete_folder` refuses it, by the same rule.
1129
+ const trimmedFolder = filePath.replace(/\/+$/, '');
1130
+ const folderRel = trimmedFolder === kbDirName ? '' : toKbRelative(trimmedFolder, kbDirName);
1131
+ if (folderRel !== null && isPlatformFolder(folderRel, kb.layout)) {
1132
+ res.status(409).json({ error: platformFolderRefusal(folderRel) });
1133
+ return;
1134
+ }
1101
1135
  const branch = branchForWorkspaceId(id);
1102
1136
  // In the folder's turn: keeping a folder under this one (a file delete
1103
1137
  // racing this one) waits until the sweep is done, and then finds the
@@ -1166,6 +1200,14 @@ export function createWorkspaceRoutes(
1166
1200
  res.json({ status: 'deleted', count: filesInDir.length });
1167
1201
  return;
1168
1202
  }
1203
+ // The root's `access.md` and `roles.yaml` govern the whole repository:
1204
+ // nobody deletes them, here or through the agent tools. A nested
1205
+ // `access.md` is deleted like any file its caller may write.
1206
+ const rel = toKbRelative(filePath, kbDirName);
1207
+ if (rel !== null && isRepositoryOwnFile(rel, kb.layout)) {
1208
+ res.status(409).json({ error: repositoryOwnFileDeleteRefusal(rel) });
1209
+ return;
1210
+ }
1169
1211
  await withLock(id, user, filePath, () => workspaceService.deleteFile(id, filePath));
1170
1212
  // Deleting content is not deleting structure: an emptied folder stays.
1171
1213
  await keepFolderOf(id, user, filePath);