@bevel-software/platform-core-backend 0.7.4 → 0.8.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 (246) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +51 -7
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +5 -3
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +38 -21
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/modules/access/access-control.interface.d.ts +8 -7
  12. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  13. package/dist/modules/access/access-control.service.d.ts +1 -1
  14. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.js +6 -6
  16. package/dist/modules/access/access-control.service.js.map +1 -1
  17. package/dist/modules/access/access-declarations.d.ts +5 -5
  18. package/dist/modules/access/access-declarations.js +3 -3
  19. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  20. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  21. package/dist/modules/access/access-mutation.service.js +3 -3
  22. package/dist/modules/access/access-mutation.service.js.map +1 -1
  23. package/dist/modules/access/access-splice.js +4 -4
  24. package/dist/modules/access/access-splice.js.map +1 -1
  25. package/dist/modules/access/access.routes.js +19 -19
  26. package/dist/modules/access/access.routes.js.map +1 -1
  27. package/dist/modules/access/creator-access.d.ts +2 -2
  28. package/dist/modules/access/creator-access.js +5 -5
  29. package/dist/modules/access/creator-access.js.map +1 -1
  30. package/dist/modules/access/roles-admin.service.d.ts +18 -4
  31. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  32. package/dist/modules/access/roles-admin.service.js +14 -4
  33. package/dist/modules/access/roles-admin.service.js.map +1 -1
  34. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  35. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  36. package/dist/modules/code-mode/code-mode-names.js +5 -27
  37. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  38. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  39. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  40. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  41. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  42. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  43. package/dist/modules/mcp/mcp.service.js +33 -395
  44. package/dist/modules/mcp/mcp.service.js.map +1 -1
  45. package/dist/modules/plugins/index.d.ts +7 -0
  46. package/dist/modules/plugins/index.d.ts.map +1 -0
  47. package/dist/modules/plugins/index.js +6 -0
  48. package/dist/modules/plugins/index.js.map +1 -0
  49. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  50. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  51. package/dist/modules/plugins/join-proposals.js +67 -0
  52. package/dist/modules/plugins/join-proposals.js.map +1 -0
  53. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  54. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  55. package/dist/modules/plugins/join-requests.service.js +135 -0
  56. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  57. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  58. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  59. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  60. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  61. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  62. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  63. package/dist/modules/plugins/plugins.contract.js +36 -0
  64. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  65. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  66. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugins.routes.js +379 -0
  68. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  69. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  70. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.service.js +172 -0
  72. package/dist/modules/plugins/plugins.service.js.map +1 -0
  73. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  74. package/dist/modules/skills/pending-skills.service.js +7 -7
  75. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  76. package/dist/modules/skills/skills.contract.d.ts +4 -4
  77. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  78. package/dist/modules/skills/skills.contract.js +1 -1
  79. package/dist/modules/skills/skills.service.js +5 -5
  80. package/dist/modules/skills/skills.service.js.map +1 -1
  81. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  82. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  83. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  84. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  85. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  86. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  87. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  88. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  89. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  90. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  91. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  92. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  93. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  94. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  95. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  96. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  97. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  98. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  99. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  100. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  101. package/dist/modules/workflow/git/git.service.js +2 -2
  102. package/dist/modules/workflow/git/git.service.js.map +1 -1
  103. package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
  104. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  105. package/dist/modules/workflow/pending-commits.service.js +37 -9
  106. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  107. package/dist/modules/workflow/pending-commits.worker.d.ts +17 -4
  108. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  109. package/dist/modules/workflow/pending-commits.worker.js +63 -9
  110. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  111. package/dist/modules/workflow/workflow.service.d.ts +1 -0
  112. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  113. package/dist/modules/workflow/workflow.service.js +6 -1
  114. package/dist/modules/workflow/workflow.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/workspace.routes.js +3 -3
  124. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  125. package/dist/modules/workspace/workspace.service.d.ts +26 -0
  126. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  127. package/dist/modules/workspace/workspace.service.js +83 -12
  128. package/dist/modules/workspace/workspace.service.js.map +1 -1
  129. package/dist/shared/kb-layout.test.js +3 -3
  130. package/dist/shared/kb-layout.test.js.map +1 -1
  131. package/dist/shared/utcp-namespace.d.ts +6 -27
  132. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  133. package/dist/shared/utcp-namespace.js +6 -63
  134. package/dist/shared/utcp-namespace.js.map +1 -1
  135. package/dist/shared/variable-refs.d.ts +42 -0
  136. package/dist/shared/variable-refs.d.ts.map +1 -0
  137. package/dist/shared/variable-refs.js +60 -0
  138. package/dist/shared/variable-refs.js.map +1 -0
  139. package/kb-template/.bevelignore +1 -1
  140. package/kb-template/AGENTS.md +88 -35
  141. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  142. package/kb-template/access.md +36 -36
  143. package/migrations/meta/0000_snapshot.json +1479 -1479
  144. package/package.json +5 -4
  145. package/src/assets.ts +25 -25
  146. package/src/core/core-ports.ts +106 -106
  147. package/src/core/create-core-server.ts +55 -9
  148. package/src/core/create-core-services.ts +48 -24
  149. package/src/index.ts +69 -69
  150. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  151. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  152. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  153. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  154. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  155. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  156. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  157. package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
  158. package/src/modules/access/access-control.interface.ts +8 -7
  159. package/src/modules/access/access-control.service.ts +7 -7
  160. package/src/modules/access/access-declarations.ts +5 -5
  161. package/src/modules/access/access-mutation.service.ts +3 -3
  162. package/src/modules/access/access-splice.ts +4 -4
  163. package/src/modules/access/access.routes.ts +20 -20
  164. package/src/modules/access/creator-access.ts +5 -5
  165. package/src/modules/access/roles-admin.service.ts +13 -2
  166. package/src/modules/admin/admin-access.routes.ts +29 -29
  167. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  168. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  169. package/src/modules/auth/rate-limit.ts +45 -45
  170. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  171. package/src/modules/code-mode/code-mode-names.ts +10 -36
  172. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  173. package/src/modules/database/connection.ts +15 -15
  174. package/src/modules/database/schema.ts +11 -11
  175. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  176. package/src/modules/mcp/mcp.service.ts +57 -435
  177. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  178. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  179. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  180. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  181. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  182. package/src/modules/plugins/index.ts +17 -0
  183. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  184. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  185. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  186. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  187. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  188. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  189. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  190. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  191. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  192. package/src/modules/skills/pending-skills.service.ts +7 -7
  193. package/src/modules/skills/skills.contract.ts +4 -4
  194. package/src/modules/skills/skills.service.ts +5 -5
  195. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  196. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  197. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  198. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  199. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  200. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  201. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  202. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  203. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  204. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  205. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  206. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  207. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  208. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  209. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +118 -0
  210. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  211. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  212. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  213. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  214. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  215. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  216. package/src/modules/workflow/git/git.service.ts +2 -2
  217. package/src/modules/workflow/pending-commits.service.ts +48 -12
  218. package/src/modules/workflow/pending-commits.worker.ts +64 -8
  219. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  220. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  221. package/src/modules/workflow/workflow-hooks.ts +101 -101
  222. package/src/modules/workflow/workflow.service.ts +13 -2
  223. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
  224. package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
  225. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  226. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  227. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  228. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  229. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  230. package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
  231. package/src/modules/workspace/bevel-ignore.ts +66 -66
  232. package/src/modules/workspace/kb-seed.service.ts +38 -9
  233. package/src/modules/workspace/plugins-migration.ts +479 -0
  234. package/src/modules/workspace/session-sink.ts +25 -25
  235. package/src/modules/workspace/workspace.routes.ts +3 -3
  236. package/src/modules/workspace/workspace.service.ts +85 -14
  237. package/src/modules/workspace/workspace.tools.ts +922 -922
  238. package/src/shared/__tests__/join-request.test.ts +13 -13
  239. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  240. package/src/shared/kb-layout.test.ts +3 -3
  241. package/src/shared/utcp-namespace.ts +10 -68
  242. package/src/shared/variable-refs.ts +64 -0
  243. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  244. package/src/modules/groups/index.ts +0 -17
  245. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  246. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -11,35 +11,35 @@ import type { IAccessControl } from '../access/access-control.interface.js';
