@bevel-software/platform-core-backend 0.4.1 → 0.5.1

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 (305) hide show
  1. package/THIRD-PARTY-NOTICES.md +6118 -0
  2. package/dist/core/core-ports.d.ts +15 -0
  3. package/dist/core/core-ports.d.ts.map +1 -1
  4. package/dist/core/core-ports.js.map +1 -1
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +40 -6
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +17 -1
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +153 -29
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core-config.d.ts +70 -21
  13. package/dist/core-config.d.ts.map +1 -1
  14. package/dist/core-config.js +134 -58
  15. package/dist/core-config.js.map +1 -1
  16. package/dist/modules/access/access-control.interface.d.ts +6 -0
  17. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  18. package/dist/modules/access/access-control.service.d.ts +74 -5
  19. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  20. package/dist/modules/access/access-control.service.js +116 -15
  21. package/dist/modules/access/access-control.service.js.map +1 -1
  22. package/dist/modules/access/access-declarations.d.ts +93 -0
  23. package/dist/modules/access/access-declarations.d.ts.map +1 -0
  24. package/dist/modules/access/access-declarations.js +168 -0
  25. package/dist/modules/access/access-declarations.js.map +1 -0
  26. package/dist/modules/access/access-mutation.service.js +4 -4
  27. package/dist/modules/access/access-mutation.service.js.map +1 -1
  28. package/dist/modules/access/access-splice.d.ts +16 -1
  29. package/dist/modules/access/access-splice.d.ts.map +1 -1
  30. package/dist/modules/access/access-splice.js +40 -21
  31. package/dist/modules/access/access-splice.js.map +1 -1
  32. package/dist/modules/access/access.routes.d.ts.map +1 -1
  33. package/dist/modules/access/access.routes.js +51 -0
  34. package/dist/modules/access/access.routes.js.map +1 -1
  35. package/dist/modules/access/creator-access.d.ts +8 -0
  36. package/dist/modules/access/creator-access.d.ts.map +1 -1
  37. package/dist/modules/access/creator-access.js +21 -2
  38. package/dist/modules/access/creator-access.js.map +1 -1
  39. package/dist/modules/admin/admin-access.service.d.ts +1 -1
  40. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  41. package/dist/modules/admin/admin-access.service.js +3 -2
  42. package/dist/modules/admin/admin-access.service.js.map +1 -1
  43. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  44. package/dist/modules/auth/auth.routes.js +50 -1
  45. package/dist/modules/auth/auth.routes.js.map +1 -1
  46. package/dist/modules/auth/auth.service.d.ts +39 -10
  47. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  48. package/dist/modules/auth/auth.service.js +38 -16
  49. package/dist/modules/auth/auth.service.js.map +1 -1
  50. package/dist/modules/database/core-schema.d.ts +124 -0
  51. package/dist/modules/database/core-schema.d.ts.map +1 -1
  52. package/dist/modules/database/core-schema.js +45 -0
  53. package/dist/modules/database/core-schema.js.map +1 -1
  54. package/dist/modules/groups/group-provision.service.d.ts +130 -0
  55. package/dist/modules/groups/group-provision.service.d.ts.map +1 -0
  56. package/dist/modules/groups/group-provision.service.js +288 -0
  57. package/dist/modules/groups/group-provision.service.js.map +1 -0
  58. package/dist/modules/groups/groups.contract.d.ts +106 -0
  59. package/dist/modules/groups/groups.contract.d.ts.map +1 -0
  60. package/dist/modules/groups/groups.contract.js +36 -0
  61. package/dist/modules/groups/groups.contract.js.map +1 -0
  62. package/dist/modules/groups/groups.routes.d.ts +42 -0
  63. package/dist/modules/groups/groups.routes.d.ts.map +1 -0
  64. package/dist/modules/groups/groups.routes.js +379 -0
  65. package/dist/modules/groups/groups.routes.js.map +1 -0
  66. package/dist/modules/groups/groups.service.d.ts +60 -0
  67. package/dist/modules/groups/groups.service.d.ts.map +1 -0
  68. package/dist/modules/groups/groups.service.js +172 -0
  69. package/dist/modules/groups/groups.service.js.map +1 -0
  70. package/dist/modules/groups/index.d.ts +7 -0
  71. package/dist/modules/groups/index.d.ts.map +1 -0
  72. package/dist/modules/groups/index.js +6 -0
  73. package/dist/modules/groups/index.js.map +1 -0
  74. package/dist/modules/groups/join-proposals.d.ts +53 -0
  75. package/dist/modules/groups/join-proposals.d.ts.map +1 -0
  76. package/dist/modules/groups/join-proposals.js +67 -0
  77. package/dist/modules/groups/join-proposals.js.map +1 -0
  78. package/dist/modules/groups/join-requests.service.d.ts +81 -0
  79. package/dist/modules/groups/join-requests.service.d.ts.map +1 -0
  80. package/dist/modules/groups/join-requests.service.js +135 -0
  81. package/dist/modules/groups/join-requests.service.js.map +1 -0
  82. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +20 -0
  83. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  84. package/dist/modules/secrets-vault/secrets-vault.routes.js +59 -11
  85. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  86. package/dist/modules/settings/deployment-settings.service.d.ts +129 -0
  87. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -0
  88. package/dist/modules/settings/deployment-settings.service.js +368 -0
  89. package/dist/modules/settings/deployment-settings.service.js.map +1 -0
  90. package/dist/modules/settings/setup.routes.d.ts +49 -0
  91. package/dist/modules/settings/setup.routes.d.ts.map +1 -0
  92. package/dist/modules/settings/setup.routes.js +292 -0
  93. package/dist/modules/settings/setup.routes.js.map +1 -0
  94. package/dist/modules/skills/index.d.ts +2 -1
  95. package/dist/modules/skills/index.d.ts.map +1 -1
  96. package/dist/modules/skills/index.js +1 -0
  97. package/dist/modules/skills/index.js.map +1 -1
  98. package/dist/modules/skills/pending-skills.service.d.ts +40 -0
  99. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -0
  100. package/dist/modules/skills/pending-skills.service.js +147 -0
  101. package/dist/modules/skills/pending-skills.service.js.map +1 -0
  102. package/dist/modules/skills/skills.contract.d.ts +32 -3
  103. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  104. package/dist/modules/skills/skills.contract.js +1 -1
  105. package/dist/modules/skills/skills.routes.d.ts +3 -2
  106. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  107. package/dist/modules/skills/skills.routes.js +18 -1
  108. package/dist/modules/skills/skills.routes.js.map +1 -1
  109. package/dist/modules/skills/skills.service.d.ts +22 -0
  110. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  111. package/dist/modules/skills/skills.service.js +24 -12
  112. package/dist/modules/skills/skills.service.js.map +1 -1
  113. package/dist/modules/tool-manuals/index.d.ts +1 -1
  114. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  115. package/dist/modules/tool-manuals/index.js.map +1 -1
  116. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +47 -2
  117. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  118. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  119. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +4 -0
  120. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  121. package/dist/modules/tool-manuals/tool-manuals.routes.js +23 -0
  122. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  123. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +3 -1
  124. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  125. package/dist/modules/tool-manuals/tool-manuals.service.js +84 -17
  126. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  127. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  128. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -2
  129. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  130. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  131. package/dist/modules/workflow/agent-tools/workflow.tools.js +4 -2
  132. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  133. package/dist/modules/workflow/event-bus.d.ts +11 -0
  134. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  135. package/dist/modules/workflow/event-bus.js +22 -0
  136. package/dist/modules/workflow/event-bus.js.map +1 -1
  137. package/dist/modules/workflow/file-lock.service.d.ts +9 -0
  138. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  139. package/dist/modules/workflow/file-lock.service.js +16 -0
  140. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  141. package/dist/modules/workflow/git/branch-name.d.ts +1 -1
  142. package/dist/modules/workflow/git/branch-name.d.ts.map +1 -1
  143. package/dist/modules/workflow/git/branch-name.js +1 -1
  144. package/dist/modules/workflow/git/branch-name.js.map +1 -1
  145. package/dist/modules/workflow/git/git.service.d.ts +31 -2
  146. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  147. package/dist/modules/workflow/git/git.service.js +144 -52
  148. package/dist/modules/workflow/git/git.service.js.map +1 -1
  149. package/dist/modules/workflow/git/pull-request.service.d.ts +12 -6
  150. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  151. package/dist/modules/workflow/git/pull-request.service.js +19 -9
  152. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  153. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  154. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  155. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  156. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  157. package/dist/modules/workflow/workflow.routes.js +95 -14
  158. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  159. package/dist/modules/workflow/workflow.service.d.ts +92 -3
  160. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  161. package/dist/modules/workflow/workflow.service.js +381 -108
  162. package/dist/modules/workflow/workflow.service.js.map +1 -1
  163. package/dist/modules/workspace/bevel-ignore.d.ts +5 -0
  164. package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -1
  165. package/dist/modules/workspace/bevel-ignore.js +5 -1
  166. package/dist/modules/workspace/bevel-ignore.js.map +1 -1
  167. package/dist/modules/workspace/kb-seed.interface.d.ts +1 -1
  168. package/dist/modules/workspace/kb-seed.service.d.ts +60 -5
  169. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  170. package/dist/modules/workspace/kb-seed.service.js +228 -51
  171. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  172. package/dist/modules/workspace/workspace.routes.d.ts +2 -1
  173. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  174. package/dist/modules/workspace/workspace.routes.js +51 -18
  175. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  176. package/dist/modules/workspace/workspace.service.d.ts +16 -4
  177. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  178. package/dist/modules/workspace/workspace.service.js +36 -9
  179. package/dist/modules/workspace/workspace.service.js.map +1 -1
  180. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  181. package/dist/modules/workspace/workspace.tools.js +8 -3
  182. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  183. package/dist/shared/kb-layout.test.js +2 -2
  184. package/dist/test-setup.d.ts +2 -0
  185. package/dist/test-setup.d.ts.map +1 -0
  186. package/dist/test-setup.js +18 -0
  187. package/dist/test-setup.js.map +1 -0
  188. package/kb-template/.bevelignore +6 -2
  189. package/kb-template/AGENTS.md +272 -0
  190. package/kb-template/KnowledgeBase/How to get started.md +65 -0
  191. package/kb-template/access.md +36 -36
  192. package/migrations/0002_user_onboarding_done.sql +1 -0
  193. package/migrations/0003_deployment_settings.sql +9 -0
  194. package/migrations/meta/0001_snapshot.json +1486 -0
  195. package/migrations/meta/0002_snapshot.json +1493 -0
  196. package/migrations/meta/0003_snapshot.json +1552 -0
  197. package/migrations/meta/_journal.json +15 -1
  198. package/package.json +5 -4
  199. package/src/__tests__/branch-model.test.ts +79 -0
  200. package/src/__tests__/core-config.admin.test.ts +114 -0
  201. package/src/__tests__/database-url.test.ts +70 -0
  202. package/src/core/core-ports.ts +15 -0
  203. package/src/core/create-core-server.ts +55 -6
  204. package/src/core/create-core-services.ts +201 -36
  205. package/src/core-config.ts +139 -59
  206. package/src/modules/access/__tests__/access-declarations.test.ts +195 -0
  207. package/src/modules/access/__tests__/access-md-format.test.ts +191 -0
  208. package/src/modules/access/__tests__/access-splice.test.ts +21 -0
  209. package/src/modules/access/__tests__/access.routes.overrides.test.ts +214 -0
  210. package/src/modules/access/access-control.interface.ts +11 -0
  211. package/src/modules/access/access-control.service.ts +113 -15
  212. package/src/modules/access/access-declarations.ts +251 -0
  213. package/src/modules/access/access-mutation.service.ts +4 -4
  214. package/src/modules/access/access-splice.ts +64 -21
  215. package/src/modules/access/access.routes.ts +61 -0
  216. package/src/modules/access/creator-access.ts +22 -2
  217. package/src/modules/admin/admin-access.service.ts +4 -3
  218. package/src/modules/auth/__tests__/auth.service.test.ts +57 -0
  219. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +147 -0
  220. package/src/modules/auth/auth.routes.ts +50 -1
  221. package/src/modules/auth/auth.service.ts +55 -17
  222. package/src/modules/database/core-schema.ts +46 -0
  223. package/src/modules/groups/__tests__/group-index.service.test.ts +225 -0
  224. package/src/modules/groups/__tests__/group-provision.service.test.ts +247 -0
  225. package/src/modules/groups/__tests__/groups.routes.test.ts +493 -0
  226. package/src/modules/groups/__tests__/join-proposals.test.ts +101 -0
  227. package/src/modules/groups/__tests__/join-requests.service.test.ts +175 -0
  228. package/src/modules/groups/group-provision.service.ts +328 -0
  229. package/src/modules/groups/groups.contract.ts +107 -0
  230. package/src/modules/groups/groups.routes.ts +433 -0
  231. package/src/modules/groups/groups.service.ts +193 -0
  232. package/src/modules/groups/index.ts +17 -0
  233. package/src/modules/groups/join-proposals.ts +114 -0
  234. package/src/modules/groups/join-requests.service.ts +185 -0
  235. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +283 -0
  236. package/src/modules/secrets-vault/secrets-vault.routes.ts +68 -12
  237. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +256 -0
  238. package/src/modules/settings/__tests__/setup.routes.test.ts +201 -0
  239. package/src/modules/settings/deployment-settings.service.ts +419 -0
  240. package/src/modules/settings/setup.routes.ts +317 -0
  241. package/src/modules/skills/__tests__/pending-skills.service.test.ts +240 -0
  242. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  243. package/src/modules/skills/index.ts +3 -0
  244. package/src/modules/skills/pending-skills.service.ts +168 -0
  245. package/src/modules/skills/skills.contract.ts +34 -3
  246. package/src/modules/skills/skills.routes.ts +23 -2
  247. package/src/modules/skills/skills.service.ts +24 -16
  248. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +262 -0
  249. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  250. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +61 -23
  251. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -156
  252. package/src/modules/tool-manuals/index.ts +2 -0
  253. package/src/modules/tool-manuals/tool-manuals.contract.ts +50 -2
  254. package/src/modules/tool-manuals/tool-manuals.routes.ts +21 -0
  255. package/src/modules/tool-manuals/tool-manuals.service.ts +86 -17
  256. package/src/modules/tool-manuals/tool-manuals.tools.ts +206 -202
  257. package/src/modules/workflow/__tests__/event-bus.test.ts +35 -0
  258. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +251 -5
  259. package/src/modules/workflow/agent-tools/workflow.tools.ts +4 -2
  260. package/src/modules/workflow/event-bus.ts +25 -0
  261. package/src/modules/workflow/file-lock.service.ts +17 -0
  262. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +9 -49
  263. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +101 -0
  264. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +219 -211
  265. package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +132 -0
  266. package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +4 -2
  267. package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +275 -272
  268. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +5 -0
  269. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +44 -0
  270. package/src/modules/workflow/git/branch-name.ts +5 -1
  271. package/src/modules/workflow/git/git.service.ts +159 -62
  272. package/src/modules/workflow/git/pull-request.service.ts +22 -11
  273. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +4 -1
  274. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  275. package/src/modules/workflow/workflow.routes.ts +91 -13
  276. package/src/modules/workflow/workflow.service.ts +353 -13
  277. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +255 -24
  278. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -233
  279. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -176
  280. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -303
  281. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -276
  282. package/src/modules/workspace/__tests__/workspace.tools.test.ts +14 -6
  283. package/src/modules/workspace/bevel-ignore.ts +66 -62
  284. package/src/modules/workspace/kb-seed.interface.ts +1 -1
  285. package/src/modules/workspace/kb-seed.service.ts +247 -53
  286. package/src/modules/workspace/workspace.routes.ts +54 -18
  287. package/src/modules/workspace/workspace.service.ts +45 -7
  288. package/src/modules/workspace/workspace.tools.ts +922 -917
  289. package/src/shared/__tests__/frontmatter-id.test.ts +53 -0
  290. package/src/shared/__tests__/join-request.test.ts +96 -0
  291. package/src/shared/__tests__/kb-layout.group.test.ts +45 -0
  292. package/src/shared/kb-layout.test.ts +2 -2
  293. package/src/test-setup.ts +18 -0
  294. package/kb-template/Agents/README.md +0 -38
  295. package/kb-template/CLAUDE.md +0 -419
  296. package/kb-template/Data/.gitkeep +0 -0
  297. package/kb-template/Data/README.md +0 -39
  298. package/kb-template/KnowledgeBase/README.md +0 -37
  299. package/kb-template/Pipelines/.gitkeep +0 -0
  300. package/kb-template/Pipelines/README.md +0 -38
  301. package/kb-template/Skills/.gitkeep +0 -0
  302. package/kb-template/Skills/README.md +0 -32
  303. package/kb-template/Tools/.gitkeep +0 -0
  304. package/kb-template/Tools/README.md +0 -42
  305. /package/kb-template/{Agents → Groups}/.gitkeep +0 -0
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import fs from 'node:fs/promises';
@@ -28,8 +28,20 @@ async function git(cwd: string, args: string[]): Promise<string> {
28
28
  return stdout.toString();
29
29
  }
