@bevel-software/platform-core-backend 0.25.2 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/agent-guide/access-control.md +251 -0
  2. package/agent-guide/conventions.md +35 -0
  3. package/agent-guide/directory-structure.md +145 -0
  4. package/agent-guide/finding-things.md +7 -0
  5. package/agent-guide/html-views.md +83 -0
  6. package/agent-guide/introduction.md +27 -0
  7. package/agent-guide/new-knowledge-base.md +24 -0
  8. package/agent-guide/skills.md +47 -0
  9. package/agent-guide/tool-manuals.md +227 -0
  10. package/agent-guide/where-a-new-file-goes.md +36 -0
  11. package/dist/assets.d.ts +26 -0
  12. package/dist/assets.d.ts.map +1 -1
  13. package/dist/assets.js +35 -2
  14. package/dist/assets.js.map +1 -1
  15. package/dist/core/catalog-revision.d.ts +19 -1
  16. package/dist/core/catalog-revision.d.ts.map +1 -1
  17. package/dist/core/catalog-revision.js +14 -3
  18. package/dist/core/catalog-revision.js.map +1 -1
  19. package/dist/core/core-ports.d.ts +11 -0
  20. package/dist/core/core-ports.d.ts.map +1 -1
  21. package/dist/core/core-ports.js.map +1 -1
  22. package/dist/core/create-core-server.d.ts +5 -0
  23. package/dist/core/create-core-server.d.ts.map +1 -1
  24. package/dist/core/create-core-server.js +65 -3
  25. package/dist/core/create-core-server.js.map +1 -1
  26. package/dist/core/create-core-services.d.ts +35 -0
  27. package/dist/core/create-core-services.d.ts.map +1 -1
  28. package/dist/core/create-core-services.js +114 -12
  29. package/dist/core/create-core-services.js.map +1 -1
  30. package/dist/core/lifecycle.d.ts +16 -1
  31. package/dist/core/lifecycle.d.ts.map +1 -1
  32. package/dist/core/lifecycle.js +18 -1
  33. package/dist/core/lifecycle.js.map +1 -1
  34. package/dist/core-config.d.ts +21 -0
  35. package/dist/core-config.d.ts.map +1 -1
  36. package/dist/core-config.js +22 -1
  37. package/dist/core-config.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/modules/access/access-control.interface.d.ts +17 -0
  43. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  44. package/dist/modules/access/access-control.service.d.ts +2 -0
  45. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-control.service.js +27 -0
  47. package/dist/modules/access/access-control.service.js.map +1 -1
  48. package/dist/modules/access/directory-sync-bot.js +1 -1
  49. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  50. package/dist/modules/agent-guide/agent-guide.d.ts +145 -0
  51. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
  52. package/dist/modules/agent-guide/agent-guide.js +199 -0
  53. package/dist/modules/agent-guide/agent-guide.js.map +1 -0
  54. package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
  55. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
  56. package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
  57. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
  58. package/dist/modules/agent-guide/index.d.ts +4 -0
  59. package/dist/modules/agent-guide/index.d.ts.map +1 -0
  60. package/dist/modules/agent-guide/index.js +4 -0
  61. package/dist/modules/agent-guide/index.js.map +1 -0
  62. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
  63. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  64. package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
  65. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  66. package/dist/modules/agent-instructions/compose.d.ts +14 -6
  67. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  68. package/dist/modules/agent-instructions/compose.js +28 -8
  69. package/dist/modules/agent-instructions/compose.js.map +1 -1
  70. package/dist/modules/agent-instructions/index.d.ts +1 -1
  71. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  72. package/dist/modules/agent-instructions/index.js +1 -1
  73. package/dist/modules/agent-instructions/index.js.map +1 -1
  74. package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
  75. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  76. package/dist/modules/agent-instructions/shared-file-rules.js +51 -104
  77. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  78. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  79. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  80. package/dist/modules/audit/agent-audit.service.js +43 -2
  81. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  82. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  83. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  84. package/dist/modules/audit/agent-connection.routes.js +38 -0
  85. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  86. package/dist/modules/audit/audit.contract.d.ts +24 -0
  87. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  88. package/dist/modules/audit/audit.contract.js.map +1 -1
  89. package/dist/modules/audit/index.d.ts +1 -0
  90. package/dist/modules/audit/index.d.ts.map +1 -1
  91. package/dist/modules/audit/index.js +1 -0
  92. package/dist/modules/audit/index.js.map +1 -1
  93. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  94. package/dist/modules/auth/account.routes.js +10 -3
  95. package/dist/modules/auth/account.routes.js.map +1 -1
  96. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  97. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  98. package/dist/modules/auth/auth.middleware.js +1 -1
  99. package/dist/modules/auth/auth.middleware.js.map +1 -1
  100. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  101. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  102. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  103. package/dist/modules/database/core-schema.d.ts +96 -0
  104. package/dist/modules/database/core-schema.d.ts.map +1 -1
  105. package/dist/modules/database/core-schema.js +32 -3
  106. package/dist/modules/database/core-schema.js.map +1 -1
  107. package/dist/modules/embed/account-link.service.d.ts +42 -0
  108. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  109. package/dist/modules/embed/account-link.service.js +50 -0
  110. package/dist/modules/embed/account-link.service.js.map +1 -0
  111. package/dist/modules/embed/embed-link.d.ts +58 -0
  112. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  113. package/dist/modules/embed/embed-link.js +160 -0
  114. package/dist/modules/embed/embed-link.js.map +1 -0
  115. package/dist/modules/embed/embed.errors.d.ts +30 -0
  116. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  117. package/dist/modules/embed/embed.errors.js +42 -0
  118. package/dist/modules/embed/embed.errors.js.map +1 -0
  119. package/dist/modules/embed/embed.interface.d.ts +204 -0
  120. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  121. package/dist/modules/embed/embed.interface.js +2 -0
  122. package/dist/modules/embed/embed.interface.js.map +1 -0
  123. package/dist/modules/embed/embed.routes.d.ts +59 -0
  124. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  125. package/dist/modules/embed/embed.routes.js +343 -0
  126. package/dist/modules/embed/embed.routes.js.map +1 -0
  127. package/dist/modules/embed/embed.service.d.ts +169 -0
  128. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  129. package/dist/modules/embed/embed.service.js +627 -0
  130. package/dist/modules/embed/embed.service.js.map +1 -0
  131. package/dist/modules/embed/embed.tools.d.ts +51 -0
  132. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  133. package/dist/modules/embed/embed.tools.js +170 -0
  134. package/dist/modules/embed/embed.tools.js.map +1 -0
  135. package/dist/modules/embed/mcp-app.d.ts +64 -0
  136. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  137. package/dist/modules/embed/mcp-app.js +147 -0
  138. package/dist/modules/embed/mcp-app.js.map +1 -0
  139. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  140. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  141. package/dist/modules/embed/mcp-app.routes.js +36 -0
  142. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  143. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  144. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  145. package/dist/modules/github-app/github-app.connection.js +34 -3
  146. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  147. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  148. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  149. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  150. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  151. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  152. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  153. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  154. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  155. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  156. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  158. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +57 -3
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +177 -24
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
  168. package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
  169. package/dist/modules/mcp/tool-schema-guard.js +171 -0
  170. package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
  171. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  172. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  173. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  174. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  175. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  176. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  177. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  178. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  179. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  180. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  181. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  182. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  183. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  184. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  185. package/dist/modules/onboarding/starter-packs.js +156 -0
  186. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  187. package/dist/modules/plugins/index.d.ts +1 -1
  188. package/dist/modules/plugins/index.d.ts.map +1 -1
  189. package/dist/modules/plugins/index.js +1 -1
  190. package/dist/modules/plugins/index.js.map +1 -1
  191. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  192. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  193. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  194. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  195. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  196. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  197. package/dist/modules/plugins/plugins.routes.js +31 -0
  198. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  199. package/dist/modules/plugins/plugins.tools.d.ts +36 -2
  200. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
  201. package/dist/modules/plugins/plugins.tools.js +71 -14
  202. package/dist/modules/plugins/plugins.tools.js.map +1 -1
  203. package/dist/modules/settings/deployment-settings.service.d.ts +34 -7
  204. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  205. package/dist/modules/settings/deployment-settings.service.js +148 -54
  206. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  207. package/dist/modules/settings/repository-source.d.ts +1 -0
  208. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  209. package/dist/modules/settings/repository-source.js +3 -3
  210. package/dist/modules/settings/repository-source.js.map +1 -1
  211. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  212. package/dist/modules/settings/setup.routes.js +3 -6
  213. package/dist/modules/settings/setup.routes.js.map +1 -1
  214. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  215. package/dist/modules/skills/skills.tools.js +103 -43
  216. package/dist/modules/skills/skills.tools.js.map +1 -1
  217. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  218. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  219. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  220. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  221. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  222. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  223. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  224. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  225. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  226. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  227. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  228. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  229. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  230. package/dist/modules/tool-helpers/index.d.ts +28 -1
  231. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  232. package/dist/modules/tool-helpers/index.js +28 -1
  233. package/dist/modules/tool-helpers/index.js.map +1 -1
  234. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  235. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  236. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  237. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  238. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  239. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  240. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  241. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  242. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  243. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  244. package/dist/modules/tool-helpers/tool-def.js +70 -6
  245. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  246. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  247. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  248. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  249. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  250. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  251. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  253. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
  254. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  255. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  256. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
  257. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  258. package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
  259. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  260. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
  261. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  262. package/dist/modules/tool-manuals/tool-manuals.tools.js +77 -38
  263. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  264. package/dist/modules/tool-registry/description-length.d.ts +20 -16
  265. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  266. package/dist/modules/tool-registry/description-length.js +50 -30
  267. package/dist/modules/tool-registry/description-length.js.map +1 -1
  268. package/dist/modules/tool-registry/guide-first.d.ts +27 -0
  269. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
  270. package/dist/modules/tool-registry/guide-first.js +40 -0
  271. package/dist/modules/tool-registry/guide-first.js.map +1 -0
  272. package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
  273. package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
  274. package/dist/modules/tool-registry/tool-registry.js +9 -2
  275. package/dist/modules/tool-registry/tool-registry.js.map +1 -1
  276. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
  277. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
  278. package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
  279. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
  280. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +126 -0
  281. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
  282. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +611 -0
  283. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
  284. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
  285. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
  286. package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
  287. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
  288. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  289. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  290. package/dist/modules/workflow/agent-tools/workflow.tools.js +133 -37
  291. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  292. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  293. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  294. package/dist/modules/workflow/file-lock.service.js +62 -5
  295. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  296. package/dist/modules/workflow/git/git.service.d.ts +282 -14
  297. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  298. package/dist/modules/workflow/git/git.service.js +627 -126
  299. package/dist/modules/workflow/git/git.service.js.map +1 -1
  300. package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
  301. package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
  302. package/dist/modules/workflow/git/merge-commit.js +89 -0
  303. package/dist/modules/workflow/git/merge-commit.js.map +1 -0
  304. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  305. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  306. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  307. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  308. package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
  309. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  310. package/dist/modules/workflow/git/pull-request.service.js +332 -37
  311. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  312. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  313. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  314. package/dist/modules/workflow/pending-commits.service.js +25 -1
  315. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  316. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  317. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  318. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  319. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  320. package/dist/modules/workflow/recovery-bot.js +1 -1
  321. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  322. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
  323. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  324. package/dist/modules/workflow/review-workflow/review-workflow.service.js +180 -13
  325. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  326. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  327. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  328. package/dist/modules/workflow/sanitize-error.js +45 -0
  329. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  330. package/dist/modules/workflow/workflow.routes.d.ts +6 -2
  331. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  332. package/dist/modules/workflow/workflow.routes.js +7 -2
  333. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  334. package/dist/modules/workflow/workflow.service.d.ts +187 -15
  335. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  336. package/dist/modules/workflow/workflow.service.js +808 -106
  337. package/dist/modules/workflow/workflow.service.js.map +1 -1
  338. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  339. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  340. package/dist/modules/workspace/agent-download.builder.js +238 -0
  341. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  342. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  343. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  344. package/dist/modules/workspace/agent-download.routes.js +156 -0
  345. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  346. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  347. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  348. package/dist/modules/workspace/agent-download.store.js +423 -0
  349. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  350. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  351. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  352. package/dist/modules/workspace/agent-upload.store.js +16 -6
  353. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  354. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  355. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  356. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  357. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  358. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  359. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  360. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  361. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  362. package/dist/modules/workspace/first-run.d.ts +111 -0
  363. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  364. package/dist/modules/workspace/first-run.js +181 -0
  365. package/dist/modules/workspace/first-run.js.map +1 -0
  366. package/dist/modules/workspace/move-links.d.ts +92 -0
  367. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  368. package/dist/modules/workspace/move-links.js +173 -0
  369. package/dist/modules/workspace/move-links.js.map +1 -0
  370. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  371. package/dist/modules/workspace/startup/steps/seed-tree.js +24 -39
  372. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  373. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
  374. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  375. package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
  376. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  377. package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
  378. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  379. package/dist/modules/workspace/startup/steps/template-source.js +5 -3
  380. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  381. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  382. package/dist/modules/workspace/workspace.routes.js +63 -22
  383. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  384. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  385. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  386. package/dist/modules/workspace/workspace.service.js +197 -24
  387. package/dist/modules/workspace/workspace.service.js.map +1 -1
  388. package/dist/modules/workspace/workspace.tools.d.ts +68 -1
  389. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  390. package/dist/modules/workspace/workspace.tools.js +642 -71
  391. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  392. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  393. package/dist/modules/write-access/write-access.js +9 -0
  394. package/dist/modules/write-access/write-access.js.map +1 -1
  395. package/dist/shared/domain-errors.d.ts +84 -1
  396. package/dist/shared/domain-errors.d.ts.map +1 -1
  397. package/dist/shared/domain-errors.js +100 -12
  398. package/dist/shared/domain-errors.js.map +1 -1
  399. package/dist/shared/git.contract.d.ts +10 -1
  400. package/dist/shared/git.contract.d.ts.map +1 -1
  401. package/dist/shared/git.contract.js.map +1 -1
  402. package/dist/shared/hidden-tools.d.ts +44 -0
  403. package/dist/shared/hidden-tools.d.ts.map +1 -0
  404. package/dist/shared/hidden-tools.js +13 -0
  405. package/dist/shared/hidden-tools.js.map +1 -0
  406. package/dist/shared/utf8-text.d.ts +15 -0
  407. package/dist/shared/utf8-text.d.ts.map +1 -0
  408. package/dist/shared/utf8-text.js +20 -0
  409. package/dist/shared/utf8-text.js.map +1 -0
  410. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  411. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  412. package/dist/tenancy/static-tenant-source.js +9 -2
  413. package/dist/tenancy/static-tenant-source.js.map +1 -1
  414. package/kb-template/.bevelignore +0 -5
  415. package/mcp-app/page.html +349 -0
  416. package/migrations/0017_atlassian_account_links.sql +37 -0
  417. package/migrations/meta/0017_snapshot.json +2396 -0
  418. package/migrations/meta/_journal.json +8 -1
  419. package/package.json +6 -3
  420. package/src/__tests__/kb-layout-config.test.ts +10 -100
  421. package/src/__tests__/packaged-assets-ship.test.ts +66 -0
  422. package/src/assets.ts +38 -2
  423. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  424. package/src/core/__tests__/gated-boot.test.ts +1 -0
  425. package/src/core/__tests__/lifecycle.test.ts +14 -0
  426. package/src/core/catalog-revision.ts +15 -2
  427. package/src/core/core-ports.ts +11 -0
  428. package/src/core/create-core-server.ts +78 -3
  429. package/src/core/create-core-services.ts +168 -10
  430. package/src/core/lifecycle.ts +28 -2
  431. package/src/core-config.ts +24 -1
  432. package/src/index.ts +2 -2
  433. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
  434. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
  435. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  436. package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
  437. package/src/modules/access/access-control.interface.ts +28 -0
  438. package/src/modules/access/access-control.service.ts +37 -0
  439. package/src/modules/access/directory-sync-bot.ts +1 -1
  440. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +386 -0
  441. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +207 -0
  442. package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
  443. package/src/modules/agent-guide/agent-guide.ts +300 -0
  444. package/src/modules/agent-guide/index.ts +22 -0
  445. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +39 -118
  446. package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
  447. package/src/modules/agent-instructions/compose.ts +26 -8
  448. package/src/modules/agent-instructions/index.ts +0 -3
  449. package/src/modules/agent-instructions/shared-file-rules.ts +50 -112
  450. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  451. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  452. package/src/modules/audit/agent-audit.service.ts +58 -3
  453. package/src/modules/audit/agent-connection.routes.ts +56 -0
  454. package/src/modules/audit/audit.contract.ts +26 -0
  455. package/src/modules/audit/index.ts +1 -0
  456. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  457. package/src/modules/auth/account.routes.ts +10 -3
  458. package/src/modules/auth/auth.middleware.ts +1 -1
  459. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  460. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  461. package/src/modules/database/core-schema.ts +33 -3
  462. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  463. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  464. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  465. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  466. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  467. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  468. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  469. package/src/modules/embed/account-link.service.ts +76 -0
  470. package/src/modules/embed/embed-link.ts +180 -0
  471. package/src/modules/embed/embed.errors.ts +43 -0
  472. package/src/modules/embed/embed.interface.ts +203 -0
  473. package/src/modules/embed/embed.routes.ts +363 -0
  474. package/src/modules/embed/embed.service.ts +705 -0
  475. package/src/modules/embed/embed.tools.ts +206 -0
  476. package/src/modules/embed/mcp-app.routes.ts +41 -0
  477. package/src/modules/embed/mcp-app.ts +173 -0
  478. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  479. package/src/modules/github-app/github-app.connection.ts +38 -4
  480. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  481. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  482. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  483. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  484. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  485. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  486. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
  487. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  488. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  489. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +252 -0
  490. package/src/modules/mcp/__tests__/mcp.service.test.ts +42 -24
  491. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  492. package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
  493. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  494. package/src/modules/mcp/mcp.service.ts +218 -31
  495. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  496. package/src/modules/mcp/tool-schema-guard.ts +196 -0
  497. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  498. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  499. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  500. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  501. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  502. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  503. package/src/modules/onboarding/starter-packs.ts +182 -0
  504. package/src/modules/plugins/__tests__/plugins.tools.test.ts +177 -4
  505. package/src/modules/plugins/index.ts +1 -0
  506. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  507. package/src/modules/plugins/plugins.routes.ts +29 -0
  508. package/src/modules/plugins/plugins.tools.ts +75 -15
  509. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +176 -56
  510. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  511. package/src/modules/settings/deployment-settings.service.ts +144 -55
  512. package/src/modules/settings/repository-source.ts +4 -4
  513. package/src/modules/settings/setup.routes.ts +3 -6
  514. package/src/modules/skills/__tests__/skills.tools.description.test.ts +95 -0
  515. package/src/modules/skills/skills.tools.ts +112 -46
  516. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  517. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  518. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  519. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  520. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  521. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  522. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  523. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  524. package/src/modules/tool-helpers/index.ts +34 -1
  525. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  526. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  527. package/src/modules/tool-helpers/tool-def.ts +132 -7
  528. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  529. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  530. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
  531. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
  532. package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
  533. package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
  534. package/src/modules/tool-manuals/tool-manuals.tools.ts +86 -38
  535. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +173 -0
  536. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +81 -66
  537. package/src/modules/tool-registry/description-length.ts +57 -32
  538. package/src/modules/tool-registry/guide-first.ts +41 -0
  539. package/src/modules/tool-registry/tool-registry.ts +9 -2
  540. package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
  541. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  542. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  543. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  544. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  545. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  546. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  547. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  548. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  549. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  550. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  551. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  552. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  553. package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
  554. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1554 -0
  555. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +150 -4
  556. package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
  557. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +759 -0
  558. package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
  559. package/src/modules/workflow/agent-tools/workflow.tools.ts +137 -35
  560. package/src/modules/workflow/file-lock.service.ts +67 -5
  561. package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
  562. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
  563. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  564. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
  565. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  566. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  567. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
  568. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
  569. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
  570. package/src/modules/workflow/git/git.service.ts +714 -133
  571. package/src/modules/workflow/git/merge-commit.ts +88 -0
  572. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  573. package/src/modules/workflow/git/pull-request.service.ts +380 -54
  574. package/src/modules/workflow/pending-commits.service.ts +29 -1
  575. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  576. package/src/modules/workflow/recovery-bot.ts +1 -1
  577. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
  578. package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
  579. package/src/modules/workflow/review-workflow/review-workflow.service.ts +191 -12
  580. package/src/modules/workflow/sanitize-error.ts +50 -0
  581. package/src/modules/workflow/workflow.routes.ts +7 -2
  582. package/src/modules/workflow/workflow.service.ts +886 -112
  583. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  584. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
  585. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  586. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  587. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  588. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  589. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  590. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  591. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  592. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  593. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  594. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
  595. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  596. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  597. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
  598. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  599. package/src/modules/workspace/__tests__/workspace.tools.test.ts +664 -34
  600. package/src/modules/workspace/agent-download.builder.ts +308 -0
  601. package/src/modules/workspace/agent-download.routes.ts +183 -0
  602. package/src/modules/workspace/agent-download.store.ts +533 -0
  603. package/src/modules/workspace/agent-upload.store.ts +23 -7
  604. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  605. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  606. package/src/modules/workspace/first-run.ts +222 -0
  607. package/src/modules/workspace/move-links.ts +237 -0
  608. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
  609. package/src/modules/workspace/startup/steps/seed-tree.ts +23 -38
  610. package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
  611. package/src/modules/workspace/startup/steps/template-source.ts +5 -3
  612. package/src/modules/workspace/workspace.routes.ts +64 -22
  613. package/src/modules/workspace/workspace.service.ts +202 -25
  614. package/src/modules/workspace/workspace.tools.ts +707 -68
  615. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  616. package/src/modules/write-access/write-access.ts +9 -0
  617. package/src/shared/domain-errors.ts +123 -11
  618. package/src/shared/git.contract.ts +8 -1
  619. package/src/shared/hidden-tools.ts +45 -0
  620. package/src/shared/utf8-text.ts +21 -0
  621. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  622. package/src/tenancy/static-tenant-source.ts +10 -2
  623. package/starter-packs/README.md +103 -0
  624. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  625. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  626. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  627. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  628. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  629. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  630. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  631. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  632. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  633. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  634. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  635. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  636. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  637. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  638. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  639. package/starter-packs/engineering/pack.yaml +11 -0
  640. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  641. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  642. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  643. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  644. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  645. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  646. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  647. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  648. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  649. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  650. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  651. package/starter-packs/general/pack.yaml +10 -0
  652. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  653. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  654. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  655. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  656. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  657. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  658. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  659. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  660. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  661. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  662. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  663. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  664. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  665. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  666. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  667. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  668. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  669. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  670. package/starter-packs/operations/pack.yaml +12 -0
  671. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  672. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  673. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  674. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  675. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  676. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  677. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  678. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  679. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  680. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  681. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  682. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  683. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  684. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  685. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  686. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  687. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  688. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  689. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  690. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  691. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  692. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  693. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  694. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  695. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  696. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  697. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  698. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  699. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  700. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  701. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  702. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  703. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  704. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  705. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  706. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  707. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  708. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  709. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  710. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  711. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  712. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  713. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  714. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  715. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  716. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  717. package/starter-packs/sales/pack.yaml +13 -0
  718. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  719. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  720. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  721. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  722. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  723. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  724. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  725. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  726. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  727. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  728. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  729. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  730. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  731. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  732. package/starter-packs/support/pack.yaml +12 -0
  733. package/kb-template/AGENTS.md +0 -730