11
11
  import { spliceGrant } from '../access/access-splice.js';
12
12
  import { WorkflowDomainError } from '../workflow/workflow.errors.js';
13
13
  import type { WorkspaceService } from '../workspace/workspace.service.js';
14
- import { groupsWorkspaceId } from './groups.service.js';
15
- import { GroupProvisionError, type GroupProvisionService } from './group-provision.service.js';
14
+ import { pluginsWorkspaceId } from './plugins.service.js';
15
+ import { PluginProvisionError, type PluginProvisionService } from './plugin-provision.service.js';
16
16
  import type { JoinRequestsService } from './join-requests.service.js';
17
17
  import type {
18
- GroupCatalogEntry,
19
- GroupSummary,
20
- IGroupIndexService,
21
- } from './groups.contract.js';
18
+ PluginCatalogEntry,
19
+ PluginSummary,
20
+ IPluginIndexService,
21
+ } from './plugins.contract.js';
22
22
 
23
23
  /**
24
- * Browser-facing (JWT) group routes, mounted behind `authMiddleware`:
24
+ * Browser-facing (JWT) plugin routes, mounted behind `authMiddleware`:
25
25
  *
26
- * GET /api/groups → { groups: GroupSummary[] }
27
- * DELETE /api/groups/:name → { ok } (owners)
28
- * POST /api/groups/:name/join-request → { ok, number } (opens a CR)
29
- * GET /api/groups/:name/join-requests → { requests } (managers)
30
- * POST /api/groups/:name/join-requests/:n/reconcile → { closed }
26
+ * GET /api/plugins → { plugins: PluginSummary[] }
27
+ * DELETE /api/plugins/:name → { ok } (owners)
28
+ * POST /api/plugins/:name/join-request → { ok, number } (opens a CR)
29
+ * GET /api/plugins/:name/join-requests → { requests } (managers)
30
+ * POST /api/plugins/:name/join-requests/:n/reconcile → { closed }
31
31
  *
32
- * Enumeration is three verdicts per (caller, group), all of them ordinary
32
+ * Enumeration is three verdicts per (caller, plugin), all of them ordinary
33
33
  * access resolution — no special cases, no side tables:
34
34
  *
35
- * member canRead on the group FOLDER
35
+ * member canRead on the plugin FOLDER
36
36
  * manager canWrite on the folder's access.md (admin-rescued)
37
37
  * discoverable canRead on the access.md FILE — in the body-governed format
38
38
  * its own `read: everyone` frontmatter grants this
39
39
  *
40
- * All three false ⇒ the group is absent from the response entirely.
40
+ * All three false ⇒ the plugin is absent from the response entirely.
41
41
  *
42
- * A join request is a plain change request whose branch edits the group's
42
+ * A join request is a plain change request whose branch edits the plugin's
43
43
  * `access.md`. Managers do NOT merge it: they read its individual proposals
44
44
  * (see `join-proposals.ts`), grant the ones they accept through the ordinary
45
45
  * access path, and the request retires itself once its rules are a subset of
@@ -52,13 +52,13 @@ import type {
52
52
  * middleware change can never silently un-gate one. Nothing here is reachable
53
53
  * with an agent connection key or a manual-auth bearer.
54
54
  */