30
30
 
31
- function makeSeeder(upstream: string, admins: readonly string[] = ADMINS): KbSeedService {
32
- return new KbSeedService(upstream, TEMPLATE_DIR, PROTECTED, DEFAULT_BRANCH, admins);
31
+ function makeSeeder(
32
+ upstream: string,
33
+ admins: readonly string[] = ADMINS,
34
+ extraDirs: readonly string[] = [],
35
+ ): KbSeedService {
36
+ return new KbSeedService(
37
+ upstream,
38
+ TEMPLATE_DIR,
39
+ PROTECTED,
40
+ DEFAULT_BRANCH,
41
+ admins,
42
+ 'x-access-token',
43
+ extraDirs,
44
+ );
33
45
  }
34
46
 
35
47
  /** Clone a branch of the upstream into a fresh checkout for inspection. */
@@ -102,12 +114,18 @@ describe('KbSeedService', () => {
102
114
  for (const branch of PROTECTED) {
103
115
  const dir = await checkout(root, upstream, branch);
104
116
  expect(await exists(path.join(dir, 'access.md'))).toBe(true);
105
- expect(await exists(path.join(dir, 'CLAUDE.md'))).toBe(true);
117
+ expect(await exists(path.join(dir, 'AGENTS.md'))).toBe(true);
106
118
  expect(await exists(path.join(dir, '.bevelignore'))).toBe(true);
107
119
  expect(await exists(path.join(dir, '.gitignore'))).toBe(true);
108
- // The well-known KB roots are seeded (kept present via their .gitkeep).
120
+ // Core's two roots are seeded (kept present via their .gitkeep).
109
121
  expect(await exists(path.join(dir, 'KnowledgeBase/.gitkeep'))).toBe(true);
110
- expect(await exists(path.join(dir, 'Skills/.gitkeep'))).toBe(true);
122
+ expect(await exists(path.join(dir, 'Groups/.gitkeep'))).toBe(true);
123
+ // …and only those two. `Data/`, `Agents/` and `Pipelines/` scaffold an
124
+ // agentic execution layer this platform does not have, so seeding them
125
+ // would hand every operator three empty folders nothing can fill.
126
+ for (const enterpriseOnly of ['Data', 'Agents', 'Pipelines']) {
127
+ expect(await exists(path.join(dir, enterpriseOnly))).toBe(false);
128
+ }
111
129
 
112
130
  const roles = await fs.readFile(path.join(dir, 'roles.yaml'), 'utf8');
113
131
  for (const email of ADMINS) expect(roles).toContain(email);
@@ -116,10 +134,14 @@ describe('KbSeedService', () => {
116
134
  }
117
135
  });
118
136
 
119
- it('throws when no admin emails are configured', async () => {
137
+ // Unreachable through the app — `ADMIN_EMAIL` is required at boot, so the
138
+ // list always has exactly one entry. Kept as a guard on the seeder's own
139
+ // contract: a KB seeded with no Admin cannot resolve access at all, and
140
+ // silently writing an Admin-less roles.yaml would be worse than refusing.
141
+ it('refuses to seed an empty remote with no initial Admin', async () => {
120
142
  const upstream = await emptyUpstream();
121
143
  await expect(makeSeeder(upstream, []).ensureRemoteSeeded()).rejects.toThrow(
122
- /SEED_ADMIN_EMAILS/,
144
+ /no initial Admin/,
123
145
  );
124
146
  });
125
147
  });
@@ -130,11 +152,11 @@ describe('KbSeedService', () => {
130
152
  const upstream = await seededUpstream([DEFAULT_BRANCH], {
131
153
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
132
154
  'access.md': 'existing',
133
- 'CLAUDE.md': 'existing',
155
+ 'AGENTS.md': 'existing',
134
156
  '.bevelignore': 'x',
135
157
  '.gitignore': 'x',
136
158
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
137
- 'Skills/.gitkeep': '',
159
+ 'Groups/.gitkeep': '',
138
160
  });
139
161
 
140
162
  await makeSeeder(upstream).ensureRemoteSeeded();
@@ -148,9 +170,9 @@ describe('KbSeedService', () => {
148
170
  // roles.yaml + access.md already exist with custom content — must survive.
149
171
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
150
172
  'access.md': 'CUSTOM ACCESS RULES',
151
- // CLAUDE.md / .bevelignore / .gitignore are absent → should be added.
173
+ // AGENTS.md / .bevelignore / .gitignore are absent → should be added.
152
174
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
153
- 'Skills/.gitkeep': '',
175
+ 'Groups/.gitkeep': '',
154
176
  });
155
177
 
156
178
  // Simulate the app loading the branch: a fresh clone, then top-up on it.
@@ -163,7 +185,7 @@ describe('KbSeedService', () => {
163
185
  expect(await fs.readFile(path.join(dir, 'access.md'), 'utf8')).toBe('CUSTOM ACCESS RULES');
164
186
  expect(await fs.readFile(path.join(dir, 'roles.yaml'), 'utf8')).toContain('keep@example.com');
165
187
  // Missing scaffolding added.
166
- expect(await exists(path.join(dir, 'CLAUDE.md'))).toBe(true);
188
+ expect(await exists(path.join(dir, 'AGENTS.md'))).toBe(true);
167
189
  expect(await exists(path.join(dir, '.bevelignore'))).toBe(true);
168
190
  // Sample content is NOT restored — the real KnowledgeBase already had content,
169
191
  // so no placeholder .gitkeep is dropped into it, and no ExampleOntology appears.
@@ -171,11 +193,68 @@ describe('KbSeedService', () => {
171
193
  expect(await exists(path.join(dir, 'KnowledgeBase/ExampleOntology'))).toBe(false);
172
194
  });
173
195
 
196
+ it('replaces an edited AGENTS.md with the current template — the file is managed', async () => {
197
+ const upstream = await seededUpstream(PROTECTED, {
198
+ 'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
199
+ 'AGENTS.md': '# my own conventions\n',
200
+ '.bevelignore': 'AGENTS.md\n',
201
+ '.gitignore': '',
202
+ 'access.md': 'CUSTOM ACCESS RULES',
203
+ 'KnowledgeBase/Real/Knowledge/.gitkeep': '',
204
+ 'Groups/.gitkeep': '',
205
+ });
206
+
207
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
208
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
209
+
210
+ const dir = await checkout(root, upstream, DEFAULT_BRANCH);
211
+ const agents = await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8');
212
+ // The template's copy, not the edit — and the template says WHY, so the
213
+ // overwrite is a stated contract rather than a surprise.
214
+ expect(agents).not.toContain('my own conventions');
215
+ expect(agents).toContain('managed by the platform');
216
+ // Everything the platform does NOT manage is preserved verbatim.
217
+ expect(await fs.readFile(path.join(dir, 'access.md'), 'utf8')).toBe('CUSTOM ACCESS RULES');
218
+ const log = await git(dir, ['log', '-1', '--pretty=%s']);
219
+ expect(log.trim()).toBe('Update AGENTS.md to the current platform template');
220
+ });
221
+
222
+ it('a current AGENTS.md makes no refresh commit, even with CRLF endings', async () => {
223
+ const template = await fs.readFile(
224
+ path.join(__dirname, '../../../../kb-template/AGENTS.md'),
225
+ 'utf8',
226
+ );
227
+ const upstream = await seededUpstream(PROTECTED, {
228
+ 'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
229
+ // Same content, Windows endings — must read as "same", or the managed
230
+ // refresh would commit churn on every boot forever.
231
+ //
232
+ // `\r?\n`, not `\n`: on a Windows checkout (`core.autocrlf=true`) the
233
+ // template already HAS CRLF, so converting every `\n` would produce
234
+ // `\r\r\n` — a file that differs from the template for real, failing
235
+ // this test for a reason that has nothing to do with what it asserts.
236
+ // Matching the optional `\r` normalizes first, so the fixture is
237
+ // exactly-CRLF on every platform.
238
+ 'AGENTS.md': template.replace(/\r?\n/g, '\r\n'),
239
+ '.bevelignore': 'AGENTS.md\n',
240
+ '.gitignore': '',
241
+ 'access.md': 'CUSTOM ACCESS RULES',
242
+ 'KnowledgeBase/Real/Knowledge/.gitkeep': '',
243
+ 'Groups/.gitkeep': '',
244
+ });
245
+
246
+ const first = await checkout(root, upstream, DEFAULT_BRANCH);
247
+ await makeSeeder(upstream).topUpWorkspace(first, DEFAULT_BRANCH);
248
+ const after = await headCommitCount(root, upstream, DEFAULT_BRANCH);
249
+ // Only the pre-seed commit: nothing was missing, nothing was stale.
250
+ expect(after).toBe(1);
251
+ });
252
+
174
253
  it('topUpWorkspace is idempotent — a second load makes no new commit', async () => {
175
254
  const upstream = await seededUpstream(PROTECTED, {
176
255
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
177
256
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
178
- 'Skills/.gitkeep': '',
257
+ 'Groups/.gitkeep': '',
179
258
  });
180
259
 
181
260
  const first = await checkout(root, upstream, DEFAULT_BRANCH);
@@ -191,23 +270,175 @@ describe('KbSeedService', () => {
191
270
  expect(after2).toBe(after1);
192
271
  });
193
272
 
194
- it('topUpWorkspace fills scaffolding on a non-protected feature branch too', async () => {
273
+ });
274
+
275
+ /**
276
+ * A distribution that owns folders core has no feature for — the agentic
277
+ * execution layer's `Data/`, `Agents/`, `Pipelines/` — claims them through
278
+ * `extraDirs` (wired from `CorePorts.kbExtraRootDirs`).
279
+ *
280
+ * The point is that claiming a root does NOT require forking the packaged
281
+ * template: `TEMPLATE_DIR` here is core's, which carries no entry for any of
282
+ * these, and the folders still arrive. Their `.gitkeep` is written rather
283
+ * than copied for exactly that reason.
284
+ */
285
+ /**
286
+ * A knowledge base seeded before AGENTS.md was named that: top-up adds the
287
+ * file, and the `.bevelignore` already there lists only the old name — so
288
+ * without this the conventions doc starts showing in the file tree and the
289
+ * agent view on every existing deployment. We created the mismatch by adding
290
+ * the file, so top-up closes it.
291
+ */
292
+ describe('the pre-rename ignore file', () => {
293
+ const legacy = (ignore: string): Record<string, string> => ({
294
+ 'KnowledgeBase/.gitkeep': '',
295
+ 'Groups/.gitkeep': '',
296
+ 'access.md': '---\nwrite:\n - Admin\n---\n',
297
+ 'roles.yaml': 'roles:\n Admin:\n - a@example.com\n',
298
+ '.gitignore': '',
299
+ '.bevelignore': ignore,
300
+ });
301
+
302
+ it('learns about AGENTS.md when top-up adds one', async () => {
303
+ const upstream = await seededUpstream([DEFAULT_BRANCH], {
304
+ ...legacy('# mine\n.gitignore\nCLAUDE.md\nMy-Own-Rule/\n'),
305
+ 'CLAUDE.md': '# conventions',
306
+ });
307
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
308
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
309
+
310
+ const dir = await checkout(root, upstream, DEFAULT_BRANCH);
311
+ expect(await exists(path.join(dir, 'AGENTS.md'))).toBe(true);
312
+ const ignore = await fs.readFile(path.join(dir, '.bevelignore'), 'utf8');
313
+ expect(ignore.split('\n').map((l) => l.trim())).toContain('AGENTS.md');
314
+ // The operator's own rules survive — this appends, it does not rewrite.
315
+ expect(ignore).toContain('My-Own-Rule/');
316
+ expect(ignore).toContain('CLAUDE.md');
317
+ });
318
+
319
+ it('is left alone when it already knows the pattern', async () => {
320
+ const upstream = await seededUpstream([DEFAULT_BRANCH], legacy('AGENTS.md\n'));
321
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
322
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
323
+
324
+ const dir = await checkout(root, upstream, DEFAULT_BRANCH);
325
+ const ignore = await fs.readFile(path.join(dir, '.bevelignore'), 'utf8');
326
+ expect(ignore.split('\n').filter((l) => l.trim() === 'AGENTS.md')).toHaveLength(1);
327
+ });
328
+ });
329
+
330
+ describe('distribution-reserved roots', () => {
331
+ it('creates extra roots that the template knows nothing about', async () => {
332
+ const upstream = await emptyUpstream();
333
+ await makeSeeder(upstream, ADMINS, ['Data', 'Agents', 'Pipelines']).ensureRemoteSeeded();
334
+
335
+ const dir = await checkout(root, upstream, DEFAULT_BRANCH);
336
+ // Core's two, still there.
337
+ expect(await exists(path.join(dir, 'KnowledgeBase/.gitkeep'))).toBe(true);
338
+ expect(await exists(path.join(dir, 'Groups/.gitkeep'))).toBe(true);
339
+ // …and the three this distribution asked for.
340
+ for (const claimed of ['Data', 'Agents', 'Pipelines']) {
341
+ expect(await exists(path.join(dir, `${claimed}/.gitkeep`))).toBe(true);
342
+ }
343
+ });
344
+
345
+ /**
346
+ * The name is joined onto the repo root and onto `<dir>/.gitkeep`, so a
347
+ * separator or a `..` would write outside the repo being seeded. Refused
348
+ * at CONSTRUCTION, so a bad value surfaces at boot rather than part-way
349
+ * through seeding somebody's knowledge base.
350
+ */
351
+ it.each(['../escape', 'nested/dir', '/absolute', '..', '.', ''])(
352
+ 'refuses %j as a reserved root',
353
+ (bad) => {
354
+ expect(() => makeSeeder('unused', ADMINS, [bad])).toThrow(/single path segment/);
355
+ },
356
+ );
357
+
358
+ /**
359
+ * Being reserved is what makes a name worth claiming — the file tree
360
+ * renders it as its own root instead of folding it into Knowledge — so the
361
+ * names in `kb-layout.ts` must stay ACCEPTED here. A guard that rejected
362
+ * them would reject the only real use of this argument.
363
+ */
364
+ /**
365
+ * A root named after a required FILE is a typo whose outcome is silence:
366
+ * the file is laid down first in both seed paths, so `ensureRequiredDirs`
367
+ * finds the path taken and skips it, and the directory never appears with
368
+ * nothing said about why. Refused at construction instead.
369
+ */
370
+ it.each(['access.md', 'AGENTS.md', '.bevelignore', '.gitignore'])(
371
+ 'refuses %j, which is a required file rather than a root',
372
+ (collide) => {
373
+ expect(() => makeSeeder('unused', ADMINS, [collide])).toThrow(/collides with a required file/);
374
+ },
375
+ );
376
+
377
+ it('accepts the reserved names a distribution actually claims', () => {
378
+ expect(() => makeSeeder('unused', ADMINS, ['Data', 'Agents', 'Pipelines'])).not.toThrow();
379
+ });
380
+
381
+ /**
382
+ * A FILE named `Groups` used to satisfy the presence check — `fs.access`
383
+ * answers "is there something here?" — so the seeder skipped it, said
384
+ * nothing, and left a knowledge base permanently missing a root it claims
385
+ * to guarantee. Now it says so.
386
+ *
387
+ * Surfaces as a warning rather than a crash on this path: `topUpWorkspace`
388
+ * is documented best-effort and must never block a user from loading a
389
+ * branch. The point is that the condition is REPORTED at all.
390
+ */
391
+ it('refuses a required root that exists as a file, instead of skipping it', async () => {
392
+ const upstream = await seededUpstream([DEFAULT_BRANCH], {
393
+ 'AGENTS.md': '# x',
394
+ 'access.md': 'read:\n - everyone\n',
395
+ '.bevelignore': '',
396
+ '.gitignore': '',
397
+ 'KnowledgeBase/.gitkeep': '',
398
+ // Not a folder.
399
+ Groups: 'this is a file, not the groups root',
400
+ });
401
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
402
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
403
+ try {
404
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
405
+ expect(warn.mock.calls.flat().join(' ')).toMatch(/Groups.*not a directory/);
406
+ } finally {
407
+ warn.mockRestore();
408
+ }
409
+ // …and it did not quietly replace the file either.
410
+ expect((await fs.lstat(path.join(repoDir, 'Groups'))).isFile()).toBe(true);
411
+ });
412
+
413
+ /**
414
+ * A non-protected branch is somebody's change-in-waiting — a suggestions
415
+ * branch behind an open change request, a personal draft — and a
416
+ * scaffolding commit landing on it shows up as noise in their diff
417
+ * against the default branch (a stray scaffolding file riding along in a
418
+ * skill proposal was the observed symptom). Whatever the protected
419
+ * branches lack, they get when THEY load; drafts fork from them.
420
+ */
421
+ it('topUpWorkspace leaves non-protected branches alone', async () => {
195
422
  const upstream = await seededUpstream(PROTECTED, {
196
423
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
197
424
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
198
- 'Skills/.gitkeep': '',
425
+ 'Groups/.gitkeep': '',
199
426
  });
200
- // Branch a feature off the default and load it.
427
+ // Branch a suggestion off the default and load it.
201
428
  const seed = await checkout(root, upstream, DEFAULT_BRANCH);
202
- await git(seed, ['checkout', '-b', 'alice/feature']);
203
- await git(seed, ['push', 'origin', 'alice/feature']);
429
+ await git(seed, ['checkout', '-b', 'suggestions/alice/knowledge']);
430
+ await git(seed, ['push', 'origin', 'suggestions/alice/knowledge']);
204
431
 
205
- const repoDir = await checkout(root, upstream, 'alice/feature');
206
- await makeSeeder(upstream).topUpWorkspace(repoDir, 'alice/feature');
432
+ const before = await headCommitCount(root, upstream, 'suggestions/alice/knowledge');
433
+ const repoDir = await checkout(root, upstream, 'suggestions/alice/knowledge');
434
+ await makeSeeder(upstream).topUpWorkspace(repoDir, 'suggestions/alice/knowledge');
207
435
 
208
- const dir = await checkout(root, upstream, 'alice/feature');
209
- expect(await exists(path.join(dir, 'CLAUDE.md'))).toBe(true);
210
- expect(await exists(path.join(dir, 'access.md'))).toBe(true);
436
+ // No scaffolding commit: the branch's diff against the default branch
437
+ // stays exactly what its author put there.
438
+ const after = await headCommitCount(root, upstream, 'suggestions/alice/knowledge');
439
+ expect(after).toBe(before);
440
+ const dir = await checkout(root, upstream, 'suggestions/alice/knowledge');
441
+ expect(await exists(path.join(dir, 'AGENTS.md'))).toBe(false);
211
442
  });
212
443
  });
213
444
  });