@bevel-software/platform-core-backend 0.25.0 → 0.26.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 (254) hide show
  1. package/agent-guide/access-control.md +234 -0
  2. package/agent-guide/conventions.md +27 -0
  3. package/agent-guide/directory-structure.md +145 -0
  4. package/agent-guide/finding-things.md +7 -0
  5. package/agent-guide/introduction.md +27 -0
  6. package/agent-guide/skills.md +47 -0
  7. package/agent-guide/tool-manuals.md +217 -0
  8. package/agent-guide/where-a-new-file-goes.md +36 -0
  9. package/dist/assets.d.ts +7 -0
  10. package/dist/assets.d.ts.map +1 -1
  11. package/dist/assets.js +9 -0
  12. package/dist/assets.js.map +1 -1
  13. package/dist/core/core-ports.d.ts +11 -0
  14. package/dist/core/core-ports.d.ts.map +1 -1
  15. package/dist/core/core-ports.js.map +1 -1
  16. package/dist/core/create-core-server.d.ts.map +1 -1
  17. package/dist/core/create-core-server.js +13 -2
  18. package/dist/core/create-core-server.js.map +1 -1
  19. package/dist/core/create-core-services.d.ts +9 -0
  20. package/dist/core/create-core-services.d.ts.map +1 -1
  21. package/dist/core/create-core-services.js +14 -4
  22. package/dist/core/create-core-services.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/modules/access/access-control.interface.d.ts +9 -0
  28. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  29. package/dist/modules/access/access-control.service.d.ts +1 -0
  30. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  31. package/dist/modules/access/access-control.service.js +16 -0
  32. package/dist/modules/access/access-control.service.js.map +1 -1
  33. package/dist/modules/agent-guide/agent-guide.d.ts +139 -0
  34. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
  35. package/dist/modules/agent-guide/agent-guide.js +191 -0
  36. package/dist/modules/agent-guide/agent-guide.js.map +1 -0
  37. package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
  38. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
  39. package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
  40. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
  41. package/dist/modules/agent-guide/index.d.ts +4 -0
  42. package/dist/modules/agent-guide/index.d.ts.map +1 -0
  43. package/dist/modules/agent-guide/index.js +4 -0
  44. package/dist/modules/agent-guide/index.js.map +1 -0
  45. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
  46. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  47. package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
  48. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  49. package/dist/modules/agent-instructions/compose.d.ts +9 -6
  50. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  51. package/dist/modules/agent-instructions/compose.js +9 -6
  52. package/dist/modules/agent-instructions/compose.js.map +1 -1
  53. package/dist/modules/agent-instructions/index.d.ts +1 -1
  54. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  55. package/dist/modules/agent-instructions/index.js +1 -1
  56. package/dist/modules/agent-instructions/index.js.map +1 -1
  57. package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
  58. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  59. package/dist/modules/agent-instructions/shared-file-rules.js +32 -85
  60. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  61. package/dist/modules/mcp/mcp.service.d.ts +29 -2
  62. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  63. package/dist/modules/mcp/mcp.service.js +113 -16
  64. package/dist/modules/mcp/mcp.service.js.map +1 -1
  65. package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
  66. package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
  67. package/dist/modules/mcp/tool-schema-guard.js +171 -0
  68. package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
  69. package/dist/modules/plugins/plugins.tools.d.ts +36 -2
  70. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
  71. package/dist/modules/plugins/plugins.tools.js +71 -14
  72. package/dist/modules/plugins/plugins.tools.js.map +1 -1
  73. package/dist/modules/settings/deployment-settings.service.d.ts +0 -7
  74. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  75. package/dist/modules/settings/deployment-settings.service.js +14 -53
  76. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  77. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  78. package/dist/modules/settings/setup.routes.js +3 -6
  79. package/dist/modules/settings/setup.routes.js.map +1 -1
  80. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  81. package/dist/modules/skills/skills.tools.js +58 -16
  82. package/dist/modules/skills/skills.tools.js.map +1 -1
  83. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
  84. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  85. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  86. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
  87. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  88. package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
  89. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  90. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
  91. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  92. package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -36
  93. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  94. package/dist/modules/tool-registry/description-length.d.ts +14 -14
  95. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  96. package/dist/modules/tool-registry/description-length.js +24 -26
  97. package/dist/modules/tool-registry/description-length.js.map +1 -1
  98. package/dist/modules/tool-registry/guide-first.d.ts +23 -0
  99. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
  100. package/dist/modules/tool-registry/guide-first.js +32 -0
  101. package/dist/modules/tool-registry/guide-first.js.map +1 -0
  102. package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
  103. package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
  104. package/dist/modules/tool-registry/tool-registry.js +9 -2
  105. package/dist/modules/tool-registry/tool-registry.js.map +1 -1
  106. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
  107. package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
  108. package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
  109. package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
  110. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +73 -0
  111. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
  112. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +582 -0
  113. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
  114. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
  115. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
  116. package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
  117. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
  118. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  119. package/dist/modules/workflow/agent-tools/workflow.tools.js +9 -0
  120. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  121. package/dist/modules/workflow/git/git.service.d.ts +210 -13
  122. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  123. package/dist/modules/workflow/git/git.service.js +456 -91
  124. package/dist/modules/workflow/git/git.service.js.map +1 -1
  125. package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
  126. package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
  127. package/dist/modules/workflow/git/merge-commit.js +89 -0
  128. package/dist/modules/workflow/git/merge-commit.js.map +1 -0
  129. package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
  130. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  131. package/dist/modules/workflow/git/pull-request.service.js +332 -37
  132. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  133. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
  134. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  135. package/dist/modules/workflow/review-workflow/review-workflow.service.js +178 -12
  136. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  137. package/dist/modules/workflow/workflow.routes.d.ts +6 -2
  138. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  139. package/dist/modules/workflow/workflow.routes.js +7 -2
  140. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  141. package/dist/modules/workflow/workflow.service.d.ts +4 -0
  142. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  143. package/dist/modules/workflow/workflow.service.js +3 -0
  144. package/dist/modules/workflow/workflow.service.js.map +1 -1
  145. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +70 -0
  146. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  147. package/dist/modules/workspace/startup/kb-startup-runner.js +213 -20
  148. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  149. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  150. package/dist/modules/workspace/startup/steps/seed-tree.js +22 -27
  151. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  152. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
  153. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  154. package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
  155. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  156. package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
  157. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  158. package/dist/modules/workspace/startup/steps/template-source.js +5 -3
  159. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  160. package/dist/modules/workspace/workspace.tools.d.ts +10 -1
  161. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  162. package/dist/modules/workspace/workspace.tools.js +211 -18
  163. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  164. package/dist/shared/domain-errors.d.ts +11 -0
  165. package/dist/shared/domain-errors.d.ts.map +1 -1
  166. package/dist/shared/domain-errors.js +14 -0
  167. package/dist/shared/domain-errors.js.map +1 -1
  168. package/dist/shared/hidden-tools.d.ts +44 -0
  169. package/dist/shared/hidden-tools.d.ts.map +1 -0
  170. package/dist/shared/hidden-tools.js +13 -0
  171. package/dist/shared/hidden-tools.js.map +1 -0
  172. package/kb-template/.bevelignore +0 -5
  173. package/package.json +4 -3
  174. package/src/__tests__/kb-layout-config.test.ts +10 -100
  175. package/src/__tests__/packaged-assets-ship.test.ts +54 -0
  176. package/src/assets.ts +10 -0
  177. package/src/core/core-ports.ts +11 -0
  178. package/src/core/create-core-server.ts +13 -2
  179. package/src/core/create-core-services.ts +28 -4
  180. package/src/index.ts +2 -2
  181. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
  182. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
  183. package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
  184. package/src/modules/access/access-control.interface.ts +15 -0
  185. package/src/modules/access/access-control.service.ts +21 -0
  186. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +328 -0
  187. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +189 -0
  188. package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
  189. package/src/modules/agent-guide/agent-guide.ts +291 -0
  190. package/src/modules/agent-guide/index.ts +21 -0
  191. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +28 -121
  192. package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
  193. package/src/modules/agent-instructions/compose.ts +9 -6
  194. package/src/modules/agent-instructions/index.ts +0 -3
  195. package/src/modules/agent-instructions/shared-file-rules.ts +31 -93
  196. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
  197. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +250 -0
  198. package/src/modules/mcp/__tests__/mcp.service.test.ts +31 -23
  199. package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
  200. package/src/modules/mcp/mcp.service.ts +137 -19
  201. package/src/modules/mcp/tool-schema-guard.ts +196 -0
  202. package/src/modules/plugins/__tests__/plugins.tools.test.ts +154 -4
  203. package/src/modules/plugins/plugins.tools.ts +75 -15
  204. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +26 -55
  205. package/src/modules/settings/deployment-settings.service.ts +13 -54
  206. package/src/modules/settings/setup.routes.ts +3 -6
  207. package/src/modules/skills/__tests__/skills.tools.description.test.ts +91 -0
  208. package/src/modules/skills/skills.tools.ts +62 -16
  209. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
  210. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
  211. package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
  212. package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
  213. package/src/modules/tool-manuals/tool-manuals.tools.ts +74 -36
  214. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +160 -0
  215. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +61 -59
  216. package/src/modules/tool-registry/description-length.ts +24 -26
  217. package/src/modules/tool-registry/guide-first.ts +34 -0
  218. package/src/modules/tool-registry/tool-registry.ts +9 -2
  219. package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
  220. package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
  221. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1518 -0
  222. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +23 -2
  223. package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
  224. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +724 -0
  225. package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
  226. package/src/modules/workflow/agent-tools/workflow.tools.ts +8 -0
  227. package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
  228. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
  229. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
  230. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
  231. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
  232. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
  233. package/src/modules/workflow/git/git.service.ts +537 -94
  234. package/src/modules/workflow/git/merge-commit.ts +88 -0
  235. package/src/modules/workflow/git/pull-request.service.ts +380 -54
  236. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
  237. package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
  238. package/src/modules/workflow/review-workflow/review-workflow.service.ts +189 -11
  239. package/src/modules/workflow/workflow.routes.ts +7 -2
  240. package/src/modules/workflow/workflow.service.ts +7 -0
  241. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
  242. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
  243. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
  244. package/src/modules/workspace/__tests__/workspace.tools.test.ts +255 -22
  245. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +231 -1
  246. package/src/modules/workspace/startup/kb-startup-runner.ts +216 -19
  247. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
  248. package/src/modules/workspace/startup/steps/seed-tree.ts +21 -27
  249. package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
  250. package/src/modules/workspace/startup/steps/template-source.ts +5 -3
  251. package/src/modules/workspace/workspace.tools.ts +226 -16
  252. package/src/shared/domain-errors.ts +15 -0
  253. package/src/shared/hidden-tools.ts +45 -0
  254. package/kb-template/AGENTS.md +0 -730