55
- export function createGroupsRoutes(
56
- groupIndex: IGroupIndexService,
55
+ export function createPluginsRoutes(
56
+ pluginIndex: IPluginIndexService,
57
57
  accessControl: IAccessControl,
58
58
  workflow: IWorkflowService,
59
59
  workspaceService: WorkspaceService,
60
60
  joinRequests: JoinRequestsService,
61
- provision: GroupProvisionService,
61
+ provision: PluginProvisionService,
62
62
  kbDirName: string,
63
63
  resolveUser: (req: express.Request) => Promise<AuthUser | null>,
64
64
  ): express.Router {
@@ -69,27 +69,27 @@ export function createGroupsRoutes(
69
69
  /** The FILE probe for discovery/management — the folder's access.md. */
70
70
  const accessMdOf = (folder: string) => `${folder}/access.md`;
71
71
 
72
- const probesFor = (groups: GroupCatalogEntry[]): string[] => [
73
- ...new Set(groups.flatMap((g) => g.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]))),
72
+ const probesFor = (plugins: PluginCatalogEntry[]): string[] => [
73
+ ...new Set(plugins.flatMap((g) => g.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]))),
74
74
  ];
75
75
 
76
- /** The caller's open join CR for `group`, or null. */
77
- const openJoinCr = (mine: ChangeRequest[], email: string, group: string): ChangeRequest | null =>
78
- mine.find((cr) => cr.state === 'open' && cr.branch === joinBranchFor(email, group)) ?? null;
76
+ /** The caller's open join CR for `plugin`, or null. */
77
+ const openJoinCr = (mine: ChangeRequest[], email: string, plugin: string): ChangeRequest | null =>
78
+ mine.find((cr) => cr.state === 'open' && cr.branch === joinBranchFor(email, plugin)) ?? null;
79
79
 
