@bevel-software/platform-core-backend 0.15.1 → 0.20.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 (1385) hide show
  1. package/THIRD-PARTY-NOTICES.md +4 -4
  2. package/dist/core/catalog-cache-invalidation.d.ts +16 -0
  3. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  4. package/dist/core/catalog-cache-invalidation.js +6 -1
  5. package/dist/core/catalog-cache-invalidation.js.map +1 -1
  6. package/dist/core/catalog-revision.d.ts +39 -0
  7. package/dist/core/catalog-revision.d.ts.map +1 -0
  8. package/dist/core/catalog-revision.js +94 -0
  9. package/dist/core/catalog-revision.js.map +1 -0
  10. package/dist/core/create-core-server.d.ts +7 -0
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +221 -75
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +52 -2
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +222 -87
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +138 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -0
  20. package/dist/core/lifecycle.js +218 -0
  21. package/dist/core/lifecycle.js.map +1 -0
  22. package/dist/core/public-config.d.ts +82 -0
  23. package/dist/core/public-config.d.ts.map +1 -0
  24. package/dist/core/public-config.js +78 -0
  25. package/dist/core/public-config.js.map +1 -0
  26. package/dist/core/readiness.d.ts +86 -0
  27. package/dist/core/readiness.d.ts.map +1 -0
  28. package/dist/core/readiness.js +56 -0
  29. package/dist/core/readiness.js.map +1 -0
  30. package/dist/core-config.d.ts +30 -0
  31. package/dist/core-config.d.ts.map +1 -1
  32. package/dist/core-config.js +64 -2
  33. package/dist/core-config.js.map +1 -1
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/modules/access/access-control.interface.d.ts +215 -11
  39. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.d.ts +57 -11
  41. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  42. package/dist/modules/access/access-control.service.js +616 -155
  43. package/dist/modules/access/access-control.service.js.map +1 -1
  44. package/dist/modules/access/access-mutation.service.d.ts +71 -5
  45. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-mutation.service.js +161 -13
  47. package/dist/modules/access/access-mutation.service.js.map +1 -1
  48. package/dist/modules/access/access-view.d.ts +147 -0
  49. package/dist/modules/access/access-view.d.ts.map +1 -0
  50. package/dist/modules/access/access-view.js +211 -0
  51. package/dist/modules/access/access-view.js.map +1 -0
  52. package/dist/modules/access/access.routes.d.ts.map +1 -1
  53. package/dist/modules/access/access.routes.js +255 -79
  54. package/dist/modules/access/access.routes.js.map +1 -1
  55. package/dist/modules/access/admin-locked-commit.d.ts +2 -3
  56. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  57. package/dist/modules/access/admin-locked-commit.js +9 -8
  58. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  59. package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
  60. package/dist/modules/access/admin-route-helpers.js +4 -4
  61. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  62. package/dist/modules/access/change-read-gate.d.ts +53 -0
  63. package/dist/modules/access/change-read-gate.d.ts.map +1 -0
  64. package/dist/modules/access/change-read-gate.js +134 -0
  65. package/dist/modules/access/change-read-gate.js.map +1 -0
  66. package/dist/modules/access/creator-access.d.ts +33 -39
  67. package/dist/modules/access/creator-access.d.ts.map +1 -1
  68. package/dist/modules/access/creator-access.js +92 -119
  69. package/dist/modules/access/creator-access.js.map +1 -1
  70. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  71. package/dist/modules/access/directory-sync-bot.js +3 -1
  72. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  73. package/dist/modules/access/groups-admin.service.d.ts +3 -3
  74. package/dist/modules/access/groups-admin.service.js +2 -2
  75. package/dist/modules/access/reference-scan.d.ts +1 -1
  76. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  77. package/dist/modules/access/reference-scan.js +5 -4
  78. package/dist/modules/access/reference-scan.js.map +1 -1
  79. package/dist/modules/access/roles-admin.service.d.ts +72 -13
  80. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  81. package/dist/modules/access/roles-admin.service.js +71 -13
  82. package/dist/modules/access/roles-admin.service.js.map +1 -1
  83. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  84. package/dist/modules/access/synced-groups-committer.js +8 -6
  85. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  86. package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
  87. package/dist/modules/access/synced-groups-writer.js +3 -8
  88. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  89. package/dist/modules/access/user-access-removal.service.d.ts +209 -0
  90. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
  91. package/dist/modules/access/user-access-removal.service.js +662 -0
  92. package/dist/modules/access/user-access-removal.service.js.map +1 -0
  93. package/dist/modules/access-model/access-errors.d.ts +32 -1
  94. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  95. package/dist/modules/access-model/access-errors.js +38 -11
  96. package/dist/modules/access-model/access-errors.js.map +1 -1
  97. package/dist/modules/access-model/access-grammar.d.ts +49 -24
  98. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  99. package/dist/modules/access-model/access-grammar.js +68 -48
  100. package/dist/modules/access-model/access-grammar.js.map +1 -1
  101. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  102. package/dist/modules/access-model/access-splice.js +18 -18
  103. package/dist/modules/access-model/access-splice.js.map +1 -1
  104. package/dist/modules/access-model/access-template.d.ts +22 -0
  105. package/dist/modules/access-model/access-template.d.ts.map +1 -0
  106. package/dist/modules/access-model/access-template.js +42 -0
  107. package/dist/modules/access-model/access-template.js.map +1 -0
  108. package/dist/modules/access-model/change-gate.d.ts +65 -0
  109. package/dist/modules/access-model/change-gate.d.ts.map +1 -0
  110. package/dist/modules/access-model/change-gate.js +25 -0
  111. package/dist/modules/access-model/change-gate.js.map +1 -0
  112. package/dist/modules/access-model/creator.d.ts +13 -16
  113. package/dist/modules/access-model/creator.d.ts.map +1 -1
  114. package/dist/modules/access-model/creator.js.map +1 -1
  115. package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
  116. package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
  117. package/dist/modules/access-model/frontmatter-lines.js +74 -0
  118. package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
  119. package/dist/modules/access-model/plugin-principals.d.ts +5 -3
  120. package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
  121. package/dist/modules/access-model/plugin-principals.js +11 -17
  122. package/dist/modules/access-model/plugin-principals.js.map +1 -1
  123. package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
  124. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  125. package/dist/modules/access-model/roles-yaml-guard.js +153 -3
  126. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  127. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  128. package/dist/modules/admin/admin-access.service.js +5 -2
  129. package/dist/modules/admin/admin-access.service.js.map +1 -1
  130. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
  131. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
  132. package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
  133. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
  134. package/dist/modules/agent-instructions/compose.d.ts +71 -0
  135. package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
  136. package/dist/modules/agent-instructions/compose.js +234 -0
  137. package/dist/modules/agent-instructions/compose.js.map +1 -0
  138. package/dist/modules/agent-instructions/index.d.ts +4 -0
  139. package/dist/modules/agent-instructions/index.d.ts.map +1 -0
  140. package/dist/modules/agent-instructions/index.js +4 -0
  141. package/dist/modules/agent-instructions/index.js.map +1 -0
  142. package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
  143. package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
  144. package/dist/modules/agent-instructions/read-preamble.js +81 -0
  145. package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
  146. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  147. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  148. package/dist/modules/audit/agent-audit.service.js +290 -0
  149. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  150. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  151. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  152. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  153. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  154. package/dist/modules/audit/audit.contract.d.ts +145 -0
  155. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  156. package/dist/modules/audit/audit.contract.js +35 -0
  157. package/dist/modules/audit/audit.contract.js.map +1 -0
  158. package/dist/modules/audit/audit.routes.d.ts +21 -0
  159. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  160. package/dist/modules/audit/audit.routes.js +157 -0
  161. package/dist/modules/audit/audit.routes.js.map +1 -0
  162. package/dist/modules/audit/event-classifier.d.ts +52 -0
  163. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  164. package/dist/modules/audit/event-classifier.js +123 -0
  165. package/dist/modules/audit/event-classifier.js.map +1 -0
  166. package/dist/modules/audit/index.d.ts +7 -0
  167. package/dist/modules/audit/index.d.ts.map +1 -0
  168. package/dist/modules/audit/index.js +7 -0
  169. package/dist/modules/audit/index.js.map +1 -0
  170. package/dist/modules/audit/request-audit.d.ts +57 -0
  171. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  172. package/dist/modules/audit/request-audit.js +117 -0
  173. package/dist/modules/audit/request-audit.js.map +1 -0
  174. package/dist/modules/auth/account-erasure.service.d.ts +30 -4
  175. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  176. package/dist/modules/auth/account-erasure.service.js +96 -10
  177. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  178. package/dist/modules/auth/account.routes.d.ts +6 -3
  179. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  180. package/dist/modules/auth/account.routes.js +109 -10
  181. package/dist/modules/auth/account.routes.js.map +1 -1
  182. package/dist/modules/auth/auth.routes.d.ts +10 -3
  183. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  184. package/dist/modules/auth/auth.routes.js +9 -4
  185. package/dist/modules/auth/auth.routes.js.map +1 -1
  186. package/dist/modules/auth/auth.service.d.ts +89 -5
  187. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  188. package/dist/modules/auth/auth.service.js +160 -25
  189. package/dist/modules/auth/auth.service.js.map +1 -1
  190. package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
  191. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  192. package/dist/modules/auth/oidc-auth-provider.js +99 -21
  193. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  194. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  195. package/dist/modules/code-mode/code-mode.tool.js +14 -2
  196. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  197. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  198. package/dist/modules/connection-probe/connection-probe.service.js +56 -7
  199. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  200. package/dist/modules/database/advisory-lock.d.ts +103 -0
  201. package/dist/modules/database/advisory-lock.d.ts.map +1 -0
  202. package/dist/modules/database/advisory-lock.js +320 -0
  203. package/dist/modules/database/advisory-lock.js.map +1 -0
  204. package/dist/modules/database/connection.d.ts.map +1 -1
  205. package/dist/modules/database/connection.js +9 -1
  206. package/dist/modules/database/connection.js.map +1 -1
  207. package/dist/modules/database/core-schema.d.ts +859 -99
  208. package/dist/modules/database/core-schema.d.ts.map +1 -1
  209. package/dist/modules/database/core-schema.js +218 -0
  210. package/dist/modules/database/core-schema.js.map +1 -1
  211. package/dist/modules/database/migrate.d.ts.map +1 -1
  212. package/dist/modules/database/migrate.js +26 -6
  213. package/dist/modules/database/migrate.js.map +1 -1
  214. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  215. package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
  216. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  217. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  218. package/dist/modules/diff/diff.routes.js +2 -4
  219. package/dist/modules/diff/diff.routes.js.map +1 -1
  220. package/dist/modules/diff/diff.service.d.ts +4 -1
  221. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  222. package/dist/modules/diff/diff.service.js +84 -81
  223. package/dist/modules/diff/diff.service.js.map +1 -1
  224. package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
  225. package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
  226. package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
  227. package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
  228. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  229. package/dist/modules/kb-fs/file-change-notifier.js +3 -1
  230. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  231. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
  232. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
  233. package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
  234. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
  235. package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
  236. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  237. package/dist/modules/kb-fs/locking-filesystem.js +353 -76
  238. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  239. package/dist/modules/kb-fs/node-fs.d.ts +23 -0
  240. package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
  241. package/dist/modules/kb-fs/node-fs.js +158 -0
  242. package/dist/modules/kb-fs/node-fs.js.map +1 -0
  243. package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
  244. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
  245. package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
  246. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
  247. package/dist/modules/kb-fs/repo-path.d.ts +100 -3
  248. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  249. package/dist/modules/kb-fs/repo-path.js +195 -3
  250. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  251. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  252. package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
  253. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  254. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  255. package/dist/modules/kb-sync/kb-sync.service.js +7 -5
  256. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  257. package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
  258. package/dist/modules/marketplace/git-http.routes.js +6 -4
  259. package/dist/modules/marketplace/git-http.routes.js.map +1 -1
  260. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
  261. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
  262. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
  263. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
  264. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
  265. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
  266. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
  267. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
  268. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
  269. package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
  270. package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
  271. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
  272. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
  273. package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
  274. package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
  275. package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
  276. package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
  277. package/dist/modules/marketplace/github-facade/index.js +1 -1
  278. package/dist/modules/marketplace/github-facade/index.js.map +1 -1
  279. package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
  280. package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
  281. package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
  282. package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
  283. package/dist/modules/mcp/downstream-pool.d.ts +107 -0
  284. package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
  285. package/dist/modules/mcp/downstream-pool.js +221 -0
  286. package/dist/modules/mcp/downstream-pool.js.map +1 -0
  287. package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
  288. package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
  289. package/dist/modules/mcp/downstream-token-refresh.js +166 -0
  290. package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
  291. package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
  292. package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
  293. package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
  294. package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
  295. package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
  296. package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
  297. package/dist/modules/mcp/first-call-probe.cli.js +62 -0
  298. package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
  299. package/dist/modules/mcp/first-call-probe.d.ts +165 -0
  300. package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
  301. package/dist/modules/mcp/first-call-probe.js +298 -0
  302. package/dist/modules/mcp/first-call-probe.js.map +1 -0
  303. package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
  304. package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
  305. package/dist/modules/mcp/manual-failure-memo.js +79 -23
  306. package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
  307. package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
  308. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  309. package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
  310. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  311. package/dist/modules/mcp/mcp.routes.d.ts +4 -4
  312. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  313. package/dist/modules/mcp/mcp.routes.js +91 -180
  314. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  315. package/dist/modules/mcp/mcp.service.d.ts +226 -61
  316. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  317. package/dist/modules/mcp/mcp.service.js +758 -161
  318. package/dist/modules/mcp/mcp.service.js.map +1 -1
  319. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  320. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  321. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
  322. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  323. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  324. package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
  325. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  326. package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
  327. package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
  328. package/dist/modules/mcp/surface-log-throttle.js +94 -0
  329. package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
  330. package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
  331. package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
  332. package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
  333. package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
  334. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
  335. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
  336. package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
  337. package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
  338. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
  339. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
  340. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
  341. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
  342. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
  343. package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
  344. package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
  345. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
  346. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
  347. package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
  348. package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
  349. package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
  350. package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
  351. package/dist/modules/plugins/discovery/native.source.js +22 -4
  352. package/dist/modules/plugins/discovery/native.source.js.map +1 -1
  353. package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
  354. package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
  355. package/dist/modules/plugins/index.d.ts +6 -3
  356. package/dist/modules/plugins/index.d.ts.map +1 -1
  357. package/dist/modules/plugins/index.js +6 -3
  358. package/dist/modules/plugins/index.js.map +1 -1
  359. package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
  360. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
  361. package/dist/modules/plugins/join-request-jobs.service.js +602 -0
  362. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
  363. package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
  364. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
  365. package/dist/modules/plugins/join-request-records.store.js +179 -0
  366. package/dist/modules/plugins/join-request-records.store.js.map +1 -0
  367. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  368. package/dist/modules/plugins/join-requests.service.js +4 -2
  369. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  370. package/dist/modules/plugins/plugin-links.d.ts +4 -1
  371. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  372. package/dist/modules/plugins/plugin-links.js +6 -3
  373. package/dist/modules/plugins/plugin-links.js.map +1 -1
  374. package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
  375. package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
  376. package/dist/modules/plugins/plugin-links.service.js +4 -13
  377. package/dist/modules/plugins/plugin-links.service.js.map +1 -1
  378. package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
  379. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  380. package/dist/modules/plugins/plugin-provision.service.js +183 -38
  381. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  382. package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
  383. package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
  384. package/dist/modules/plugins/plugin-rename.service.js +20 -11
  385. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  386. package/dist/modules/plugins/plugins.contract.d.ts +52 -4
  387. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
  388. package/dist/modules/plugins/plugins.routes.d.ts +38 -5
  389. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  390. package/dist/modules/plugins/plugins.routes.js +190 -116
  391. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  392. package/dist/modules/plugins/plugins.service.d.ts +38 -6
  393. package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
  394. package/dist/modules/plugins/plugins.service.js +86 -17
  395. package/dist/modules/plugins/plugins.service.js.map +1 -1
  396. package/dist/modules/plugins/plugins.tools.d.ts +25 -0
  397. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
  398. package/dist/modules/plugins/plugins.tools.js +82 -0
  399. package/dist/modules/plugins/plugins.tools.js.map +1 -0
  400. package/dist/modules/plugins/teams.routes.d.ts +42 -0
  401. package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
  402. package/dist/modules/plugins/teams.routes.js +79 -0
  403. package/dist/modules/plugins/teams.routes.js.map +1 -0
  404. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
  405. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  406. package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
  407. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  408. package/dist/modules/secrets-vault/index.d.ts +1 -1
  409. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  410. package/dist/modules/secrets-vault/index.js.map +1 -1
  411. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
  412. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
  413. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
  414. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
  415. package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
  416. package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
  417. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
  418. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  419. package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
  420. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  421. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  422. package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
  423. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  424. package/dist/modules/settings/connection-check.d.ts +118 -0
  425. package/dist/modules/settings/connection-check.d.ts.map +1 -0
  426. package/dist/modules/settings/connection-check.js +287 -0
  427. package/dist/modules/settings/connection-check.js.map +1 -0
  428. package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
  429. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  430. package/dist/modules/settings/deployment-settings.service.js +375 -52
  431. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  432. package/dist/modules/settings/git-root-folders.d.ts +54 -0
  433. package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
  434. package/dist/modules/settings/git-root-folders.js +130 -0
  435. package/dist/modules/settings/git-root-folders.js.map +1 -0
  436. package/dist/modules/settings/oidc-check.d.ts +85 -0
  437. package/dist/modules/settings/oidc-check.d.ts.map +1 -0
  438. package/dist/modules/settings/oidc-check.js +172 -0
  439. package/dist/modules/settings/oidc-check.js.map +1 -0
  440. package/dist/modules/settings/setup.routes.d.ts +31 -1
  441. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  442. package/dist/modules/settings/setup.routes.js +400 -108
  443. package/dist/modules/settings/setup.routes.js.map +1 -1
  444. package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
  445. package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
  446. package/dist/modules/skills/allowed-tools-check.js +260 -0
  447. package/dist/modules/skills/allowed-tools-check.js.map +1 -0
  448. package/dist/modules/skills/index.d.ts +3 -1
  449. package/dist/modules/skills/index.d.ts.map +1 -1
  450. package/dist/modules/skills/index.js +1 -0
  451. package/dist/modules/skills/index.js.map +1 -1
  452. package/dist/modules/skills/pending-skills.service.d.ts +6 -11
  453. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  454. package/dist/modules/skills/pending-skills.service.js +18 -70
  455. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  456. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  457. package/dist/modules/skills/skill-access-requests.routes.js +9 -6
  458. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  459. package/dist/modules/skills/skills.contract.d.ts +44 -10
  460. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  461. package/dist/modules/skills/skills.routes.d.ts +8 -1
  462. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  463. package/dist/modules/skills/skills.routes.js +16 -2
  464. package/dist/modules/skills/skills.routes.js.map +1 -1
  465. package/dist/modules/skills/skills.service.d.ts +31 -7
  466. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  467. package/dist/modules/skills/skills.service.js +191 -81
  468. package/dist/modules/skills/skills.service.js.map +1 -1
  469. package/dist/modules/skills/skills.tools.d.ts +4 -1
  470. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  471. package/dist/modules/skills/skills.tools.js +41 -6
  472. package/dist/modules/skills/skills.tools.js.map +1 -1
  473. package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
  474. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
  475. package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
  476. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
  477. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
  478. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
  479. package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
  480. package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
  481. package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
  482. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  483. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  484. package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
  485. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  486. package/dist/modules/tool-auth/external-api-key.service.js +83 -27
  487. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  488. package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
  489. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  490. package/dist/modules/tool-auth/internal-token.service.js +6 -2
  491. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  492. package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
  493. package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
  494. package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
  495. package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
  496. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
  497. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  498. package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
  499. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  500. package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
  501. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  502. package/dist/modules/tool-helpers/tool-context.js +28 -6
  503. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  504. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  505. package/dist/modules/tool-helpers/tool-handler.js +22 -5
  506. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  507. package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
  508. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  509. package/dist/modules/tool-helpers/tool.contract.js +7 -1
  510. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  511. package/dist/modules/tool-manuals/index.d.ts +2 -1
  512. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  513. package/dist/modules/tool-manuals/index.js +1 -0
  514. package/dist/modules/tool-manuals/index.js.map +1 -1
  515. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  516. package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
  517. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  518. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
  519. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  520. package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
  521. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  522. package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
  523. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
  524. package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
  525. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
  526. package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
  527. package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
  528. package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
  529. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
  530. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
  531. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  532. package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
  533. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  534. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
  535. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  536. package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
  537. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  538. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
  539. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  540. package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
  541. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  542. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  543. package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
  544. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  545. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  546. package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
  547. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  548. package/dist/modules/workflow/event-bus.d.ts +10 -4
  549. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  550. package/dist/modules/workflow/event-bus.js +14 -11
  551. package/dist/modules/workflow/event-bus.js.map +1 -1
  552. package/dist/modules/workflow/events.routes.d.ts +17 -2
  553. package/dist/modules/workflow/events.routes.d.ts.map +1 -1
  554. package/dist/modules/workflow/events.routes.js +31 -12
  555. package/dist/modules/workflow/events.routes.js.map +1 -1
  556. package/dist/modules/workflow/file-lock.service.d.ts +22 -4
  557. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  558. package/dist/modules/workflow/file-lock.service.js +27 -4
  559. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  560. package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
  561. package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
  562. package/dist/modules/workflow/git/change-request-link.js +34 -0
  563. package/dist/modules/workflow/git/change-request-link.js.map +1 -0
  564. package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
  565. package/dist/modules/workflow/git/git-version.js +3 -1
  566. package/dist/modules/workflow/git/git-version.js.map +1 -1
  567. package/dist/modules/workflow/git/git.service.d.ts +263 -4
  568. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  569. package/dist/modules/workflow/git/git.service.js +528 -118
  570. package/dist/modules/workflow/git/git.service.js.map +1 -1
  571. package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
  572. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
  573. package/dist/modules/workflow/git/node-git-runner.js +273 -0
  574. package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
  575. package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
  576. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  577. package/dist/modules/workflow/git/pull-request.service.js +157 -25
  578. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  579. package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
  580. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  581. package/dist/modules/workflow/pending-commits.service.js +24 -4
  582. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  583. package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
  584. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  585. package/dist/modules/workflow/pending-commits.worker.js +107 -58
  586. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  587. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  588. package/dist/modules/workflow/recovery-bot.js +3 -1
  589. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  590. package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
  591. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
  592. package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
  593. package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
  594. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
  595. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
  596. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
  597. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  598. package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
  599. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  600. package/dist/modules/workflow/sanitize-error.d.ts +5 -2
  601. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  602. package/dist/modules/workflow/sanitize-error.js +8 -4
  603. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  604. package/dist/modules/workflow/workflow.routes.d.ts +2 -0
  605. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  606. package/dist/modules/workflow/workflow.routes.js +259 -29
  607. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  608. package/dist/modules/workflow/workflow.service.d.ts +166 -14
  609. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  610. package/dist/modules/workflow/workflow.service.js +906 -106
  611. package/dist/modules/workflow/workflow.service.js.map +1 -1
  612. package/dist/modules/workspace/empty-dirs.d.ts +15 -0
  613. package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
  614. package/dist/modules/workspace/empty-dirs.js +90 -0
  615. package/dist/modules/workspace/empty-dirs.js.map +1 -0
  616. package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
  617. package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
  618. package/dist/modules/workspace/file-readers/content-mode.js +55 -0
  619. package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
  620. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
  621. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  622. package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
  623. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  624. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
  625. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
  626. package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
  627. package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
  628. package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
  629. package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
  630. package/dist/modules/workspace/file-readers/document-reader.js +5 -0
  631. package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
  632. package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
  633. package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
  634. package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
  635. package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
  636. package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
  637. package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
  638. package/dist/modules/workspace/file-readers/file-reader.js +11 -6
  639. package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
  640. package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
  641. package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
  642. package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
  643. package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
  644. package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
  645. package/dist/modules/workspace/file-readers/image-reader.js +9 -5
  646. package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
  647. package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
  648. package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
  649. package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
  650. package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
  651. package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
  652. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  653. package/dist/modules/workspace/file-readers/text-reader.js +57 -3
  654. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  655. package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
  656. package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
  657. package/dist/modules/workspace/git-internals.middleware.js +93 -0
  658. package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
  659. package/dist/modules/workspace/not-found.d.ts +80 -0
  660. package/dist/modules/workspace/not-found.d.ts.map +1 -0
  661. package/dist/modules/workspace/not-found.js +112 -0
  662. package/dist/modules/workspace/not-found.js.map +1 -0
  663. package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
  664. package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
  665. package/dist/modules/workspace/session-ontology.gate.js +1 -1
  666. package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
  667. package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
  668. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
  669. package/dist/modules/workspace/startup/beside-checkout.js +110 -0
  670. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
  671. package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
  672. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  673. package/dist/modules/workspace/startup/kb-git.js +34 -35
  674. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  675. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
  676. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  677. package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
  678. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  679. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
  680. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
  681. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
  682. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  683. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
  684. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
  685. package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
  686. package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
  687. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
  688. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
  689. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
  690. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
  691. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
  692. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
  693. package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
  694. package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
  695. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
  696. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
  697. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
  698. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
  699. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
  700. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
  701. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
  702. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  703. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
  704. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  705. package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
  706. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  707. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
  708. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  709. package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
  710. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  711. package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
  712. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
  713. package/dist/modules/workspace/startup/steps/template-source.js +160 -0
  714. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
  715. package/dist/modules/workspace/workspace.routes.d.ts +14 -2
  716. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  717. package/dist/modules/workspace/workspace.routes.js +649 -238
  718. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  719. package/dist/modules/workspace/workspace.service.d.ts +298 -12
  720. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  721. package/dist/modules/workspace/workspace.service.js +933 -269
  722. package/dist/modules/workspace/workspace.service.js.map +1 -1
  723. package/dist/modules/workspace/workspace.tools.d.ts +42 -1
  724. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  725. package/dist/modules/workspace/workspace.tools.js +1626 -146
  726. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  727. package/dist/modules/workspace/write-denial.d.ts +76 -0
  728. package/dist/modules/workspace/write-denial.d.ts.map +1 -0
  729. package/dist/modules/workspace/write-denial.js +138 -0
  730. package/dist/modules/workspace/write-denial.js.map +1 -0
  731. package/dist/shared/canonical-file-identity.d.ts +64 -0
  732. package/dist/shared/canonical-file-identity.d.ts.map +1 -0
  733. package/dist/shared/canonical-file-identity.js +92 -0
  734. package/dist/shared/canonical-file-identity.js.map +1 -0
  735. package/dist/shared/domain-errors.d.ts +108 -0
  736. package/dist/shared/domain-errors.d.ts.map +1 -1
  737. package/dist/shared/domain-errors.js +137 -0
  738. package/dist/shared/domain-errors.js.map +1 -1
  739. package/dist/shared/email-identity.d.ts +32 -0
  740. package/dist/shared/email-identity.d.ts.map +1 -0
  741. package/dist/shared/email-identity.js +37 -0
  742. package/dist/shared/email-identity.js.map +1 -0
  743. package/dist/shared/fs.contract.d.ts +206 -0
  744. package/dist/shared/fs.contract.d.ts.map +1 -0
  745. package/dist/shared/fs.contract.js +79 -0
  746. package/dist/shared/fs.contract.js.map +1 -0
  747. package/dist/shared/git-failure.d.ts +80 -0
  748. package/dist/shared/git-failure.d.ts.map +1 -0
  749. package/dist/shared/git-failure.js +139 -0
  750. package/dist/shared/git-failure.js.map +1 -0
  751. package/dist/shared/git-internals.d.ts +23 -0
  752. package/dist/shared/git-internals.d.ts.map +1 -0
  753. package/dist/shared/git-internals.js +181 -0
  754. package/dist/shared/git-internals.js.map +1 -0
  755. package/dist/shared/git.contract.d.ts +157 -0
  756. package/dist/shared/git.contract.d.ts.map +1 -0
  757. package/dist/shared/git.contract.js +103 -0
  758. package/dist/shared/git.contract.js.map +1 -0
  759. package/dist/shared/http-errors.d.ts +33 -0
  760. package/dist/shared/http-errors.d.ts.map +1 -0
  761. package/dist/shared/http-errors.js +35 -0
  762. package/dist/shared/http-errors.js.map +1 -0
  763. package/dist/shared/logger.contract.d.ts +63 -0
  764. package/dist/shared/logger.contract.d.ts.map +1 -0
  765. package/dist/shared/logger.contract.js +126 -0
  766. package/dist/shared/logger.contract.js.map +1 -0
  767. package/dist/shared/logging.d.ts +31 -0
  768. package/dist/shared/logging.d.ts.map +1 -0
  769. package/dist/shared/logging.js +92 -0
  770. package/dist/shared/logging.js.map +1 -0
  771. package/dist/shared/path-containment.d.ts +21 -0
  772. package/dist/shared/path-containment.d.ts.map +1 -0
  773. package/dist/shared/path-containment.js +34 -0
  774. package/dist/shared/path-containment.js.map +1 -0
  775. package/dist/shared/pending-proposals.d.ts +71 -0
  776. package/dist/shared/pending-proposals.d.ts.map +1 -0
  777. package/dist/shared/pending-proposals.js +98 -0
  778. package/dist/shared/pending-proposals.js.map +1 -0
  779. package/dist/shared/printable.d.ts +22 -0
  780. package/dist/shared/printable.d.ts.map +1 -0
  781. package/dist/shared/printable.js +65 -0
  782. package/dist/shared/printable.js.map +1 -0
  783. package/dist/shared/redact-secret.d.ts +30 -0
  784. package/dist/shared/redact-secret.d.ts.map +1 -0
  785. package/dist/shared/redact-secret.js +114 -0
  786. package/dist/shared/redact-secret.js.map +1 -0
  787. package/dist/shared/rename-no-replace.d.ts +119 -0
  788. package/dist/shared/rename-no-replace.d.ts.map +1 -0
  789. package/dist/shared/rename-no-replace.js +323 -0
  790. package/dist/shared/rename-no-replace.js.map +1 -0
  791. package/dist/shared/ssrf.d.ts.map +1 -1
  792. package/dist/shared/ssrf.js +49 -18
  793. package/dist/shared/ssrf.js.map +1 -1
  794. package/dist/version.d.ts +10 -6
  795. package/dist/version.d.ts.map +1 -1
  796. package/dist/version.js +10 -6
  797. package/dist/version.js.map +1 -1
  798. package/kb-template/.bevelignore +4 -3
  799. package/kb-template/AGENTS.md +281 -36
  800. package/kb-template/KnowledgeBase/How to get started.md +1 -1
  801. package/kb-template/access.md +5 -3
  802. package/kb-template/mcp-description.md +27 -0
  803. package/migrations/0006_api_tokens_revoked_by.sql +1 -0
  804. package/migrations/0007_github_facade_registered.sql +1 -0
  805. package/migrations/0008_change_request_apply_failure.sql +4 -0
  806. package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
  807. package/migrations/0010_plugin_join_requests.sql +15 -0
  808. package/migrations/0011_plugin_join_request_claim.sql +11 -0
  809. package/migrations/0012_agent_audit.sql +41 -0
  810. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  811. package/migrations/meta/0006_snapshot.json +1723 -0
  812. package/migrations/meta/0007_snapshot.json +1729 -0
  813. package/migrations/meta/0008_snapshot.json +1753 -0
  814. package/migrations/meta/0009_snapshot.json +1759 -0
  815. package/migrations/meta/0010_snapshot.json +1872 -0
  816. package/migrations/meta/0011_snapshot.json +1884 -0
  817. package/migrations/meta/0012_snapshot.json +2247 -0
  818. package/migrations/meta/0013_snapshot.json +2259 -0
  819. package/migrations/meta/_journal.json +56 -0
  820. package/package.json +7 -6
  821. package/src/__tests__/core-config.domain.test.ts +67 -2
  822. package/src/__tests__/kb-layout-config.test.ts +186 -1
  823. package/src/__tests__/open-change-gate.ts +15 -0
  824. package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
  825. package/src/core/__tests__/catalog-revision.test.ts +190 -0
  826. package/src/core/__tests__/lifecycle.test.ts +369 -0
  827. package/src/core/__tests__/public-config.test.ts +30 -0
  828. package/src/core/__tests__/readiness.test.ts +109 -0
  829. package/src/core/catalog-cache-invalidation.ts +20 -1
  830. package/src/core/catalog-revision.ts +110 -0
  831. package/src/core/create-core-server.ts +286 -79
  832. package/src/core/create-core-services.ts +315 -81
  833. package/src/core/lifecycle.ts +312 -0
  834. package/src/core/public-config.ts +86 -0
  835. package/src/core/readiness.ts +123 -0
  836. package/src/core-config.ts +70 -4
  837. package/src/index.ts +15 -0
  838. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
  839. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
  840. package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
  841. package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
  842. package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
  843. package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
  844. package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
  845. package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
  846. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
  847. package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
  848. package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
  849. package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
  850. package/src/modules/access/__tests__/access-groups.test.ts +47 -14
  851. package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
  852. package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
  853. package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
  854. package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
  855. package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
  856. package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
  857. package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
  858. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
  859. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
  860. package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
  861. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
  862. package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
  863. package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
  864. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
  865. package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
  866. package/src/modules/access/__tests__/creator-access.test.ts +81 -83
  867. package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
  868. package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
  869. package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
  870. package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
  871. package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
  872. package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
  873. package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
  874. package/src/modules/access/__tests__/users-db-double.ts +63 -0
  875. package/src/modules/access/access-control.interface.ts +251 -11
  876. package/src/modules/access/access-control.service.ts +719 -164
  877. package/src/modules/access/access-mutation.service.ts +182 -13
  878. package/src/modules/access/access-view.ts +316 -0
  879. package/src/modules/access/access.routes.ts +293 -95
  880. package/src/modules/access/admin-locked-commit.ts +12 -14
  881. package/src/modules/access/admin-route-helpers.ts +4 -4
  882. package/src/modules/access/change-read-gate.ts +148 -0
  883. package/src/modules/access/creator-access.ts +89 -124
  884. package/src/modules/access/directory-sync-bot.ts +4 -1
  885. package/src/modules/access/groups-admin.service.ts +3 -3
  886. package/src/modules/access/reference-scan.ts +5 -5
  887. package/src/modules/access/roles-admin.service.ts +94 -15
  888. package/src/modules/access/synced-groups-committer.ts +10 -15
  889. package/src/modules/access/synced-groups-writer.ts +3 -12
  890. package/src/modules/access/user-access-removal.service.ts +711 -0
  891. package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
  892. package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
  893. package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
  894. package/src/modules/access-model/access-errors.ts +56 -15
  895. package/src/modules/access-model/access-grammar.ts +85 -48
  896. package/src/modules/access-model/access-splice.ts +18 -18
  897. package/src/modules/access-model/access-template.ts +44 -0
  898. package/src/modules/access-model/change-gate.ts +80 -0
  899. package/src/modules/access-model/creator.ts +18 -21
  900. package/src/modules/access-model/frontmatter-lines.ts +98 -0
  901. package/src/modules/access-model/plugin-principals.ts +12 -19
  902. package/src/modules/access-model/roles-yaml-guard.ts +182 -4
  903. package/src/modules/admin/admin-access.service.ts +6 -2
  904. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
  905. package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
  906. package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
  907. package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
  908. package/src/modules/agent-instructions/compose.ts +271 -0
  909. package/src/modules/agent-instructions/index.ts +14 -0
  910. package/src/modules/agent-instructions/read-preamble.ts +88 -0
  911. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  912. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  913. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  914. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  915. package/src/modules/audit/agent-audit.service.ts +316 -0
  916. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  917. package/src/modules/audit/audit.contract.ts +164 -0
  918. package/src/modules/audit/audit.routes.ts +168 -0
  919. package/src/modules/audit/event-classifier.ts +155 -0
  920. package/src/modules/audit/index.ts +14 -0
  921. package/src/modules/audit/request-audit.ts +141 -0
  922. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
  923. package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
  924. package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
  925. package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
  926. package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
  927. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
  928. package/src/modules/auth/account-erasure.service.ts +117 -10
  929. package/src/modules/auth/account.routes.ts +115 -14
  930. package/src/modules/auth/auth.routes.ts +20 -7
  931. package/src/modules/auth/auth.service.ts +187 -28
  932. package/src/modules/auth/oidc-auth-provider.ts +120 -22
  933. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
  934. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  935. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
  936. package/src/modules/connection-probe/connection-probe.service.ts +59 -7
  937. package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
  938. package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
  939. package/src/modules/database/advisory-lock.ts +346 -0
  940. package/src/modules/database/connection.ts +9 -1
  941. package/src/modules/database/core-schema.ts +227 -0
  942. package/src/modules/database/migrate.ts +27 -6
  943. package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
  944. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
  945. package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
  946. package/src/modules/diff/diff.routes.ts +2 -4
  947. package/src/modules/diff/diff.service.ts +80 -79
  948. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
  949. package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
  950. package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
  951. package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
  952. package/src/modules/kb-fs/file-change-notifier.ts +4 -1
  953. package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
  954. package/src/modules/kb-fs/locking-filesystem.ts +399 -109
  955. package/src/modules/kb-fs/node-fs.ts +165 -0
  956. package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
  957. package/src/modules/kb-fs/repo-path.ts +213 -3
  958. package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
  959. package/src/modules/kb-sync/kb-sync.service.ts +8 -5
  960. package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
  961. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
  962. package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
  963. package/src/modules/marketplace/git-http.routes.ts +7 -4
  964. package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
  965. package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
  966. package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
  967. package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
  968. package/src/modules/marketplace/github-facade/index.ts +1 -0
  969. package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
  970. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  971. package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
  972. package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
  973. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
  974. package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
  975. package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
  976. package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
  977. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
  978. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
  979. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
  980. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  981. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
  982. package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
  983. package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
  984. package/src/modules/mcp/downstream-pool.ts +256 -0
  985. package/src/modules/mcp/downstream-token-refresh.ts +170 -0
  986. package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
  987. package/src/modules/mcp/first-call-probe.cli.ts +60 -0
  988. package/src/modules/mcp/first-call-probe.ts +418 -0
  989. package/src/modules/mcp/manual-failure-memo.ts +81 -23
  990. package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
  991. package/src/modules/mcp/mcp.routes.ts +386 -482
  992. package/src/modules/mcp/mcp.service.ts +905 -177
  993. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
  994. package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
  995. package/src/modules/mcp/surface-log-throttle.ts +120 -0
  996. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
  997. package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
  998. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
  999. package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
  1000. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
  1001. package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
  1002. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
  1003. package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
  1004. package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
  1005. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
  1006. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
  1007. package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
  1008. package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
  1009. package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
  1010. package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
  1011. package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
  1012. package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
  1013. package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
  1014. package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
  1015. package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
  1016. package/src/modules/plugins/discovery/native.source.ts +21 -3
  1017. package/src/modules/plugins/discovery/plugin-source.ts +17 -3
  1018. package/src/modules/plugins/index.ts +16 -3
  1019. package/src/modules/plugins/join-request-jobs.service.ts +675 -0
  1020. package/src/modules/plugins/join-request-records.store.ts +353 -0
  1021. package/src/modules/plugins/join-requests.service.ts +7 -4
  1022. package/src/modules/plugins/plugin-links.service.ts +4 -17
  1023. package/src/modules/plugins/plugin-links.ts +6 -3
  1024. package/src/modules/plugins/plugin-provision.service.ts +209 -37
  1025. package/src/modules/plugins/plugin-rename.service.ts +21 -9
  1026. package/src/modules/plugins/plugins.contract.ts +52 -4
  1027. package/src/modules/plugins/plugins.routes.ts +204 -124
  1028. package/src/modules/plugins/plugins.service.ts +92 -18
  1029. package/src/modules/plugins/plugins.tools.ts +88 -0
  1030. package/src/modules/plugins/teams.routes.ts +129 -0
  1031. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
  1032. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
  1033. package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
  1034. package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
  1035. package/src/modules/secrets-vault/index.ts +1 -0
  1036. package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
  1037. package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
  1038. package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
  1039. package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
  1040. package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
  1041. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
  1042. package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
  1043. package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
  1044. package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
  1045. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
  1046. package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
  1047. package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
  1048. package/src/modules/settings/connection-check.ts +351 -0
  1049. package/src/modules/settings/deployment-settings.service.ts +444 -59
  1050. package/src/modules/settings/git-root-folders.ts +171 -0
  1051. package/src/modules/settings/oidc-check.ts +229 -0
  1052. package/src/modules/settings/setup.routes.ts +456 -110
  1053. package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
  1054. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
  1055. package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
  1056. package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
  1057. package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
  1058. package/src/modules/skills/allowed-tools-check.ts +352 -0
  1059. package/src/modules/skills/index.ts +4 -0
  1060. package/src/modules/skills/pending-skills.service.ts +21 -82
  1061. package/src/modules/skills/skill-access-requests.routes.ts +10 -6
  1062. package/src/modules/skills/skills.contract.ts +43 -11
  1063. package/src/modules/skills/skills.routes.ts +16 -1
  1064. package/src/modules/skills/skills.service.ts +214 -84
  1065. package/src/modules/skills/skills.tools.ts +43 -5
  1066. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
  1067. package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
  1068. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
  1069. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  1070. package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
  1071. package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
  1072. package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
  1073. package/src/modules/tool-auth/external-api-key.service.ts +98 -33
  1074. package/src/modules/tool-auth/internal-token.service.ts +16 -2
  1075. package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
  1076. package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
  1077. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
  1078. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
  1079. package/src/modules/tool-helpers/tool-context.ts +46 -7
  1080. package/src/modules/tool-helpers/tool-handler.ts +23 -5
  1081. package/src/modules/tool-helpers/tool.contract.ts +5 -0
  1082. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
  1083. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
  1084. package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
  1085. package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
  1086. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
  1087. package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
  1088. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
  1089. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
  1090. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
  1091. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
  1092. package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
  1093. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
  1094. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
  1095. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
  1096. package/src/modules/tool-manuals/index.ts +3 -0
  1097. package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
  1098. package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
  1099. package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
  1100. package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
  1101. package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
  1102. package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
  1103. package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
  1104. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
  1105. package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
  1106. package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
  1107. package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
  1108. package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
  1109. package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
  1110. package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
  1111. package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
  1112. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
  1113. package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
  1114. package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
  1115. package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
  1116. package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
  1117. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
  1118. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
  1119. package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
  1120. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
  1121. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
  1122. package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
  1123. package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
  1124. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
  1125. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
  1126. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
  1127. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
  1128. package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
  1129. package/src/modules/workflow/event-bus.ts +26 -28
  1130. package/src/modules/workflow/events.routes.ts +56 -21
  1131. package/src/modules/workflow/file-lock.service.ts +27 -4
  1132. package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
  1133. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
  1134. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
  1135. package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
  1136. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
  1137. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
  1138. package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
  1139. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
  1140. package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
  1141. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
  1142. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
  1143. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  1144. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
  1145. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
  1146. package/src/modules/workflow/git/change-request-link.ts +35 -0
  1147. package/src/modules/workflow/git/git-version.ts +5 -2
  1148. package/src/modules/workflow/git/git.service.ts +684 -144
  1149. package/src/modules/workflow/git/node-git-runner.ts +315 -0
  1150. package/src/modules/workflow/git/pull-request.service.ts +179 -27
  1151. package/src/modules/workflow/pending-commits.service.ts +26 -7
  1152. package/src/modules/workflow/pending-commits.worker.ts +114 -69
  1153. package/src/modules/workflow/recovery-bot.ts +4 -1
  1154. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
  1155. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  1156. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
  1157. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
  1158. package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
  1159. package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
  1160. package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
  1161. package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
  1162. package/src/modules/workflow/sanitize-error.ts +9 -4
  1163. package/src/modules/workflow/workflow.routes.ts +273 -29
  1164. package/src/modules/workflow/workflow.service.ts +1087 -171
  1165. package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
  1166. package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
  1167. package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
  1168. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
  1169. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
  1170. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
  1171. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
  1172. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
  1173. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
  1174. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
  1175. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
  1176. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
  1177. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
  1178. package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
  1179. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
  1180. package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
  1181. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
  1182. package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
  1183. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
  1184. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
  1185. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
  1186. package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
  1187. package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
  1188. package/src/modules/workspace/empty-dirs.ts +79 -0
  1189. package/src/modules/workspace/file-readers/README.md +32 -0
  1190. package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
  1191. package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
  1192. package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
  1193. package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
  1194. package/src/modules/workspace/file-readers/content-mode.ts +87 -0
  1195. package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
  1196. package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
  1197. package/src/modules/workspace/file-readers/document-reader.ts +6 -0
  1198. package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
  1199. package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
  1200. package/src/modules/workspace/file-readers/file-reader.ts +27 -6
  1201. package/src/modules/workspace/file-readers/image-reader.ts +10 -5
  1202. package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
  1203. package/src/modules/workspace/file-readers/text-reader.ts +61 -4
  1204. package/src/modules/workspace/git-internals.middleware.ts +86 -0
  1205. package/src/modules/workspace/not-found.ts +119 -0
  1206. package/src/modules/workspace/session-ontology.gate.ts +1 -1
  1207. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
  1208. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
  1209. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
  1210. package/src/modules/workspace/startup/beside-checkout.ts +112 -0
  1211. package/src/modules/workspace/startup/kb-git.ts +43 -36
  1212. package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
  1213. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
  1214. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
  1215. package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
  1216. package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
  1217. package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
  1218. package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
  1219. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
  1220. package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
  1221. package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
  1222. package/src/modules/workspace/startup/steps/template-source.ts +168 -0
  1223. package/src/modules/workspace/workspace.routes.ts +687 -257
  1224. package/src/modules/workspace/workspace.service.ts +1000 -303
  1225. package/src/modules/workspace/workspace.tools.ts +1823 -180
  1226. package/src/modules/workspace/write-denial.ts +189 -0
  1227. package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
  1228. package/src/shared/__tests__/email-identity.test.ts +35 -0
  1229. package/src/shared/__tests__/filename.test.ts +0 -0
  1230. package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
  1231. package/src/shared/__tests__/git-failure.test.ts +198 -0
  1232. package/src/shared/__tests__/git-internals.test.ts +156 -0
  1233. package/src/shared/__tests__/http-errors.test.ts +42 -0
  1234. package/src/shared/__tests__/logging.test.ts +185 -0
  1235. package/src/shared/__tests__/path-containment.test.ts +60 -0
  1236. package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
  1237. package/src/shared/__tests__/printable.test.ts +64 -0
  1238. package/src/shared/__tests__/redact-secret.test.ts +122 -0
  1239. package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
  1240. package/src/shared/__tests__/ssrf.test.ts +20 -0
  1241. package/src/shared/canonical-file-identity.ts +92 -0
  1242. package/src/shared/domain-errors.ts +149 -0
  1243. package/src/shared/email-identity.ts +38 -0
  1244. package/src/shared/fs.contract.ts +225 -0
  1245. package/src/shared/git-failure.ts +196 -0
  1246. package/src/shared/git-internals.ts +179 -0
  1247. package/src/shared/git.contract.ts +170 -0
  1248. package/src/shared/http-errors.ts +36 -0
  1249. package/src/shared/logger.contract.ts +141 -0
  1250. package/src/shared/logging.ts +96 -0
  1251. package/src/shared/path-containment.ts +34 -0
  1252. package/src/shared/pending-proposals.ts +161 -0
  1253. package/src/shared/printable.ts +68 -0
  1254. package/src/shared/redact-secret.ts +118 -0
  1255. package/src/shared/rename-no-replace.ts +386 -0
  1256. package/src/shared/ssrf.ts +44 -16
  1257. package/src/version.ts +10 -6
  1258. package/dist/modules/access/access-errors.d.ts +0 -34
  1259. package/dist/modules/access/access-errors.d.ts.map +0 -1
  1260. package/dist/modules/access/access-errors.js +0 -40
  1261. package/dist/modules/access/access-errors.js.map +0 -1
  1262. package/dist/modules/access/access-splice.d.ts +0 -140
  1263. package/dist/modules/access/access-splice.d.ts.map +0 -1
  1264. package/dist/modules/access/access-splice.js +0 -389
  1265. package/dist/modules/access/access-splice.js.map +0 -1
  1266. package/dist/modules/access/group-files.d.ts +0 -83
  1267. package/dist/modules/access/group-files.d.ts.map +0 -1
  1268. package/dist/modules/access/group-files.js +0 -167
  1269. package/dist/modules/access/group-files.js.map +0 -1
  1270. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  1271. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  1272. package/dist/modules/access/kb-read-filter.js +0 -60
  1273. package/dist/modules/access/kb-read-filter.js.map +0 -1
  1274. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  1275. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  1276. package/dist/modules/access/render-roles-yaml.js +0 -56
  1277. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  1278. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  1279. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  1280. package/dist/modules/access/roles-yaml-guard.js +0 -79
  1281. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  1282. package/dist/modules/diff/diff-paths.d.ts +0 -2
  1283. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  1284. package/dist/modules/diff/diff-paths.js +0 -9
  1285. package/dist/modules/diff/diff-paths.js.map +0 -1
  1286. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  1287. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  1288. package/dist/modules/groups/group-provision.service.js +0 -288
  1289. package/dist/modules/groups/group-provision.service.js.map +0 -1
  1290. package/dist/modules/groups/groups.contract.d.ts +0 -106
  1291. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  1292. package/dist/modules/groups/groups.contract.js +0 -36
  1293. package/dist/modules/groups/groups.contract.js.map +0 -1
  1294. package/dist/modules/groups/groups.routes.d.ts +0 -42
  1295. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  1296. package/dist/modules/groups/groups.routes.js +0 -379
  1297. package/dist/modules/groups/groups.routes.js.map +0 -1
  1298. package/dist/modules/groups/groups.service.d.ts +0 -60
  1299. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  1300. package/dist/modules/groups/groups.service.js +0 -172
  1301. package/dist/modules/groups/groups.service.js.map +0 -1
  1302. package/dist/modules/groups/index.d.ts +0 -7
  1303. package/dist/modules/groups/index.d.ts.map +0 -1
  1304. package/dist/modules/groups/index.js +0 -6
  1305. package/dist/modules/groups/index.js.map +0 -1
  1306. package/dist/modules/groups/join-proposals.d.ts +0 -53
  1307. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  1308. package/dist/modules/groups/join-proposals.js +0 -67
  1309. package/dist/modules/groups/join-proposals.js.map +0 -1
  1310. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  1311. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  1312. package/dist/modules/groups/join-requests.service.js +0 -135
  1313. package/dist/modules/groups/join-requests.service.js.map +0 -1
  1314. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  1315. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  1316. package/dist/modules/mcp/mcp-session-store.js +0 -131
  1317. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  1318. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  1319. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  1320. package/dist/modules/workflow/file-change-notifier.js +0 -22
  1321. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  1322. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  1323. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  1324. package/dist/modules/workflow/git/branch-name.js +0 -76
  1325. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  1326. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  1327. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  1328. package/dist/modules/workflow/git/clone-config.js +0 -69
  1329. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  1330. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  1331. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  1332. package/dist/modules/workflow/git/mutex.js +0 -23
  1333. package/dist/modules/workflow/git/mutex.js.map +0 -1
  1334. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  1335. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  1336. package/dist/modules/workflow/locking-filesystem.js +0 -553
  1337. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  1338. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  1339. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  1340. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  1341. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  1342. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  1343. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  1344. package/dist/modules/workflow/workflow.errors.js +0 -298
  1345. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  1346. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  1347. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  1348. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  1349. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  1350. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  1351. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  1352. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  1353. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  1354. package/dist/modules/workspace/kb-seed.service.js +0 -535
  1355. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  1356. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  1357. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  1358. package/dist/modules/workspace/plugins-migration.js +0 -379
  1359. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  1360. package/dist/shared/fs-errors.d.ts +0 -8
  1361. package/dist/shared/fs-errors.d.ts.map +0 -1
  1362. package/dist/shared/fs-errors.js +0 -11
  1363. package/dist/shared/fs-errors.js.map +0 -1
  1364. package/dist/shared/fs-walk.d.ts +0 -23
  1365. package/dist/shared/fs-walk.d.ts.map +0 -1
  1366. package/dist/shared/fs-walk.js +0 -42
  1367. package/dist/shared/fs-walk.js.map +0 -1
  1368. package/dist/shared/hash-email.d.ts +0 -11
  1369. package/dist/shared/hash-email.d.ts.map +0 -1
  1370. package/dist/shared/hash-email.js +0 -14
  1371. package/dist/shared/hash-email.js.map +0 -1
  1372. package/dist/shared/kb-walk.d.ts +0 -52
  1373. package/dist/shared/kb-walk.d.ts.map +0 -1
  1374. package/dist/shared/kb-walk.js +0 -76
  1375. package/dist/shared/kb-walk.js.map +0 -1
  1376. package/src/modules/diff/diff-paths.ts +0 -9
  1377. package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
  1378. package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
  1379. package/src/modules/mcp/mcp-session-store.ts +0 -162
  1380. package/src/shared/__tests__/fs-walk.test.ts +0 -63
  1381. package/src/shared/__tests__/kb-walk.test.ts +0 -107
  1382. package/src/shared/fs-errors.ts +0 -10
  1383. package/src/shared/fs-walk.ts +0 -45
  1384. package/src/shared/hash-email.ts +0 -14
  1385. package/src/shared/kb-walk.ts +0 -95