@@ -14,9 +14,12 @@ import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
14
14
  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
- import { sharedFileRules, sharedFileRulesSection, sharedRulesPointer } from '../../agent-instructions/shared-file-rules.js';
17
+ import { sharedFileRules, sharedFileRulesSection } from '../../agent-instructions/shared-file-rules.js';
18
+ import { composeAgentGuide } from '../../agent-guide/agent-guide.js';
19
+ import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
18
20
  import { RoutineWritePolicyService } from '../routine-write-policy.js';
19
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';
20
23
  import { WorkflowHooks, type AgentOperationContext } from '../../workflow/workflow-hooks.js';
21
24
  import { SESSION_ID_DESCRIPTION, ToolDescriptionNotes } from '../agent-access.gate.js';
22
25
  import { SpillStore } from '../spill-store.js';
@@ -26,13 +29,16 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
26
29
  import { AccessControlService } from '../../access/access-control.service.js';
27
30
  import { NodeFs } from '../../kb-fs/node-fs.js';
28
31
  import type { WorkspaceService } from '../workspace.service.js';
29
- import { isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
32
+ import { DEFAULT_KB_LAYOUT, isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
30
33
  import { assertValidBranchName } from '../../kb-fs/branch-name.js';
31
34
  import { normalizeWorkspacePath } from '../../kb-fs/repo-path.js';
32
35
  import { GIT_INTERNALS_MESSAGE, PathNotFoundError } from '../../../shared/domain-errors.js';
33
36
  import { AccessDeniedError } from '../../access-model/access-errors.js';
34
37
  import { proposalTitleFor } from '../write-denial.js';
35
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';
36
42
 
37
43
  const KB_DIR = 'knowledge-base';
38
44
 
@@ -98,6 +104,8 @@ let unzipEntries: string[] = [];
98
104
  let folderTurns: string[] = [];
99
105
  /** The policy instance the tools were mounted with, so a test can restrict a session. */
100
106
  let writePolicy: RoutineWritePolicyService;
107
+ /** What the platform's guide reads as, for the tests about the guide's name. */
108
+ let guideText = 'THE PLATFORM GUIDE\n';
101
109
  /**
102
110
  * The focused branch the resolved `ToolContext` carries — mirrors the branch an
103
111
  * internal token bakes for the in-process agent. A test sets it to prove a
@@ -297,7 +305,7 @@ async function start(
297
305
  recoveryBotEmail: RECOVERY_BOT,
298
306
  hooks,
299
307
  notes,
300
- }, writePolicy, {} as never /* sessionSink — start_session not exercised here */);
308
+ }, writePolicy, {} as never /* sessionSink — start_session not exercised here */, undefined, undefined, undefined, async () => guideText);
301
309
  app.use('/api', router);