80
- router.get('/groups', async (req, res) => {
80
+ router.get('/plugins', async (req, res) => {
81
81
  const email = req.userEmail;
82
82
  if (!email) {
83
83
  res.status(401).json({ error: 'Unauthenticated' });
84
84
  return;
85
85
  }
86
86
  try {
87
- const catalog = await groupIndex.catalog();
87
+ const catalog = await pluginIndex.catalog();
88
88
  if (catalog.length === 0) {
89
- res.json({ groups: [] });
89
+ res.json({ plugins: [] });
90
90
  return;
91
91
  }
92
- const wsId = groupsWorkspaceId();
92
+ const wsId = pluginsWorkspaceId();
93
93
  const probes = probesFor(catalog);
94
94
  const [readable, writable, owned] = await Promise.all([
95
95
  accessControl.canReadBatch(wsId, email, probes),
@@ -97,22 +97,22 @@ export function createGroupsRoutes(
97
97
  accessControl.canOwnerBatch(wsId, email, probes),
98
98
  ]);
99
99
  // Fail closed on every verdict: a path missing from the map is denied.
100
- const any = (map: Map<string, boolean>, g: GroupCatalogEntry, probe: (f: string) => string) =>
100
+ const any = (map: Map<string, boolean>, g: PluginCatalogEntry, probe: (f: string) => string) =>
101
101
  g.folders.some((f) => map.get(probe(f)) === true);
102
102
 
103
103
  // The caller's own open join CRs — drives `hasRequested`. A CR-listing
104
- // hiccup must not take group enumeration down: degrade to "nothing
104
+ // hiccup must not take plugin enumeration down: degrade to "nothing
105
105
  // requested" and let the next load repair it.
106
106
  let mine: ChangeRequest[] = [];
107
107
  try {
108
108
  mine = await workflow.listChangeRequestsAuthoredBy(email);
109
109
  } catch (err) {
110
110
  console.warn(
111
- `[groups] join-request lookup failed: ${err instanceof Error ? err.message : String(err)}`,
111
+ `[plugins] join-request lookup failed: ${err instanceof Error ? err.message : String(err)}`,
112
112
  );
113
113
  }
114
114
 
115
- const groups: GroupSummary[] = [];
115
+ const plugins: PluginSummary[] = [];
116
116
  for (const g of catalog) {
117
117
  const member = any(readable, g, memberProbe);
118
118
  const manager = any(writable, g, accessMdOf);
@@ -123,7 +123,7 @@ export function createGroupsRoutes(
123
123
  const discoverable = member || any(readable, g, accessMdOf);
124
124
  if (!member && !manager && !discoverable) continue; // absent — fail closed
125
125
  const joinCr = member ? null : openJoinCr(mine, email, g.name);
126
- groups.push({
126
+ plugins.push({
127
127
  name: g.name,
128
128
  folders: g.folders,
129
129
  canRead: member,
@@ -138,10 +138,10 @@ export function createGroupsRoutes(
138
138
  requestNumber: joinCr?.number ?? null,
139
139
  });
140
140
  }
141
- res.json({ groups });
141
+ res.json({ plugins });
142
142
  } catch (err) {
143
- console.error('[groups] failed to list groups:', err);
144
- res.status(500).json({ error: 'Failed to list groups' });
143
+ console.error('[plugins] failed to list plugins:', err);
144
+ res.status(500).json({ error: 'Failed to list plugins' });
145
145
  }
146
146
  });
147
147
 
@@ -155,13 +155,13 @@ export function createGroupsRoutes(
155
155
  * approver who can write the touched access.md.
156
156
  */
157
157
  /**
158
- * Create a group — the ONE door through which `Groups/<name>/` folders come
158
+ * Create a plugin — the ONE door through which `Plugins/<name>/` folders come
159
159
  * to exist. Any authenticated user may create one; that is the product
160
- * model (making a group makes you the one who runs it), and the seeded
160
+ * model (making a plugin makes you the one who runs it), and the seeded
161
161
  * access.md immediately fences the new folder off from everyone else. See
162
- * `GroupProvisionService` for why this is an endpoint and not a write path.
162
+ * `PluginProvisionService` for why this is an endpoint and not a write path.
163
163
  */
164
- router.post('/groups', async (req, res) => {
164
+ router.post('/plugins', async (req, res) => {
165
165
  const user = await resolveUser(req);
166
166
  if (!user) {
167
167
  res.status(401).json({ error: 'Unauthenticated' });
@@ -175,50 +175,50 @@ export function createGroupsRoutes(
175
175
  return;
176
176
  }
177
177
  try {
178
- const result = await provision.createGroup(user, name);
178
+ const result = await provision.createPlugin(user, name);
179
179
  res.status(201).json(result);
180
180
  } catch (err) {
181
- if (err instanceof GroupProvisionError) {
181
+ if (err instanceof PluginProvisionError) {
182
182
  res.status(err.status).json({ error: err.message });
183
183
  return;
184
184
  }
185
- console.error('[groups] create failed:', err);
186
- res.status(500).json({ error: 'Failed to create the group' });
185
+ console.error('[plugins] create failed:', err);
186
+ res.status(500).json({ error: 'Failed to create the plugin' });
187
187
  }
188
188
  });
189
189
 
190
190
  /**
191
- * Ensure the caller's personal folder (`Groups/personal-<id>/`) exists —
191
+ * Ensure the caller's personal folder (`Plugins/personal-<id>/`) exists —
192
192
  * idempotent; the UI calls it lazily right before the first personal-skill
193
193
  * write. Private by construction: its access.md names only the caller.
194
194
  */
195
- router.post('/groups/personal', async (req, res) => {
195
+ router.post('/plugins/personal', async (req, res) => {
196
196
  const user = await resolveUser(req);
197
197
  if (!user) {
198
198
  res.status(401).json({ error: 'Unauthenticated' });
199
199
  return;
200
200
  }
201
201
  try {
202
- res.json(await provision.ensurePersonalGroup(user));
202
+ res.json(await provision.ensurePersonalPlugin(user));
203
203
  } catch (err) {
204
- console.error('[groups] personal-folder ensure failed:', err);
204
+ console.error('[plugins] personal-folder ensure failed:', err);
205
205
  res.status(500).json({ error: 'Failed to prepare your personal folder' });
206
206
  }
207
207
  });
208
208
 
209
209
  /**
210
- * Delete a group — the OWNER's verb, and only theirs. Creating a group
210
+ * Delete a plugin — the OWNER's verb, and only theirs. Creating a plugin
211
211
  * makes you the one who runs it; deleting it is the other end of that same
212
212
  * promise, so the gate is the `owner` verdict on the folder (owner-lists
213
213
  * only, no admin rescue) — a manager who merely writes the access.md, and
214
214
  * an admin rescued into managing it, do not get it.
215
215
  *
216
- * Fail-closed like every other group surface: an unknown group and a group
216
+ * Fail-closed like every other plugin surface: an unknown plugin and a plugin
217
217
  * the caller does not own answer IDENTICALLY, so probing the endpoint can
218
218
  * confirm nothing about what exists. The mechanism (park, one commit,
219
- * rollback on refusal) lives in `GroupProvisionService.deleteGroup`.
219
+ * rollback on refusal) lives in `PluginProvisionService.deletePlugin`.
220
220
  */
221
- router.delete('/groups/:name', async (req, res) => {
221
+ router.delete('/plugins/:name', async (req, res) => {
222
222
  const email = req.userEmail;
223
223
  if (!email) {
224
224
  res.status(401).json({ error: 'Unauthenticated' });
@@ -230,32 +230,32 @@ export function createGroupsRoutes(
230
230
  res.status(401).json({ error: 'Unauthenticated' });
231
231
  return;
232
232
  }
233
- // Case-sensitive, like `groupOfPath` — the group name IS the folder name.
234
- const group = (await groupIndex.catalog()).find((g) => g.name === req.params.name);
235
- const wsId = groupsWorkspaceId();
236
- const ownerVerdicts = group
233
+ // Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
234
+ const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
235
+ const wsId = pluginsWorkspaceId();
236
+ const ownerVerdicts = plugin
237
237
  ? await Promise.all(
238
- group.folders.map((f) => accessControl.canOwner(wsId, email, memberProbe(f))),
238
+ plugin.folders.map((f) => accessControl.canOwner(wsId, email, memberProbe(f))),
239
239
  )
240
240
  : [];
241
- if (!group || !ownerVerdicts.some((v) => v === true)) {
242
- res.status(404).json({ error: 'Unknown group', kind: 'unknown-group' });
241
+ if (!plugin || !ownerVerdicts.some((v) => v === true)) {
242
+ res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
243
243
  return;
244
244
  }
245
- await provision.deleteGroup(user, group.name);
246
- groupIndex.invalidate();
245
+ await provision.deletePlugin(user, plugin.name);
246
+ pluginIndex.invalidate();
247
247
  res.json({ ok: true });
248
248
  } catch (err) {
249
- if (err instanceof GroupProvisionError) {
249
+ if (err instanceof PluginProvisionError) {
250
250
  res.status(err.status).json({ error: err.message });
251
251
  return;
252
252
  }
253
- console.error('[groups] delete failed:', err);
254
- res.status(500).json({ error: 'Failed to delete the group' });
253
+ console.error('[plugins] delete failed:', err);
254
+ res.status(500).json({ error: 'Failed to delete the plugin' });
255
255
  }
256
256
  });
257
257
 
258
- router.post('/groups/:name/join-request', async (req, res) => {
258
+ router.post('/plugins/:name/join-request', async (req, res) => {
259
259
  const email = req.userEmail;
260
260
  if (!email) {
261
261
  res.status(401).json({ error: 'Unauthenticated' });
@@ -267,39 +267,39 @@ export function createGroupsRoutes(
267
267
  res.status(401).json({ error: 'Unauthenticated' });
268
268
  return;
269
269
  }
270
- const catalog = await groupIndex.catalog();
271
- // Case-sensitive, like `groupOfPath` — the group name IS the folder name.
272
- const group = catalog.find((g) => g.name === req.params.name);
273
- const wsId = groupsWorkspaceId();
274
- // Same ANY-folder shape `GET /groups` resolves with, so a group can
270
+ const catalog = await pluginIndex.catalog();
271
+ // Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
272
+ const plugin = catalog.find((g) => g.name === req.params.name);
273
+ const wsId = pluginsWorkspaceId();
274
+ // Same ANY-folder shape `GET /plugins` resolves with, so a plugin can
275
275
  // never be listed as discoverable there and rejected as unknown here.
276
- const verdicts = group
276
+ const verdicts = plugin
277
277
  ? await accessControl.canReadBatch(
278
278
  wsId,
279
279
  email,
280
- group.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]),
280
+ plugin.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]),
281
281
  )
282
282
  : new Map<string, boolean>();
283
283
  const any = (probe: (f: string) => string) =>
284
- group?.folders.some((f) => verdicts.get(probe(f)) === true) ?? false;
285
- // Discovery gate, fail-closed: an unknown group and a group the caller
284
+ plugin?.folders.some((f) => verdicts.get(probe(f)) === true) ?? false;
285
+ // Discovery gate, fail-closed: an unknown plugin and a plugin the caller
286
286
  // cannot discover answer IDENTICALLY, so probing can't confirm existence.
287
- if (!group || !any(accessMdOf)) {
288
- res.status(404).json({ error: 'Unknown group', kind: 'unknown-group' });
287
+ if (!plugin || !any(accessMdOf)) {
288
+ res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
289
289
  return;
290
290
  }
291
291
  if (any(memberProbe)) {
292
292
  // Access landed between page load and click — reload, don't ask.
293
- res.status(409).json({ error: 'You can already read this group', kind: 'already-readable' });
293
+ res.status(409).json({ error: 'You can already read this plugin', kind: 'already-readable' });
294
294
  return;
295
295
  }
296
- // The grant is written to the group's primary folder — the one the
296
+ // The grant is written to the plugin's primary folder — the one the
297
297
  // summary's `folders[0]` names and the banner's touched-path check
298
298
  // expects.
299
- const folder = group.folders[0];
299
+ const folder = plugin.folders[0];
300
300
 
301
- const branch = joinBranchFor(email, group.name);
302
- const existing = openJoinCr(await workflow.listChangeRequestsAuthoredBy(email), email, group.name);
301
+ const branch = joinBranchFor(email, plugin.name);
302
+ const existing = openJoinCr(await workflow.listChangeRequestsAuthoredBy(email), email, plugin.name);
303
303
  if (existing) {
304
304
  res.json({ ok: true, number: existing.number });
305
305
  return;
@@ -308,7 +308,7 @@ export function createGroupsRoutes(
308
308
  // A leftover branch from a rejected/withdrawn request is reused — the
309
309
  // grant commit is already on it and the splice below no-ops.
310
310
  try {
311
- await workflow.createBranch(groupsWorkspaceId(), branch, DEFAULT_BRANCH);
311
+ await workflow.createBranch(pluginsWorkspaceId(), branch, DEFAULT_BRANCH);
312
312
  } catch {
313
313
  // exists (or raced) — proceed against it
314
314
  }
@@ -323,14 +323,14 @@ export function createGroupsRoutes(
323
323
  );
324
324
  if (spliced.changed) {
325
325
  await workspaceService.writeFile(ws.id, accessPath, spliced.text);
326
- await workflow.commitChanges(ws.id, user, `Request access to ${group.name}`);
326
+ await workflow.commitChanges(ws.id, user, `Request access to ${plugin.name}`);
327
327
  }
328
328
  const detail = await workflow.openChangeRequest(ws.id, user, {
329
329
  sourceBranch: branch,
330
330
  targetBranch: DEFAULT_BRANCH,
331
- title: `Join request: ${group.name}`,
331
+ title: `Join request: ${plugin.name}`,
332
332
  description:
333
- `${user.name} asked to join ${group.name}. A manager of the group accepts by ` +
333
+ `${user.name} asked to join ${plugin.name}. A manager of the plugin accepts by ` +
334
334
  `granting the access this branch proposes; the request closes itself once ` +
335
335
  `every proposal has landed.`,
336
336
  });
@@ -340,41 +340,41 @@ export function createGroupsRoutes(
340
340
  res.status(err.status).json({ error: err.message, ...(err.payload ?? {}) });
341
341
  return;
342
342
  }
343
- console.error('[groups] failed to open a join request:', err);
343
+ console.error('[plugins] failed to open a join request:', err);
344
344
  res.status(500).json({ error: 'Failed to request access' });
345
345
  }
346
346
  });
347
347
 
348
348
  /**
349
349
  * Resolve the caller as a MANAGER of `name` — the only role that may see or
350
- * settle its join requests. Returns the group's primary folder, or null
350
+ * settle its join requests. Returns the plugin's primary folder, or null
351
351
  * after answering the request.
352
352
  *
353
353
  * A non-manager gets an empty list rather than a 403 (the frontend asks
354
- * unconditionally, exactly as it does for every other group surface), and
355
- * an unknown group is indistinguishable from an unmanaged one.
354
+ * unconditionally, exactly as it does for every other plugin surface), and
355
+ * an unknown plugin is indistinguishable from an unmanaged one.
356
356
  */
357
357
  async function requireManager(
358
358
  req: express.Request,
359
359
  res: express.Response,
360
360
  onDenied: () => void,
361
- ): Promise<{ group: GroupCatalogEntry; folder: string; user: AuthUser } | null> {
361
+ ): Promise<{ plugin: PluginCatalogEntry; folder: string; user: AuthUser } | null> {
362
362
  const email = req.userEmail;
363
363
  if (!email) {
364
364
  res.status(401).json({ error: 'Unauthenticated' });
365
365
  return null;
366
366
  }
367
- const group = (await groupIndex.catalog()).find((g) => g.name === req.params.name);
368
- if (!group) {
367
+ const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
368
+ if (!plugin) {
369
369
  onDenied();
370
370
  return null;
371
371
  }
372
372
  const writable = await accessControl.canWriteBatch(
373
- groupsWorkspaceId(),
373
+ pluginsWorkspaceId(),
374
374
  email,
375
- group.folders.map(accessMdOf),
375
+ plugin.folders.map(accessMdOf),
376
376
  );
377
- if (!group.folders.some((f) => writable.get(accessMdOf(f)) === true)) {
377
+ if (!plugin.folders.some((f) => writable.get(accessMdOf(f)) === true)) {
378
378
  onDenied();
379
379
  return null;
380
380
  }
@@ -383,19 +383,19 @@ export function createGroupsRoutes(
383
383
  res.status(401).json({ error: 'Unauthenticated' });
384
384
  return null;
385
385
  }
386
- return { group, folder: group.folders[0], user };
386
+ return { plugin, folder: plugin.folders[0], user };
387
387
  }
388
388
 
389
- router.get('/groups/:name/join-requests', async (req, res) => {
389
+ router.get('/plugins/:name/join-requests', async (req, res) => {
390
390
  try {
391
391
  const ctx = await requireManager(req, res, () => res.json({ requests: [] }));
392
392
  if (!ctx) return;
393
393
  const crs = await workflow.listChangeRequests();
394
394
  res.json({
395
- requests: await joinRequests.list(ctx.group.name, ctx.folder, crs, ctx.user),
395
+ requests: await joinRequests.list(ctx.plugin.name, ctx.folder, crs, ctx.user),
396
396
  });
397
397
  } catch (err) {
398
- console.error('[groups] failed to list join requests:', err);
398
+ console.error('[plugins] failed to list join requests:', err);
399
399
  res.status(500).json({ error: 'Failed to list join requests' });
400
400
  }
401
401
  });
@@ -406,7 +406,7 @@ export function createGroupsRoutes(
406
406
  * round-trip; the listing does the same thing lazily, so skipping this (a
407
407
  * dropped response, a closed tab) only delays it.
408
408
  */
409
- router.post('/groups/:name/join-requests/:number/reconcile', async (req, res) => {
409
+ router.post('/plugins/:name/join-requests/:number/reconcile', async (req, res) => {
410
410
  try {
411
411
  const ctx = await requireManager(req, res, () =>
412
412
  res.status(404).json({ error: 'Not found' }),
@@ -422,9 +422,9 @@ export function createGroupsRoutes(
422
422
  res.status(404).json({ error: 'Not found' });
423
423
  return;
424
424
  }
425
- res.json({ closed: await joinRequests.reconcile(ctx.group.name, ctx.folder, cr, ctx.user) });
425
+ res.json({ closed: await joinRequests.reconcile(ctx.plugin.name, ctx.folder, cr, ctx.user) });
426
426
  } catch (err) {
427
- console.error('[groups] failed to reconcile a join request:', err);
427
+ console.error('[plugins] failed to reconcile a join request:', err);
428
428
  res.status(500).json({ error: 'Failed to update the request' });
429
429
  }
430
430
  });