@@ -52,8 +52,13 @@ function toHttpError(
52
52
  return { status: 500, body: { error: 'Internal server error' } };
53
53
  }
54
54
 
55
- /** What a viewer who cannot read every touched file reads in place of a refusal's reason. */
56
- export const APPLY_FAILURE_REASON_WITHHELD = 'The reason names files you do not have access to read.';
55
+ /**
56
+ * What a viewer who cannot read every touched file reads in place of a
57
+ * refusal's reason — one string, shared with the agent read tools, which
58
+ * withhold on the same predicate.
59
+ */
60
+ export { APPLY_FAILURE_REASON_WITHHELD } from './agent-tools/change-request-read-shape.js';
61
+ import { APPLY_FAILURE_REASON_WITHHELD } from './agent-tools/change-request-read-shape.js';
57
62
 
58
63
  function parsePrNumber(raw: string): number | null {
59
64
  if (!/^\d+$/.test(raw)) return null;
@@ -1730,6 +1730,13 @@ export class WorkflowService implements IWorkflowService {
1730
1730
  return this.prs.listOpenPrs(opts);
1731
1731
  }
1732
1732
 
1733
+ listChangeRequestsByState(
1734
+ states: ChangeRequestState[],
1735
+ opts?: { fresh?: boolean; workspaceId?: string },
1736
+ ): Promise<ChangeRequest[]> {
1737
+ return this.prs.listPrsByState(states, opts);
1738
+ }
1739
+
1733
1740
  listChangeRequestsAuthoredBy(
1734
1741
  emailOrLogin: string,
1735
1742
  opts?: { fresh?: boolean },
@@ -21,7 +21,8 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
21
21
  import { createMcpRoutes } from '../../mcp/mcp.routes.js';
22
22
  import { McpService } from '../../mcp/mcp.service.js';
23
23
  import { testKbContext } from '../../../__tests__/kb-context.js';
24
- import { sharedFileRules, sharedFileRulesSection, sharedRulesPointer } from '../../agent-instructions/shared-file-rules.js';
24
+ import { sharedFileRules, sharedFileRulesSection } from '../../agent-instructions/shared-file-rules.js';
25
+ import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
25
26
 
26
27
  /**
27
28
  * Who decodes escape sequences in written content a second time — settled on
@@ -386,9 +387,9 @@ describe('what the write tools tell an agent about escape sequences', () => {
386
387
  .filter(([, d]) => d.includes('decode escape sequences in arguments before sending') || d.includes('some clients decode them in arguments'))
387
388
  .map(([n]) => n);
388
389
  expect(carrying).toEqual([]);
389
- // Each of the three still points at where the warning is.
390
+ // Each of the three still opens by sending the agent to the guide, where the warning is.
390
391
  for (const name of ['write_file', 'write_files', 'edit_file']) {
391
- expect(served[name], name).toContain(sharedRulesPointer(testKbContext().layout).trim());
392
+ expect(served[name]!.startsWith(GUIDE_FIRST_SENTENCE), name).toBe(true);
392
393
  }
393
394
  });
394
395
  });
@@ -136,14 +136,14 @@ describe('a platform file stays in its folder', () => {
136
136
  await fs.rm(h.workspaceDir, { recursive: true, force: true });
137
137
  });
138
138
 
139
- // Each of the four, at a place the platform reads it from. `access.md` and
140
- // `.bevelignore` are read wherever they sit; `roles.yaml` and `AGENTS.md`
141
- // are read from the repository root only.
139
+ // Each of the three, at a place the platform reads it from. `access.md` and
140
+ // `.bevelignore` are read wherever they sit; `roles.yaml` is read from the
141
+ // repository root only. (`AGENTS.md` is not among them: the agent guide is
142
+ // served from code, so a root `AGENTS.md` is the organisation's own page.)
142
143
  const PLATFORM_FILES = [
143
144
  { name: 'access.md', at: `${KB}/Sales/access.md` },
144
145
  { name: '.bevelignore', at: `${KB}/Sales/.bevelignore` },
145
146
  { name: 'roles.yaml', at: `${KB}/roles.yaml` },
146
- { name: 'AGENTS.md', at: `${KB}/AGENTS.md` },
147
147
  ];
148
148
 
149
149
  it.each(PLATFORM_FILES)('refuses a rename of $name with the sentence', async ({ name, at }) => {
@@ -188,13 +188,12 @@ describe('a platform file stays in its folder', () => {
188
188
  },
189
189
  );
190
190
 
191
- // The recovery move, for each of the four, from the place a misplaced copy
191
+ // The recovery move, for each of the three, from the place a misplaced copy
192
192
  // is actually found to the place the platform reads it.
193
193
  const RESTORES = [
194
194
  { name: 'access.md', from: `${KB}/Misplaced/access.md`, to: `${KB}/access.md`, dest: 'access.md' },
195
195
  { name: '.bevelignore', from: `${KB}/Misplaced/.bevelignore`, to: `${KB}/.bevelignore`, dest: '.bevelignore' },
196
196
  { name: 'roles.yaml', from: `${KB}/Misplaced/roles.yaml`, to: `${KB}/roles.yaml`, dest: 'roles.yaml' },
197
- { name: 'AGENTS.md', from: `${KB}/Misplaced/AGENTS.md`, to: `${KB}/AGENTS.md`, dest: 'AGENTS.md' },
198
197
  ];
199
198
 
200
199
  it.each(RESTORES)(
@@ -265,6 +264,14 @@ describe('a platform file stays in its folder', () => {
265
264
 
266
265
  expect(h.moveEntry).not.toHaveBeenCalled();
267
266
  expect(h.acquireLock).not.toHaveBeenCalled();
267
+
268
+ // A root `AGENTS.md` is NOT a platform file any more — the guide is served
269
+ // from code — so a note renamed to it is an ordinary move that lands.
270
+ const guide = await move(h, `${KB}/Sales/deal.md`, `${KB}/AGENTS.md`);
271
+ expect(guide.status).toBe(200);
272
+ expect(guide.body).toEqual({ status: 'moved' });
273
+ expect(h.moveEntry).toHaveBeenCalledTimes(1);
274
+ expect(h.moveEntry).toHaveBeenCalledWith(WORKSPACE_ID, `${KB}/Sales/deal.md`, `${KB}/AGENTS.md`);
268
275
  });
269
276
 
270
277
  it('a destination that fills up after the access check is caught under the lock', async () => {
@@ -318,11 +325,15 @@ describe('a platform file stays in its folder', () => {
318
325
  }
319
326
  });
320
327
 
321
- it('a nested roles.yaml or AGENTS.md is ordinary content and moves freely', async () => {
322
- // They are read from the root and nowhere else, so a nested file of
323
- // either name carries no platform meaning to protect.
328
+ it('a nested roles.yaml, and an AGENTS.md anywhere, are ordinary content and move freely', async () => {
329
+ // `roles.yaml` is read from the root and nowhere else, so a nested file
330
+ // of that name carries no platform meaning to protect. An `AGENTS.md` is
331
+ // never read as configuration: the platform's guide is served from code,
332
+ // so the organisation's own file moves and renames like any page, at the
333
+ // root included.
324
334
  expect((await move(h, `${KB}/Sales/roles.yaml`, `${KB}/Sales/old-roles.yaml`)).status).toBe(200);
325
335
  expect((await move(h, `${KB}/Sales/AGENTS.md`, `${KB}/Legal/AGENTS.md`)).status).toBe(200);
326
- expect(h.moveEntry).toHaveBeenCalledTimes(2);
336
+ expect((await move(h, `${KB}/AGENTS.md`, `${KB}/Handbook/AGENTS.md`)).status).toBe(200);
337
+ expect(h.moveEntry).toHaveBeenCalledTimes(3);
327
338
  });
328
339
  });
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { DEFAULT_KB_LAYOUT } from '@bevel-software/platform-shared';
6
6
  import { testKbContext } from '../../../__tests__/kb-context.js';
7
7
  import { ToolRegistry } from '../../tool-registry/tool-registry.js';
8
+ import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
8
9
  import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
9
10
  import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
10
11
  import type { IAccessControl } from '../../access/access-control.interface.js';
@@ -14,19 +15,18 @@ import { SpillStore } from '../spill-store.js';
14
15
  import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
15
16
  import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
16
17
  import { registerWorkspaceTools } from '../workspace.tools.js';
17
- import { sharedFileRulesSection, sharedRulesPointer } from '../../agent-instructions/shared-file-rules.js';
18
+ import { sharedFileRulesSection } from '../../agent-instructions/shared-file-rules.js';
18
19
 
19
20
  /**
20
- * What the platform TELLS AN AGENT about the conventions file, under the
21
- * default name and under a deployment's own.
21
+ * What the platform TELLS AN AGENT about the guide.
22
22
  *
23
- * The conventions reminder and the platform-file list are SHARED RULES now —
24
- * stated in the handshake instructions and in the managed agent guide, once
25
- * each — so their wording is asserted against that one text. What each tool
26
- * description still has to get right is the POINTER at the end, which names the
27
- * guide by the name this deployment gave it; that is the only place a remote
28
- * agent learns which file to open, and it has no checkout, so no harness reads
29
- * that file for it.
23
+ * The conventions reminder and the platform-file list are SHARED RULES —
24
+ * stated in the handshake instructions and in the guide, once each — so their
25
+ * wording is asserted against that one text. What each tool description has
26
+ * to get right is its OPENING: the one sentence saying to call
27
+ * `get_agent_guide` first, which the catalog puts at the front of every tool
28
+ * (see tool-registry/guide-first.ts); a remote agent has no checkout, so that
29
+ * sentence and the rule are the only places it learns what to read first.
30
30
  */
31
31
  const KB_DIR = 'knowledge-base';
32
32
  /** The context the tools read; a case applies a deployment's own names to it. */
@@ -64,70 +64,48 @@ async function listed(registry: ToolRegistry): Promise<Map<string, string>> {
64
64
 
65
65
  afterEach(() => kb.applyLayout({ ...DEFAULT_KB_LAYOUT }));
66
66
 
67
- describe("what names the guide, under the default name and under a deployment's own", () => {
68
- it('names AGENTS.md, and CLAUDE.md beside it, under the default name', () => {
67
+ describe('what tells an agent to read the guide first', () => {
68
+ it('tells an agent to call get_agent_guide first, and that AGENTS.md answers with the same guide after the organisation\'s own', () => {
69
69
  const rules = sharedFileRulesSection(kb.layout);
70
- expect(rules).toContain(
71
- 'read `AGENTS.md` at the KB root — or `CLAUDE.md` on a knowledge base seeded before it was renamed',
72
- );
70
+ expect(rules).toContain("call `get_agent_guide` and read the platform's guide");
71
+ expect(rules).toContain('read_file on `AGENTS.md` at the KB root answers with the same guide');
72
+ expect(rules).toContain("after the organisation's own conventions file of that name");
73
+ expect(rules).toContain('`CLAUDE.md`');
74
+ // One name on every deployment: the rule names no other.
75
+ expect(rules).not.toContain('HEXIS.md');
73
76
  });
74
77
 
75
- it("points every entrypoint at the section, by the guide's own name", async () => {
78
+ it('opens every entrypoint with the guide-first sentence, reads included', async () => {
76
79
  const byName = await descriptions();
77
- // On EVERY entrypoint, reads included: any of them can be a session's first.
78
- for (const name of ['grep', 'list_files', 'file_stat', 'write_file', 'move_file']) {
79
- expect(byName.get(name), name).toContain('see "Working with files" in AGENTS.md.');
80
+ for (const name of ['grep', 'list_files', 'file_stat', 'read_file', 'write_file', 'move_file', 'start_session']) {
81
+ expect(byName.get(name)!.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
82
+ // Once, at the front — never again further down.
83
+ expect(byName.get(name)!.split(GUIDE_FIRST_SENTENCE), name).toHaveLength(2);
80
84
  }
81
85
  });
82
86
 
83
- it("names the configured file first and the organisation's own AGENTS.md second", () => {
84
- kb.applyLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
85
- const note = sharedFileRulesSection(kb.layout);
86
- expect(note).toContain('read `HEXIS.md` at the KB root, then `AGENTS.md` if it also exists');
87
- expect(note).toContain("the organisation's own conventions");
88
- // Ours first: an agent that reads only one must read the platform's.
89
- expect(note.indexOf('`HEXIS.md`')).toBeLessThan(note.indexOf('`AGENTS.md`'));
90
- // The legacy name is still offered, for a KB seeded before the rename.
91
- expect(note).toContain('`CLAUDE.md`');
92
- });
93
-
94
- it('lists the platform files under the configured name, and no longer under AGENTS.md', () => {
87
+ it('lists the guide under no name as a platform file, and ignores a name a deployment saved for it', () => {
95
88
  kb.applyLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
96
89
  const rules = sharedFileRulesSection(kb.layout);
97
- // Listed with the depth each one counts at — `access.md` and
98
- // `.bevelignore` govern the folder they sit in, the other two are read from
99
- // the root — because the names alone would have an agent refuse a nested
100
- // `HEXIS.md` it may rename.
101
- expect(rules).toContain('`access.md` or `.bevelignore` in any folder, `roles.yaml` or `HEXIS.md` at the repository root');
102
- // The customer's file is content on such a deployment, so the rule that
103
- // refuses a move must not claim it.
104
- expect(rules.replace(/`AGENTS\.md` if it also exists/g, '')).not.toContain('`AGENTS.md`');
105
- });
106
-
107
- it('keeps naming AGENTS.md as a platform file under the default name', () => {
108
- expect(sharedFileRulesSection(kb.layout)).toContain(
109
- '`access.md` or `.bevelignore` in any folder, `roles.yaml` or `AGENTS.md` at the repository root',
110
- );
90
+ expect(rules).toContain('`access.md` or `.bevelignore` in any folder, `roles.yaml` at the repository root');
91
+ expect(rules).not.toContain('HEXIS.md');
92
+ expect(rules).toBe(sharedFileRulesSection(DEFAULT_KB_LAYOUT));
111
93
  });
112
94
 
113
95
  /**
114
96
  * First-run setup on a fresh deployment: the tools are mounted at boot, under
115
97
  * the defaults, and the save that COMPLETES setup applies the admin's names
116
- * in that same request — without a restart, deliberately, so the KB phase it
117
- * runs next scaffolds the names they chose. A catalog built once at boot
118
- * would go on naming `AGENTS.md` to every agent that connected afterwards.
98
+ * in that same request — without a restart. Nothing in the opening sentence
99
+ * moves with a name, so what the catalog said at boot is what it says after.
119
100
  */
120
- it('follows a layout applied after the tools were mounted', async () => {
101
+ it('says the same thing after a layout is applied as it did at the mount', async () => {
121
102
  const registry = new ToolRegistry();
122
103
  const atMount = await descriptions(registry);
123
- expect(atMount.get('grep')).toContain('in AGENTS.md.');
124
-
125
- kb.applyLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
126
-
104
+ kb.applyLayout({ ...DEFAULT_KB_LAYOUT, knowledgeBaseDir: 'Docs' });
127
105
  const now = await listed(registry);
128
106
  for (const name of ['grep', 'file_stat', 'delete_file', 'move_file']) {
129
- expect(now.get(name), name).toContain(sharedRulesPointer(kb.layout));
130
- expect(now.get(name), name).not.toContain('in AGENTS.md.');
107
+ expect(now.get(name)!.startsWith(GUIDE_FIRST_SENTENCE), name).toBe(true);
108
+ expect(now.get(name), name).toBe(atMount.get(name));
131
109
  }
132
110
  });
133
111
  });