@bevel-software/platform-core-backend 0.7.5 → 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 (230) 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 +30 -18
  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/workspace/kb-seed.service.d.ts +2 -2
  104. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  105. package/dist/modules/workspace/kb-seed.service.js +43 -10
  106. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  107. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  108. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  109. package/dist/modules/workspace/plugins-migration.js +379 -0
  110. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  111. package/dist/modules/workspace/workspace.routes.js +3 -3
  112. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  113. package/dist/modules/workspace/workspace.service.d.ts +26 -0
  114. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  115. package/dist/modules/workspace/workspace.service.js +83 -12
  116. package/dist/modules/workspace/workspace.service.js.map +1 -1
  117. package/dist/shared/kb-layout.test.js +3 -3
  118. package/dist/shared/kb-layout.test.js.map +1 -1
  119. package/dist/shared/utcp-namespace.d.ts +6 -27
  120. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  121. package/dist/shared/utcp-namespace.js +6 -63
  122. package/dist/shared/utcp-namespace.js.map +1 -1
  123. package/dist/shared/variable-refs.d.ts +42 -0
  124. package/dist/shared/variable-refs.d.ts.map +1 -0
  125. package/dist/shared/variable-refs.js +60 -0
  126. package/dist/shared/variable-refs.js.map +1 -0
  127. package/kb-template/.bevelignore +1 -1
  128. package/kb-template/AGENTS.md +88 -35
  129. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  130. package/kb-template/access.md +36 -36
  131. package/migrations/meta/0000_snapshot.json +1479 -1479
  132. package/package.json +5 -4
  133. package/src/assets.ts +25 -25
  134. package/src/core/core-ports.ts +106 -106
  135. package/src/core/create-core-server.ts +55 -9
  136. package/src/core/create-core-services.ts +40 -20
  137. package/src/index.ts +69 -69
  138. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  139. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  140. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  141. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  142. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  143. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  144. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  145. package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
  146. package/src/modules/access/access-control.interface.ts +8 -7
  147. package/src/modules/access/access-control.service.ts +7 -7
  148. package/src/modules/access/access-declarations.ts +5 -5
  149. package/src/modules/access/access-mutation.service.ts +3 -3
  150. package/src/modules/access/access-splice.ts +4 -4
  151. package/src/modules/access/access.routes.ts +20 -20
  152. package/src/modules/access/creator-access.ts +5 -5
  153. package/src/modules/access/roles-admin.service.ts +13 -2
  154. package/src/modules/admin/admin-access.routes.ts +29 -29
  155. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  156. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  157. package/src/modules/auth/rate-limit.ts +45 -45
  158. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  159. package/src/modules/code-mode/code-mode-names.ts +10 -36
  160. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  161. package/src/modules/database/connection.ts +15 -15
  162. package/src/modules/database/schema.ts +11 -11
  163. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  164. package/src/modules/mcp/mcp.service.ts +57 -435
  165. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  166. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  167. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  168. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  169. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  170. package/src/modules/plugins/index.ts +17 -0
  171. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  172. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  173. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  174. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  175. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  176. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  177. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  178. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  179. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  180. package/src/modules/skills/pending-skills.service.ts +7 -7
  181. package/src/modules/skills/skills.contract.ts +4 -4
  182. package/src/modules/skills/skills.service.ts +5 -5
  183. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  184. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  185. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  186. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  187. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  188. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  189. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  190. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  191. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  192. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  193. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  194. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  195. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  196. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  197. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  198. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  199. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  200. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  201. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  202. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  203. package/src/modules/workflow/git/git.service.ts +2 -2
  204. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  205. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  206. package/src/modules/workflow/workflow-hooks.ts +101 -101
  207. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
  208. package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
  209. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  210. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  211. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  212. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  213. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  214. package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
  215. package/src/modules/workspace/bevel-ignore.ts +66 -66
  216. package/src/modules/workspace/kb-seed.service.ts +38 -9
  217. package/src/modules/workspace/plugins-migration.ts +479 -0
  218. package/src/modules/workspace/session-sink.ts +25 -25
  219. package/src/modules/workspace/workspace.routes.ts +3 -3
  220. package/src/modules/workspace/workspace.service.ts +85 -14
  221. package/src/modules/workspace/workspace.tools.ts +922 -922
  222. package/src/shared/__tests__/join-request.test.ts +13 -13
  223. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  224. package/src/shared/kb-layout.test.ts +3 -3
  225. package/src/shared/utcp-namespace.ts +10 -68
  226. package/src/shared/variable-refs.ts +64 -0
  227. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  228. package/src/modules/groups/index.ts +0 -17
  229. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  230. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -1,101 +1,101 @@