302
310
  httpServer = await new Promise<HttpServer>((r) => {
303
311
  const s = app.listen(0, () => r(s));
@@ -313,7 +321,9 @@ const postRaw = (url: string, body: unknown = {}) =>
313
321
  * POST a well-formed call. Every KB tool requires `branch` — a call that names
314
322
  * none is refused at the mount with 400 `branch-required` — so this names one
315
323
  * unless the test already did. A test ABOUT the missing input uses `postRaw`,
316
- * 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.
317
327
  */
318
328
  const post = (url: string, body: unknown = {}) =>
319
329
  postRaw(
@@ -325,6 +335,7 @@ const post = (url: string, body: unknown = {}) =>
325
335
 
326
336
  beforeEach(() => {
327
337
  /* fresh per test via start() */
338
+ guideText = 'THE PLATFORM GUIDE\n';
328
339
  });
329
340
  afterEach(async () => {
330
341
  if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
@@ -963,6 +974,8 @@ describe('write modes and per-path outcomes', () => {
963
974
  for (const res of [
964
975
  await writeFile(base, { path: `${KB_DIR}/a.md`, content: 'x', mode: 'replace' }),
965
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' }),
966
979
  ]) {
967
980
  expect(res.status).toBe(400);
968
981
  const body = (await res.json()) as { error: string; code: string };
@@ -1096,6 +1109,202 @@ describe('write modes and per-path outcomes', () => {
1096
1109
  });
1097
1110
  });
1098
1111
 
1112
+ /**
1113
+ * The agent guide at the repository root. It is not a file: `read_file` of
1114
+ * its name answers with the platform's guide, after the knowledge base's own
1115
+ * file of that name when it has one, and `file_stat` says what is there.
1116
+ */
1117
+ describe("the agent guide at the guide's name", () => {
1118
+ const GUIDE = `${KB_DIR}/AGENTS.md`;
1119
+ const read = (base: string, p = GUIDE, extra: Record<string, unknown> = {}) =>
1120
+ post(`${base}/api/agent/tools/read_file`, { path: p, ...extra }).then((r) => r.json() as Promise<{ path: string; content: string }>);
1121
+ const statOf = (base: string, p = GUIDE) =>
1122
+ post(`${base}/api/agent/tools/file_stat`, { path: p }).then((r) => r.json() as Promise<Record<string, unknown>>);
1123
+
1124
+ it('answers with the guide when the knowledge base has no file of that name', async () => {
1125
+ guideText = 'THE PLATFORM GUIDE\n';
1126
+ const base = await start();
1127
+ expect(await read(base)).toEqual({ path: GUIDE, content: 'THE PLATFORM GUIDE\n' });
1128
+ // By the root-anchored and the prefix-less spellings too, like any path.
1129
+ expect((await read(base, `/${GUIDE}`)).content).toBe('THE PLATFORM GUIDE\n');
1130
+ expect((await read(base, 'AGENTS.md')).content).toBe('THE PLATFORM GUIDE\n');
1131
+ // Sliced like any content.
1132
+ expect((await read(base, GUIDE, { offset: 4, limit: 8 })).content).toBe('PLATFORM');
1133
+ });
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
+
1144
+ it("puts the knowledge base's own AGENTS.md first, then the separator, then the guide", async () => {
1145
+ guideText = 'THE PLATFORM GUIDE\n';
1146
+ const base = await start();
1147
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nWrite tickets in the present tense.\n');
1148
+ const { content } = await read(base);
1149
+ expect(content.startsWith('# Acme\n\nWrite tickets in the present tense.\n\n---\n')).toBe(true);
1150
+ expect(content.endsWith('\n\nTHE PLATFORM GUIDE\n')).toBe(true);
1151
+ expect(content).toContain("The text above is this knowledge base's own conventions file.");
1152
+ });
1153
+
1154
+ it('never serves a copy of the guide an earlier release left on disk a second time', async () => {
1155
+ guideText = 'THE PLATFORM GUIDE\n';
1156
+ const base = await start();
1157
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
1158
+ expect((await read(base)).content).toBe('THE PLATFORM GUIDE\n');
1159
+ });
1160
+
1161
+ it("never tells a caller who may not read the knowledge base's own file that it exists", async () => {
1162
+ guideText = 'THE PLATFORM GUIDE\n';
1163
+ const denied = await start('read', denyReads(new Set(['AGENTS.md'])));
1164
+ // Nothing of theirs there: the guide is everyone's.
1165
+ const absent = await read(denied);
1166
+ const absentStat = await statOf(denied);
1167
+ expect(absent.content).toBe('THE PLATFORM GUIDE\n');
1168
+ // A file they may not read answers EXACTLY as no file does — a refusal
1169
+ // would be the one thing the platform never says about a restricted
1170
+ // file, which is that it is there.
1171
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nThe secret conventions.\n');
1172
+ const closed = await post(`${denied}/api/agent/tools/read_file`, { path: GUIDE });
1173
+ expect(closed.status).toBe(200);
1174
+ expect(await closed.json()).toEqual(absent);
1175
+ expect(await statOf(denied)).toEqual(absentStat);
1176
+ });
1177
+
1178
+ it("serves the knowledge base's own file to a caller who may read it", async () => {
1179
+ guideText = 'THE PLATFORM GUIDE\n';
1180
+ const allowed = await start('read');
1181
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nThe conventions.\n');
1182
+ expect((await read(allowed)).content).toContain('The conventions.');
1183
+ });
1184
+
1185
+ it("answers the guide alone when the knowledge base's own file vanishes between the probe and the read", async () => {
1186
+ guideText = 'THE PLATFORM GUIDE\n';
1187
+ const base = await start();
1188
+ // The file is there when it is probed and gone when it is read: a
1189
+ // concurrent delete, which is the absent case and never a failure.
1190
+ const probed = fs.stat.bind(fs);
1191
+ let vanish = false;
1192
+ (fs as unknown as Record<string, unknown>).stat = async (p: string) => {
1193
+ const st = await probed(p);
1194
+ if (vanish && p.endsWith('AGENTS.md')) {
1195
+ vanish = false;
1196
+ await fs.deleteFile(p);
1197
+ }
1198
+ return st;
1199
+ };
1200
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
1201
+ vanish = true;
1202
+ expect(await read(base)).toEqual({ path: GUIDE, content: 'THE PLATFORM GUIDE\n' });
1203
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
1204
+ vanish = true;
1205
+ expect(await statOf(base)).toMatchObject({ platformGuide: true });
1206
+ });
1207
+
1208
+ it('leaves a folder at the guide\'s name to the ordinary stat, and never reads it as a copy', async () => {
1209
+ guideText = 'THE PLATFORM GUIDE\n';
1210
+ const base = await start();
1211
+ await fs.mkdir(`${KB_DIR}/AGENTS.md`);
1212
+ const folder = await statOf(base);
1213
+ expect(folder).toMatchObject({ type: 'directory' });
1214
+ expect(folder.platformGuide).toBeUndefined();
1215
+ });
1216
+
1217
+ it('is a nested AGENTS.md no concern of: that is a file like any other', async () => {
1218
+ guideText = 'THE PLATFORM GUIDE\n';
1219
+ const base = await start();
1220
+ await fs.writeFile(`${KB_DIR}/Handbook/AGENTS.md`, '# Handbook\n');
1221
+ expect((await read(base, `${KB_DIR}/Handbook/AGENTS.md`)).content).toBe('# Handbook\n');
1222
+ expect((await post(`${base}/api/agent/tools/read_file`, { path: `${KB_DIR}/Handbook/HEXIS.md` })).status).toBe(404);
1223
+ });
1224
+
1225
+ it('grep finds the guide where read_file serves it: from the root and at its own path, own file first, denied file absent', async () => {
1226
+ guideText = '# Guide\n\nName every needle you plant.\n';
1227
+ const grep = (base: string, pattern: string, path?: string) =>
1228
+ post(`${base}/api/agent/tools/grep`, path === undefined ? { pattern } : { pattern, path }).then(
1229
+ (r) => r.json() as Promise<{ matches: { path: string; line: number; text: string }[] }>,
1230
+ );
1231
+ const base = await start();
1232
+ await fs.writeFile(`${KB_DIR}/Handbook/a.md`, 'a needle on disk\n');
1233
+ // A search of the whole knowledge base reaches the guide, under the path
1234
+ // a read of it answers to, with the line number that read gives.
1235
+ const fromRoot = await grep(base, 'needle');
1236
+ expect(fromRoot.matches).toContainEqual({ path: GUIDE, line: 3, text: 'Name every needle you plant.' });
1237
+ expect(fromRoot.matches).toContainEqual({ path: `${KB_DIR}/Handbook/a.md`, line: 1, text: 'a needle on disk' });
1238
+ // A search of the guide's own path is a search of what read_file answers.
1239
+ expect((await grep(base, 'needle', GUIDE)).matches).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
1240
+ expect((await grep(base, 'needle', 'AGENTS.md')).matches).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
1241
+ // A search under a folder does not reach a file that is not under it.
1242
+ expect((await grep(base, 'needle', `${KB_DIR}/Handbook`)).matches.map((m) => m.path)).toEqual([`${KB_DIR}/Handbook/a.md`]);
1243
+
1244
+ // With the knowledge base's own AGENTS.md, the search covers the composed
1245
+ // text — the own file first, then the guide — and once: the walk's own
1246
+ // matches in that file are the same lines, so they are not repeated.
1247
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
1248
+ const { content } = await read(base);
1249
+ const guideLine = content.split('\n').indexOf('Name every needle you plant.') + 1;
1250
+ const composed = (await grep(base, 'needle')).matches.filter((m) => m.path === GUIDE);
1251
+ expect(composed).toEqual([
1252
+ { path: GUIDE, line: 3, text: 'Our needle rule.' },
1253
+ { path: GUIDE, line: guideLine, text: 'Name every needle you plant.' },
1254
+ ]);
1255
+ expect((await grep(base, 'needle', GUIDE)).matches).toEqual(composed);
1256
+
1257
+ // The own file does not use up `max_results` twice: the walk leaves it to
1258
+ // the composed search, so a file later in the tree is still reached when
1259
+ // the own file alone has more matches than the cap. Without that, the
1260
+ // walk filled the cap from AGENTS.md, those matches were dropped as
1261
+ // duplicates, and Handbook/ was never searched.
1262
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, `# Acme\n\n${'needle\n'.repeat(5)}`);
1263
+ const capped = (await (await post(`${base}/api/agent/tools/grep`, { pattern: 'needle', max_results: 3 })).json()) as {
1264
+ matches: { path: string }[];
1265
+ };
1266
+ expect(capped.matches.map((m) => m.path)).toContain(`${KB_DIR}/Handbook/a.md`);
1267
+ expect(capped.matches).toHaveLength(3);
1268
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
1269
+
1270
+ // A caller who may not read the own file searches the guide alone, from
1271
+ // the root and at the path — as read_file answers them, with no sign that
1272
+ // anything of the organisation's is there.
1273
+ const denied = await start('read', denyReads(new Set(['AGENTS.md'])));
1274
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
1275
+ for (const found of [await grep(denied, 'needle'), await grep(denied, 'needle', GUIDE)]) {
1276
+ expect(found.matches.filter((m) => m.path === GUIDE)).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
1277
+ expect(JSON.stringify(found)).not.toContain('Acme');
1278
+ }
1279
+ });
1280
+
1281
+ it('file_stat says a text file is there to read, and that nothing can be written, moved or deleted at it', async () => {
1282
+ guideText = 'THE PLATFORM GUIDE\n';
1283
+ const base = await start();
1284
+ expect(await statOf(base)).toMatchObject({
1285
+ name: 'AGENTS.md',
1286
+ type: 'file',
1287
+ size: Buffer.byteLength('THE PLATFORM GUIDE\n'),
1288
+ platformGuide: true,
1289
+ managed: true,
1290
+ movable: false,
1291
+ deletable: false,
1292
+ contentMode: 'text',
1293
+ textEditable: false,
1294
+ access: { read: true, write: false },
1295
+ });
1296
+ // With a file of the knowledge base's own there, stat describes THAT file.
1297
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
1298
+ const own = await statOf(base);
1299
+ expect(own).toMatchObject({ name: 'AGENTS.md', type: 'file', managed: false, movable: true, textEditable: true });
1300
+ expect(own.platformGuide).toBeUndefined();
1301
+ // A copy an earlier release wrote is what read_file does not serve, so
1302
+ // stat says the same thing it says for no file at all.
1303
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
1304
+ expect(await statOf(base)).toMatchObject({ platformGuide: true, managed: true, movable: false });
1305
+ });
1306
+ });
1307
+
1099
1308
  describe('read-permission gating', () => {
1100
1309
  // Seed two KB nodes; the access stub denies read on the "secret" one.
1101
1310
  async function startGated(): Promise<string> {
@@ -1714,19 +1923,18 @@ describe('office documents and PDFs', () => {
1714
1923
  expect(await readContent(base, `${KB_DIR}/deck.pptx`)).toContain('Original');
1715
1924
  });
1716
1925
 
1717
- it('every mounted tool ends with the one sentence pointing at the shared rules, and repeats none of them', async () => {
1926
+ it('every mounted tool opens with the one sentence sending the agent to the guide, and repeats none of the rules', async () => {
1718
1927
  await start();
1719
1928
  const tools = await toolRegistry.listInternal();
1720
- const pointer = sharedRulesPointer(testKbContext().layout);
1721
1929
  // The shell is in the list too: it carried the agent-guide reminder before,
1722
1930
  // and that reminder is one of the rules that moved.
1723
1931
  const mounted = ['read_file', 'list_files', 'file_stat', 'grep', 'write_file', 'write_files', 'edit_file', 'delete_file', 'delete_folder', 'mkdir', 'move_file', 'copy_file', 'unzip', 'execute_command'];
1724
1932
  for (const name of mounted) {
1725
1933
  const def = tools.find((t) => t.name === name);
1726
1934
  expect(def, name).toBeDefined();
1727
- expect(def!.description!.endsWith(pointer), name).toBe(true);
1728
- // Once, at the end — not once per paragraph that used to be appended.
1729
- expect(def!.description!.split(pointer), name).toHaveLength(2);
1935
+ expect(def!.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
1936
+ // Once, at the front — not once per paragraph that used to be appended.
1937
+ expect(def!.description!.split(GUIDE_FIRST_SENTENCE), name).toHaveLength(2);
1730
1938
  // EVERY shared rule, in full, is in the two shared places now (see
1731
1939
  // agent-instructions/__tests__/shared-file-rules.test.ts) and in no
1732
1940
  // description. Checked on the whole body rather than on a phrase: the
@@ -1740,10 +1948,57 @@ describe('office documents and PDFs', () => {
1740
1948
  expect(def!.description, name).not.toContain('Content rule (the same on every file tool)');
1741
1949
  expect(def!.description, name).not.toContain('Before your first read or change in a workspace');
1742
1950
  }
1743
- // start_session carried none of the shared paragraphs and gains no pointer:
1744
- // it touches no file. External-only, so it is looked up on that surface.
1951
+ // start_session touches no file, yet it opens with the same sentence: the
1952
+ // guide is read before ANYTHING in the platform, a session included.
1953
+ // External-only, so it is looked up on that surface.
1745
1954
  const external = await toolRegistry.listExternal();
1746
- expect(external.find((t) => t.name === 'start_session')!.description).not.toContain(pointer);
1955
+ expect(external.find((t) => t.name === 'start_session')!.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `)).toBe(true);
1956
+ });
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
+ }
1747
2002
  });
1748
2003
 
1749
2004
  describe('binary capability contract: a text file, a document, an image and a zip', () => {
@@ -2011,6 +2266,15 @@ describe('start_session', () => {
2011
2266
  // `UuidSessionSink`, because "did fifty first calls collide?" is a question
2012
2267
  // only the real minting can answer.
2013
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,
2014
2278
  ): Promise<string> {
2015
2279
  created = [];
2016
2280
  const registry = new ToolRegistry();
@@ -2019,7 +2283,12 @@ describe('start_session', () => {
2019
2283
  scope: auth.scope,
2020
2284
  source: auth.source,
2021
2285
  abortSignal: signal,
2022
- workspaceService: {} as never,
2286
+ workspaceService: (defaultWorkspaceDir
2287
+ ? {
2288
+ hasBootstrappedWorkspace: async () => true,
2289
+ getWorkspacePath: async () => defaultWorkspaceDir,
2290
+ }
2291
+ : {}) as never,
2023
2292
  workflowService: {} as never,
2024
2293
  events: {} as never,
2025
2294
  getFilesystem: async () => ({}) as never,
@@ -2042,10 +2311,17 @@ describe('start_session', () => {
2042
2311
  const router = express.Router();
2043
2312
  registerWorkspaceTools(
2044
2313
  registry, router, auth, toolHandler,
2045
- 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 }),
2046
2315
  { recoveryBotEmail: RECOVERY_BOT, hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
2047
2316
  new RoutineWritePolicyService(),
2048
2317
  sink ?? fakeSessionSink,
2318
+ undefined,
2319
+ undefined,
2320
+ undefined,
2321
+ undefined,
2322
+ undefined /* downloads */,
2323
+ starterPacks,
2324
+ new NodeFs(),
2049
2325
  );
2050
2326
  app.use('/api', router);
2051
2327
  server = await new Promise<HttpServer>((r) => {
@@ -2061,13 +2337,13 @@ describe('start_session', () => {
2061
2337
 
2062
2338
  it('returns the sink-minted id as sessionId', async () => {
2063
2339
  const base = await startSessionApp();
2064
- 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 };
2065
2341
  expect(res.sessionId).toBe('thread-xyz');
2066
2342
  });
2067
2343
 
2068
2344
  it('mints the session for the authenticated user', async () => {
2069
2345
  const base = await startSessionApp();
2070
- await post(`${base}/api/agent/tools/start_session`);
2346
+ await postRaw(`${base}/api/agent/tools/start_session`);
2071
2347
  expect(created).toHaveLength(1);
2072
2348
  expect(created[0].userId).toBe('user-42');
2073
2349
  expect(created[0].startedAt).toBeInstanceOf(Date);
@@ -2078,7 +2354,7 @@ describe('start_session', () => {
2078
2354
  // loopback token resolves to source 'external' at the verifier (see
2079
2355
  // tool-auth), so it is admitted here like any external agent.
2080
2356
  const base = await startSessionApp('internal');
2081
- const res = await post(`${base}/api/agent/tools/start_session`);
2357
+ const res = await postRaw(`${base}/api/agent/tools/start_session`);
2082
2358
  expect(res.status).toBe(403);
2083
2359
  expect(created).toHaveLength(0);
2084
2360
  });
@@ -2102,7 +2378,7 @@ describe('start_session', () => {
2102
2378
  it('answers every one of them with a session id of its own', async () => {
2103
2379
  const base = await startSessionApp('external', new UuidSessionSink());
2104
2380
 
2105
- 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`)));
2106
2382
  const bodies = (await Promise.all(responses.map((r) => r.json()))) as Array<{ sessionId?: string }>;
2107
2383
 
2108
2384
  expect(responses.map((r) => r.status)).toEqual(Array.from({ length: 50 }, () => 200));
@@ -2135,13 +2411,13 @@ describe('start_session', () => {
2135
2411
  const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {});
2136
2412
  let failed: Awaited<ReturnType<typeof post>>;
2137
2413
  try {
2138
- failed = await post(`${base}/api/agent/tools/start_session`);
2414
+ failed = await postRaw(`${base}/api/agent/tools/start_session`);
2139
2415
  } finally {
2140
2416
  errorLog.mockRestore();
2141
2417
  }
2142
2418
  expect(failed.status).toBeGreaterThanOrEqual(500);
2143
2419
 
2144
- 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 };
2145
2421
  expect(retried.sessionId).toBe('session-2');
2146
2422
  expect(calls).toBe(2);
2147
2423
  });
@@ -2153,8 +2429,8 @@ describe('start_session', () => {
2153
2429
  // to other tools, and the spare is simply never mentioned again.
2154
2430
  const base = await startSessionApp('external', new UuidSessionSink());
2155
2431
 
2156
- const first = (await (await post(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
2157
- 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 };
2158
2434
 
2159
2435
  expect(retry.sessionId).not.toBe(first.sessionId);
2160
2436
  expect(first.sessionId).toBeTruthy();
@@ -2182,6 +2458,141 @@ describe('start_session', () => {
2182
2458
  expect(description).toMatch(/retry/i);
2183
2459
  expect(description).toMatch(/created nothing/i);
2184
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
+ });
2185
2596
  });
2186
2597
  });
2187
2598
 
@@ -2755,6 +3166,108 @@ describe('preflight for moves and deletes', () => {
2755
3166
  });
2756
3167
 
2757
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
+
2758
3271
  it('a same-access move: the dry run changes nothing, the real call runs without confirm', async () => {
2759
3272
  const base = await seeded();
2760
3273
  const args = { src: KB('Sales/deal.md'), dest: KB('Sales/2026/deal.md') };
@@ -2998,19 +3511,17 @@ describe('preflight for moves and deletes', () => {
2998
3511
  expect(await exists(args.src)).toBe(true);
2999
3512
  });
3000
3513
 
3001
- it('every one of the four platform files gets the same sentence, and the agent never gets the admin restore', async () => {
3514
+ it('every one of the three platform files gets the same sentence, and the agent never gets the admin restore', async () => {
3002
3515
  // The agent move tool has no exception: the recovery move is a person's,
3003
3516
  // made as an admin, and an agent is neither.
3004
3517
  const base = await seeded();
3005
3518
  await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
3006
- await fs.writeFile(KB('AGENTS.md'), 'agents\n');
3007
3519
  await fs.writeFile(KB('.bevelignore'), '*.tmp\n');
3008
3520
  await fs.writeFile(KB('Misplaced/access.md'), '---\nread: everyone\n---\n');
3009
3521
  const cases: [string, string][] = [
3010
3522
  [KB('access.md'), KB('Sales/access.md')],
3011
3523
  [KB('roles.yaml'), KB('Sales/roles.yaml')],
3012
3524
  [KB('.bevelignore'), KB('Sales/.bevelignore')],
3013
- [KB('AGENTS.md'), KB('Sales/AGENTS.md')],
3014
3525
  // Including the shape of the admin's recovery move: a misplaced
3015
3526
  // access.md into a folder that has none. A person holding the Admin
3016
3527
  // role is allowed exactly this move from the UI; the agent is not.
@@ -3115,20 +3626,98 @@ describe('preflight for moves and deletes', () => {
3115
3626
  expect(run.status).toBe(403);
3116
3627
  expect(run.body).toMatchObject({ kind: 'write-denied', path: KB('Sales/deal.md'), canPropose: true });
3117
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
+ });
3118
3703
  });
3119
3704
 
3120
3705
  describe('what counts as the platform\'s own, and what a move or delete may reach', () => {
3121
3706
  const caseSensitiveDisk = process.platform === 'linux';
3122
3707
 
3123
- it('roles.yaml and AGENTS.md are platform files only at the root; access.md and .bevelignore at any depth', async () => {
3708
+ it('roles.yaml is a platform file only at the root; access.md and .bevelignore at any depth; AGENTS.md nowhere', async () => {
3124
3709
  const base = await seeded();
3125
3710
  await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
3126
3711
  await fs.writeFile(KB('Sales/roles.yaml'), 'content');
3712
+ await fs.writeFile(KB('AGENTS.md'), 'content');
3127
3713
  await fs.writeFile(KB('Sales/AGENTS.md'), 'content');
3128
3714
  await fs.writeFile(KB('Sales/.bevelignore'), '*.tmp\n');
3129
3715
  const managed = async (p: string) => (await call(base, 'file_stat', { path: KB(p) })).body.managed;
3130
3716
  expect(await managed('roles.yaml')).toBe(true);
3131
3717
  expect(await managed('Sales/roles.yaml')).toBe(false);
3718
+ // The organisation's own conventions file: the guide is served from
3719
+ // code, so nothing under this name is the platform's.
3720
+ expect(await managed('AGENTS.md')).toBe(false);
3132
3721
  expect(await managed('Sales/AGENTS.md')).toBe(false);
3133
3722
  expect(await managed('Sales/.bevelignore')).toBe(true);
3134
3723
  expect((await call(base, 'move_file', { src: KB('Sales/roles.yaml'), dest: KB('Sales/old-roles.yaml') })).body).toMatchObject({ moved: true });
@@ -3497,7 +4086,7 @@ describe('preflight for moves and deletes', () => {
3497
4086
  // is stated once in the shared rules rather than on each of them.
3498
4087
  expect(sharedFileRulesSection(testKbContext().layout)).toContain('`write-denied`');
3499
4088
  for (const name of ['move_file', 'delete_file', 'delete_folder']) {
3500
- expect((await def(name)).description, name).toContain(sharedRulesPointer(testKbContext().layout));
4089
+ expect((await def(name)).description.startsWith(GUIDE_FIRST_SENTENCE), name).toBe(true);
3501
4090
  }
3502
4091
  });
3503
4092
  });
@@ -4015,6 +4604,47 @@ describe('agent read/write hooks', () => {
4015
4604
  expect(writes).toEqual([]);
4016
4605
  });
4017
4606
 
4607
+ it("file_stat at the guide's name tells the read hook once when it reads the organisation's own file, and never for the guide alone", async () => {
4608
+ guideText = 'THE PLATFORM GUIDE\n';
4609
+ const base = await start();
4610
+ record();
4611
+ // Nothing of the organisation's there: the guide is everyone's and no
4612
+ // file is read, so the hook hears nothing.
4613
+ expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
4614
+ expect(reads).toEqual([]);
4615
+ // The organisation's own file: telling it from a stale copy reads it,
4616
+ // and the hook hears of that read exactly once — as it does of a read_file.
4617
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
4618
+ expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
4619
+ expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`]);
4620
+ // A stale copy of the guide is read to be recognised, so it is heard of too.
4621
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
4622
+ expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
4623
+ expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`, `${KB_DIR}/AGENTS.md`]);
4624
+ });
4625
+
4626
+ it("grep at the guide's name tells the read hook the same way: never for the guide alone, once for the organisation's own file", async () => {
4627
+ guideText = 'THE PLATFORM GUIDE\n';
4628
+ const base = await start();
4629
+ record();
4630
+ // The guide alone: nothing of the organisation's is read, so the hook
4631
+ // hears nothing — a search of the guide's path is not a read of a file.
4632
+ expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
4633
+ expect(reads).toEqual([]);
4634
+ // The organisation's own file: once, as read_file tells it — not once
4635
+ // for the search root and again for the file.
4636
+ await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme GUIDE\n');
4637
+ expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
4638
+ expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`]);
4639
+ // A search of the whole knowledge base: the root once, then each file the
4640
+ // walk opens once — the own AGENTS.md among them exactly once, from the
4641
+ // composed search, never again from the walk.
4642
+ reads = [];
4643
+ expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', sessionId: 's1' })).status).toBe(200);
4644
+ expect(reads[0]?.wsPath).toBe(KB_DIR);
4645
+ expect(reads.filter((op) => op.wsPath === `${KB_DIR}/AGENTS.md`)).toHaveLength(1);
4646
+ });
4647
+
4018
4648
  it('the read hook covers list_files, file_stat, grep, delete_file and delete_folder', async () => {
4019
4649
  const base = await start();
4020
4650
  record();
@@ -4275,22 +4905,22 @@ describe('tool descriptions and the deployment note', () => {
4275
4905
  expect(all.get('read_file')!.description).not.toContain('Stay within one');
4276
4906
  });
4277
4907
 
4278
- it("a registered note lands after every gated tool's own text, ahead of the shared-rules pointer, and on the sessionId input", async () => {
4908
+ it("a registered note lands after every gated tool's own text, behind the guide-first opening, and on the sessionId input", async () => {
4279
4909
  await start();
4280
4910
  notes.registerGatedToolNote(' One folder per conversation.');
4281
4911
  notes.registerSessionIdNote(' It also pins that folder.');
4282
4912
  const all = await defs();
4283
- // The POINTER is last, always: that is the one sentence an agent needs to
4284
- // find the shared rules, and a description cut short must not lose it. The
4285
- // deployment's note sits directly before it, after the tool's own text.
4286
- const pointer = sharedRulesPointer(testKbContext().layout);
4913
+ // The guide-first sentence is FIRST, always: that is the one instruction
4914
+ // an agent needs, and a description cut short from the end must not lose
4915
+ // it. The deployment's note is last, after the tool's own text.
4287
4916
  for (const name of ['read_file', 'list_files', 'file_stat', 'grep', 'write_file', 'write_files', 'edit_file', 'delete_file', 'delete_folder', 'mkdir', 'move_file', 'copy_file', 'unzip']) {
4288
- expect(all.get(name)!.description.endsWith(` One folder per conversation.${pointer}`), name).toBe(true);
4917
+ expect(all.get(name)!.description.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
4918
+ expect(all.get(name)!.description.endsWith(' One folder per conversation.'), name).toBe(true);
4289
4919
  expect(sessionIdDescriptionOf(all.get(name)!), name).toBe(`${SESSION_ID_DESCRIPTION} It also pins that folder.`);
4290
4920
  }
4291
4921
  // `execute_command` is internal-only, so it is checked on that surface.
4292
4922
  const internal = new Map((await toolRegistry.listInternal()).map((t) => [t.name, t]));
4293
- expect(internal.get('execute_command')!.description.endsWith(` One folder per conversation.${pointer}`)).toBe(true);
4923
+ expect(internal.get('execute_command')!.description.endsWith(' One folder per conversation.')).toBe(true);
4294
4924
  });
4295
4925
 
4296
4926
  it('a tool that is not gated carries no note', async () => {