@@ -1,19 +1,30 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { NodeFs } from '../../../../kb-fs/node-fs.js';
2
3
  import { execFile } from 'node:child_process';
3
4
  import { promisify } from 'node:util';
4
5
  import fs from 'node:fs/promises';
5
6
  import os from 'node:os';
6
7
  import path from 'node:path';
7
8
  import { KbStartupRunner } from '../../kb-startup-runner.js';
9
+ import { NodeGitRunner } from '../../../../workflow/git/node-git-runner.js';
8
10
  import type { OnServerStart, ServerStartContext, StepResult } from '../../on-server-start.js';
9
11
  import { GroupsToPluginsStep } from '../groups-to-plugins.step.js';
12
+ import { PluginDisplayNamesStep } from '../plugin-display-names.step.js';
10
13
  import { PluginManifestsStep } from '../plugin-manifests.step.js';
14
+ import { PersonalSpacesStep } from '../personal-spaces.step.js';
11
15
  import { RolesYamlStep } from '../roles-yaml.step.js';
12
16
  import { renderRolesYaml } from '../../../../access-model/render-roles-yaml.js';
17
+ import { mergeGroupsIntoRoles, parseRolesYaml } from '../../../../access-model/access-grammar.js';
13
18
  import { TemplateFilesStep } from '../template-files.step.js';
