@bevel-software/platform-core-backend 0.7.5 → 0.9.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 (282) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +8 -1
  4. package/dist/core/core-ports.d.ts.map +1 -1
  5. package/dist/core/core-ports.js.map +1 -1
  6. package/dist/core/create-core-server.d.ts.map +1 -1
  7. package/dist/core/create-core-server.js +75 -18
  8. package/dist/core/create-core-server.js.map +1 -1
  9. package/dist/core/create-core-services.d.ts +12 -5
  10. package/dist/core/create-core-services.d.ts.map +1 -1
  11. package/dist/core/create-core-services.js +69 -36
  12. package/dist/core/create-core-services.js.map +1 -1
  13. package/dist/modules/access/access-control.interface.d.ts +8 -7
  14. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.d.ts +1 -1
  16. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  17. package/dist/modules/access/access-control.service.js +6 -6
  18. package/dist/modules/access/access-control.service.js.map +1 -1
  19. package/dist/modules/access/access-declarations.d.ts +5 -5
  20. package/dist/modules/access/access-declarations.js +3 -3
  21. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  22. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  23. package/dist/modules/access/access-mutation.service.js +3 -3
  24. package/dist/modules/access/access-mutation.service.js.map +1 -1
  25. package/dist/modules/access/access-splice.js +4 -4
  26. package/dist/modules/access/access-splice.js.map +1 -1
  27. package/dist/modules/access/access.routes.d.ts +3 -1
  28. package/dist/modules/access/access.routes.d.ts.map +1 -1
  29. package/dist/modules/access/access.routes.js +22 -20
  30. package/dist/modules/access/access.routes.js.map +1 -1
  31. package/dist/modules/access/creator-access.d.ts +2 -2
  32. package/dist/modules/access/creator-access.js +5 -5
  33. package/dist/modules/access/creator-access.js.map +1 -1
  34. package/dist/modules/access/render-roles-yaml.d.ts +22 -0
  35. package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
  36. package/dist/modules/access/render-roles-yaml.js +56 -0
  37. package/dist/modules/access/render-roles-yaml.js.map +1 -0
  38. package/dist/modules/access/roles-admin.service.d.ts +33 -5
  39. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  40. package/dist/modules/access/roles-admin.service.js +44 -19
  41. package/dist/modules/access/roles-admin.service.js.map +1 -1
  42. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  43. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  44. package/dist/modules/code-mode/code-mode-names.js +5 -27
  45. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  46. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  47. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  48. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  49. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  50. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  51. package/dist/modules/mcp/mcp.service.js +33 -395
  52. package/dist/modules/mcp/mcp.service.js.map +1 -1
  53. package/dist/modules/plugins/index.d.ts +7 -0
  54. package/dist/modules/plugins/index.d.ts.map +1 -0
  55. package/dist/modules/plugins/index.js +6 -0
  56. package/dist/modules/plugins/index.js.map +1 -0
  57. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  58. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  59. package/dist/modules/plugins/join-proposals.js +67 -0
  60. package/dist/modules/plugins/join-proposals.js.map +1 -0
  61. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  62. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  63. package/dist/modules/plugins/join-requests.service.js +135 -0
  64. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  65. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  66. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  68. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  69. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  70. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.contract.js +36 -0
  72. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  73. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  74. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  75. package/dist/modules/plugins/plugins.routes.js +379 -0
  76. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  77. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  78. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  79. package/dist/modules/plugins/plugins.service.js +172 -0
  80. package/dist/modules/plugins/plugins.service.js.map +1 -0
  81. package/dist/modules/settings/setup.routes.d.ts +10 -1
  82. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  83. package/dist/modules/settings/setup.routes.js +111 -6
  84. package/dist/modules/settings/setup.routes.js.map +1 -1
  85. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  86. package/dist/modules/skills/pending-skills.service.js +7 -7
  87. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  88. package/dist/modules/skills/skills.contract.d.ts +4 -4
  89. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  90. package/dist/modules/skills/skills.contract.js +1 -1
  91. package/dist/modules/skills/skills.service.js +5 -5
  92. package/dist/modules/skills/skills.service.js.map +1 -1
  93. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  94. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  95. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  96. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  97. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  98. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  99. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  100. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  101. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  102. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  103. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  104. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  105. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  106. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  107. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  108. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  109. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  110. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  111. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  112. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  113. package/dist/modules/workflow/git/git.service.js +2 -2
  114. package/dist/modules/workflow/git/git.service.js.map +1 -1
  115. package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
  116. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  117. package/dist/modules/workspace/kb-seed.service.js +43 -10
  118. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  119. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  120. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  121. package/dist/modules/workspace/plugins-migration.js +379 -0
  122. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  123. package/dist/modules/workspace/startup/kb-git.d.ts +23 -0
  124. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
  125. package/dist/modules/workspace/startup/kb-git.js +86 -0
  126. package/dist/modules/workspace/startup/kb-git.js.map +1 -0
  127. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
  128. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
  129. package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
  130. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
  131. package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
  132. package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
  133. package/dist/modules/workspace/startup/on-server-start.js +21 -0
  134. package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
  135. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
  136. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
  137. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
  138. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
  139. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
  140. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
  141. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
  142. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
  143. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
  144. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
  145. package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
  146. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
  147. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
  148. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
  149. package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
  150. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -0
  151. package/dist/modules/workspace/workspace.routes.js +3 -3
  152. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  153. package/dist/modules/workspace/workspace.service.d.ts +0 -9
  154. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  155. package/dist/modules/workspace/workspace.service.js +2 -30
  156. package/dist/modules/workspace/workspace.service.js.map +1 -1
  157. package/dist/shared/kb-layout.test.js +3 -3
  158. package/dist/shared/kb-layout.test.js.map +1 -1
  159. package/dist/shared/utcp-namespace.d.ts +6 -27
  160. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  161. package/dist/shared/utcp-namespace.js +6 -63
  162. package/dist/shared/utcp-namespace.js.map +1 -1
  163. package/dist/shared/variable-refs.d.ts +42 -0
  164. package/dist/shared/variable-refs.d.ts.map +1 -0
  165. package/dist/shared/variable-refs.js +60 -0
  166. package/dist/shared/variable-refs.js.map +1 -0
  167. package/kb-template/.bevelignore +1 -1
  168. package/kb-template/AGENTS.md +88 -35
  169. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  170. package/kb-template/access.md +36 -36
  171. package/kb-template/gitignore.template +16 -0
  172. package/migrations/meta/0000_snapshot.json +1479 -1479
  173. package/package.json +5 -4
  174. package/src/assets.ts +25 -25
  175. package/src/core/core-ports.ts +113 -106
  176. package/src/core/create-core-server.ts +85 -20
  177. package/src/core/create-core-services.ts +83 -42
  178. package/src/index.ts +69 -69
  179. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  180. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  181. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  182. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  183. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  184. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  185. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  186. package/src/modules/access/__tests__/roles-admin.service.test.ts +36 -2
  187. package/src/modules/access/access-control.interface.ts +8 -7
  188. package/src/modules/access/access-control.service.ts +7 -7
  189. package/src/modules/access/access-declarations.ts +5 -5
  190. package/src/modules/access/access-mutation.service.ts +3 -3
  191. package/src/modules/access/access-splice.ts +4 -4
  192. package/src/modules/access/access.routes.ts +23 -20
  193. package/src/modules/access/creator-access.ts +5 -5
  194. package/src/modules/access/render-roles-yaml.ts +65 -0
  195. package/src/modules/access/roles-admin.service.ts +45 -16
  196. package/src/modules/admin/admin-access.routes.ts +29 -29
  197. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  198. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  199. package/src/modules/auth/rate-limit.ts +45 -45
  200. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  201. package/src/modules/code-mode/code-mode-names.ts +10 -36
  202. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  203. package/src/modules/database/connection.ts +15 -15
  204. package/src/modules/database/schema.ts +11 -11
  205. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  206. package/src/modules/mcp/mcp.service.ts +57 -435
  207. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  208. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  209. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  210. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  211. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  212. package/src/modules/plugins/index.ts +17 -0
  213. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  214. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  215. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  216. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  217. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  218. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  219. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  220. package/src/modules/settings/__tests__/setup.routes.test.ts +124 -3
  221. package/src/modules/settings/setup.routes.ts +112 -5
  222. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  223. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  224. package/src/modules/skills/pending-skills.service.ts +7 -7
  225. package/src/modules/skills/skills.contract.ts +4 -4
  226. package/src/modules/skills/skills.service.ts +5 -5
  227. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  228. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  229. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  230. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  231. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  232. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  233. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  234. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  235. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  236. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  237. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  238. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  239. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  240. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  241. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  242. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  243. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  244. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  245. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  246. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  247. package/src/modules/workflow/git/git.service.ts +2 -2
  248. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  249. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  250. package/src/modules/workflow/workflow-hooks.ts +101 -101
  251. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  252. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  253. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  254. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  255. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  256. package/src/modules/workspace/__tests__/workspace.service.test.ts +27 -0
  257. package/src/modules/workspace/bevel-ignore.ts +66 -66
  258. package/src/modules/workspace/session-sink.ts +25 -25
  259. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
  260. package/src/modules/workspace/startup/kb-git.ts +94 -0
  261. package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
  262. package/src/modules/workspace/startup/on-server-start.ts +97 -0
  263. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
  264. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +561 -0
  265. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
  266. package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
  267. package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
  268. package/src/modules/workspace/workspace.routes.ts +3 -3
  269. package/src/modules/workspace/workspace.service.ts +2 -35
  270. package/src/modules/workspace/workspace.tools.ts +922 -922
  271. package/src/shared/__tests__/join-request.test.ts +13 -13
  272. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  273. package/src/shared/kb-layout.test.ts +3 -3
  274. package/src/shared/utcp-namespace.ts +10 -68
  275. package/src/shared/variable-refs.ts +64 -0
  276. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  277. package/src/modules/groups/index.ts +0 -17
  278. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +0 -444
  279. package/src/modules/workspace/kb-seed.interface.ts +0 -36
  280. package/src/modules/workspace/kb-seed.service.ts +0 -555
  281. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  282. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -0,0 +1,492 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { HEXIS_EXTENSION_NS, HEXIS_TOOLS_DIR, LEGACY_GROUPS_DIR, PLUGINS_DIR, PLUGIN_MANIFEST_FILE, PLUGIN_MCP_FILE, PLUGIN_MCP_SCHEMA, PLUGIN_SKILLS_DIR, renderPluginManifest, } from '@bevel-software/platform-shared';