1
- import type { ValidationReport } from '@bevel-software/platform-shared';
2
-
3
- /**
4
- * Workflow lifecycle hooks — the generic seam that replaced the
5
- * constructor-injected KB validator (and the core-owned ontology write block).
6
- * Core code invokes the hooks at fixed lifecycle points; the modules that OWN
7
- * the behavior (the enterprise kb/kb-graph modules today) register handlers in
8
- * the composition root, right after `createCoreServices` returns. Core
9
- * registers none, so a core-only deployment runs every hook point as a no-op.
10
- *
11
- * Two hook kinds with deliberately different failure semantics:
12
- *
13
- * - `commitValidation` — ADVISORY. Runs at commit time in `GitService`
14
- * (`commit` / `commitFile`), exactly where the injected `IKbValidator`
15
- * used to run. A returned report with `mustFix` entries is logged/surfaced
16
- * but NEVER blocks the commit, and a hook that throws is caught and logged
17
- * (see CLAUDE.md "Validation is advisory"). The signature mirrors the old
18
- * `runValidation(workspaceId)` shape so `KbValidatorService` registers
19
- * with a one-line adapter.
20
- *
21
- * - `preWrite` — BLOCKING. Runs before a gated agent write (file-write
22
- * tools) or a write-capable shell command; a hook that throws REJECTS the
23
- * operation (the error propagates to the tool caller). This is where the
24
- * enterprise ontology-session block registers; core registers nothing, so
25
- * core never blocks (touch TRACKING stays core — see
26
- * `session-ontology.gate.ts`).
27
- */
28
-
29
- /** What `GitService` knows at the advisory commit-validation point. */
30
- export interface CommitValidationContext {
31
- workspaceId: string;
32
- branch: string;
33
- /**
34
- * Repo-relative paths the commit will include — the full touched set for
35
- * `commit()`, a single element for the one-file `commitFile()` path.
36
- */
37
- paths: string[];
38
- }
39
-
40
- /**
41
- * Advisory commit-time validation. Return a report to have the caller log /
42
- * surface it (or `void` for "nothing to say"). Throwing is tolerated — the
43
- * caller catches and logs, never blocks the commit.
44
- */
45
- export type CommitValidationHook = (
46
- ctx: CommitValidationContext,
47
- ) => Promise<ValidationReport | void>;
48
-
49
- /**
50
- * What the pre-write gate knows when it consults the blocking hooks. The core
51
- * gate has already resolved all skip conditions (boundary flag off, neutral
52
- * path, non-agent caller, recovery bot, missing session id — see
53
- * `resolveGatedSession`), so by hook time there is always a session id.
54
- */
55
- export type PreWriteContext =
56
- /** A path-addressed content write (write_file/edit_file/mkdir/move/copy/unzip…). */
57
- | { kind: 'file-write'; sessionId: string; wsPath: string }
58
- /** A write-capable operation with no resolvable target path (execute_command). */
59
- | { kind: 'shell'; sessionId: string };
60
-
61
- /** Blocking pre-write hook: throw to reject the write; return to allow. */
62
- export type PreWriteHook = (ctx: PreWriteContext) => Promise<void>;
63
-
64
- /**
65
- * The registry instance. ONE per composition (created in
66
- * `createCoreServices`, shared by `GitService` and exposed as
67
- * `WorkflowService.hooks` for the composition root to register against).
68
- */
69
- export class WorkflowHooks {
70
- private readonly commitValidation: CommitValidationHook[] = [];
71
- private readonly preWrite: PreWriteHook[] = [];
72
-
73
- /** Register an advisory commit-time validation hook. */
74
- onCommitValidation(hook: CommitValidationHook): void {
75
- this.commitValidation.push(hook);
76
- }
77
-
78
- /** Register a blocking pre-write hook. */
79
- onPreWrite(hook: PreWriteHook): void {
80
- this.preWrite.push(hook);
81
- }
82
-
83
- /**
84
- * The registered commit-validation hooks, for the caller (`GitService`) to
85
- * iterate with its own advisory catch-and-log semantics.
86
- */
87
- commitValidationHooks(): readonly CommitValidationHook[] {
88
- return this.commitValidation;
89
- }
90
-
91
- /**
92
- * Run every blocking pre-write hook in registration order. The first throw
93
- * propagates and rejects the write; with no hooks registered (core-only)
94
- * this resolves immediately.
95
- */
96
- async runPreWrite(ctx: PreWriteContext): Promise<void> {
97
- for (const hook of this.preWrite) {
98
- await hook(ctx);
99
- }
100
- }
101
- }
1
+ import type { ValidationReport } from '@bevel-software/platform-shared';
2
+
3
+ /**
4
+ * Workflow lifecycle hooks — the generic seam that replaced the
5
+ * constructor-injected KB validator (and the core-owned ontology write block).
6
+ * Core code invokes the hooks at fixed lifecycle points; the modules that OWN
7
+ * the behavior (the enterprise kb/kb-graph modules today) register handlers in
8
+ * the composition root, right after `createCoreServices` returns. Core
9
+ * registers none, so a core-only deployment runs every hook point as a no-op.
10
+ *
11
+ * Two hook kinds with deliberately different failure semantics:
12
+ *
13
+ * - `commitValidation` — ADVISORY. Runs at commit time in `GitService`
14
+ * (`commit` / `commitFile`), exactly where the injected `IKbValidator`
15
+ * used to run. A returned report with `mustFix` entries is logged/surfaced
16
+ * but NEVER blocks the commit, and a hook that throws is caught and logged
17
+ * (see CLAUDE.md "Validation is advisory"). The signature mirrors the old
18
+ * `runValidation(workspaceId)` shape so `KbValidatorService` registers
19
+ * with a one-line adapter.
20
+ *
21
+ * - `preWrite` — BLOCKING. Runs before a gated agent write (file-write
22
+ * tools) or a write-capable shell command; a hook that throws REJECTS the
23
+ * operation (the error propagates to the tool caller). This is where the
24
+ * enterprise ontology-session block registers; core registers nothing, so
25
+ * core never blocks (touch TRACKING stays core — see
26
+ * `session-ontology.gate.ts`).
27
+ */
28
+
29
+ /** What `GitService` knows at the advisory commit-validation point. */
30
+ export interface CommitValidationContext {
31
+ workspaceId: string;
32
+ branch: string;
33
+ /**
34
+ * Repo-relative paths the commit will include — the full touched set for
35
+ * `commit()`, a single element for the one-file `commitFile()` path.
36
+ */
37
+ paths: string[];
38
+ }
39
+
40
+ /**
41
+ * Advisory commit-time validation. Return a report to have the caller log /
42
+ * surface it (or `void` for "nothing to say"). Throwing is tolerated — the
43
+ * caller catches and logs, never blocks the commit.
44
+ */
45
+ export type CommitValidationHook = (
46
+ ctx: CommitValidationContext,
47
+ ) => Promise<ValidationReport | void>;
48
+
49
+ /**
50
+ * What the pre-write gate knows when it consults the blocking hooks. The core
51
+ * gate has already resolved all skip conditions (boundary flag off, neutral
52
+ * path, non-agent caller, recovery bot, missing session id — see
53
+ * `resolveGatedSession`), so by hook time there is always a session id.
54
+ */
55
+ export type PreWriteContext =
56
+ /** A path-addressed content write (write_file/edit_file/mkdir/move/copy/unzip…). */
57
+ | { kind: 'file-write'; sessionId: string; wsPath: string }
58
+ /** A write-capable operation with no resolvable target path (execute_command). */
59
+ | { kind: 'shell'; sessionId: string };
60
+
61
+ /** Blocking pre-write hook: throw to reject the write; return to allow. */
62
+ export type PreWriteHook = (ctx: PreWriteContext) => Promise<void>;
63
+
64
+ /**
65
+ * The registry instance. ONE per composition (created in
66
+ * `createCoreServices`, shared by `GitService` and exposed as
67
+ * `WorkflowService.hooks` for the composition root to register against).
68
+ */
69
+ export class WorkflowHooks {
70
+ private readonly commitValidation: CommitValidationHook[] = [];
71
+ private readonly preWrite: PreWriteHook[] = [];
72
+
73
+ /** Register an advisory commit-time validation hook. */
74
+ onCommitValidation(hook: CommitValidationHook): void {
75
+ this.commitValidation.push(hook);
76
+ }
77
+
78
+ /** Register a blocking pre-write hook. */
79
+ onPreWrite(hook: PreWriteHook): void {
80
+ this.preWrite.push(hook);
81
+ }
82
+
83
+ /**
84
+ * The registered commit-validation hooks, for the caller (`GitService`) to
85
+ * iterate with its own advisory catch-and-log semantics.
86
+ */
87
+ commitValidationHooks(): readonly CommitValidationHook[] {
88
+ return this.commitValidation;
89
+ }
90
+
91
+ /**
92
+ * Run every blocking pre-write hook in registration order. The first throw
93
+ * propagates and rejects the write; with no hooks registered (core-only)
94
+ * this resolves immediately.
95
+ */
96
+ async runPreWrite(ctx: PreWriteContext): Promise<void> {
97
+ for (const hook of this.preWrite) {
98
+ await hook(ctx);
99
+ }
100
+ }
101
+ }
@@ -119,7 +119,7 @@ describe('KbSeedService', () => {
119
119
  expect(await exists(path.join(dir, '.gitignore'))).toBe(true);
120
120
  // Core's two roots are seeded (kept present via their .gitkeep).
121
121
  expect(await exists(path.join(dir, 'KnowledgeBase/.gitkeep'))).toBe(true);
122
- expect(await exists(path.join(dir, 'Groups/.gitkeep'))).toBe(true);
122
+ expect(await exists(path.join(dir, 'Plugins/.gitkeep'))).toBe(true);
123
123
  // …and only those two. `Data/`, `Agents/` and `Pipelines/` scaffold an
124
124
  // agentic execution layer this platform does not have, so seeding them
125
125
  // would hand every operator three empty folders nothing can fill.
@@ -156,7 +156,7 @@ describe('KbSeedService', () => {
156
156
  '.bevelignore': 'x',
157
157
  '.gitignore': 'x',
158
158
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
159
- 'Groups/.gitkeep': '',
159
+ 'Plugins/.gitkeep': '',
160
160
  });
161
161
 
162
162
  await makeSeeder(upstream).ensureRemoteSeeded();
@@ -172,7 +172,7 @@ describe('KbSeedService', () => {
172
172
  'access.md': 'CUSTOM ACCESS RULES',
173
173
  // AGENTS.md / .bevelignore / .gitignore are absent → should be added.
174
174
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
175
- 'Groups/.gitkeep': '',
175
+ 'Plugins/.gitkeep': '',
176
176
  });
177
177
 
178
178
  // Simulate the app loading the branch: a fresh clone, then top-up on it.
@@ -201,7 +201,7 @@ describe('KbSeedService', () => {
201
201
  '.gitignore': '',
202
202
  'access.md': 'CUSTOM ACCESS RULES',
203
203
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
204
- 'Groups/.gitkeep': '',
204
+ 'Plugins/.gitkeep': '',
205
205
  });
206
206
 
207
207
  const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
@@ -240,7 +240,7 @@ describe('KbSeedService', () => {
240
240
  '.gitignore': '',
241
241
  'access.md': 'CUSTOM ACCESS RULES',
242
242
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
243
- 'Groups/.gitkeep': '',
243
+ 'Plugins/.gitkeep': '',
244
244
  });
245
245
 
246
246
  const first = await checkout(root, upstream, DEFAULT_BRANCH);
@@ -254,7 +254,7 @@ describe('KbSeedService', () => {
254
254
  const upstream = await seededUpstream(PROTECTED, {
255
255
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
256
256
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
257
- 'Groups/.gitkeep': '',
257
+ 'Plugins/.gitkeep': '',
258
258
  });
259
259
 
260
260
  const first = await checkout(root, upstream, DEFAULT_BRANCH);
@@ -270,6 +270,74 @@ describe('KbSeedService', () => {
270
270
  expect(after2).toBe(after1);
271
271
  });
272
272
 
273
+ it('a note-only migration outcome stages nothing and raises no top-up warning', async () => {
274
+ const template = await fs.readFile(
275
+ path.join(__dirname, '../../../../kb-template/AGENTS.md'),
276
+ 'utf8',
277
+ );
278
+ const upstream = await seededUpstream(PROTECTED, {
279
+ 'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
280
+ 'access.md': 'x',
281
+ 'AGENTS.md': template,
282
+ '.bevelignore': 'AGENTS.md\nPlugins/\n',
283
+ '.gitignore': '',
284
+ 'KnowledgeBase/Real/Knowledge/.gitkeep': '',
285
+ 'Plugins/GTM/access.md': 'write:\n - Admin\n',
286
+ // An unparsable manifest keeps this manual a `.tool` — its variables
287
+ // have nowhere to go. The migration NOTES the refusal every run and
288
+ // changes nothing; staging on the note used to reach `git commit`
289
+ // with an empty index and warn about it on every boot.
290
+ 'Plugins/GTM/plugin.json': '{ not json at all',
291
+ 'Plugins/GTM/vendor.tool': JSON.stringify({
292
+ name: 'vendor',
293
+ type: 'mcp',
294
+ url: 'https://mcp.vendor.example/mcp',
295
+ variables: [{ name: 'VENDOR_KEY', scope: 'user' }],
296
+ }),
297
+ });
298
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
299
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
300
+ try {
301
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
302
+ expect(warn.mock.calls.flat().join(' ')).not.toMatch(/top-up/i);
303
+ } finally {
304
+ warn.mockRestore();
305
+ }
306
+ // Only the pre-seed commit — a note is not a change.
307
+ expect(await headCommitCount(root, upstream, DEFAULT_BRANCH)).toBe(1);
308
+ });
309
+
310
+ it('the root rename rewrites the stale .bevelignore rule and commits it along', async () => {
311
+ const template = await fs.readFile(
312
+ path.join(__dirname, '../../../../kb-template/AGENTS.md'),
313
+ 'utf8',
314
+ );
315
+ const upstream = await seededUpstream(PROTECTED, {
316
+ 'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
317
+ 'access.md': 'x',
318
+ 'AGENTS.md': template,
319
+ // The pre-rename rule: without the rewrite the migrated KB has a rule
320
+ // for a root that no longer exists and none for the one that does.
321
+ '.bevelignore': 'AGENTS.md\nGroups/\n',
322
+ '.gitignore': '',
323
+ 'KnowledgeBase/Real/Knowledge/.gitkeep': '',
324
+ 'Groups/GTM/access.md': 'write:\n - Admin\n',
325
+ });
326
+ const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
327
+ await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
328
+
329
+ // An independent clone proves the rewrite was STAGED (the ignore file
330
+ // is a repo-root path outside the two roots' pathspecs) and pushed.
331
+ const dir = await checkout(root, upstream, DEFAULT_BRANCH);
332
+ expect(await exists(path.join(dir, 'Plugins/GTM/access.md'))).toBe(true);
333
+ const ignore = await fs.readFile(path.join(dir, '.bevelignore'), 'utf8');
334
+ expect(ignore.split('\n').map((l) => l.trim())).toContain('Plugins/');
335
+ expect(ignore).not.toContain('Groups/');
336
+ expect(ignore).toContain('AGENTS.md');
337
+ const log = await git(dir, ['log', '-1', '--pretty=%s']);
338
+ expect(log.trim()).toBe('Move Groups/ to Plugins/ (Agent Plugins layout)');
339
+ });
340
+
273
341
  });
274
342
 
275
343
  /**
@@ -292,7 +360,7 @@ describe('KbSeedService', () => {
292
360
  describe('the pre-rename ignore file', () => {
293
361
  const legacy = (ignore: string): Record<string, string> => ({
294
362
  'KnowledgeBase/.gitkeep': '',
295
- 'Groups/.gitkeep': '',
363
+ 'Plugins/.gitkeep': '',
296
364
  'access.md': '---\nwrite:\n - Admin\n---\n',
297
365
  'roles.yaml': 'roles:\n Admin:\n - a@example.com\n',
298
366
  '.gitignore': '',
@@ -335,7 +403,7 @@ describe('KbSeedService', () => {
335
403
  const dir = await checkout(root, upstream, DEFAULT_BRANCH);
336
404
  // Core's two, still there.
337
405
  expect(await exists(path.join(dir, 'KnowledgeBase/.gitkeep'))).toBe(true);
338
- expect(await exists(path.join(dir, 'Groups/.gitkeep'))).toBe(true);
406
+ expect(await exists(path.join(dir, 'Plugins/.gitkeep'))).toBe(true);
339
407
  // …and the three this distribution asked for.
340
408
  for (const claimed of ['Data', 'Agents', 'Pipelines']) {
341
409
  expect(await exists(path.join(dir, `${claimed}/.gitkeep`))).toBe(true);
@@ -379,7 +447,7 @@ describe('KbSeedService', () => {
379
447
  });
380
448
 
381
449
  /**
382
- * A FILE named `Groups` used to satisfy the presence check — `fs.access`
450
+ * A FILE named `Plugins` used to satisfy the presence check — `fs.access`
383
451
  * answers "is there something here?" — so the seeder skipped it, said
384
452
  * nothing, and left a knowledge base permanently missing a root it claims
385
453
  * to guarantee. Now it says so.
@@ -396,18 +464,18 @@ describe('KbSeedService', () => {
396
464
  '.gitignore': '',
397
465
  'KnowledgeBase/.gitkeep': '',
398
466
  // Not a folder.
399
- Groups: 'this is a file, not the groups root',
467
+ Plugins: 'this is a file, not the plugins root',
400
468
  });
401
469
  const repoDir = await checkout(root, upstream, DEFAULT_BRANCH);
402
470
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
403
471
  try {
404
472
  await makeSeeder(upstream).topUpWorkspace(repoDir, DEFAULT_BRANCH);
405
- expect(warn.mock.calls.flat().join(' ')).toMatch(/Groups.*not a directory/);
473
+ expect(warn.mock.calls.flat().join(' ')).toMatch(/Plugins.*not a directory/);
406
474
  } finally {
407
475
  warn.mockRestore();
408
476
  }
409
477
  // …and it did not quietly replace the file either.
410
- expect((await fs.lstat(path.join(repoDir, 'Groups'))).isFile()).toBe(true);
478
+ expect((await fs.lstat(path.join(repoDir, 'Plugins'))).isFile()).toBe(true);
411
479
  });
412
480
 
413
481
  /**
@@ -422,7 +490,7 @@ describe('KbSeedService', () => {
422
490
  const upstream = await seededUpstream(PROTECTED, {
423
491
  'roles.yaml': 'roles:\n Admin:\n - keep@example.com\n',
424
492
  'KnowledgeBase/Real/Knowledge/.gitkeep': '',
425
- 'Groups/.gitkeep': '',
493
+ 'Plugins/.gitkeep': '',
426
494
  });
427
495
  // Branch a suggestion off the default and load it.
428
496
  const seed = await checkout(root, upstream, DEFAULT_BRANCH);