14
19
  import { buildSeedTree } from '../seed-tree.js';
15
20
  import { defaultKbTemplateDir } from '../../../../../assets.js';
16
- import { DEFAULT_KB_LAYOUT, configureKbLayout, renderKbLayoutPlaceholders } from '@bevel-software/platform-shared';
21
+ import {
22
+ DEFAULT_KB_LAYOUT,
23
+ agentsFilePointerSentence,
24
+ configureKbLayout,
25
+ renderKbLayoutPlaceholders,
26
+ } from '@bevel-software/platform-shared';
27
+ import { PLATFORM_HEADER, TOOL_PREFIX_LINE, composeAgentInstructions } from '../../../../agent-instructions/index.js';
17
28
 
18
29
  const execFileAsync = promisify(execFile);
19
30
 
@@ -61,7 +72,11 @@ afterEach(async () => {
61
72
  });
62
73
 
63
74
  /** A populated upstream carrying `files`: one commit, both protected refs. Returns the seed clone. */
64
- async function seedUpstream(files: Record<string, string>): Promise<string> {
75
+ async function seedUpstream(
76
+ files: Record<string, string>,
77
+ /** Symlinks to seed beside them: path in the repo → the target it points at. */
78
+ symlinks: Record<string, string> = {},
79
+ ): Promise<string> {
65
80
  const seed = path.join(root, '.seed');
66
81
  await fs.mkdir(seed, { recursive: true });
67
82
  await git(seed, ['init', '-b', DEFAULT_BRANCH]);
@@ -70,6 +85,11 @@ async function seedUpstream(files: Record<string, string>): Promise<string> {
70
85
  await fs.mkdir(path.dirname(abs), { recursive: true });
71
86
  await fs.writeFile(abs, content, 'utf8');
72
87
  }
88
+ for (const [rel, target] of Object.entries(symlinks)) {
89
+ const abs = path.join(seed, rel);
90
+ await fs.mkdir(path.dirname(abs), { recursive: true });
91
+ await fs.symlink(target, abs);
92
+ }
73
93
  await git(seed, ['add', '-A']);
74
94
  await git(seed, ['commit', '-m', 'init']);
75
95
  await git(seed, ['branch', PROTECTED[1]!]);
@@ -80,6 +100,7 @@ async function seedUpstream(files: Record<string, string>): Promise<string> {
80
100
 
81
101
  function makeRunner(steps: OnServerStart[], templateDir: string = TEMPLATE_DIR) {
82
102
  return new KbStartupRunner({
103
+ gitRunner: new NodeGitRunner(),
83
104
  kbRepoUrl: () => upstream,
84
105
  gitUsername: () => 'x-access-token',
85
106
  workspacesRoot,
@@ -110,7 +131,14 @@ async function exists(dir: string, rel: string): Promise<boolean> {
110
131
  return fs.access(path.join(dir, rel)).then(() => true, () => false);
111
132
  }
112
133
 
134
+ async function readJson(dir: string, rel: string): Promise<Record<string, any>> {
135
+ return JSON.parse(await fs.readFile(path.join(dir, rel), 'utf8'));
136
+ }
137
+
113
138
  const norm = (text: string) => text.replace(/\r\n?/g, '\n');
139
+ const PREAMBLE_IGNORE_BLOCK =
140
+ '\n# Added by the platform: agent instructions are edited from External agent access.\n' +
141
+ '/mcp-description.md\n';
114
142
 
115
143
  /** The template as the step writes it under the default layout — placeholders rendered. */
116
144
  async function template(name: string): Promise<string> {
@@ -124,6 +152,7 @@ async function fullScaffold(): Promise<Record<string, string>> {
124
152
  'AGENTS.md': await template('AGENTS.md'),
125
153
  '.bevelignore': await template('.bevelignore'),
126
154
  '.gitignore': await template('gitignore.template'),
155
+ 'mcp-description.md': await template('mcp-description.md'),
127
156
  'KnowledgeBase/.gitkeep': '',
128
157
  'Plugins/.gitkeep': '',
129
158
  'Skills/.gitkeep': '',
@@ -133,11 +162,11 @@ async function fullScaffold(): Promise<Record<string, string>> {
133
162
  describe('TemplateFilesStep', () => {
134
163
  it('adds the missing scaffolding — .gitignore arriving from its packable template spelling', async () => {
135
164
  await seedUpstream({ 'marker.txt': 'seeded' });
136
- await makeRunner([new TemplateFilesStep()]).runAll();
165
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
137
166
 
138
167
  for (const b of PROTECTED) {
139
168
  const dir = await checkout(b);
140
- for (const rel of ['access.md', 'AGENTS.md', '.bevelignore', '.gitignore']) {
169
+ for (const rel of ['access.md', 'AGENTS.md', '.bevelignore', '.gitignore', 'mcp-description.md']) {
141
170
  expect(await exists(dir, rel), `${b}: ${rel}`).toBe(true);
142
171
  }
143
172
  // The packaged template cannot ship a literal .gitignore (npm strips
@@ -161,7 +190,7 @@ describe('TemplateFilesStep', () => {
161
190
  // the folders it will find, and an ignore file hiding the real ones.
162
191
  configureKbLayout({ knowledgeBaseDir: 'docs', skillsDir: 'skills', pluginsDir: 'plugins' });
163
192
  await seedUpstream({ 'marker.txt': 'seeded' });
164
- await makeRunner([new TemplateFilesStep()]).runAll();
193
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
165
194
 
166
195
  const dir = await checkout(DEFAULT_BRANCH);
167
196
  const agents = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
@@ -170,21 +199,91 @@ describe('TemplateFilesStep', () => {
170
199
  expect(agents).not.toContain('{{');
171
200
  expect(agents).not.toContain('KnowledgeBase/');
172
201
  const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
173
- expect(ignore).toContain('plugins/');
174
- // The skills root stays VISIBLE: the Skills & Tools sidebar reads it from the workspace tree.
202
+ // Both roots stay VISIBLE, whatever they are called: the Skills & Tools
203
+ // sidebar reads each from the workspace tree.
204
+ expect(ignore).not.toContain('plugins/');
175
205
  expect(ignore).not.toContain('skills/');
176
206
  expect(ignore).not.toContain('Plugins/');
207
+ expect(ignore).toContain('roles.yaml');
177
208
  expect(await exists(dir, 'docs/.gitkeep')).toBe(true);
178
209
 
179
210
  // And a second boot sees the rendered guide as current: no churn commit.
180
- await makeRunner([new TemplateFilesStep()]).runAll();
211
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
181
212
  const again = await checkout(DEFAULT_BRANCH);
182
213
  expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + scaffolding
183
214
  });
184
215
 
216
+ /**
217
+ * The placement rule. A tester's agent filed a ticket into a plugin folder
218
+ * and explained itself: it had found no convention saying where tickets go,
219
+ * and Plugins was where it already held write rights. The guide named the
220
+ * three roots but never said which KIND of file belongs in which — so the
221
+ * section has to survive into the guide agents actually read, which is this
222
+ * written file (an MCP session reads the branch's copy of it), under the
223
+ * deployment's own root names rather than the placeholders.
224
+ */
225
+ it('writes the "Where a new file goes" placement rule, named for the deployment\'s roots', async () => {
226
+ configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'Abilities', pluginsDir: 'Extensions' });
227
+ await seedUpstream({ 'marker.txt': 'seeded' });
228
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
229
+
230
+ const dir = await checkout(DEFAULT_BRANCH);
231
+ const agents = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
232
+ const start = agents.indexOf('## Where a new file goes\n');
233
+ expect(start, 'the written guide has no placement section').toBeGreaterThan(-1);
234
+ // The section alone, so a phrase matched elsewhere in the guide cannot
235
+ // stand in for a rule that is missing here.
236
+ const section = agents.slice(start).split('\n## ')[0]!;
237
+
238
+ // A document goes to the knowledge root, a shared skill to the skills
239
+ // root or the plugin's own skills folder, machinery inside a plugin.
240
+ expect(section).toContain('`Docs/`');
241
+ expect(section).toContain('`Abilities/`');
242
+ expect(section).toContain('Extensions/<Plugin>/skills/<skill>/SKILL.md');
243
+ expect(section).toContain('Extensions/<Plugin>/software.bevel.hexis/tools/');
244
+ for (const kind of ['notes', 'reports', 'tickets', 'mcp.json', 'plugin.json']) {
245
+ expect(section, kind).toContain(kind);
246
+ }
247
+ // And the two rules the tester's agent broke.
248
+ expect(section).toMatch(/never holds a document/);
249
+ expect(section).toMatch(/\bask\b/);
250
+
251
+ // Rendered, not raw: a leftover placeholder would name a folder that is
252
+ // not there in a section whose whole job is naming the right folder.
253
+ expect(section).not.toContain('{{');
254
+ expect(section).not.toContain('KnowledgeBase/');
255
+ });
256
+
257
+ /**
258
+ * The step is built at boot, while the process still runs the defaults; the
259
+ * save that completes first-run setup applies the admin's names afterwards.
260
+ * A root list snapshotted at construction scaffolded `Skills/` beside the
261
+ * `skills/` they had just chosen — the very bug the names exist to avoid.
262
+ */
263
+ it('scaffolds the layout in effect when it RUNS, not the one it was built under', async () => {
264
+ const builtAtBoot = new TemplateFilesStep(new NodeFs());
265
+ await seedUpstream({ 'skills/deploy/SKILL.md': '# deploy\n' });
266
+ configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'skills', pluginsDir: 'Plugins' });
267
+ await makeRunner([builtAtBoot]).runAll();
268
+
269
+ const dir = await checkout(DEFAULT_BRANCH);
270
+ // Listed rather than probed: a probe for `Skills` answers yes on a
271
+ // case-insensitive filesystem that holds `skills`.
272
+ const roots = (await fs.readdir(dir, { withFileTypes: true }))
273
+ .filter((e) => e.isDirectory() && e.name !== '.git')
274
+ .map((e) => e.name)
275
+ .sort();
276
+ expect(roots).toEqual(['Docs', 'Plugins', 'skills']);
277
+ expect(await exists(dir, 'Docs/.gitkeep')).toBe(true);
278
+ const subject = (await git(dir, ['log', '--format=%s', '-1'])).trim();
279
+ expect(subject).toContain('Docs/.gitkeep');
280
+ expect(subject).not.toContain('KnowledgeBase/');
281
+ expect(subject).not.toContain('Skills/');
282
+ });
283
+
185
284
  it('replaces a drifted AGENTS.md, and says so when that is the only change', async () => {
186
285
  await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': 'stale conventions\n' });
187
- await makeRunner([new TemplateFilesStep()]).runAll();
286
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
188
287
 
189
288
  const dir = await checkout(DEFAULT_BRANCH);
190
289
  expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(norm(await template('AGENTS.md')));
@@ -192,13 +291,79 @@ describe('TemplateFilesStep', () => {
192
291
  expect(subject).toBe('Update AGENTS.md to the current platform template');
193
292
  });
194
293
 
195
- it('rejects .git — any case — as a reserved root name', async () => {
196
- for (const bad of ['.git', '.GIT', '.Git']) {
197
- expect(() => new TemplateFilesStep([bad]), bad).toThrow(/must not be "\.git"/);
294
+ it('ships the roles-vs-groups guide, and a deployment on an older template receives it at boot', async () => {
295
+ const guide = await template('AGENTS.md');
296
+ // The section runs from its own heading to the next heading of any level (or the end).
297
+ const section = guide.split(/\n(?=#{1,6} )/).find((s) => s.startsWith('### Roles are pre-set')) ?? '';
298
+ const prose = section.replace(/\s+/g, ' ');
299
+ // What roles are, that agents never create them, the group test, what to do instead.
300
+ expect(prose).toContain('A role in `roles.yaml` is an app role');
301
+ expect(prose).toContain('**Agents never create roles.**');
302
+ expect(prose).toContain('**Is it really a group?**');
303
+ expect(prose).toContain('**What to do instead.**');
304
+ expect(prose).toContain('add people to existing roles, and use a GROUP for a task- or team-scoped set of people');
305
+
306
+ // An existing deployment's AGENTS.md from before the guide existed.
307
+ await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': guide.replace(section, '') });
308
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
309
+
310
+ const dir = await checkout(DEFAULT_BRANCH);
311
+ expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toContain('**Agents never create roles.**');
312
+ });
313
+
314
+ it('documents giving a role to a group, with a valid example, in the guide the step writes', async () => {
315
+ const guide = await template('AGENTS.md');
316
+ const section = guide.split(/\n(?=#{2,3} )/).find((s) => s.startsWith('### Giving a role to a group')) ?? '';
317
+ const prose = section.replace(/\s+/g, ' ');
318
+ expect(prose).toContain('`- group:<Name>`');
319
+ expect(prose).toContain('case- and whitespace-insensitively against the active group source');
320
+ expect(prose).toContain('validation error');
321
+ expect(prose).toContain('names the entry');
322
+ expect(prose).toContain("removes the role's contribution for everyone in the group");
323
+ // A bare name resolves to a same-named group first, so role denials use `role/`.
324
+ expect(prose).toContain('`deny role/Reviewer`');
325
+ // Closeness first: a person's own entry wins only inside the same access.md.
326
+ expect(prose).toContain('The nearest `access.md` that says anything about the person decides');
327
+ expect(prose).toContain('in the SAME `access.md` as the denial keeps that access');
328
+ expect(prose).toContain('A group under `Admin` makes every member a full admin');
329
+ // A change request cannot carry a roles.yaml edit — the merge restores the
330
+ // base file — so the guide says who changes roles and where, and never
331
+ // walks an agent through drafting one.
332
+ expect(prose).toContain('Only an Admin changes `roles.yaml`');
333
+ expect(prose).toContain('A change request cannot carry the edit');
334
+ expect(prose).toContain('Do not propose one');
335
+ expect(prose).toContain('`edit_file`');
336
+ for (const tool of ['create_branch', 'commit_change', 'open_change_request']) {
337
+ expect(prose).not.toContain(`\`${tool}\``);
198
338
  }
339
+
340
+ // The example parses as a roles.yaml whose group entry names a real group.
341
+ const example = /```yaml\n([\s\S]*?)```/.exec(section)?.[1] ?? '';
342
+ expect(example).toContain('- group:Platform Team');
343
+ const parsed = parseRolesYaml(example);
344
+ expect(parsed.ok).toBe(true);
345
+ if (!parsed.ok) return;
346
+ const groups = new Map([['platform team', { displayName: 'Platform Team', emails: new Set(['pat@example.com']) }]]);
347
+ expect(mergeGroupsIntoRoles(parsed.index, groups, 'groups.yaml')).toEqual([]);
348
+ // The example hands out an ordinary role, never Admin.
349
+ expect(parsed.index.byEmail.get('pat@example.com')?.has('reviewer')).toBe(true);
350
+ expect(parsed.index.byEmail.get('pat@example.com')?.has('role/admin')).toBe(false);
351
+
352
+ // What the step writes — the file agents read through the MCP server — carries it.
353
+ await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': guide.replace(section, '') });
354
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
355
+ const written = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'));
356
+ expect(written).toContain('### Giving a role to a group');
357
+ expect(written).toContain(example);
199
358
  });
200
359
 
201
- it('appends the AGENTS.md rule to a custom template\'s .bevelignore that lacks it, at declaration time', async () => {
360
+ it('rejects the git folder — in any spelling — as a reserved root name', async () => {
361
+ for (const bad of ['.git', '.GIT', '.Git', '.git.', '%2egit']) {
362
+ expect(() => new TemplateFilesStep(new NodeFs(), [bad]), bad).toThrow(/must not name the git folder/);
363
+ }
364
+ });
365
+
366
+ it('appends the managed root-file rules to a custom template that lacks them', async () => {
202
367
  // A distribution's own template whose ignore file does not carry the rule
203
368
  // the on-disk merge assumes: the merge only runs against an EXISTING file,
204
369
  // so the declared content itself must arrive with the rule in it.
@@ -210,12 +375,13 @@ describe('TemplateFilesStep', () => {
210
375
  delete scaffold['.bevelignore']; // the one file the step will declare from the template
211
376
  await seedUpstream(scaffold);
212
377
 
213
- await makeRunner([new TemplateFilesStep()], customTemplate).runAll();
378
+ await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
214
379
 
215
380
  const dir = await checkout(DEFAULT_BRANCH);
216
381
  const lines = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
217
382
  expect(lines).toContain('MyStuff/'); // the operator's rules survive
218
383
  expect(lines).toContain('AGENTS.md'); // the platform's rule was appended
384
+ expect(lines).toContain('/mcp-description.md');
219
385
  expect(lines).not.toContain('Skills/'); // never the skills root — the Library's tree needs it
220
386
  });
221
387
 
@@ -229,13 +395,14 @@ describe('TemplateFilesStep', () => {
229
395
  '# mine\n.git/\nAGENTS.md\nPlugins/\n\n# Added by the platform: the conventions doc is not node content.\nSkills/\n';
230
396
  await seedUpstream(scaffold);
231
397
 
232
- await makeRunner([new TemplateFilesStep()]).runAll();
398
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
233
399
 
234
400
  const dir = await checkout(DEFAULT_BRANCH);
235
401
  const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
236
- expect(text).toBe('# mine\n.git/\nAGENTS.md\nPlugins/\n');
402
+ // The plugins-root rule goes with it — the same sidebar draws that root now.
403
+ expect(text).toBe('# mine\n.git/\nAGENTS.md\n' + PREAMBLE_IGNORE_BLOCK);
237
404
  // Idempotent: a second boot has nothing to change.
238
- await makeRunner([new TemplateFilesStep()]).runAll();
405
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
239
406
  const again = await checkout(DEFAULT_BRANCH);
240
407
  expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(text);
241
408
  });
@@ -249,10 +416,12 @@ describe('TemplateFilesStep', () => {
249
416
  'AGENTS.md\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like Groups/.\nSkills/\n';
250
417
  await seedUpstream(scaffold);
251
418
 
252
- await makeRunner([new TemplateFilesStep()]).runAll();
419
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
253
420
 
254
421
  const dir = await checkout(DEFAULT_BRANCH);
255
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('AGENTS.md\nPlugins/\n');
422
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
423
+ 'AGENTS.md\n' + PREAMBLE_IGNORE_BLOCK,
424
+ );
256
425
  });
257
426
 
258
427
  it('recognises the legacy comment for a renamed plugins root with a space in its name', async () => {
@@ -264,10 +433,12 @@ describe('TemplateFilesStep', () => {
264
433
  'AGENTS.md\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like My Plugins/.\nSkills/\n';
265
434
  await seedUpstream(scaffold);
266
435
 
267
- await makeRunner([new TemplateFilesStep()]).runAll();
436
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
268
437
 
269
438
  const dir = await checkout(DEFAULT_BRANCH);
270
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('AGENTS.md\nPlugins/\n');
439
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
440
+ 'AGENTS.md\n' + PREAMBLE_IGNORE_BLOCK,
441
+ );
271
442
  });
272
443
 
273
444
  it("keeps an operator's Skills/ rule whose own comment merely opens like the platform's", async () => {
@@ -275,29 +446,88 @@ describe('TemplateFilesStep', () => {
275
446
  // same way and goes on differently was never written by the platform.
276
447
  const scaffold = await fullScaffold();
277
448
  const text =
278
- 'AGENTS.md\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, and I hide it anyway\nSkills/\n';
449
+ 'AGENTS.md\n# The shared-skills root is rendered by the Skills & Tools app, and I hide it anyway\nSkills/\n';
279
450
  scaffold['.bevelignore'] = text;
280
451
  await seedUpstream(scaffold);
281
452
 
282
- await makeRunner([new TemplateFilesStep()]).runAll();
453
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
283
454
 
284
455
  const dir = await checkout(DEFAULT_BRANCH);
285
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(text);
456
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
457
+ text + PREAMBLE_IGNORE_BLOCK,
458
+ );
286
459
  });
287
460
 
288
461
  it("keeps a Skills/ rule the operator wrote themselves — provenance is the platform's comment", async () => {
289
462
  const scaffold = await fullScaffold();
290
- scaffold['.bevelignore'] = 'AGENTS.md\nPlugins/\n# I hide skills on purpose\nSkills/\n';
463
+ scaffold['.bevelignore'] = 'AGENTS.md\n# I hide skills on purpose\nSkills/\n';
291
464
  await seedUpstream(scaffold);
292
465
 
293
- await makeRunner([new TemplateFilesStep()]).runAll();
466
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
294
467
 
295
468
  const dir = await checkout(DEFAULT_BRANCH);
296
469
  expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
297
- 'AGENTS.md\nPlugins/\n# I hide skills on purpose\nSkills/\n',
470
+ 'AGENTS.md\n# I hide skills on purpose\nSkills/\n' + PREAMBLE_IGNORE_BLOCK,
298
471
  );
299
472
  });
300
473
 
474
+ it('drops EVERY Plugins/ rule, whoever wrote it — the sidebar draws that root now, and the seed left no comment to know it by', async () => {
475
+ // The first template ever seeded hid the plugins root as a bare line at
476
+ // the end of the file, so provenance cannot tell the platform's copy
477
+ // from an operator's; both would empty the Plugins tree, and both go.
478
+ // One under the platform's own comment loses the comment with it; a
479
+ // `!Plugins/` negation is not the rule and stays.
480
+ const scaffold = await fullScaffold();
481
+ scaffold['.bevelignore'] =
482
+ '# mine\nAGENTS.md\nPlugins/\nMy-Own-Rule/\n# Added by the platform: the conventions doc is not node content.\nPlugins/\n!Plugins/\n';
483
+ await seedUpstream(scaffold);
484
+
485
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
486
+
487
+ const dir = await checkout(DEFAULT_BRANCH);
488
+ const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
489
+ expect(text).toBe('# mine\nAGENTS.md\nMy-Own-Rule/\n!Plugins/\n' + PREAMBLE_IGNORE_BLOCK);
490
+ // Idempotent: a second boot has nothing to change.
491
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
492
+ const again = await checkout(DEFAULT_BRANCH);
493
+ expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(text);
494
+ });
495
+
496
+ it("declares a custom template's ignore file without the Plugins/ rule it still ships", async () => {
497
+ // A distribution's own template may still carry the rule the packaged
498
+ // one dropped; a KB seeded from it must not start out hiding the root.
499
+ const customTemplate = path.join(root, 'custom-template-plugins-rule');
500
+ await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
501
+ await fs.writeFile(path.join(customTemplate, '.bevelignore'), '.git/\nAGENTS.md\n\nPlugins/\n');
502
+ await seedUpstream({ 'marker.txt': 'seeded' });
503
+
504
+ await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
505
+
506
+ const dir = await checkout(DEFAULT_BRANCH);
507
+ const lines = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
508
+ expect(lines).not.toContain('Plugins/');
509
+ expect(lines).toContain('AGENTS.md');
510
+ });
511
+
512
+ it('never seeds a template entry that links into a git folder', async () => {
513
+ // A template that IS a working tree (this repo in a Docker build) can hold
514
+ // a link into its own .git. The name rule cannot see it; the target can.
515
+ const customTemplate = path.join(root, 'custom-template-gitlink');
516
+ await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
517
+ await fs.mkdir(path.join(customTemplate, '.git'), { recursive: true });
518
+ await fs.writeFile(path.join(customTemplate, '.git', 'config'), '[credential]\n\thelper = store\n');
519
+ await fs.symlink(path.join('.git', 'config'), path.join(customTemplate, 'notes.md'));
520
+
521
+ const dir = path.join(root, 'seeded-gitlink');
522
+ await fs.mkdir(dir, { recursive: true });
523
+ await buildSeedTree(new NodeFs(), customTemplate, [], ['admin@example.com'])(dir);
524
+
525
+ await expect(fs.readFile(path.join(dir, 'notes.md'), 'utf8')).rejects.toMatchObject({ code: 'ENOENT' });
526
+ await expect(fs.readdir(path.join(dir, '.git'))).rejects.toMatchObject({ code: 'ENOENT' });
527
+ // The rest of the template still seeded.
528
+ expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8')).toContain('#');
529
+ });
530
+
301
531
  it('seeds a binary template file byte for byte and keeps a script executable — text is what decodes', async () => {
302
532
  const customTemplate = path.join(root, 'custom-template-bytes');
303
533
  // The packaged template as a base, plus two files it does not ship.
@@ -312,7 +542,7 @@ describe('TemplateFilesStep', () => {
312
542
  // The empty-remote seed is the one path that copies a whole template.
313
543
  const dir = path.join(root, 'seeded-bytes');
314
544
  await fs.mkdir(dir, { recursive: true });
315
- await buildSeedTree(customTemplate, [], ['admin@example.com'])(dir);
545
+ await buildSeedTree(new NodeFs(),customTemplate, [], ['admin@example.com'])(dir);
316
546
 
317
547
  expect(await fs.readFile(path.join(dir, 'assets', '.logo.bin'))).toEqual(binary);
318
548
  expect(norm(await fs.readFile(path.join(dir, 'scripts', 'run.sh'), 'utf8'))).toBe('#!/bin/sh\necho Skills\n');
@@ -338,7 +568,7 @@ describe('TemplateFilesStep', () => {
338
568
  delete scaffold['.bevelignore'];
339
569
  await seedUpstream(scaffold);
340
570
 
341
- await makeRunner([new TemplateFilesStep()], customTemplate).runAll();
571
+ await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
342
572
 
343
573
  const dir = await checkout(DEFAULT_BRANCH);
344
574
  const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
@@ -349,45 +579,485 @@ describe('TemplateFilesStep', () => {
349
579
 
350
580
  it("leaves an operator's !Skills/ negation alone — there is nothing of the platform's to remove", async () => {
351
581
  const scaffold = await fullScaffold();
352
- scaffold['.bevelignore'] = 'AGENTS.md\nPlugins/\n!Skills/\n';
582
+ scaffold['.bevelignore'] = 'AGENTS.md\n!Skills/\n';
353
583
  await seedUpstream(scaffold);
354
584
 
355
- await makeRunner([new TemplateFilesStep()]).runAll();
585
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
356
586
 
357
587
  const dir = await checkout(DEFAULT_BRANCH);
358
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('AGENTS.md\nPlugins/\n!Skills/\n');
588
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
589
+ 'AGENTS.md\n!Skills/\n' + PREAMBLE_IGNORE_BLOCK,
590
+ );
359
591
  });
360
592
 
361
593
  it('respects an explicit !AGENTS.md negation — hiding the doc is a default, not a mandate', async () => {
362
594
  // Appending the positive rule after the negation would WIN under ordered
363
595
  // matching and silently defeat the operator's stated choice to show it.
364
596
  const scaffold = await fullScaffold();
365
- scaffold['.bevelignore'] = '# operator wants the doc visible\n!AGENTS.md\nPlugins/\n';
597
+ scaffold['.bevelignore'] = '# operator wants the doc visible\n!AGENTS.md\nMyStuff/\n';
598
+ await seedUpstream(scaffold);
599
+
600
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
601
+
602
+ const dir = await checkout(DEFAULT_BRANCH);
603
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
604
+ '# operator wants the doc visible\n!AGENTS.md\nMyStuff/\n' + PREAMBLE_IGNORE_BLOCK,
605
+ );
606
+ });
607
+
608
+ it('respects an explicit !mcp-description.md negation', async () => {
609
+ const scaffold = await fullScaffold();
610
+ scaffold['.bevelignore'] = 'AGENTS.md\n!mcp-description.md\n';
611
+ await seedUpstream(scaffold);
612
+
613
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
614
+
615
+ const dir = await checkout(DEFAULT_BRANCH);
616
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
617
+ 'AGENTS.md\n!mcp-description.md\n',
618
+ );
619
+ });
620
+
621
+ it("respells the platform's OWN unanchored preamble rule instead of leaving it standing", async () => {
622
+ // What the release that shipped the unanchored spelling wrote. The bare
623
+ // name hides a nested `mcp-description.md` page too, so the platform's own
624
+ // line is corrected rather than treated as somebody's choice.
625
+ const scaffold = await fullScaffold();
626
+ scaffold['.bevelignore'] =
627
+ 'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\nmcp-description.md\n';
628
+ await seedUpstream(scaffold);
629
+
630
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
631
+
632
+ const dir = await checkout(DEFAULT_BRANCH);
633
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
634
+ 'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\n/mcp-description.md\n',
635
+ );
636
+ // Idempotent: a second boot has nothing to change.
637
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
638
+ const again = await checkout(DEFAULT_BRANCH);
639
+ expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(
640
+ 'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\n/mcp-description.md\n',
641
+ );
642
+ });
643
+
644
+ it("keeps an unanchored preamble rule the operator wrote themselves, and adds nothing beside it", async () => {
645
+ const scaffold = await fullScaffold();
646
+ scaffold['.bevelignore'] = 'AGENTS.md\n# I hide it everywhere on purpose\nmcp-description.md\n';
366
647
  await seedUpstream(scaffold);
367
648
 
368
- await makeRunner([new TemplateFilesStep()]).runAll();
649
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
369
650
 
370
651
  const dir = await checkout(DEFAULT_BRANCH);
371
652
  expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
372
- '# operator wants the doc visible\n!AGENTS.md\nPlugins/\n',
653
+ 'AGENTS.md\n# I hide it everywhere on purpose\nmcp-description.md\n',
373
654
  );
374
655
  });
375
656
 
657
+ it('seeds mcp-description.md on every protected branch, from the template, when it is missing', async () => {
658
+ const scaffold = await fullScaffold();
659
+ delete scaffold['mcp-description.md'];
660
+ await seedUpstream(scaffold);
661
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
662
+
663
+ for (const b of PROTECTED) {
664
+ const dir = await checkout(b);
665
+ expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(
666
+ norm(await template('mcp-description.md')),
667
+ );
668
+ const subject = (await git(dir, ['log', '--format=%s', '-1'])).trim();
669
+ expect(subject).toBe('Add missing KB scaffolding: mcp-description.md');
670
+ }
671
+ });
672
+
673
+ it('leaves an existing mcp-description.md untouched: seeded once, never refreshed', async () => {
674
+ const mine = 'Acme builds solar farms.\n\n## What is where\n\n- Projects/\n';
675
+ await seedUpstream({ ...(await fullScaffold()), 'mcp-description.md': mine });
676
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
677
+
678
+ const dir = await checkout(DEFAULT_BRANCH);
679
+ expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(mine);
680
+ expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1'); // init only
681
+ });
682
+
683
+ it('keeps a deliberately emptied mcp-description.md empty: the top-up restores only a MISSING file', async () => {
684
+ await seedUpstream({ ...(await fullScaffold()), 'mcp-description.md': '' });
685
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
686
+
687
+ const dir = await checkout(DEFAULT_BRANCH);
688
+ expect(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8')).toBe('');
689
+ expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1');
690
+ });
691
+
692
+ it('ships a template that composes to the platform header alone: one comment, nothing broadcast', async () => {
693
+ const shipped = await template('mcp-description.md');
694
+ expect(shipped.trimStart().startsWith('<!--')).toBe(true);
695
+ expect(shipped.trimEnd().endsWith('-->')).toBe(true);
696
+ // The first line says how to turn the text on; the caps are stated inside.
697
+ expect(shipped.split('\n')[0]).toMatch(/Remove this comment wrapper/);
698
+ expect(shipped).toContain('6,000 characters');
699
+ expect(shipped).toContain('first paragraph under about 220');
700
+ expect(shipped).not.toContain('{{');
701
+ const composed = composeAgentInstructions(shipped);
702
+ expect(composed.instructions).toBe(PLATFORM_HEADER);
703
+ expect(composed.toolPrefix).toBe(TOOL_PREFIX_LINE);
704
+ expect(composed.unterminatedComment).toBe(false);
705
+ expect(composed.preambleChars).toBe(0);
706
+ });
707
+
708
+ it('seeds mcp-description.md from the packaged template when a custom template predates it, instead of failing the boot', async () => {
709
+ // A distribution's own KB_TEMPLATE_DIR forked before this file existed:
710
+ // the upgrade must not stop startup over a file whose content is one
711
+ // comment. The packaged copy stands in, and the log says so once.
712
+ const customTemplate = path.join(root, 'custom-template-old');
713
+ await fs.mkdir(customTemplate, { recursive: true });
714
+ await fs.writeFile(path.join(customTemplate, 'AGENTS.md'), await template('AGENTS.md'), 'utf8');
715
+ await fs.writeFile(path.join(customTemplate, 'access.md'), await template('access.md'), 'utf8');
716
+ const scaffold = await fullScaffold();
717
+ delete scaffold['mcp-description.md'];
718
+ await seedUpstream(scaffold);
719
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
720
+
721
+ await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
722
+
723
+ const dir = await checkout(DEFAULT_BRANCH);
724
+ expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(
725
+ norm(await template('mcp-description.md')),
726
+ );
727
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('no "mcp-description.md"'));
728
+ });
729
+
730
+ it('gives the stand-in to that file only: a custom template missing access.md still fails loudly', async () => {
731
+ const stricter = path.join(root, 'custom-template-broken');
732
+ await fs.mkdir(stricter, { recursive: true });
733
+ await fs.writeFile(path.join(stricter, 'AGENTS.md'), await template('AGENTS.md'), 'utf8');
734
+ const scaffold = await fullScaffold();
735
+ delete scaffold['access.md'];
736
+ await seedUpstream(scaffold);
737
+ await expect(makeRunner([new TemplateFilesStep(new NodeFs())], stricter).runAll()).rejects.toThrow(/ENOENT/);
738
+ });
739
+
740
+ it('points a clone at mcp-description.md from the managed AGENTS.md, before the platform mechanics', async () => {
741
+ const agents = await template('AGENTS.md');
742
+ const pointer = agents.indexOf('mcp-description.md');
743
+ expect(pointer).toBeGreaterThan(-1);
744
+ expect(pointer).toBeLessThan(agents.indexOf('## Directory Structure'));
745
+ expect(agents).toMatch(/default branch's copy inline/);
746
+ });
747
+
376
748
  it('treats a CRLF checkout of identical AGENTS.md content as current — no churn commit', async () => {
377
749
  const scaffold = await fullScaffold();
378
750
  scaffold['AGENTS.md'] = norm(scaffold['AGENTS.md']!).replace(/\n/g, '\r\n');
379
751
  await seedUpstream(scaffold);
380
- await makeRunner([new TemplateFilesStep()]).runAll();
752
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
381
753
 
382
754
  const dir = await checkout(DEFAULT_BRANCH);
383
755
  expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1'); // init only
384
756
  });
757
+
758
+ /**
759
+ * The guide under a name of the deployment's own — and what becomes of the
760
+ * `AGENTS.md` beside it, which on such a deployment is the CUSTOMER'S file.
761
+ */
762
+ describe('a renamed agent guide', () => {
763
+ /** The scaffold minus the guide, so each test says for itself what `AGENTS.md` holds. */
764
+ async function scaffoldWithoutGuide(): Promise<Record<string, string>> {
765
+ const scaffold = await fullScaffold();
766
+ delete scaffold['AGENTS.md'];
767
+ return scaffold;
768
+ }
769
+
770
+ const CUSTOMER_GUIDE = '# Acme conventions\n\nWrite tickets in the present tense.\n';
771
+
772
+ it('writes and refreshes the guide under the configured name, on every protected branch', async () => {
773
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
774
+ await seedUpstream({ 'marker.txt': 'seeded' });
775
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
776
+
777
+ for (const branch of PROTECTED) {
778
+ const dir = await checkout(branch);
779
+ const guide = norm(await fs.readFile(path.join(dir, 'HEXIS.md'), 'utf8'));
780
+ // The header names the file it lives in, not the name it ships under.
781
+ expect(guide).toContain('`HEXIS.md`');
782
+ expect(guide).not.toContain('{{');
783
+ expect(await exists(dir, 'AGENTS.md')).toBe(false);
784
+ }
785
+
786
+ // A second boot sees the rendered guide as current: no churn commit…
787
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
788
+ expect((await git(await checkout(DEFAULT_BRANCH), ['rev-list', '--count', 'HEAD'])).trim()).toBe('2');
789
+
790
+ // …and a drifted copy is replaced, exactly as `AGENTS.md` is by default.
791
+ const dir = await checkout(DEFAULT_BRANCH);
792
+ await fs.writeFile(path.join(dir, 'HEXIS.md'), 'stale conventions\n', 'utf8');
793
+ await git(dir, ['commit', '-am', 'drift']);
794
+ await git(dir, ['push', 'origin', DEFAULT_BRANCH]);
795
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
796
+ const after = await checkout(DEFAULT_BRANCH);
797
+ expect(norm(await fs.readFile(path.join(after, 'HEXIS.md'), 'utf8'))).toContain('# Knowledge base');
798
+ expect((await git(after, ['log', '--format=%s', '-1'])).trim()).toBe(
799
+ 'Update HEXIS.md to the current platform template',
800
+ );
801
+ });
802
+
803
+ it('leaves a customer AGENTS.md byte for byte alone, and shows it in the tree', async () => {
804
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
805
+ // Their file already names the guide, so the pointer has nothing to add
806
+ // — this test is about the bytes and the ignore rules alone.
807
+ const theirs = `${CUSTOMER_GUIDE}\nSee HEXIS.md for the platform.\n`;
808
+ await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': theirs });
809
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
810
+
811
+ const dir = await checkout(DEFAULT_BRANCH);
812
+ expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(theirs);
813
+ expect(await exists(dir, 'HEXIS.md')).toBe(true);
814
+ const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))
815
+ .split('\n')
816
+ .map((l) => l.trim());
817
+ // The configured file is hidden; the customer's shows.
818
+ expect(ignore).toContain('HEXIS.md');
819
+ expect(ignore).not.toContain('AGENTS.md');
820
+ });
821
+
822
+ it('removes the platform-written AGENTS.md — only its own header proves it ours', async () => {
823
+ // A knowledge base the platform seeded, whose admin then renames the guide.
824
+ await seedUpstream({ 'marker.txt': 'seeded' });
825
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
826
+ expect(await exists(await checkout(DEFAULT_BRANCH), 'AGENTS.md')).toBe(true);
827
+
828
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
829
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
830
+
831
+ // On EVERY protected branch: the step tops up each one, and a rename
832
+ // that only reached the default branch would leave the others carrying a
833
+ // stale platform guide under the customer's name.
834
+ for (const branch of PROTECTED) {
835
+ const dir = await checkout(branch);
836
+ expect(await exists(dir, 'AGENTS.md'), branch).toBe(false);
837
+ expect(await exists(dir, 'HEXIS.md'), branch).toBe(true);
838
+ const message = await git(dir, ['log', '--format=%B', '-1']);
839
+ expect(message, branch).toContain(
840
+ 'Remove the platform-written AGENTS.md — the agent guide is now HEXIS.md',
841
+ );
842
+ }
843
+ });
844
+
845
+ it('keeps an AGENTS.md it cannot prove it wrote, and says so', async () => {
846
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
847
+ // Edited by the customer: the managed header is gone, so it is theirs.
848
+ const edited = `${CUSTOMER_GUIDE}\nMentions HEXIS.md already.\n`;
849
+ await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': edited });
850
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
851
+
852
+ for (const branch of PROTECTED) {
853
+ const dir = await checkout(branch);
854
+ expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8')), branch).toBe(edited);
855
+ expect(await git(dir, ['log', '--format=%B', '-1']), branch).toContain('Keep AGENTS.md');
856
+ }
857
+ });
858
+
859
+ it('leaves a root AGENTS.md that is a symbolic link exactly where it points', async () => {
860
+ // Following the link would read the managed header at the other end and
861
+ // delete the customer's entry — or, with the pointer on, write our
862
+ // sentence into a file nobody asked us to edit.
863
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
864
+ await seedUpstream({ ...(await scaffoldWithoutGuide()), 'conventions/ours.md': await template('AGENTS.md') });
865
+ // Committed as a link, so every checkout the step makes carries one.
866
+ const seeding = await checkout(DEFAULT_BRANCH);
867
+ await fs.symlink('conventions/ours.md', path.join(seeding, 'AGENTS.md'));
868
+ await git(seeding, ['add', '-A']);
869
+ await git(seeding, ['commit', '-m', 'link AGENTS.md at our own file']);
870
+ await git(seeding, ['push', 'origin', DEFAULT_BRANCH]);
871
+
872
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
873
+
874
+ const dir = await checkout(DEFAULT_BRANCH);
875
+ expect((await fs.lstat(path.join(dir, 'AGENTS.md'))).isSymbolicLink()).toBe(true);
876
+ expect(await fs.readlink(path.join(dir, 'AGENTS.md'))).toBe('conventions/ours.md');
877
+ // The file at the other end is untouched — neither removed nor appended to.
878
+ expect(norm(await fs.readFile(path.join(dir, 'conventions/ours.md'), 'utf8'))).toBe(
879
+ norm(await template('AGENTS.md')),
880
+ );
881
+ });
882
+
883
+ it("keeps an operator's own AGENTS.md ignore rule while dropping the platform's", async () => {
884
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
885
+ const scaffold = await scaffoldWithoutGuide();
886
+ // Their own rule, written by hand, nowhere near the template's slot.
887
+ scaffold['.bevelignore'] = '# mine\nAGENTS.md\nMyStuff/\n';
888
+ scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
889
+ await seedUpstream(scaffold);
890
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
891
+
892
+ const text = norm(
893
+ await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
894
+ );
895
+ expect(text).toContain('# mine\nAGENTS.md\nMyStuff/');
896
+ expect(text.split('\n').map((l) => l.trim())).toContain('HEXIS.md');
897
+ });
898
+
899
+ it("keeps an operator's rule that merely happens to sit under .gitattributes", async () => {
900
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
901
+ const scaffold = await scaffoldWithoutGuide();
902
+ // Hand-written, and by coincidence in the position the template's own
903
+ // rule used to occupy. One matching line above is not provenance — the
904
+ // block the template wrote is not there, so the rule is theirs.
905
+ scaffold['.bevelignore'] = '# ours\n.gitattributes\nAGENTS.md\nMyStuff/\n';
906
+ scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
907
+ await seedUpstream(scaffold);
908
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
909
+
910
+ const text = norm(
911
+ await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
912
+ );
913
+ expect(text).toContain('# ours\n.gitattributes\nAGENTS.md\nMyStuff/');
914
+ expect(text.split('\n').map((l) => l.trim())).toContain('HEXIS.md');
915
+ });
916
+
917
+ it("drops the bare rule an earlier release seeded in the template's hygiene block", async () => {
918
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
919
+ const scaffold = await scaffoldWithoutGuide();
920
+ // The `.bevelignore` of a knowledge base seeded before the rule had a
921
+ // comment above it: bare, at the end of the template's own block.
922
+ scaffold['.bevelignore'] =
923
+ '# Repo hygiene files that clutter the tree without being node content.\n' +
924
+ '.gitignore\n.gitattributes\nAGENTS.md\nroles.yaml\n';
925
+ scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
926
+ await seedUpstream(scaffold);
927
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
928
+
929
+ const lines = norm(
930
+ await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
931
+ )
932
+ .split('\n')
933
+ .map((l) => l.trim());
934
+ // The platform's line goes; the block above it is content and stays.
935
+ expect(lines).not.toContain('AGENTS.md');
936
+ expect(lines).toContain('.gitattributes');
937
+ expect(lines).toContain('.gitignore');
938
+ expect(lines).toContain('HEXIS.md');
939
+ });
940
+
941
+ describe('the pointer in the customer\'s own AGENTS.md', () => {
942
+ /** Boot once with the guide renamed, against a root `AGENTS.md` holding `theirs`. */
943
+ async function bootWith(theirs: string | null, link = true): Promise<string> {
944
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
945
+ const scaffold = await scaffoldWithoutGuide();
946
+ if (theirs !== null) scaffold['AGENTS.md'] = theirs;
947
+ await seedUpstream(scaffold);
948
+ await makeRunner([new TemplateFilesStep(new NodeFs(), [], () => link)]).runAll();
949
+ return checkout(DEFAULT_BRANCH);
950
+ }
951
+
952
+ const SENTENCE = agentsFilePointerSentence('HEXIS.md');
953
+
954
+ it('appends the sentence when the guide is not mentioned, and says so', async () => {
955
+ const dir = await bootWith(CUSTOMER_GUIDE);
956
+ const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
957
+ // Their bytes, then a blank line, then exactly the shared sentence.
958
+ expect(text).toBe(`${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${SENTENCE}\n`);
959
+ expect(await git(dir, ['log', '--format=%B', '-1'])).toContain(
960
+ 'Add a pointer to HEXIS.md at the end of AGENTS.md',
961
+ );
962
+ });
963
+
964
+ /**
965
+ * The sentence spells a punctuated name twice and neither spelling is
966
+ * the raw one — the label is escaped, the destination percent-encoded.
967
+ * A second boot must still see the guide as mentioned, or it appends
968
+ * another copy, and the boot after that a third.
969
+ */
970
+ it('appends once and only once, even when the name is nothing but punctuation', async () => {
971
+ const guide = 'Our [Agent] Guide (v2).md';
972
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: guide });
973
+ await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': CUSTOMER_GUIDE });
974
+ const boot = () => makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
975
+
976
+ await boot();
977
+ const once = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'));
978
+ expect(once).toBe(
979
+ `${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${agentsFilePointerSentence(guide)}\n`,
980
+ );
981
+
982
+ const head = (await git(await checkout(DEFAULT_BRANCH), ['rev-parse', 'HEAD'])).trim();
983
+ await boot();
984
+ const after = await checkout(DEFAULT_BRANCH);
985
+ expect(norm(await fs.readFile(path.join(after, 'AGENTS.md'), 'utf8'))).toBe(once);
986
+ // And the second boot committed nothing at all.
987
+ expect((await git(after, ['rev-parse', 'HEAD'])).trim()).toBe(head);
988
+ });
989
+
990
+ /**
991
+ * The guide's name is a setting, and a setting can be changed twice. The
992
+ * sentence the last rename wrote points at a file that is no longer
993
+ * there, and the new name is nowhere in the text — so "is it mentioned?"
994
+ * says no and an unaimed sentence would be joined by a second one.
995
+ */
996
+ it('aims the sentence it already wrote at the new guide, rather than adding another', async () => {
997
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
998
+ await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': CUSTOMER_GUIDE });
999
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
1000
+ expect(norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'))).toContain(
1001
+ agentsFilePointerSentence('HEXIS.md'),
1002
+ );
1003
+
1004
+ // The admin renames the guide a second time.
1005
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'GUIDE.md' });
1006
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
1007
+
1008
+ const dir = await checkout(DEFAULT_BRANCH);
1009
+ const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
1010
+ // Their bytes, then ONE sentence, pointing at the guide that exists.
1011
+ expect(text).toBe(
1012
+ `${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${agentsFilePointerSentence('GUIDE.md')}\n`,
1013
+ );
1014
+ expect(text).not.toContain('HEXIS.md');
1015
+ expect(await git(dir, ['log', '--format=%B', '-1'])).toContain(
1016
+ 'Point the sentence in AGENTS.md at GUIDE.md',
1017
+ );
1018
+ });
1019
+
1020
+ it('writes nothing when the name appears anywhere in the text, in any wording', async () => {
1021
+ const theirs = `${CUSTOMER_GUIDE}\nThe platform keeps its own notes in HEXIS.md; read those too.\n`;
1022
+ const dir = await bootWith(theirs);
1023
+ expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(theirs);
1024
+ });
1025
+
1026
+ it('writes nothing while the setting is off', async () => {
1027
+ const dir = await bootWith(CUSTOMER_GUIDE, false);
1028
+ expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(CUSTOMER_GUIDE);
1029
+ });
1030
+
1031
+ it('never creates an AGENTS.md for the pointer', async () => {
1032
+ const dir = await bootWith(null);
1033
+ expect(await exists(dir, 'AGENTS.md')).toBe(false);
1034
+ expect(await exists(dir, 'HEXIS.md')).toBe(true);
1035
+ });
1036
+
1037
+ it('never appends to a file the platform wrote — it removes that one instead', async () => {
1038
+ const dir = await bootWith(await template('AGENTS.md'));
1039
+ expect(await exists(dir, 'AGENTS.md')).toBe(false);
1040
+ });
1041
+
1042
+ it('does nothing at all under the default name', async () => {
1043
+ const theirs = `${CUSTOMER_GUIDE}`;
1044
+ // The default guide IS AGENTS.md, so the platform owns that file and
1045
+ // the pointer question never arises: the drifted copy is replaced.
1046
+ await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': theirs });
1047
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
1048
+ const dir = await checkout(DEFAULT_BRANCH);
1049
+ const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
1050
+ expect(text).toBe(norm(await template('AGENTS.md')));
1051
+ expect(text).not.toContain(agentsFilePointerSentence('AGENTS.md'));
1052
+ });
1053
+ });
1054
+ });
385
1055
  });
386
1056
 
387
1057
  describe('RolesYamlStep', () => {
388
1058
  it('generates roles.yaml with the configured admins on every protected branch', async () => {
389
1059
  await seedUpstream({ 'marker.txt': 'seeded' });
390
- await makeRunner([new RolesYamlStep(['admin@example.com'])]).runAll();
1060
+ await makeRunner([new RolesYamlStep(new NodeFs(), ['admin@example.com'])]).runAll();
391
1061
 
392
1062
  for (const b of PROTECTED) {
393
1063
  const dir = await checkout(b);
@@ -405,7 +1075,7 @@ describe('RolesYamlStep', () => {
405
1075
  // skip-if-present check, reporting success over a KB whose access roster
406
1076
  // cannot be read. Fail closed instead.
407
1077
  await seedUpstream({ 'roles.yaml/placeholder.txt': 'squatter' });
408
- await expect(makeRunner([new RolesYamlStep(['admin@example.com'])]).runAll()).rejects.toThrow(
1078
+ await expect(makeRunner([new RolesYamlStep(new NodeFs(), ['admin@example.com'])]).runAll()).rejects.toThrow(
409
1079
  /"roles\.yaml" on branch "current-company-state" exists but is not a regular file \(directory\)/,
410
1080
  );
411
1081
  });
@@ -413,7 +1083,7 @@ describe('RolesYamlStep', () => {
413
1083
  it('declares a skip when the file is missing and no admins are configured', async () => {
414
1084
  await seedUpstream({ 'marker.txt': 'seeded' });
415
1085
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
416
- await makeRunner([new RolesYamlStep([])]).runAll(); // resolves — a declared skip, not a failure
1086
+ await makeRunner([new RolesYamlStep(new NodeFs(), [])]).runAll(); // resolves — a declared skip, not a failure
417
1087
 
418
1088
  expect(warn.mock.calls.some((c) => String(c[0]).includes('roles-yaml: skipped'))).toBe(true);
419
1089
  const dir = await checkout(DEFAULT_BRANCH);
@@ -459,7 +1129,7 @@ describe('buildSeedTree', () => {
459
1129
 
460
1130
  const dest = path.join(root, 'seed-dest');
461
1131
  await fs.mkdir(dest, { recursive: true });
462
- const generated = await buildSeedTree(templateDir, [], ['admin@example.com'])(dest);
1132
+ const generated = await buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest);
463
1133
 
464
1134
  expect(await exists(dest, '.git')).toBe(false);
465
1135
  expect(await exists(dest, 'docs/.git')).toBe(false);
@@ -469,6 +1139,122 @@ describe('buildSeedTree', () => {
469
1139
  expect(generated.sort()).toEqual(['KnowledgeBase/.gitkeep', 'Plugins/.gitkeep', 'Skills/.gitkeep', 'roles.yaml']);
470
1140
  expect(await exists(dest, 'roles.yaml')).toBe(true);
471
1141
  });
1142
+
1143
+ it('seeds the layout in effect when it seeds, not the one it was composed under', async () => {
1144
+ const templateDir = path.join(root, 'seed-template-layout');
1145
+ await fs.mkdir(templateDir, { recursive: true });
1146
+ await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
1147
+ // Composed at boot, under the defaults…
1148
+ const seed = buildSeedTree(new NodeFs(), templateDir, [], ['admin@example.com']);
1149
+ // …and run after the completing save applied the admin's names.
1150
+ configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'skills', pluginsDir: 'Plugins' });
1151
+ const dest = path.join(root, 'seed-dest-layout');
1152
+ await fs.mkdir(dest, { recursive: true });
1153
+ const generated = await seed(dest);
1154
+ expect(generated.sort()).toEqual(['Docs/.gitkeep', 'Plugins/.gitkeep', 'roles.yaml', 'skills/.gitkeep']);
1155
+ });
1156
+
1157
+ /**
1158
+ * An empty remote seeded by a deployment that renamed its guide. Done in the
1159
+ * SEED rather than left to the top-up step, which would otherwise lay down
1160
+ * `AGENTS.md` and delete it one commit later — two commits saying opposite
1161
+ * things about a knowledge base nobody has used yet.
1162
+ */
1163
+ it('seeds the packaged guide under the configured name, and no AGENTS.md beside it', async () => {
1164
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
1165
+ const dest = path.join(root, 'seed-dest-guide');
1166
+ await fs.mkdir(dest, { recursive: true });
1167
+ await buildSeedTree(new NodeFs(), TEMPLATE_DIR, [], ['admin@example.com'])(dest);
1168
+ expect(await exists(dest, 'AGENTS.md')).toBe(false);
1169
+ const guide = norm(await fs.readFile(path.join(dest, 'HEXIS.md'), 'utf8'));
1170
+ expect(guide).toContain('`HEXIS.md`');
1171
+ expect(guide).not.toContain('{{');
1172
+ // And the ignore file hides the name it actually seeded.
1173
+ const ignore = norm(await fs.readFile(path.join(dest, '.bevelignore'), 'utf8'))
1174
+ .split('\n')
1175
+ .map((l) => l.trim());
1176
+ expect(ignore).toContain('HEXIS.md');
1177
+ expect(ignore).not.toContain('AGENTS.md');
1178
+ });
1179
+
1180
+ it('lets the managed guide win a template that carries a root file of the same name', async () => {
1181
+ // A custom KB_TEMPLATE_DIR that happens to ship `HEXIS.md` at its root, on
1182
+ // a deployment that named its guide `HEXIS.md`: two walk entries, one
1183
+ // destination. Whichever the walk reached last used to decide what an
1184
+ // empty deployment was seeded with. The managed guide decides.
1185
+ const customTemplate = path.join(root, 'custom-template-guide-clash');
1186
+ await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
1187
+ await fs.writeFile(path.join(customTemplate, 'HEXIS.md'), '# Not the platform guide\n', 'utf8');
1188
+
1189
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
1190
+ const dest = path.join(root, 'seed-dest-guide-clash');
1191
+ await fs.mkdir(dest, { recursive: true });
1192
+ await buildSeedTree(new NodeFs(), customTemplate, [], ['admin@example.com'])(dest);
1193
+
1194
+ const guide = norm(await fs.readFile(path.join(dest, 'HEXIS.md'), 'utf8'));
1195
+ expect(guide).toContain('`HEXIS.md`');
1196
+ expect(guide).not.toContain('Not the platform guide');
1197
+ expect(await exists(dest, 'AGENTS.md')).toBe(false);
1198
+ });
1199
+
1200
+ it('names a template that is not there — a missing directory is a broken build, not an empty seed', async () => {
1201
+ const dest = path.join(root, 'seed-dest-missing');
1202
+ await fs.mkdir(dest, { recursive: true });
1203
+ await expect(buildSeedTree(new NodeFs(),path.join(root, 'no-such-template'), [], ['admin@example.com'])(dest)).rejects.toThrow(
1204
+ /KB template ".*no-such-template" is not a directory/,
1205
+ );
1206
+ });
1207
+
1208
+ it('seeds a file the template links to, and refuses a link to anything else', async () => {
1209
+ const templateDir = path.join(root, 'seed-template-links');
1210
+ const elsewhere = path.join(root, 'elsewhere-template');
1211
+ await fs.mkdir(path.join(templateDir, 'docs'), { recursive: true });
1212
+ await fs.mkdir(elsewhere, { recursive: true });
1213
+ await fs.writeFile(path.join(elsewhere, 'shared.md'), 'shared', 'utf8');
1214
+ await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
1215
+ await fs.symlink(path.join(elsewhere, 'shared.md'), path.join(templateDir, 'docs', 'linked.md'), 'file');
1216
+
1217
+ const dest = path.join(root, 'seed-dest-links');
1218
+ await fs.mkdir(dest, { recursive: true });
1219
+ await buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest);
1220
+ // The link's CONTENT, under the link's own name, as a real file.
1221
+ expect(await fs.readFile(path.join(dest, 'docs/linked.md'), 'utf8')).toBe('shared');
1222
+ expect((await fs.lstat(path.join(dest, 'docs/linked.md'))).isSymbolicLink()).toBe(false);
1223
+
1224
+ // A link to nothing is a broken template, named as such…
1225
+ await fs.symlink(path.join(root, 'nowhere.md'), path.join(templateDir, 'dangling.md'), 'file');
1226
+ const dest2 = path.join(root, 'seed-dest-links-2');
1227
+ await fs.mkdir(dest2, { recursive: true });
1228
+ await expect(buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest2)).rejects.toThrow(
1229
+ /KB template entry "dangling.md" links to nothing/,
1230
+ );
1231
+ // …and so is a link to a folder, named for what it is.
1232
+ await fs.rm(path.join(templateDir, 'dangling.md'));
1233
+ await fs.symlink(elsewhere, path.join(templateDir, 'dirlink'), process.platform === 'win32' ? 'junction' : 'dir');
1234
+ const dest3 = path.join(root, 'seed-dest-links-3');
1235
+ await fs.mkdir(dest3, { recursive: true });
1236
+ await expect(buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest3)).rejects.toThrow(
1237
+ /KB template entry "dirlink" links to a directory/,
1238
+ );
1239
+ });
1240
+
1241
+ it('seeds a template file whose name collides with Object.prototype', async () => {
1242
+ // The packable-spelling lookup is keyed by FILENAME. Asked about a file
1243
+ // called `constructor` an object map answers from the prototype and hands
1244
+ // a function to path.join, failing the seed of a whole knowledge base
1245
+ // with a type error that names nothing.
1246
+ const templateDir = path.join(root, 'proto-template');
1247
+ await fs.mkdir(templateDir, { recursive: true });
1248
+ await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
1249
+ await fs.writeFile(path.join(templateDir, 'constructor'), 'not a function', 'utf8');
1250
+ await fs.writeFile(path.join(templateDir, 'toString'), 'nor this', 'utf8');
1251
+
1252
+ const dest = path.join(root, 'proto-dest');
1253
+ await fs.mkdir(dest, { recursive: true });
1254
+ await buildSeedTree(new NodeFs(), templateDir, [], ['admin@example.com'])(dest);
1255
+ expect(await fs.readFile(path.join(dest, 'constructor'), 'utf8')).toBe('not a function');
1256
+ expect(await fs.readFile(path.join(dest, 'toString'), 'utf8')).toBe('nor this');
1257
+ });
472
1258
  });
473
1259
 
474
1260
  describe('PluginManifestsStep', () => {
@@ -488,32 +1274,294 @@ describe('PluginManifestsStep', () => {
488
1274
  'Plugins/functional/access.md': '---\n---\nread:\n - everyone\n',
489
1275
  // Nothing plugin-shaped at all.
490
1276
  'Plugins/notes/README.md': 'just a folder',
1277
+ // A legacy plugin whose vendored dependency ships a manifest: the walk
1278
+ // does not enter node_modules, so the folder is the plugin, not a
1279
+ // grouping folder over one.
1280
+ 'Plugins/Vendored/access.md': '---\n---\nread:\n - everyone\n',
1281
+ 'Plugins/Vendored/node_modules/some-pkg/plugin.json': '{"name":"some-pkg"}',
1282
+ // A plain folder whose only SKILL.md is vendored: not a plugin either.
1283
+ 'Plugins/deps/node_modules/some-pkg/SKILL.md': '---\ndescription: z\n---\n',
491
1284
  });
492
1285
 
493
- await makeRunner([new PluginManifestsStep()]).runAll();
1286
+ await makeRunner([new PluginManifestsStep(new NodeFs())]).runAll();
494
1287
 
495
1288
  for (const branch of PROTECTED) {
496
1289
  const dir = await checkout(branch);
497
- for (const legacy of ['GTM', 'Servers', 'Bare']) {
1290
+ for (const legacy of ['GTM', 'Servers', 'Bare', 'Vendored']) {
498
1291
  const manifest = JSON.parse(await fs.readFile(path.join(dir, `Plugins/${legacy}/plugin.json`), 'utf8'));
499
1292
  expect(manifest.name).toBe(legacy.toLowerCase());
500
1293
  }
501
1294
  expect(await exists(dir, 'Plugins/functional/plugin.json')).toBe(false);
502
1295
  expect(await exists(dir, 'Plugins/notes/plugin.json')).toBe(false);
1296
+ expect(await exists(dir, 'Plugins/deps/plugin.json')).toBe(false);
503
1297
  expect(await fs.readFile(path.join(dir, 'Plugins/Modern/plugin.json'), 'utf8')).toBe('{"name":"modern"}');
504
1298
  }
505
1299
  const dir = await checkout(DEFAULT_BRANCH);
506
1300
  const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
507
- expect(log).toContain('Add plugin manifests to 3 legacy plugin folders');
1301
+ expect(log).toContain('Add plugin manifests to 4 legacy plugin folders');
508
1302
  expect(log).toContain('Plugins/GTM: plugin.json written');
509
1303
 
510
1304
  // Idempotent: nothing left to write on the next boot.
511
- await makeRunner([new PluginManifestsStep()]).runAll();
1305
+ await makeRunner([new PluginManifestsStep(new NodeFs())]).runAll();
512
1306
  const again = await checkout(DEFAULT_BRANCH);
513
1307
  expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one migration commit
514
1308
  });
515
1309
  });
516
1310
 
1311
+ describe('PluginDisplayNamesStep', () => {
1312
+ /**
1313
+ * The one-time repair behind the display-name rule: a manifest is now the
1314
+ * only thing any reader consults for what a plugin is called, so every
1315
+ * manifest written under the old rule — which stored the field only when
1316
+ * the folder was spelled differently from the identifier — has to be told
1317
+ * what its plugin was already called, or it would appear to rename itself
1318
+ * on the boot that introduces the rule.
1319
+ */
1320
+ it('writes the folder\'s spelling into the manifests that lack the field, and touches nothing else', async () => {
1321
+ const scaffold = await fullScaffold();
1322
+ await seedUpstream({
1323
+ ...scaffold,
1324
+ // Spelled differently from its identifier and saying nothing about it:
1325
+ // exactly the plugin that would start showing `sales-team`.
1326
+ 'Plugins/Sales Team/plugin.json': '{\n "name": "sales-team",\n "version": "1.2.0"\n}\n',
1327
+ 'Plugins/Sales Team/access.md': '---\n---\nread:\n - everyone\n',
1328
+ // Nested just as deep as discovery looks.
1329
+ 'Plugins/Teams/EU Field/plugin.json': '{"name":"eu-field"}',
1330
+ // Already spelled as its identifier: called the same thing under both
1331
+ // rules, so there is nothing the folder knows that the manifest does not.
1332
+ 'Plugins/imported-tools/plugin.json': '{"name":"imported-tools"}',
1333
+ // Already carries the field — the author's own answer, never overwritten,
1334
+ // even though the folder disagrees with both of its names.
1335
+ 'Plugins/Ops Desk/plugin.json': '{"name":"ops","displayName":"Operations"}',
1336
+ // A bundle: a foreign repository's file this platform reads, never writes.
1337
+ 'Plugins/functional/cluster/example/plugin.bundle.json': '{"name":"example"}',
1338
+ });
1339
+
1340
+ await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
1341
+
1342
+ for (const branch of PROTECTED) {
1343
+ const dir = await checkout(branch);
1344
+ // Backfilled from the folder — what the plugin was called the moment
1345
+ // before the boot — with everything else in the manifest kept, and the
1346
+ // field where the renderer puts it.
1347
+ const sales = await readJson(dir, 'Plugins/Sales Team/plugin.json');
1348
+ expect(sales).toEqual({ name: 'sales-team', displayName: 'Sales Team', version: '1.2.0' });
1349
+ expect(Object.keys(sales)).toEqual(['name', 'displayName', 'version']);
1350
+ expect(await readJson(dir, 'Plugins/Teams/EU Field/plugin.json')).toEqual({
1351
+ name: 'eu-field',
1352
+ displayName: 'EU Field',
1353
+ });
1354
+ // Untouched, byte for byte.
1355
+ expect(await fs.readFile(path.join(dir, 'Plugins/imported-tools/plugin.json'), 'utf8')).toBe(
1356
+ '{"name":"imported-tools"}',
1357
+ );
1358
+ expect(await fs.readFile(path.join(dir, 'Plugins/Ops Desk/plugin.json'), 'utf8')).toBe(
1359
+ '{"name":"ops","displayName":"Operations"}',
1360
+ );
1361
+ expect(await fs.readFile(path.join(dir, 'Plugins/functional/cluster/example/plugin.bundle.json'), 'utf8')).toBe(
1362
+ '{"name":"example"}',
1363
+ );
1364
+ }
1365
+
1366
+ const dir = await checkout(DEFAULT_BRANCH);
1367
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1368
+ expect(log).toContain('Record the display names of 2 plugins in their manifests');
1369
+ expect(log).toContain('Plugins/Sales Team: displayName "Sales Team"');
1370
+
1371
+ // Idempotent: every manifest it would touch now carries the field.
1372
+ await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
1373
+ const again = await checkout(DEFAULT_BRANCH);
1374
+ expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one backfill commit
1375
+ });
1376
+
1377
+ /**
1378
+ * The three shapes the plain "lacks the field, folder differs" reading gets
1379
+ * wrong: a field that is present and says nothing, a manifest naming the
1380
+ * folder's own spelling without being an identifier, and a `plugin.json`
1381
+ * that is not this platform's file to write.
1382
+ */
1383
+ it('repairs a blank field, leaves a manifest that already shows its folder, and never writes through a link', async () => {
1384
+ const scaffold = await fullScaffold();
1385
+ await seedUpstream(
1386
+ {
1387
+ ...scaffold,
1388
+ // Present but BLANK. The new reader falls through it to `name` where
1389
+ // the old one fell through to the folder, so leaving it be is leaving
1390
+ // the rename this step exists to prevent: blank is absent, and repaired.
1391
+ 'Plugins/Growth Team/plugin.json': '{"name":"growth-team","displayName":" "}',
1392
+ // A `name` that is no identifier but IS the folder's spelling: the
1393
+ // identity is the slug `my-plugin`, and what people see is already
1394
+ // "My Plugin". The folder knows nothing the manifest does not.
1395
+ 'Plugins/My Plugin/plugin.json': '{"name":"My Plugin"}',
1396
+ // An own `__proto__` key — `JSON.parse` makes it one. Copied as DATA:
1397
+ // assignment would have set the object's prototype instead, dropping
1398
+ // the field and letting a prototype `displayName` stand in for the
1399
+ // one being written.
1400
+ 'Plugins/Odd One/plugin.json': '{"name":"odd-one","__proto__":{"displayName":"from the prototype"}}',
1401
+ // A bundle makes this folder a plugin; the `plugin.json` beside it is
1402
+ // a LINK, which discovery does not count as a native manifest.
1403
+ 'Plugins/Kit Pro/plugin.bundle.json': '{"name":"kit"}',
1404
+ 'vendor-manifest.json': '{"name":"kit"}',
1405
+ },
1406
+ { 'Plugins/Kit Pro/plugin.json': '../../vendor-manifest.json' },
1407
+ );
1408
+
1409
+ await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
1410
+
1411
+ const dir = await checkout(DEFAULT_BRANCH);
1412
+ expect(await readJson(dir, 'Plugins/Growth Team/plugin.json')).toEqual({
1413
+ name: 'growth-team',
1414
+ displayName: 'Growth Team',
1415
+ });
1416
+ // Untouched, byte for byte.
1417
+ expect(await fs.readFile(path.join(dir, 'Plugins/My Plugin/plugin.json'), 'utf8')).toBe('{"name":"My Plugin"}');
1418
+ expect(await fs.readFile(path.join(dir, 'Plugins/Odd One/plugin.json'), 'utf8')).toBe(
1419
+ '{\n "name": "odd-one",\n "displayName": "Odd One",\n "__proto__": {\n "displayName": "from the prototype"\n }\n}\n',
1420
+ );
1421
+ // Still a link, and the file it points at is still the vendor's own.
1422
+ expect((await fs.lstat(path.join(dir, 'Plugins/Kit Pro/plugin.json'))).isSymbolicLink()).toBe(true);
1423
+ expect(await fs.readFile(path.join(dir, 'vendor-manifest.json'), 'utf8')).toBe('{"name":"kit"}');
1424
+
1425
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1426
+ expect(log).toContain('Record the display names of 2 plugins in their manifests');
1427
+ expect(log).toContain('Plugins/Growth Team: displayName "Growth Team"');
1428
+ expect(log).not.toContain('Kit Pro');
1429
+
1430
+ // Idempotent over all of it: the next boot finds nothing to write.
1431
+ await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
1432
+ const again = await checkout(DEFAULT_BRANCH);
1433
+ expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one backfill commit
1434
+ });
1435
+
1436
+ it('leaves a plugin the manifests step just made alone — that manifest already says it', async () => {
1437
+ const scaffold = await fullScaffold();
1438
+ await seedUpstream({
1439
+ ...scaffold,
1440
+ // Legacy: no manifest at all. The manifests step renders one, which the
1441
+ // renderer has already given both names; the backfill then finds nothing.
1442
+ 'Plugins/Sales Team/access.md': '---\n---\nread:\n - everyone\n',
1443
+ });
1444
+
1445
+ await makeRunner([new PluginManifestsStep(new NodeFs()), new PluginDisplayNamesStep(new NodeFs())]).runAll();
1446
+
1447
+ const dir = await checkout(DEFAULT_BRANCH);
1448
+ expect(await readJson(dir, 'Plugins/Sales Team/plugin.json')).toEqual({
1449
+ $schema: 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json',
1450
+ name: 'sales-team',
1451
+ displayName: 'Sales Team',
1452
+ });
1453
+ // ONE commit — the backfill declared nothing of its own to caption.
1454
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1455
+ expect(log).toContain('Add plugin manifests to a legacy plugin folder');
1456
+ expect(log).not.toContain('Record the display name');
1457
+ });
1458
+ });
1459
+
1460
+ describe('PersonalSpacesStep', () => {
1461
+ // A personal folder as the previous template seeded it: the owner's grants
1462
+ // and nothing else — private only while nothing above grants read.
1463
+ const LEGACY_PERSONAL =
1464
+ '---\nread:\n - Ali Vega <ali@x.io>\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>\n---\nread: []\n';
1465
+
1466
+ it("adds `deny everyone` to every personal space's read rules on every branch, keeping the rest, once", async () => {
1467
+ const scaffold = await fullScaffold();
1468
+ const seed = await seedUpstream({
1469
+ ...scaffold,
1470
+ 'Plugins/personal-u1/plugin.json': '{"name":"personal-u1"}',
1471
+ 'Plugins/personal-u1/access.md': LEGACY_PERSONAL,
1472
+ // A space someone already closed (its folder rules stay; only the
1473
+ // file's own block, which said nothing, gets the same statement), one
1474
+ // its owner opened on purpose (a denial beside that grant would be
1475
+ // overruled and read as a contradiction — and the file stays open
1476
+ // with the folder), and a shared plugin: untouched.
1477
+ 'Plugins/personal-u2/access.md': '---\nread: []\n---\nread:\n - deny everyone\n - Bo <bo@x.io>\n',
1478
+ 'Plugins/personal-u3/access.md': '---\nread: []\n---\nread:\n - everyone\nowner:\n - Cy <cy@x.io>\n',
1479
+ // Entries that say nothing about READ — a denial under write — leave the
1480
+ // space open to an inherited `read: everyone`, so it is closed like any
1481
+ // other.
1482
+ 'Plugins/personal-u4/access.md': '---\nread: []\n---\nwrite:\n - deny everyone\nowner:\n - Di <di@x.io>\n',
1483
+ // The old seed's frontmatter grants are carried into the body ONLY where
1484
+ // the body has no word on that person: a denial someone wrote there
1485
+ // stands, and is not overridden by the older grant.
1486
+ 'Plugins/personal-u5/access.md': '---\nread:\n - Ed <ed@x.io>\nowner:\n - Ed <ed@x.io>\n---\nread:\n - deny Ed <ed@x.io>\n',
1487
+ // A folder whose read denial is overruled by `write: everyone` (a
1488
+ // same-scope grant wins, and write folds into read) is OPEN: the file
1489
+ // stays open with it, and nothing is written.
1490
+ 'Plugins/personal-u6/access.md': '---\nread: []\n---\nread:\n - deny everyone\nwrite:\n - everyone\nowner:\n - Fy <fy@x.io>\n',
1491
+ // `download: everyone` is a deliberate opening now that download folds
1492
+ // into read: the space is already settled OPEN, so nothing is written —
1493
+ // the same treatment `write: everyone` gets above.
1494
+ 'Plugins/personal-u7/access.md': '---\nread: []\n---\ndownload:\n - everyone\nowner:\n - Gi <gi@x.io>\n',
1495
+ 'Plugins/GTM/plugin.json': '{"name":"gtm"}',
1496
+ 'Plugins/GTM/access.md': '---\nread:\n - everyone\n---\nread:\n - Ali Vega <ali@x.io>\n',
1497
+ });
1498
+ // A draft carrying a personal space too: the step visits every branch,
1499
+ // not only the protected ones.
1500
+ await git(seed, ['checkout', '-b', 'ali/draft']);
1501
+ await git(seed, ['push', 'origin', 'ali/draft']);
1502
+
1503
+ await makeRunner([new PersonalSpacesStep(new NodeFs())]).runAll();
1504
+
1505
+ for (const branch of [...PROTECTED, 'ali/draft']) {
1506
+ const dir = await checkout(branch);
1507
+ const closed = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u1/access.md'), 'utf8'));
1508
+ // The body — which governs the folder — now denies everyone (Admin
1509
+ // included: nobody is named) and carries the owner's grants so they
1510
+ // can still read, write and own their space; the old seed's
1511
+ // frontmatter grants stay, joined by the same denial, so the file says
1512
+ // of itself what the folder says.
1513
+ expect(closed).toBe(
1514
+ '---\nread:\n - Ali Vega <ali@x.io>\n - deny everyone\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>\n---\n' +
1515
+ 'read:\n - Ali Vega <ali@x.io>\n - deny everyone\n\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>',
1516
+ );
1517
+ // Already closed: the folder rules are untouched, the empty file block
1518
+ // now states the same — the denial, then the people the folder admits.
1519
+ expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u2/access.md'), 'utf8'))).toBe(
1520
+ '---\nread:\n - deny everyone\n - Bo <bo@x.io>\n---\nread:\n - deny everyone\n - Bo <bo@x.io>\n',
1521
+ );
1522
+ expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u3/access.md'), 'utf8'))).toBe(
1523
+ '---\nread: []\n---\nread:\n - everyone\nowner:\n - Cy <cy@x.io>\n',
1524
+ );
1525
+ const u4 = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u4/access.md'), 'utf8'));
1526
+ expect(u4.slice(0, u4.indexOf('\n---\n', 4))).toBe('---\nread:\n - deny everyone\n - Di <di@x.io>');
1527
+ expect(u4.trimEnd().endsWith('\nread:\n - deny everyone')).toBe(true);
1528
+ expect(u4).toContain('write:\n - deny everyone');
1529
+ expect(u4).toContain('owner:\n - Di <di@x.io>');
1530
+ const u5 = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u5/access.md'), 'utf8'));
1531
+ const u5Body = u5.slice(u5.indexOf('\n---\n', 4) + 5);
1532
+ // Ed's read denial stands — the frontmatter's read grant is NOT carried
1533
+ // beside it — while the owner grant, which the body said nothing about, is.
1534
+ expect(u5Body.match(/read:\n((?: - [^\n]*\n)*)/)![1]).toBe(' - deny Ed <ed@x.io>\n - deny everyone\n');
1535
+ expect(u5Body).toMatch(/owner:\n - Ed <ed@x.io>/);
1536
+ // The file block keeps Ed's grant as written and gains the denial.
1537
+ expect(u5.slice(0, u5.indexOf('\n---\n', 4))).toBe('---\nread:\n - Ed <ed@x.io>\n - deny everyone\nowner:\n - Ed <ed@x.io>');
1538
+ expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u6/access.md'), 'utf8'))).toBe(
1539
+ '---\nread: []\n---\nread:\n - deny everyone\nwrite:\n - everyone\nowner:\n - Fy <fy@x.io>\n',
1540
+ );
1541
+ // Untouched: `download: everyone` settles read as GRANTED (download folds
1542
+ // into read), so the step reads the space as opened on purpose.
1543
+ expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u7/access.md'), 'utf8'))).toBe(
1544
+ '---\nread: []\n---\ndownload:\n - everyone\nowner:\n - Gi <gi@x.io>\n',
1545
+ );
1546
+ expect(norm(await fs.readFile(path.join(dir, 'Plugins/GTM/access.md'), 'utf8'))).toBe(
1547
+ '---\nread:\n - everyone\n---\nread:\n - Ali Vega <ali@x.io>\n',
1548
+ );
1549
+ }
1550
+ const dir = await checkout(DEFAULT_BRANCH);
1551
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1552
+ expect(log).toContain('Keep 4 personal spaces private');
1553
+ expect(log).toContain('Plugins/personal-u1/access.md: read denies everyone');
1554
+ expect(log).toContain('Plugins/personal-u2/access.md: read denies everyone');
1555
+ expect(log).toContain('Plugins/personal-u4/access.md: read denies everyone');
1556
+ expect(log).toContain('Plugins/personal-u5/access.md: read denies everyone');
1557
+
1558
+ // Idempotent: the next boot finds every space already closed.
1559
+ await makeRunner([new PersonalSpacesStep(new NodeFs())]).runAll();
1560
+ const again = await checkout(DEFAULT_BRANCH);
1561
+ expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2');
1562
+ });
1563
+ });
1564
+
517
1565
  describe('GroupsToPluginsStep', () => {
518
1566
  it('migrates a Groups/ tree to the Plugins layout — on the protected branches AND a draft', async () => {
519
1567
  const seed = await seedUpstream({
@@ -537,7 +1585,7 @@ describe('GroupsToPluginsStep', () => {
537
1585
  await git(seed, ['checkout', '-b', 'alice/draft']);
538
1586
  await git(seed, ['push', 'origin', 'alice/draft']);
539
1587
 
540
- await makeRunner([new GroupsToPluginsStep()]).runAll();
1588
+ await makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll();
541
1589
 
542
1590
  for (const b of [DEFAULT_BRANCH, 'alice/draft']) {
543
1591
  const dir = await checkout(b);
@@ -552,10 +1600,11 @@ describe('GroupsToPluginsStep', () => {
552
1600
  expect(mcp.mcpServers.notion).toEqual({ type: 'streamable-http', url: 'https://mcp.notion.com/mcp' });
553
1601
  const manifest = JSON.parse(await fs.readFile(path.join(dir, 'Plugins/GTM/plugin.json'), 'utf8'));
554
1602
  expect(manifest.name).toBe('gtm');
555
- // The rename's companion edit: the stale ignore rule now names Plugins/.
1603
+ // The rename's companion edit: the stale ignore rule is retired — and
1604
+ // not replaced, since the plugins root is drawn by the sidebar now.
556
1605
  const ignore = await fs.readFile(path.join(dir, '.bevelignore'), 'utf8');
557
- expect(ignore).toContain('Plugins/');
558
1606
  expect(ignore).not.toContain('Groups/');
1607
+ expect(ignore.split('\n').map((l) => l.trim())).not.toContain('Plugins/');
559
1608
  }
560
1609
 
561
1610
  const dir = await checkout(DEFAULT_BRANCH);
@@ -571,7 +1620,7 @@ describe('GroupsToPluginsStep', () => {
571
1620
  // silently skipping a branch whose reserved root cannot be a plugin tree
572
1621
  // (and on a draft nothing later would ever report it).
573
1622
  await seedUpstream({ Plugins: 'i am a file, not a folder' });
574
- await expect(makeRunner([new GroupsToPluginsStep()]).runAll()).rejects.toThrow(
1623
+ await expect(makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll()).rejects.toThrow(
575
1624
  /"Plugins" exists but is not a directory/,
576
1625
  );
577
1626
  });
@@ -583,7 +1632,7 @@ describe('GroupsToPluginsStep', () => {
583
1632
  'Plugins/B/y.md': 'new',
584
1633
  });
585
1634
  await makeRunner([
586
- new GroupsToPluginsStep(),
1635
+ new GroupsToPluginsStep(new NodeFs()),
587
1636
  // A later step dirties the branch so the refusal note surfaces in the commit.
588
1637
  step('dirty', async (ctx) => {
589
1638
  for (const b of await ctx.protectedBranches()) {
@@ -613,11 +1662,7 @@ describe('GroupsToPluginsStep', () => {
613
1662
  */
614
1663
  describe('GroupsToPluginsStep — migration edge cases', () => {
615
1664
  async function migrate(): Promise<void> {
616
- await makeRunner([new GroupsToPluginsStep()]).runAll();
617
- }
618
-
619
- async function readJson(dir: string, rel: string): Promise<Record<string, any>> {
620
- return JSON.parse(await fs.readFile(path.join(dir, rel), 'utf8'));
1665
+ await makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll();
621
1666
  }
622
1667
 
623
1668
  /** What the runner's `partial` warning carried — the named refusals. */
@@ -785,6 +1830,36 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
785
1830
  });
786
1831
  });
787
1832
 
1833
+ it('leaves a plain folder under the root alone — a .gitkeep is not a plugin, and a grouping folder holding one is not either', async () => {
1834
+ await seedUpstream({
1835
+ 'Plugins/GTM/access.md': 'write:\n - Admin\n',
1836
+ // Made with "New folder" in the tree: nothing plugin-shaped in it.
1837
+ 'Plugins/TestFolder/.gitkeep': '',
1838
+ // A grouping folder with a plugin INSIDE: a manifest on the folder
1839
+ // would hide the plugin beneath it from every catalog.
1840
+ 'Plugins/Teams/Agent Made/plugin.json': '{"name":"agent-made"}',
1841
+ // A legacy plugin whose vendored dependency happens to ship a
1842
+ // manifest: `node_modules` is nothing to the walk, so the folder is
1843
+ // still the plugin it was.
1844
+ 'Plugins/Vendored/access.md': 'write:\n - Admin\n',
1845
+ 'Plugins/Vendored/node_modules/some-pkg/plugin.json': '{"name":"some-pkg"}',
1846
+ // A folder whose ONLY plugin-shaped entry is one the walk skips. The
1847
+ // migration's own sweep never moves a dot-entry, so counting it as
1848
+ // legacy content wrote a manifest for a folder and then moved nothing
1849
+ // into it — and the manifests step, reading a walk that had already
1850
+ // dropped it, disagreed about the same folder.
1851
+ 'Plugins/HiddenOnly/.hidden.tool': 'name: hidden\n',
1852
+ });
1853
+ await migrate();
1854
+ const dir = await checkout(DEFAULT_BRANCH);
1855
+ expect(await exists(dir, 'Plugins/GTM/plugin.json')).toBe(true);
1856
+ expect(await exists(dir, 'Plugins/TestFolder/plugin.json')).toBe(false);
1857
+ expect(await exists(dir, 'Plugins/Teams/plugin.json')).toBe(false);
1858
+ expect(await exists(dir, 'Plugins/Teams/Agent Made/plugin.json')).toBe(true);
1859
+ expect(await exists(dir, 'Plugins/Vendored/plugin.json')).toBe(true);
1860
+ expect(await exists(dir, 'Plugins/HiddenOnly/plugin.json')).toBe(false);
1861
+ });
1862
+
788
1863
  it('leaves a personal folder a valid plugin', async () => {
789
1864
  await seedUpstream({
790
1865
  'Groups/personal-u-123/access.md': 'write:\n - Ali <ali@x.com>\n',
@@ -796,7 +1871,7 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
796
1871
  });
797
1872
 
798
1873
  describe('the .bevelignore root rule', () => {
799
- it('follows the rename, preserving every other line', async () => {
1874
+ it('retires the rule with the rename — nothing takes its place — preserving every other line', async () => {
800
1875
  await seedUpstream({
801
1876
  'Groups/GTM/access.md': 'write:\n - Admin\n',
802
1877
  '.bevelignore': '# mine\nGroups/\nMy-Own-Rule/\n',
@@ -804,48 +1879,115 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
804
1879
  await migrate();
805
1880
  const dir = await checkout(DEFAULT_BRANCH);
806
1881
  const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
807
- expect(ignore.split('\n')).toContain('Plugins/');
808
- expect(ignore).not.toContain('Groups/');
809
- expect(ignore).toContain('# mine');
810
- expect(ignore).toContain('My-Own-Rule/');
1882
+ expect(ignore).toBe('# mine\nMy-Own-Rule/\n');
811
1883
  });
812
1884
 
813
- it('rewrites EVERY exact Groups/ line — the first becomes Plugins/, duplicates are dropped', async () => {
1885
+ it('drops EVERY exact Groups/ line, keeping the lines between them', async () => {
814
1886
  await seedUpstream({
815
1887
  'Groups/GTM/access.md': 'write:\n - Admin\n',
816
1888
  '.bevelignore': 'Groups/\n# keep\nGroups/\n',
817
1889
  });
818
1890
  await migrate();
819
1891
  const dir = await checkout(DEFAULT_BRANCH);
820
- const lines = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n');
821
- expect(lines.filter((l) => l.trim() === 'Plugins/')).toHaveLength(1);
822
- expect(lines.filter((l) => l.trim() === 'Groups/')).toHaveLength(0);
823
- expect(lines).toContain('# keep');
1892
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# keep\n');
824
1893
  });
825
1894
 
826
- it('is left alone when Plugins/ is already listed', async () => {
1895
+ it('retires a Plugins/ line beside it too — both root rules are stale for the same reason', async () => {
827
1896
  await seedUpstream({
828
1897
  'Groups/GTM/access.md': 'write:\n - Admin\n',
829
- '.bevelignore': 'Groups/\nPlugins/\n',
1898
+ '.bevelignore': '# mine\nGroups/\nPlugins/\n!Plugins/\n',
1899
+ });
1900
+ await migrate();
1901
+ const dir = await checkout(DEFAULT_BRANCH);
1902
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# mine\n!Plugins/\n');
1903
+ });
1904
+
1905
+ it('retires the rules on a branch with neither root — nothing to migrate there, and the rules are as stale', async () => {
1906
+ await seedUpstream({
1907
+ 'marker.txt': 'seeded',
1908
+ '.bevelignore': 'Plugins/\nGroups/\nkeep/\n',
1909
+ });
1910
+ await migrate();
1911
+ const dir = await checkout(DEFAULT_BRANCH);
1912
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('keep/\n');
1913
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1914
+ expect(log).toContain('.bevelignore: Groups/, Plugins/ dropped');
1915
+ });
1916
+
1917
+ it('names the commit for what happened: a migrated branch that only lost the rules was not reorganised', async () => {
1918
+ await seedUpstream({
1919
+ 'Plugins/GTM/plugin.json': '{"name":"gtm"}',
1920
+ 'Plugins/GTM/access.md': 'write:\n - Admin\n',
1921
+ '.bevelignore': 'Plugins/\n',
830
1922
  });
831
1923
  await migrate();
832
1924
  const dir = await checkout(DEFAULT_BRANCH);
833
- // The stale line is harmlessly dead; deleting it would be editing the
834
- // operator's file beyond what the rename made stale.
835
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('Groups/\nPlugins/\n');
1925
+ const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
1926
+ expect(log.split('\n')[0]).toBe('Retire the stale Groups/ and Plugins/ ignore rules');
1927
+ expect(log).not.toContain('Reorganise');
836
1928
  });
837
1929
 
838
- it('is not touched by a run that does not rename', async () => {
1930
+ it('retires the rules on a run that does not rename — a branch migrated by an earlier release, a draft included', async () => {
1931
+ // An earlier release renamed `Groups/` to `Plugins/` in the ignore file
1932
+ // of every branch it migrated; the template step retires that line on
1933
+ // the protected branches only, so this step does it wherever it goes.
839
1934
  await seedUpstream({
840
1935
  'Plugins/GTM/access.md': 'write:\n - Admin\n',
841
1936
  'Plugins/GTM/outreach/SKILL.md': '# Outreach\n',
842
- '.bevelignore': 'Groups/\n',
1937
+ '.bevelignore': 'AGENTS.md\nPlugins/\n',
843
1938
  });
844
1939
  await migrate();
845
1940
  const dir = await checkout(DEFAULT_BRANCH);
846
- expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('Groups/\n');
847
- // The run still reorganised the folder — the rule alone was off-limits.
1941
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('AGENTS.md\n');
848
1942
  expect(await exists(dir, 'Plugins/GTM/skills/outreach/SKILL.md')).toBe(true);
1943
+ // Idempotent: a second run declares nothing.
1944
+ await migrate();
1945
+ expect(norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'))).toBe('AGENTS.md\n');
849
1946
  });
1947
+
1948
+ it("takes a platform comment above a stale rule with it, and the blank line that opened the block — the template step's own tidy-up, on a branch it never visits", async () => {
1949
+ await seedUpstream({
1950
+ 'Plugins/GTM/access.md': 'write:\n - Admin\n',
1951
+ '.bevelignore':
1952
+ '# mine\n.git/\n\n# Added by the platform: the conventions doc is not node content.\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like Groups/.\nGroups/\n',
1953
+ });
1954
+ await migrate();
1955
+ const dir = await checkout(DEFAULT_BRANCH);
1956
+ expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# mine\n.git/\n');
1957
+ });
1958
+
1959
+ it('fails the run when the ignore file cannot be read — a hole is not "no file"', async () => {
1960
+ // A directory where the file should be: readable as neither. Treating
1961
+ // that as absence would leave a possible `Plugins/` rule in place and
1962
+ // report the migration done.
1963
+ await seedUpstream({
1964
+ 'Plugins/GTM/access.md': 'write:\n - Admin\n',
1965
+ '.bevelignore/keep': '',
1966
+ });
1967
+ await expect(migrate()).rejects.toThrow(/EISDIR/);
1968
+ });
1969
+ });
1970
+ });
1971
+
1972
+ /**
1973
+ * The guide's rule in `.bevelignore` is a gitignore PATTERN, and the name rules
1974
+ * admit characters gitignore reads as syntax: written bare, `#Guide.md` is a
1975
+ * comment and hides nothing. Rendered escaped, the rule names exactly the file.
1976
+ */
1977
+ describe('TemplateFilesStep: a guide named with gitignore syntax', () => {
1978
+ it('escapes the name in the ignore rule it writes, and keeps it escaped on the next boot', async () => {
1979
+ configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: '#Guide[1].md' });
1980
+ await seedUpstream({ 'marker.txt': 'seeded' });
1981
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
1982
+ const dir = await checkout(DEFAULT_BRANCH);
1983
+ const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
1984
+ const escaped = String.raw`\#Guide\[1\].md`;
1985
+ expect(ignore).toContain(escaped);
1986
+ expect(ignore).not.toContain('#Guide[1].md');
1987
+ expect(await exists(dir, '#Guide[1].md')).toBe(true);
1988
+ // A second boot sees the escaped rule as present and adds no second line.
1989
+ await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
1990
+ const again = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'));
1991
+ expect(again.split('\n').filter((l) => l.trim() === escaped)).toHaveLength(1);
850
1992
  });
851
1993
  });