4
+ import { normalizeToolManual } from '../../../tool-manuals/tool-manuals.service.js';
5
+ import { parseOwnAccessEntries } from '../../../access/access-control.service.js';
6
+ import { containsVariableReference } from '../../../../shared/variable-refs.js';
7
+ import { IGNORE_FILENAME } from '../../bevel-ignore.js';
8
+ /**
9
+ * One-way migration of a knowledge base from `Groups/` to the Agent Plugins
10
+ * layout under `Plugins/` (https://agent-plugins.org, v1.0.0), as an
11
+ * {@link OnServerStart} step. (It began life as an in-place module run from
12
+ * the lazy top-up; that path is gone, and this is the only form.) Steps
13
+ * never write: every change is DECLARED on the branch handle
14
+ * (`move`/`write`/`remove`) and the runner applies it, while every read goes
15
+ * against the real, pre-step tree via `repoDir()`.
16
+ *
17
+ * Idempotent: a KB already on the new layout is untouched, and a half-finished
18
+ * run is completed by the next one.
19
+ *
20
+ * What moves — and what CONVERTS:
21
+ *
22
+ * Groups/GTM/access.md → Plugins/GTM/access.md (stays put)
23
+ * Groups/GTM/deploy/SKILL.md → Plugins/GTM/skills/deploy/SKILL.md
24
+ * Groups/GTM/web-search.tool → Plugins/GTM/software.bevel.hexis/tools/web-search.tool
25
+ * Groups/GTM/notion.tool (mcp) → an entry in Plugins/GTM/mcp.json, and the
26
+ * `.tool` file is DELETED — mcp.json is
27
+ * authoritative for MCP servers now.
28
+ * Plugins/GTM/plugin.json (written)
29
+ *
30
+ * The mcp.json entry is keyed by the `.tool`'s manual id: that id is the
31
+ * namespace vault secrets bind to (`<id>_<VAR>`), so keeping it is what keeps
32
+ * every configured secret and completed OAuth grant bound. What mcp.json
33
+ * cannot carry — auth headers with `${VAR}` references, variable declarations,
34
+ * the local-only flag — moves into `plugin.json`'s
35
+ * `extensions["software.bevel.hexis"].mcpServers[<id>]` block, the reverse-DNS
36
+ * namespace the spec reserves for client-specific data. `http`/`inline`
37
+ * manuals still MOVE as `.tool` files: nothing but this platform can run them.
38
+ *
39
+ * Scope: EVERY branch, drafts included and writable (`ctx.allBranches()`) —
40
+ * maintenance applied uniformly at the quiet moment is what keeps a draft's
41
+ * change-request diff down to the user's own changes; a stale draft against a
42
+ * migrated target would diff by the whole rename.
43
+ *
44
+ * Outcomes: a per-manual NOT-converted refusal makes the step `partial` with
45
+ * the reasons (the declared ops for everything else still apply); a branch
46
+ * carrying BOTH roots contributes nothing but a note; everything else is `ok`.
47
+ */
48
+ export class GroupsToPluginsStep {
49
+ name = 'groups-to-plugins';
50
+ async run(ctx) {
51
+ const refusals = [];
52
+ for (const branch of await ctx.allBranches()) {
53
+ await migrateBranch(branch, refusals);
54
+ }
55
+ return refusals.length > 0 ? { outcome: 'partial', reason: refusals.join('; ') } : { outcome: 'ok' };
56
+ }
57
+ }
58
+ // lstat, both helpers: SYMLINKS ARE NOT SUPPORTED IN PLUGINS, anywhere, so a
59
+ // link never counts as the thing it points at — following one here would let
60
+ // a symlinked directory pull files from outside the plugin into the sweep
61
+ // (and delete them from wherever they really live).
62
+ async function exists(p) {
63
+ try {
64
+ await fs.lstat(p);
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ async function isDir(p) {
72
+ try {
73
+ return (await fs.lstat(p)).isDirectory();
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ /**
80
+ * A header value referencing a vault variable rather than carrying a literal.
81
+ * The substitutor's own grammar decides (shared/variable-refs.ts): both
82
+ * spellings it expands — `${VAR}` and bare `$VAR`, digit-leading names
83
+ * included — count, because either one copied into mcp.json would be
84
+ * transmitted verbatim by a conformant client. That means a prose `$5` in a
85
+ * header routes to the non-portable half too: over-classifying costs a header
86
+ * its portability, under-classifying leaks whatever `$5TOKEN` expands to.
87
+ */
88
+ const isCredentialReference = containsVariableReference;
89
+ /** Split a manual's headers into what mcp.json may carry and what may not. */
90
+ function splitHeaders(headers) {
91
+ const literal = {};
92
+ const credential = {};
93
+ for (const [k, v] of Object.entries(headers ?? {})) {
94
+ (isCredentialReference(v) ? credential : literal)[k] = v;
95
+ }
96
+ return { literal, credential };
97
+ }
98
+ /** Read+parse a JSON file, or `null` when absent or unparsable. */
99
+ async function readJson(p) {
100
+ try {
101
+ const parsed = JSON.parse(await fs.readFile(p, 'utf-8'));
102
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
103
+ ? parsed
104
+ : null;
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * Parse a `.tool`; a convertible MCP manual (has a url) parses to a
112
+ * descriptor. A non-candidate (other types, a file that will not parse)
113
+ * is `null` and moves as a plain `.tool`; an MCP manual REFUSED conversion
114
+ * comes back as the reason string, so the migration log distinguishes a
115
+ * deliberately-retained integration from one that silently failed — the
116
+ * same courtesy the manifest-missing refusal already extends.
117
+ */
118
+ async function asMcpManual(abs, repoRel) {
119
+ try {
120
+ const content = await fs.readFile(abs, 'utf-8');
121
+ const d = normalizeToolManual(path.basename(abs).replace(/\.tool$/i, ''), repoRel, content);
122
+ // The mcp.json loader accepts only names it can serve as a namespace and
123
+ // route slug — converting a manual whose id fails that shape would DELETE
124
+ // a working integration and write an entry discovery then skips. Such a
125
+ // manual stays a `.tool`.
126
+ if (d.type !== 'mcp' || !d.url)
127
+ return null;
128
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(d.name)) {
129
+ return `its id "${d.name}" is not a valid mcp.json server name`;
130
+ }
131
+ // Same refusal for the url: the mcp.json loader accepts only a directly
132
+ // parseable http(s) url, so a templated one (`${BASE}/mcp` — legal in a
133
+ // `.tool`, where the substitutor expands it) would convert into an entry
134
+ // discovery then skips, with the source already deleted. And a parseable
135
+ // url carrying `user:pass@` may not land in the PORTABLE file at all —
136
+ // stripping it would break the server, so the manual keeps its `.tool`
137
+ // form, where the credential stays platform-internal.
138
+ let url;
139
+ try {
140
+ url = new URL(d.url);
141
+ }
142
+ catch {
143
+ return 'its url is not directly parseable (a templated url only a .tool can carry)';
144
+ }
145
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
146
+ return `its url scheme "${url.protocol}" cannot land in mcp.json (http(s) only)`;
147
+ }
148
+ if (url.username || url.password) {
149
+ return 'its url embeds credentials, which may not land in the portable mcp.json';
150
+ }
151
+ // A `.tool` can gate ITSELF with frontmatter access verbs, read by the
152
+ // access resolver from the file's own path. An mcp.json entry has no
153
+ // per-server home for those — the file's ACL governs every server in it —
154
+ // so converting would silently widen who may configure and run the
155
+ // server. Such a manual stays a `.tool`, verbs and all.
156
+ if (parseOwnAccessEntries(content) !== null) {
157
+ return 'it gates itself with frontmatter access verbs, which mcp.json cannot carry';
158
+ }
159
+ return d;
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ }
165
+ /**
166
+ * Migrate one branch. Declares ops only; a branch whose migration declares
167
+ * nothing stays clean, so a notes-only pass (advisory refusals) commits
168
+ * nothing — the same contract the in-place migration's `migrated` flag
169
+ * carried.
170
+ */
171
+ async function migrateBranch(branch, refusals) {
172
+ const repoDir = await branch.repoDir();
173
+ const legacyDir = path.join(repoDir, LEGACY_GROUPS_DIR);
174
+ const pluginsDir = path.join(repoDir, PLUGINS_DIR);
175
+ const hasLegacy = await isDir(legacyDir);
176
+ const hasPlugins = await isDir(pluginsDir);
177
+ // `hasPlugins` is false for a FILE or SYMLINK squatting the `Plugins` name.
178
+ // Checked BEFORE the nothing-to-do early return below: a branch with a
179
+ // squatter and NO Groups/ would otherwise be silently skipped (and on a
180
+ // draft nothing later ever reports it), while a branch mid-migration would
181
+ // die in the applier with a bare ENOTDIR. Throw the actionable version
182
+ // either way: under the phase's fail-closed contract this stops the boot,
183
+ // which a squatted reserved root deserves (same stance as
184
+ // template-files.step.ts's reserved-dir check).
185
+ if (!hasPlugins && (await exists(pluginsDir))) {
186
+ throw new Error(`Branch "${branch.name}": "${PLUGINS_DIR}" exists but is not a directory` +
187
+ (hasLegacy ? `, so ${LEGACY_GROUPS_DIR}/ cannot be renamed to ${PLUGINS_DIR}/` : '') +
188
+ `. Remove or rename the "${PLUGINS_DIR}" entry — the platform requires this name to be a folder.`);
189
+ }
190
+ if (hasLegacy && hasPlugins) {
191
+ // Both present: somebody is mid-migration by hand, or two branches merged
192
+ // badly. Merging them here would guess at which copy of a same-named
193
+ // plugin wins, so we refuse and say so — loudly, because the KB is in a
194
+ // state a human needs to look at. The branch contributes no ops, only a
195
+ // note (which surfaces in a commit only if a later step dirties it).
196
+ console.warn(`[groups-to-plugins] ${branch.name}: both ${LEGACY_GROUPS_DIR}/ and ${PLUGINS_DIR}/ exist — leaving both alone. ` +
197
+ `Merge ${LEGACY_GROUPS_DIR}/ into ${PLUGINS_DIR}/ by hand; nothing is being migrated automatically.`);
198
+ branch.note(`${LEGACY_GROUPS_DIR}/ and ${PLUGINS_DIR}/ both exist — merge by hand; nothing was migrated automatically`);
199
+ return;
200
+ }
201
+ if (!hasLegacy && !hasPlugins)
202
+ return;
203
+ // Every read below goes against the PRE-STEP tree: when the root rename is
204
+ // declared this run it is NOT yet on disk, so the plugin folders are still
205
+ // read under their legacy root while every declared op speaks post-rename
206
+ // `Plugins/…` paths — the root move is declared FIRST, so by the time the
207
+ // per-folder ops apply, the tree is already under `Plugins/`.
208
+ const rootOnDisk = hasLegacy ? legacyDir : pluginsDir;
209
+ // Detail notes are held back until we know ops were declared: the subject
210
+ // line must describe a commit that will actually exist.
211
+ const details = [];
212
+ let changed = false;
213
+ if (hasLegacy) {
214
+ // The Groups→Plugins root rename is ONE declared op, directory and all.
215
+ branch.move(LEGACY_GROUPS_DIR, PLUGINS_DIR);
216
+ details.push(`${LEGACY_GROUPS_DIR}/ → ${PLUGINS_DIR}/`);
217
+ changed = true;
218
+ // Rides WITH the rename, not on every run: the rename is what turned the
219
+ // ignore rule stale, so the run that renames is the run that heals it.
220
+ changed = (await rewriteIgnoreRootRule(repoDir, branch, details)) || changed;
221
+ }
222
+ // Runs whether or not the rename just happened, so a KB already on
223
+ // `Plugins/` still gets missing manifests and any half-done reorganisation
224
+ // finished. That is what makes this idempotent rather than once-only.
225
+ for (const entry of await fs.readdir(rootOnDisk, { withFileTypes: true })) {
226
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
227
+ continue;
228
+ const folderChanged = await migratePluginFolder(branch, path.join(rootOnDisk, entry.name), entry.name, details, refusals);
229
+ changed = changed || folderChanged;
230
+ }
231
+ if (!changed)
232
+ return;
233
+ // First note becomes the commit subject — the same messages the lazy
234
+ // top-up committed under; the detail notes become its body.
235
+ branch.note(hasLegacy
236
+ ? `Move ${LEGACY_GROUPS_DIR}/ to ${PLUGINS_DIR}/ (Agent Plugins layout)`
237
+ : `Reorganise ${PLUGINS_DIR}/ to the Agent Plugins layout`);
238
+ for (const line of details)
239
+ branch.note(line);
240
+ }
241
+ /**
242
+ * Rewrite the KB's `.bevelignore` rule for the renamed root: the exact line
243
+ * `Groups/` becomes `Plugins/`. Without this a migrated KB is left with a
244
+ * stale rule for a folder that no longer exists and NO rule for the new one,
245
+ * so plugin internals start showing up in the file tree and agent view.
246
+ *
247
+ * The file is the operator's — this touches ONE line, the one the platform's
248
+ * own rename invalidated, and only when `Plugins/` is not already listed
249
+ * (in which case the stale line is harmlessly dead and left alone).
250
+ */
251
+ async function rewriteIgnoreRootRule(repoDir, branch, details) {
252
+ let current;
253
+ try {
254
+ current = await fs.readFile(path.join(repoDir, IGNORE_FILENAME), 'utf-8');
255
+ }
256
+ catch {
257
+ return false; // no ignore file — nothing went stale
258
+ }
259
+ const legacyRule = `${LEGACY_GROUPS_DIR}/`;
260
+ const newRule = `${PLUGINS_DIR}/`;
261
+ const lines = current.split('\n');
262
+ if (lines.some((l) => l.trim() === newRule))
263
+ return false;
264
+ const idx = lines.findIndex((l) => l.trim() === legacyRule);
265
+ if (idx === -1)
266
+ return false;
267
+ // EVERY exact-match line follows the rename: the first becomes the new
268
+ // rule, any further duplicates are dropped — rewriting only the first would
269
+ // leave stale `Groups/` lines behind, and the already-has-Plugins guard
270
+ // above means a second pass would never touch them.
271
+ lines[idx] = newRule;
272
+ const rewritten = lines.filter((l, i) => i <= idx || l.trim() !== legacyRule);
273
+ branch.write(IGNORE_FILENAME, rewritten.join('\n'));
274
+ details.push(`${IGNORE_FILENAME}: ${legacyRule} → ${newRule}`);
275
+ return true;
276
+ }
277
+ /**
278
+ * Reorganise ONE plugin folder. `folderDir` is where the folder sits ON DISK
279
+ * (under the legacy root when the rename is buffered this run); declared op
280
+ * paths always speak `Plugins/<folder>/…`. Returns whether ops were declared.
281
+ */
282
+ async function migratePluginFolder(branch, folderDir, folderName, details, refusals) {
283
+ const relPlugin = `${PLUGINS_DIR}/${folderName}`;
284
+ let changed = false;
285
+ // When the manifest is written this run its content is remembered: the
286
+ // buffered write is not on disk yet, and the fold below must see it.
287
+ let renderedManifest = null;
288
+ if (!(await exists(path.join(folderDir, PLUGIN_MANIFEST_FILE)))) {
289
+ renderedManifest = renderPluginManifest(folderName);
290
+ branch.write(`${relPlugin}/${PLUGIN_MANIFEST_FILE}`, renderedManifest);
291
+ details.push(`${folderName}: wrote ${PLUGIN_MANIFEST_FILE}`);
292
+ changed = true;
293
+ }
294
+ const entries = await fs.readdir(folderDir, { withFileTypes: true });
295
+ const converted = [];
296
+ const convertOrMove = async (abs, rel, note) => {
297
+ const manual = await asMcpManual(abs, rel);
298
+ if (manual !== null && typeof manual !== 'string') {
299
+ // QUEUED for conversion — the `.tool`'s removal is declared only AFTER
300
+ // its entry's writes (see foldIntoPluginFiles). Buffering makes the
301
+ // apply all-or-nothing anyway, but the declaration order keeps the old
302
+ // recovery reasoning legible: the file IS the recovery path until the
303
+ // fold has landed.
304
+ converted.push({ manual, rel, note });
305
+ return;
306
+ }
307
+ if (typeof manual === 'string') {
308
+ // The operator's answer to "why is this integration not in mcp.json":
309
+ // a refused conversion must read as the deliberate retention it is —
310
+ // surfaced through the step's `partial` reason rather than a note, so
311
+ // it names what did NOT change instead of decorating a commit.
312
+ refusals.push(`${branch.name}: ${folderName}: ${note} NOT converted — ${manual}; kept as a .tool`);
313
+ }
314
+ const destDisk = path.join(folderDir, ...HEXIS_TOOLS_DIR.split('/'), path.basename(abs));
315
+ // Never overwrite: a destination that already exists means a previous run
316
+ // got there first (or a human did), and clobbering it would destroy the
317
+ // newer copy. The check is against the pre-step tree — the only tree that
318
+ // exists while this step runs.
319
+ if (abs !== destDisk && !(await exists(destDisk))) {
320
+ branch.move(rel, `${relPlugin}/${HEXIS_TOOLS_DIR}/${path.basename(abs)}`);
321
+ details.push(`${folderName}: ${note} → ${HEXIS_TOOLS_DIR}/${path.basename(abs)}`);
322
+ changed = true;
323
+ }
324
+ };
325
+ for (const entry of entries) {
326
+ if (entry.name.startsWith('.'))
327
+ continue;
328
+ if (entry.name === PLUGIN_SKILLS_DIR || entry.name === PLUGIN_MANIFEST_FILE)
329
+ continue;
330
+ if (entry.name === PLUGIN_MCP_FILE || entry.name === 'access.md')
331
+ continue;
332
+ if (entry.name === HEXIS_TOOLS_DIR.split('/')[0])
333
+ continue;
334
+ const abs = path.join(folderDir, entry.name);
335
+ // A skill is a folder carrying SKILL.md — the same rule the catalog uses.
336
+ if (entry.isDirectory() && (await exists(path.join(abs, 'SKILL.md')))) {
337
+ if (!(await exists(path.join(folderDir, PLUGIN_SKILLS_DIR, entry.name)))) {
338
+ branch.move(`${relPlugin}/${entry.name}`, `${relPlugin}/${PLUGIN_SKILLS_DIR}/${entry.name}`);
339
+ details.push(`${folderName}: ${entry.name}/ → ${PLUGIN_SKILLS_DIR}/${entry.name}/`);
340
+ changed = true;
341
+ }
342
+ continue;
343
+ }
344
+ if (entry.isFile() && entry.name.toLowerCase().endsWith('.tool')) {
345
+ await convertOrMove(abs, `${relPlugin}/${entry.name}`, entry.name);
346
+ }
347
+ }
348
+ // Second sweep: mcp `.tool`s an EARLIER run moved into the extension dir
349
+ // (when mcp.json was a projection, not the authority). Converting them here
350
+ // is what makes the migration complete itself rather than strand a twin.
351
+ // `isDir` is lstat-based, so a SYMLINK planted at this path is not swept:
352
+ // this sweep DELETES what it converts, and following a link would delete
353
+ // `.tool` files from wherever the link really points.
354
+ //
355
+ // Under buffered ops this sweep reads the PRE-STEP tree, so a `.tool` the
356
+ // first sweep just declared moved here is not visible — and does not need
357
+ // to be: same-run movables were already converted or refused by the same
358
+ // `asMcpManual` logic above. The sweep exists for files parked by EARLIER
359
+ // runs, which are on disk. (The old in-place code happened to re-see
360
+ // same-run moves and no-op on them; the buffered read makes that a
361
+ // non-event by construction.)
362
+ const extToolsDir = path.join(folderDir, ...HEXIS_TOOLS_DIR.split('/'));
363
+ if (await isDir(extToolsDir)) {
364
+ for (const entry of await fs.readdir(extToolsDir, { withFileTypes: true })) {
365
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.tool'))
366
+ continue;
367
+ const abs = path.join(extToolsDir, entry.name);
368
+ const rel = `${relPlugin}/${HEXIS_TOOLS_DIR}/${entry.name}`;
369
+ const manual = await asMcpManual(abs, rel);
370
+ // A refusal reason here is a SETTLED resident of the tools dir — it
371
+ // was named the run it moved in, and this sweep repeats every boot,
372
+ // so re-raising it would be log spam. Only a convertible manual queues.
373
+ if (manual !== null && typeof manual !== 'string') {
374
+ converted.push({ manual, rel, note: `${HEXIS_TOOLS_DIR}/${entry.name}` });
375
+ }
376
+ }
377
+ }
378
+ changed =
379
+ (await foldIntoPluginFiles(branch, folderDir, relPlugin, folderName, renderedManifest, converted, details, refusals)) ||
380
+ changed;
381
+ return changed;
382
+ }
383
+ /**
384
+ * Fold converted mcp manuals into the plugin's mcp.json and plugin.json.
385
+ *
386
+ * MERGE, never clobber: an entry already present under a manual's key — hand
387
+ * written or from a previous run — wins, because overwriting it would discard
388
+ * the newer intent. The extension block merges the same way. A plugin.json
389
+ * that does not parse costs the extension write (logged), not the migration.
390
+ */
391
+ async function foldIntoPluginFiles(branch, folderDir, relPlugin, folderName, renderedManifest, manuals, details, refusals) {
392
+ if (manuals.length === 0)
393
+ return false;
394
+ const mcp = (await readJson(path.join(folderDir, PLUGIN_MCP_FILE))) ?? {
395
+ $schema: PLUGIN_MCP_SCHEMA,
396
+ mcpServers: {},
397
+ };
398
+ // An array (or any non-object) here would take property assignments and then
399
+ // drop them at stringify — normalize to an object before merging into it.
400
+ if (typeof mcp.mcpServers !== 'object' || mcp.mcpServers === null || Array.isArray(mcp.mcpServers)) {
401
+ mcp.mcpServers = {};
402
+ }
403
+ const servers = mcp.mcpServers;
404
+ // The manifest may have been DECLARED this very run — a buffered write not
405
+ // yet on disk. Reading the disk then would see "missing" and refuse
406
+ // conversions the write-then-read in-place code performed, so the step's
407
+ // own rendered content stands in for the tree it is about to produce.
408
+ const manifest = renderedManifest !== null
409
+ ? JSON.parse(renderedManifest)
410
+ : await readJson(path.join(folderDir, PLUGIN_MANIFEST_FILE));
411
+ if (manifest === null) {
412
+ console.warn(`[groups-to-plugins] ${branch.name}: ${folderName}/${PLUGIN_MANIFEST_FILE} is missing or unparsable — ` +
413
+ 'mcp manuals convert only when they carry NOTHING for the extensions block; any ' +
414
+ 'non-portable half (auth headers, variables, a description, or the local-only flag) ' +
415
+ 'keeps the manual a `.tool` until the manifest is fixed.');
416
+ }
417
+ let wroteMcp = false;
418
+ let wroteManifest = false;
419
+ const folded = [];
420
+ for (const item of manuals.sort((a, b) => a.manual.name.localeCompare(b.manual.name))) {
421
+ const m = item.manual;
422
+ const { literal, credential } = splitHeaders(m.headers);
423
+ // A manual whose non-portable half (auth headers, variables, local flag)
424
+ // has nowhere to go — the manifest is missing or unparsable — is NOT
425
+ // converted at all: writing only its portable half and deleting the
426
+ // source would silently discard the credential wiring. It stays a
427
+ // `.tool` until the manifest is fixed.
428
+ const extEntry = {
429
+ ...(Object.keys(credential).length > 0 ? { headers: credential } : {}),
430
+ ...(m.variables && m.variables.length > 0 ? { variables: m.variables } : {}),
431
+ ...(typeof m.description === 'string' ? { description: m.description } : {}),
432
+ ...(m.remote === false ? { local: true } : {}),
433
+ };
434
+ if (manifest === null && Object.keys(extEntry).length > 0) {
435
+ refusals.push(`${branch.name}: ${folderName}: ${item.note} NOT converted — ${PLUGIN_MANIFEST_FILE} is missing/unparsable ` +
436
+ 'and the manual carries declarations (auth headers, variables, a description, or the ' +
437
+ 'local-only flag) that would be lost; fix the manifest first');
438
+ continue;
439
+ }
440
+ folded.push(item);
441
+ // Own-property check: `in` sees `constructor` and friends on the prototype,
442
+ // which would silently skip a legitimately named server.
443
+ if (!Object.prototype.hasOwnProperty.call(servers, m.name)) {
444
+ servers[m.name] = {
445
+ type: 'streamable-http',
446
+ url: m.url,
447
+ ...(Object.keys(literal).length > 0 ? { headers: literal } : {}),
448
+ };
449
+ wroteMcp = true;
450
+ details.push(`${folderName}: ${m.name} → ${PLUGIN_MCP_FILE}`);
451
+ }
452
+ // The non-portable half: auth headers, variable declarations, local-only.
453
+ if (manifest !== null && Object.keys(extEntry).length > 0) {
454
+ // Normalize each level: a parseable manifest can still carry a string or
455
+ // array where an object belongs, and mutating that would throw mid-run.
456
+ if (typeof manifest.extensions !== 'object' || manifest.extensions === null || Array.isArray(manifest.extensions)) {
457
+ manifest.extensions = {};
458
+ }
459
+ const ext = manifest.extensions;
460
+ if (typeof ext[HEXIS_EXTENSION_NS] !== 'object' || ext[HEXIS_EXTENSION_NS] === null || Array.isArray(ext[HEXIS_EXTENSION_NS])) {
461
+ ext[HEXIS_EXTENSION_NS] = {};
462
+ }
463
+ const ns = ext[HEXIS_EXTENSION_NS];
464
+ if (typeof ns.mcpServers !== 'object' || ns.mcpServers === null || Array.isArray(ns.mcpServers)) {
465
+ ns.mcpServers = {};
466
+ }
467
+ const extServers = ns.mcpServers;
468
+ if (!Object.prototype.hasOwnProperty.call(extServers, m.name)) {
469
+ extServers[m.name] = extEntry;
470
+ wroteManifest = true;
471
+ }
472
+ }
473
+ }
474
+ if (wroteMcp) {
475
+ branch.write(`${relPlugin}/${PLUGIN_MCP_FILE}`, `${JSON.stringify(mcp, null, 2)}\n`);
476
+ }
477
+ if (wroteManifest) {
478
+ // May supersede the bare manifest declared above — ops apply in order, so
479
+ // the extended content is what lands.
480
+ branch.write(`${relPlugin}/${PLUGIN_MANIFEST_FILE}`, `${JSON.stringify(manifest, null, 2)}\n`);
481
+ details.push(`${folderName}: wrote mcp-server declarations into ${PLUGIN_MANIFEST_FILE}`);
482
+ }
483
+ // Sources go LAST in declaration order, once everything they carried has
484
+ // been declared elsewhere — the same shape the in-place code used so a
485
+ // failure never stranded the non-portable half with no source to retry from.
486
+ for (const item of folded) {
487
+ branch.remove(item.rel);
488
+ details.push(`${folderName}: ${item.note} converted to an ${PLUGIN_MCP_FILE} entry`);
489
+ }
490
+ return wroteMcp || wroteManifest || folded.length > 0;
491
+ }
492
+ //# sourceMappingURL=groups-to-plugins.step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups-to-plugins.step.js","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/groups-to-plugins.step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,OAAO,mBAAmB;IACrB,IAAI,GAAG,mBAAmB,CAAC;IAEpC,KAAK,CAAC,GAAG,CAAC,GAAuB;QAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,MAAM,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7C,MAAM,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACvG,CAAC;CACF;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,oDAAoD;AACpD,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAExD,8EAA8E;AAC9E,SAAS,YAAY,CAAC,OAA2C;IAI/D,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;QACnD,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACjC,CAAC;AAED,mEAAmE;AACnE,KAAK,UAAU,QAAQ,CAAC,CAAS;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAClE,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAC5E,CAAC,CAAE,MAAkC;YACrC,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAAe;IACrD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5F,yEAAyE;QACzE,0EAA0E;QAC1E,wEAAwE;QACxE,0BAA0B;QAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAC5C,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,OAAO,WAAW,CAAC,CAAC,IAAI,uCAAuC,CAAC;QAClE,CAAC;QACD,wEAAwE;QACxE,wEAAwE;QACxE,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,sDAAsD;QACtD,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,4EAA4E,CAAC;QACtF,CAAC;QACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1D,OAAO,mBAAmB,GAAG,CAAC,QAAQ,0CAA0C,CAAC;QACnF,CAAC;QACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YACjC,OAAO,yEAAyE,CAAC;QACnF,CAAC;QACD,uEAAuE;QACvE,qEAAqE;QACrE,0EAA0E;QAC1E,mEAAmE;QACnE,wDAAwD;QACxD,IAAI,qBAAqB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5C,OAAO,4EAA4E,CAAC;QACtF,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AASD;;;;;GAKG;AACH,KAAK,UAAU,aAAa,CAAC,MAAgB,EAAE,QAAkB;IAC/D,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAEnD,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;IAE3C,4EAA4E;IAC5E,uEAAuE;IACvE,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,0EAA0E;IAC1E,0DAA0D;IAC1D,gDAAgD;IAChD,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,WAAW,MAAM,CAAC,IAAI,OAAO,WAAW,iCAAiC;YACvE,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,iBAAiB,0BAA0B,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,2BAA2B,WAAW,2DAA2D,CACpG,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,IAAI,UAAU,EAAE,CAAC;QAC5B,0EAA0E;QAC1E,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,qEAAqE;QACrE,OAAO,CAAC,IAAI,CACV,uBAAuB,MAAM,CAAC,IAAI,UAAU,iBAAiB,SAAS,WAAW,gCAAgC;YAC/G,SAAS,iBAAiB,UAAU,WAAW,qDAAqD,CACvG,CAAC;QACF,MAAM,CAAC,IAAI,CACT,GAAG,iBAAiB,SAAS,WAAW,kEAAkE,CAC3G,CAAC;QACF,OAAO;IACT,CAAC;IACD,IAAI,CAAC,SAAS,IAAI,CAAC,UAAU;QAAE,OAAO;IAEtC,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,8DAA8D;IAC9D,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IACtD,0EAA0E;IAC1E,wDAAwD;IACxD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,IAAI,SAAS,EAAE,CAAC;QACd,wEAAwE;QACxE,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,GAAG,iBAAiB,OAAO,WAAW,GAAG,CAAC,CAAC;QACxD,OAAO,GAAG,IAAI,CAAC;QACf,yEAAyE;QACzE,uEAAuE;QACvE,OAAO,GAAG,CAAC,MAAM,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC;IAC/E,CAAC;IAED,mEAAmE;IACnE,2EAA2E;IAC3E,sEAAsE;IACtE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjE,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAC7C,MAAM,EACN,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,EACjC,KAAK,CAAC,IAAI,EACV,OAAO,EACP,QAAQ,CACT,CAAC;QACF,OAAO,GAAG,OAAO,IAAI,aAAa,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,qEAAqE;IACrE,4DAA4D;IAC5D,MAAM,CAAC,IAAI,CACT,SAAS;QACP,CAAC,CAAC,QAAQ,iBAAiB,QAAQ,WAAW,0BAA0B;QACxE,CAAC,CAAC,cAAc,WAAW,+BAA+B,CAC7D,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,qBAAqB,CAAC,OAAe,EAAE,MAAgB,EAAE,OAAiB;IACvF,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,sCAAsC;IACtD,CAAC;IACD,MAAM,UAAU,GAAG,GAAG,iBAAiB,GAAG,CAAC;IAC3C,MAAM,OAAO,GAAG,GAAG,WAAW,GAAG,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,UAAU,CAAC,CAAC;IAC5D,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7B,uEAAuE;IACvE,4EAA4E;IAC5E,wEAAwE;IACxE,oDAAoD;IACpD,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IACrB,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,UAAU,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,KAAK,UAAU,MAAM,OAAO,EAAE,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,mBAAmB,CAChC,MAAgB,EAChB,SAAiB,EACjB,UAAkB,EAClB,OAAiB,EACjB,QAAkB;IAElB,MAAM,SAAS,GAAG,GAAG,WAAW,IAAI,UAAU,EAAE,CAAC;IACjD,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAC3C,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,gBAAgB,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,oBAAoB,EAAE,EAAE,gBAAgB,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,WAAW,oBAAoB,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,SAAS,GAAsB,EAAE,CAAC;IAExC,MAAM,aAAa,GAAG,KAAK,EAAE,GAAW,EAAE,GAAW,EAAE,IAAY,EAAiB,EAAE;QACpF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClD,uEAAuE;YACvE,oEAAoE;YACpE,uEAAuE;YACvE,sEAAsE;YACtE,mBAAmB;YACnB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,sEAAsE;YACtE,qEAAqE;YACrE,sEAAsE;YACtE,+DAA+D;YAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,KAAK,UAAU,KAAK,IAAI,oBAAoB,MAAM,mBAAmB,CAAC,CAAC;QACrG,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QACzF,0EAA0E;QAC1E,wEAAwE;QACxE,0EAA0E;QAC1E,+BAA+B;QAC/B,IAAI,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1E,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,IAAI,MAAM,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAClF,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB;YAAE,SAAS;QACtF,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QAC3E,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS;QAE3D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE7C,0EAA0E;QAC1E,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzE,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,SAAS,IAAI,iBAAiB,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC7F,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,KAAK,CAAC,IAAI,OAAO,iBAAiB,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;gBACpF,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACjE,MAAM,aAAa,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,sDAAsD;IACtD,EAAE;IACF,0EAA0E;IAC1E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,qEAAqE;IACrE,mEAAmE;IACnE,8BAA8B;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,IAAI,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/C,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,eAAe,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3C,oEAAoE;YACpE,oEAAoE;YACpE,wEAAwE;YACxE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAClD,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,eAAe,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,CAAC,MAAM,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YACrH,OAAO,CAAC;IACV,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,mBAAmB,CAChC,MAAgB,EAChB,SAAiB,EACjB,SAAiB,EACjB,UAAkB,EAClB,gBAA+B,EAC/B,OAA0B,EAC1B,OAAiB,EACjB,QAAkB;IAElB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI;QACrE,OAAO,EAAE,iBAAiB;QAC1B,UAAU,EAAE,EAAE;KACf,CAAC;IACF,6EAA6E;IAC7E,0EAA0E;IAC1E,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACnG,GAAG,CAAC,UAAU,GAAG,EAAE,CAAC;IACtB,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,UAAqC,CAAC;IAE1D,2EAA2E;IAC3E,oEAAoE;IACpE,yEAAyE;IACzE,sEAAsE;IACtE,MAAM,QAAQ,GACZ,gBAAgB,KAAK,IAAI;QACvB,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAA6B;QAC3D,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACjE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CACV,uBAAuB,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,oBAAoB,8BAA8B;YACrG,iFAAiF;YACjF,qFAAqF;YACrF,yDAAyD,CAC5D,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACtB,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACxD,yEAAyE;QACzE,qEAAqE;QACrE,oEAAoE;QACpE,kEAAkE;QAClE,uCAAuC;QACvC,MAAM,QAAQ,GAAG;YACf,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/C,CAAC;QACF,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CACX,GAAG,MAAM,CAAC,IAAI,KAAK,UAAU,KAAK,IAAI,CAAC,IAAI,oBAAoB,oBAAoB,yBAAyB;gBAC1G,sFAAsF;gBACtF,6DAA6D,CAChE,CAAC;YACF,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,4EAA4E;QAC5E,yDAAyD;QACzD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG;gBAChB,IAAI,EAAE,iBAAiB;gBACvB,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;YACF,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC,IAAI,MAAM,eAAe,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,0EAA0E;QAC1E,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,yEAAyE;YACzE,wEAAwE;YACxE,IAAI,OAAO,QAAQ,CAAC,UAAU,KAAK,QAAQ,IAAI,QAAQ,CAAC,UAAU,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClH,QAAQ,CAAC,UAAU,GAAG,EAAE,CAAC;YAC3B,CAAC;YACD,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAqC,CAAC;YAC3D,IAAI,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,kBAAkB,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC;gBAC9H,GAAG,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC;YAC/B,CAAC;YACD,MAAM,EAAE,GAAG,GAAG,CAAC,kBAAkB,CAA4B,CAAC;YAC9D,IAAI,OAAO,EAAE,CAAC,UAAU,KAAK,QAAQ,IAAI,EAAE,CAAC,UAAU,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChG,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC;YACrB,CAAC;YACD,MAAM,UAAU,GAAG,EAAE,CAAC,UAAqC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9D,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;gBAC9B,aAAa,GAAG,IAAI,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,eAAe,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,aAAa,EAAE,CAAC;QAClB,0EAA0E;QAC1E,sCAAsC;QACtC,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,oBAAoB,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/F,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,wCAAwC,oBAAoB,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,yEAAyE;IACzE,uEAAuE;IACvE,6EAA6E;IAC7E,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,KAAK,IAAI,CAAC,IAAI,oBAAoB,eAAe,QAAQ,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,QAAQ,IAAI,aAAa,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { OnServerStart, ServerStartContext, StepResult } from '../on-server-start.js';
2
+ /**
3
+ * Write `roles.yaml` on any protected branch missing it, generated from the
4
+ * configured seed admins (`ADMIN_EMAIL`) via the shared validated renderer
5
+ * (../../../access/render-roles-yaml.ts — a malformed email stops the boot
6
+ * with a message naming the fix). Never part of the template: a template copy
7
+ * would seed repos with a stale hard-coded Admin list.
8
+ *
9
+ * An existing regular file — whatever it says — is the operator's and is left
10
+ * alone.
11
+ *
12
+ * A missing file with NO admins configured is a declared `skipped`: the lazy
13
+ * top-up warned and left the file absent, and under the startup contract that
14
+ * survivable-but-incomplete state is the step's to declare, not to bury in a
15
+ * log. Access resolution will fail until an Admin roles.yaml exists.
16
+ */
17
+ export declare class RolesYamlStep implements OnServerStart {
18
+ private readonly seedAdminEmails;
19
+ readonly name = "roles-yaml";
20
+ constructor(seedAdminEmails: readonly string[]);
21
+ run(ctx: ServerStartContext): Promise<StepResult>;
22
+ }
23
+ //# sourceMappingURL=roles-yaml.step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles-yaml.step.d.ts","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/roles-yaml.step.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE3F;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAc,YAAW,aAAa;IAGrC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAF5C,QAAQ,CAAC,IAAI,gBAAgB;gBAEA,eAAe,EAAE,SAAS,MAAM,EAAE;IAEzD,GAAG,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;CAoCxD"}
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { renderRolesYaml } from '../../../access/render-roles-yaml.js';
4
+ /**
5
+ * Write `roles.yaml` on any protected branch missing it, generated from the
6
+ * configured seed admins (`ADMIN_EMAIL`) via the shared validated renderer
7
+ * (../../../access/render-roles-yaml.ts — a malformed email stops the boot
8
+ * with a message naming the fix). Never part of the template: a template copy
9
+ * would seed repos with a stale hard-coded Admin list.
10
+ *
11
+ * An existing regular file — whatever it says — is the operator's and is left
12
+ * alone.
13
+ *
14
+ * A missing file with NO admins configured is a declared `skipped`: the lazy
15
+ * top-up warned and left the file absent, and under the startup contract that
16
+ * survivable-but-incomplete state is the step's to declare, not to bury in a
17
+ * log. Access resolution will fail until an Admin roles.yaml exists.
18
+ */
19
+ export class RolesYamlStep {
20
+ seedAdminEmails;
21
+ name = 'roles-yaml';
22
+ constructor(seedAdminEmails) {
23
+ this.seedAdminEmails = seedAdminEmails;
24
+ }
25
+ async run(ctx) {
26
+ const adminless = [];
27
+ for (const branch of await ctx.protectedBranches()) {
28
+ const repoDir = await branch.repoDir();
29
+ // `lstat`, not `exists`: a DIRECTORY or SYMLINK squatting the name would
30
+ // read as "present" and be skipped over — reporting success over a
31
+ // knowledge base whose access roster cannot be read. Fail closed, same
32
+ // as template-files' squatter checks: this is a state a human must fix.
33
+ const found = await lstatOrNull(path.join(repoDir, 'roles.yaml'));
34
+ if (found) {
35
+ if (found.isFile())
36
+ continue; // the operator's file — leave it alone
37
+ throw new Error(`"roles.yaml" on branch "${branch.name}" exists but is not a regular file ` +
38
+ `(${found.isSymbolicLink() ? 'symlink' : found.isDirectory() ? 'directory' : 'special file'}). ` +
39
+ 'Remove or rename it — access loading requires this name to be a readable file at the repository root.');
40
+ }
41
+ if (this.seedAdminEmails.length === 0) {
42
+ adminless.push(branch.name);
43
+ continue;
44
+ }
45
+ branch.write('roles.yaml', renderRolesYaml(this.seedAdminEmails));
46
+ branch.note('Add roles.yaml granting Admin to the configured seed admins');
47
+ }
48
+ if (adminless.length > 0) {
49
+ // With an empty admin list nothing was declared for ANY branch, so the
50
+ // skip discards nothing another branch needed.
51
+ return {
52
+ outcome: 'skipped',
53
+ reason: `roles.yaml is missing on ${adminless.join(', ')} and ADMIN_EMAIL is unset — leaving it absent. ` +
54
+ 'Access resolution will fail until an Admin roles.yaml exists; set ADMIN_EMAIL or add roles.yaml manually.',
55
+ };
56
+ }
57
+ return { outcome: 'ok' };
58
+ }
59
+ }
60
+ /** `lstat` without the throw — null when nothing is at `p`. */
61
+ async function lstatOrNull(p) {
62
+ try {
63
+ return await fs.lstat(p);
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ //# sourceMappingURL=roles-yaml.step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles-yaml.step.js","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/roles-yaml.step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAGvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,aAAa;IAGK;IAFpB,IAAI,GAAG,YAAY,CAAC;IAE7B,YAA6B,eAAkC;QAAlC,oBAAe,GAAf,eAAe,CAAmB;IAAG,CAAC;IAEnE,KAAK,CAAC,GAAG,CAAC,GAAuB;QAC/B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,MAAM,GAAG,CAAC,iBAAiB,EAAE,EAAE,CAAC;YACnD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YACvC,yEAAyE;YACzE,mEAAmE;YACnE,uEAAuE;YACvE,wEAAwE;YACxE,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;YAClE,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,KAAK,CAAC,MAAM,EAAE;oBAAE,SAAS,CAAC,uCAAuC;gBACrE,MAAM,IAAI,KAAK,CACb,2BAA2B,MAAM,CAAC,IAAI,qCAAqC;oBACzE,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,KAAK;oBAChG,uGAAuG,CAC1G,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC5B,SAAS;YACX,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;YAClE,MAAM,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,uEAAuE;YACvE,+CAA+C;YAC/C,OAAO;gBACL,OAAO,EAAE,SAAS;gBAClB,MAAM,EACJ,4BAA4B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,iDAAiD;oBACjG,2GAA2G;aAC9G,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,+DAA+D;AAC/D,KAAK,UAAU,WAAW,CAAC,CAAS;IAClC,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The empty-remote seed builder the runner takes as `buildSeedTree`: the full
3
+ * template tree, every reserved root, and a generated roles.yaml. DIRECT fs
4
+ * writes are correct here — the target is a temp directory the runner inits,
5
+ * commits and pushes itself, not a branch handle with buffered ops.
6
+ *
7
+ * Resolves to the repo-relative paths it GENERATED (roles.yaml plus each
8
+ * reserved root's .gitkeep): a template `.gitignore` rule could match any of
9
+ * them, and the runner force-adds them after `git add -A` so a required seed
10
+ * file can never be silently dropped from the seed commit.
11
+ *
12
+ * `extraRootDirs` is validated eagerly, at composition time: a bad value
13
+ * should fail at boot beside the rest of the wiring, not mid-seed of
14
+ * somebody's knowledge base.
15
+ */
16
+ export declare function buildSeedTree(templateDir: string, extraRootDirs: readonly string[], seedAdminEmails: readonly string[]): (dir: string) => Promise<string[]>;
17
+ //# sourceMappingURL=seed-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-tree.d.ts","sourceRoot":"","sources":["../../../../../src/modules/workspace/startup/steps/seed-tree.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CA8BpC"}