@bevel-software/platform-core-backend 0.22.0 → 0.24.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 (466) hide show
  1. package/dist/core/core-ports.d.ts +11 -1
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts +3 -3
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +46 -13
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +12 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +137 -24
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +46 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +99 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +16 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +28 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +6 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +12 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/access/access-control.interface.d.ts +42 -12
  25. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  26. package/dist/modules/access/access-control.service.d.ts +67 -10
  27. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  28. package/dist/modules/access/access-control.service.js +214 -35
  29. package/dist/modules/access/access-control.service.js.map +1 -1
  30. package/dist/modules/access/access.routes.d.ts.map +1 -1
  31. package/dist/modules/access/access.routes.js +17 -16
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  34. package/dist/modules/access/directory-sync-bot.js +7 -3
  35. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  36. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
  37. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  38. package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
  39. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  40. package/dist/modules/agent-instructions/compose.d.ts +38 -6
  41. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  42. package/dist/modules/agent-instructions/compose.js +39 -6
  43. package/dist/modules/agent-instructions/compose.js.map +1 -1
  44. package/dist/modules/agent-instructions/index.d.ts +2 -1
  45. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  46. package/dist/modules/agent-instructions/index.js +2 -1
  47. package/dist/modules/agent-instructions/index.js.map +1 -1
  48. package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
  49. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
  50. package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
  51. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
  52. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  53. package/dist/modules/audit/agent-audit.service.js +4 -2
  54. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  55. package/dist/modules/auth/account-admission.d.ts +58 -7
  56. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  57. package/dist/modules/auth/account-admission.js +44 -1
  58. package/dist/modules/auth/account-admission.js.map +1 -1
  59. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  60. package/dist/modules/auth/account-erasure.service.js +57 -16
  61. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  62. package/dist/modules/auth/account.routes.d.ts +1 -1
  63. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  64. package/dist/modules/auth/account.routes.js +61 -5
  65. package/dist/modules/auth/account.routes.js.map +1 -1
  66. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  67. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  68. package/dist/modules/auth/auth.middleware.js +18 -7
  69. package/dist/modules/auth/auth.middleware.js.map +1 -1
  70. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  71. package/dist/modules/auth/auth.routes.js +8 -0
  72. package/dist/modules/auth/auth.routes.js.map +1 -1
  73. package/dist/modules/auth/auth.service.d.ts +70 -2
  74. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  75. package/dist/modules/auth/auth.service.js +197 -18
  76. package/dist/modules/auth/auth.service.js.map +1 -1
  77. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  78. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  79. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  80. package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
  81. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  82. package/dist/modules/code-mode/code-mode.tool.js +66 -35
  83. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  84. package/dist/modules/database/connection.d.ts +16 -0
  85. package/dist/modules/database/connection.d.ts.map +1 -1
  86. package/dist/modules/database/connection.js +117 -0
  87. package/dist/modules/database/connection.js.map +1 -1
  88. package/dist/modules/database/core-schema.d.ts +337 -120
  89. package/dist/modules/database/core-schema.d.ts.map +1 -1
  90. package/dist/modules/database/core-schema.js +116 -58
  91. package/dist/modules/database/core-schema.js.map +1 -1
  92. package/dist/modules/database/migrate.d.ts +8 -0
  93. package/dist/modules/database/migrate.d.ts.map +1 -1
  94. package/dist/modules/database/migrate.js +271 -1
  95. package/dist/modules/database/migrate.js.map +1 -1
  96. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  97. package/dist/modules/kb-fs/branch-name.js +12 -2
  98. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  99. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  100. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  101. package/dist/modules/kb-fs/repo-path.js +79 -0
  102. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  103. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  104. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  105. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  106. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  107. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  108. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  109. package/dist/modules/kb-sync/sync-auth.js +1 -1
  110. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  111. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  112. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  113. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  114. package/dist/modules/mcp/mcp.service.d.ts +8 -0
  115. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  116. package/dist/modules/mcp/mcp.service.js +38 -8
  117. package/dist/modules/mcp/mcp.service.js.map +1 -1
  118. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  119. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  120. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  121. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
  122. package/dist/modules/plugins/join-request-records.store.js +7 -4
  123. package/dist/modules/plugins/join-request-records.store.js.map +1 -1
  124. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  125. package/dist/modules/settings/deployment-settings.service.js +8 -3
  126. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  127. package/dist/modules/settings/setup.routes.d.ts +52 -1
  128. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  129. package/dist/modules/settings/setup.routes.js +207 -19
  130. package/dist/modules/settings/setup.routes.js.map +1 -1
  131. package/dist/modules/skills/skills.contract.d.ts +90 -18
  132. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  133. package/dist/modules/skills/skills.contract.js +4 -1
  134. package/dist/modules/skills/skills.contract.js.map +1 -1
  135. package/dist/modules/skills/skills.service.d.ts +98 -9
  136. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  137. package/dist/modules/skills/skills.service.js +239 -37
  138. package/dist/modules/skills/skills.service.js.map +1 -1
  139. package/dist/modules/skills/skills.tools.d.ts +7 -0
  140. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  141. package/dist/modules/skills/skills.tools.js +71 -7
  142. package/dist/modules/skills/skills.tools.js.map +1 -1
  143. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  144. package/dist/modules/tool-auth/external-api-key.service.js +12 -4
  145. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  146. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  147. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  148. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  149. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  150. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  151. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  152. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  153. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  154. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  155. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  156. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  157. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  158. package/dist/modules/tool-helpers/validate-token.js +1 -1
  159. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  160. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  161. package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
  162. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  163. package/dist/modules/tool-registry/description-length.d.ts +80 -0
  164. package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
  165. package/dist/modules/tool-registry/description-length.js +108 -0
  166. package/dist/modules/tool-registry/description-length.js.map +1 -0
  167. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  168. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  169. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  170. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  171. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  172. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  173. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  174. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  175. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  176. package/dist/modules/workflow/file-lock.service.js +30 -0
  177. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  178. package/dist/modules/workflow/git/git.service.d.ts +94 -0
  179. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  180. package/dist/modules/workflow/git/git.service.js +193 -3
  181. package/dist/modules/workflow/git/git.service.js.map +1 -1
  182. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  183. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  184. package/dist/modules/workflow/pending-commits.service.js +60 -1
  185. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  186. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  187. package/dist/modules/workflow/recovery-bot.js +7 -3
  188. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  189. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  190. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  191. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  192. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  193. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  194. package/dist/modules/workflow/workflow-hooks.js +16 -1
  195. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  196. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  197. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  198. package/dist/modules/workflow/workflow.service.js +115 -5
  199. package/dist/modules/workflow/workflow.service.js.map +1 -1
  200. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  201. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  202. package/dist/modules/workspace/agent-access.gate.js +123 -0
  203. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  204. package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
  205. package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
  206. package/dist/modules/workspace/agent-upload.routes.js +210 -0
  207. package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
  208. package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
  209. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
  210. package/dist/modules/workspace/agent-upload.store.js +553 -0
  211. package/dist/modules/workspace/agent-upload.store.js.map +1 -0
  212. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  213. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  214. package/dist/modules/workspace/routine-write-policy.js +5 -6
  215. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  216. package/dist/modules/workspace/session-sink.d.ts +5 -5
  217. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  218. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  219. package/dist/modules/workspace/set-aside-clone.js +92 -0
  220. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  221. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  222. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  223. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  224. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  225. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  226. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
  227. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  228. package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
  229. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  230. package/dist/modules/workspace/startup/steps/template-source.js +46 -4
  231. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  232. package/dist/modules/workspace/upload-limits.d.ts +13 -0
  233. package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
  234. package/dist/modules/workspace/upload-limits.js +13 -0
  235. package/dist/modules/workspace/upload-limits.js.map +1 -0
  236. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  237. package/dist/modules/workspace/workspace.routes.js +93 -4
  238. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  239. package/dist/modules/workspace/workspace.service.d.ts +128 -5
  240. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  241. package/dist/modules/workspace/workspace.service.js +316 -58
  242. package/dist/modules/workspace/workspace.service.js.map +1 -1
  243. package/dist/modules/workspace/workspace.tools.d.ts +13 -11
  244. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  245. package/dist/modules/workspace/workspace.tools.js +791 -198
  246. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  247. package/dist/modules/workspace/write-denial.d.ts +0 -6
  248. package/dist/modules/workspace/write-denial.d.ts.map +1 -1
  249. package/dist/modules/workspace/write-denial.js +0 -6
  250. package/dist/modules/workspace/write-denial.js.map +1 -1
  251. package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
  252. package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
  253. package/dist/modules/workspace/zip-entry-rules.js +154 -0
  254. package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
  255. package/dist/modules/write-access/write-access.d.ts +60 -0
  256. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  257. package/dist/modules/write-access/write-access.js +129 -0
  258. package/dist/modules/write-access/write-access.js.map +1 -0
  259. package/dist/shared/column-crypto.d.ts +194 -0
  260. package/dist/shared/column-crypto.d.ts.map +1 -0
  261. package/dist/shared/column-crypto.js +144 -0
  262. package/dist/shared/column-crypto.js.map +1 -0
  263. package/dist/shared/domain-errors.d.ts +25 -0
  264. package/dist/shared/domain-errors.d.ts.map +1 -1
  265. package/dist/shared/domain-errors.js +28 -0
  266. package/dist/shared/domain-errors.js.map +1 -1
  267. package/dist/shared/git.contract.d.ts +20 -0
  268. package/dist/shared/git.contract.d.ts.map +1 -1
  269. package/dist/shared/git.contract.js +26 -0
  270. package/dist/shared/git.contract.js.map +1 -1
  271. package/dist/shared/token-crypto.d.ts.map +1 -1
  272. package/dist/shared/token-crypto.js +25 -1
  273. package/dist/shared/token-crypto.js.map +1 -1
  274. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  275. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  276. package/dist/tenancy/static-tenant-source.js +1 -2
  277. package/dist/tenancy/static-tenant-source.js.map +1 -1
  278. package/dist/tenancy/tenant-secrets.d.ts +5 -1
  279. package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
  280. package/dist/tenancy/tenant-secrets.js +4 -0
  281. package/dist/tenancy/tenant-secrets.js.map +1 -1
  282. package/kb-template/AGENTS.md +2 -0
  283. package/migrations/0014_change_request_closed_reason.sql +1 -0
  284. package/migrations/0015_account_deactivation.sql +3 -0
  285. package/migrations/0016_pii_encryption.sql +20 -0
  286. package/migrations/meta/0014_snapshot.json +2265 -0
  287. package/migrations/meta/0015_snapshot.json +2271 -0
  288. package/migrations/meta/0016_snapshot.json +2327 -0
  289. package/migrations/meta/_journal.json +21 -0
  290. package/package.json +3 -3
  291. package/src/__tests__/kb-layout-config.test.ts +0 -2
  292. package/src/__tests__/retired-settings.test.ts +96 -0
  293. package/src/core/__tests__/gated-boot.test.ts +132 -0
  294. package/src/core/__tests__/lifecycle.test.ts +241 -5
  295. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  296. package/src/core/core-ports.ts +11 -1
  297. package/src/core/create-core-server.ts +52 -16
  298. package/src/core/create-core-services.ts +156 -24
  299. package/src/core/lifecycle.ts +117 -13
  300. package/src/core-config.ts +31 -14
  301. package/src/index.ts +30 -1
  302. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  303. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  304. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  305. package/src/modules/access/__tests__/users-db-double.ts +21 -12
  306. package/src/modules/access/access-control.interface.ts +43 -12
  307. package/src/modules/access/access-control.service.ts +234 -36
  308. package/src/modules/access/access.routes.ts +17 -16
  309. package/src/modules/access/directory-sync-bot.ts +7 -3
  310. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
  311. package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
  312. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
  313. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
  314. package/src/modules/agent-instructions/compose.ts +50 -7
  315. package/src/modules/agent-instructions/index.ts +12 -0
  316. package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
  317. package/src/modules/audit/agent-audit.service.ts +4 -2
  318. package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
  319. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
  320. package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
  321. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  322. package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
  323. package/src/modules/auth/account-admission.ts +78 -9
  324. package/src/modules/auth/account-erasure.service.ts +69 -18
  325. package/src/modules/auth/account.routes.ts +63 -7
  326. package/src/modules/auth/auth.middleware.ts +19 -8
  327. package/src/modules/auth/auth.routes.ts +8 -0
  328. package/src/modules/auth/auth.service.ts +206 -23
  329. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  330. package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
  331. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
  332. package/src/modules/code-mode/code-mode.tool.ts +80 -34
  333. package/src/modules/database/__tests__/connection.test.ts +12 -0
  334. package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
  335. package/src/modules/database/connection.ts +117 -0
  336. package/src/modules/database/core-schema.ts +116 -58
  337. package/src/modules/database/migrate.ts +353 -1
  338. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  339. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  340. package/src/modules/kb-fs/branch-name.ts +14 -1
  341. package/src/modules/kb-fs/repo-path.ts +85 -0
  342. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  343. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  344. package/src/modules/kb-sync/sync-auth.ts +2 -2
  345. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  346. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
  347. package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
  348. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  349. package/src/modules/mcp/mcp.service.ts +46 -7
  350. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  351. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  352. package/src/modules/plugins/join-request-records.store.ts +7 -4
  353. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  354. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  355. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  356. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  357. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  358. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  359. package/src/modules/settings/deployment-settings.service.ts +8 -3
  360. package/src/modules/settings/setup.routes.ts +263 -19
  361. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  362. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  363. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  364. package/src/modules/skills/skills.contract.ts +91 -18
  365. package/src/modules/skills/skills.service.ts +278 -41
  366. package/src/modules/skills/skills.tools.ts +80 -8
  367. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  368. package/src/modules/tool-auth/external-api-key.service.ts +12 -4
  369. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  370. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  371. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  372. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  373. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  374. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  375. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  376. package/src/modules/tool-helpers/validate-token.ts +1 -1
  377. package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
  378. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
  379. package/src/modules/tool-registry/description-length.ts +111 -0
  380. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  381. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  382. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  383. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  384. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  385. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  386. package/src/modules/workflow/file-lock.service.ts +31 -0
  387. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  388. package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
  389. package/src/modules/workflow/git/git.service.ts +215 -1
  390. package/src/modules/workflow/pending-commits.service.ts +64 -2
  391. package/src/modules/workflow/recovery-bot.ts +7 -3
  392. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
  393. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
  394. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  395. package/src/modules/workflow/workflow-hooks.ts +64 -26
  396. package/src/modules/workflow/workflow.service.ts +125 -5
  397. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  398. package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
  399. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
  400. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  401. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  402. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  403. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  404. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  405. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
  406. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  407. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
  408. package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
  409. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
  410. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  411. package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
  412. package/src/modules/workspace/agent-access.gate.ts +164 -0
  413. package/src/modules/workspace/agent-upload.routes.ts +214 -0
  414. package/src/modules/workspace/agent-upload.store.ts +668 -0
  415. package/src/modules/workspace/routine-write-policy.ts +5 -6
  416. package/src/modules/workspace/session-sink.ts +5 -5
  417. package/src/modules/workspace/set-aside-clone.ts +96 -0
  418. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  419. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  420. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
  421. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
  422. package/src/modules/workspace/startup/steps/template-source.ts +53 -5
  423. package/src/modules/workspace/upload-limits.ts +12 -0
  424. package/src/modules/workspace/workspace.routes.ts +96 -5
  425. package/src/modules/workspace/workspace.service.ts +319 -61
  426. package/src/modules/workspace/workspace.tools.ts +894 -214
  427. package/src/modules/workspace/write-denial.ts +0 -8
  428. package/src/modules/workspace/zip-entry-rules.ts +173 -0
  429. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  430. package/src/modules/write-access/write-access.ts +153 -0
  431. package/src/shared/__tests__/column-crypto.test.ts +217 -0
  432. package/src/shared/column-crypto.ts +218 -0
  433. package/src/shared/domain-errors.ts +31 -0
  434. package/src/shared/git.contract.ts +27 -0
  435. package/src/shared/token-crypto.ts +28 -1
  436. package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
  437. package/src/tenancy/static-tenant-source.ts +1 -3
  438. package/src/tenancy/tenant-secrets.ts +5 -1
  439. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  440. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  441. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  442. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  443. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  444. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  445. package/dist/modules/workflow/session-ontology.service.js +0 -147
  446. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  447. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  448. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  449. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  450. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  451. package/dist/shared/kb-layout.d.ts +0 -39
  452. package/dist/shared/kb-layout.d.ts.map +0 -1
  453. package/dist/shared/kb-layout.js +0 -103
  454. package/dist/shared/kb-layout.js.map +0 -1
  455. package/dist/shared/kb-layout.test.d.ts +0 -2
  456. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  457. package/dist/shared/kb-layout.test.js +0 -75
  458. package/dist/shared/kb-layout.test.js.map +0 -1
  459. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  460. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  461. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  462. package/src/modules/workflow/session-ontology.service.ts +0 -183
  463. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  464. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  465. package/src/shared/kb-layout.test.ts +0 -98
  466. package/src/shared/kb-layout.ts +0 -102
@@ -1,10 +1,11 @@
1
1
  import { describe, test, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { NodeFs } from '../../kb-fs/node-fs.js';
3
- import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
3
+ import { mkdtemp, rm, mkdir, writeFile, cp } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
7
- import { SkillService } from '../skills.service.js';
7
+ import { SkillService, unmergedSkillNotice } from '../skills.service.js';
8
+ import { BranchNotFoundError } from '../../../shared/domain-errors.js';
8
9
  import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
9
10
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
10
11
  import type { IAccessControl } from '../../access/access-control.interface.js';
@@ -38,11 +39,20 @@ You answer RFIs.
38
39
 
39
40
  describe('SkillService', () => {
40
41
  let root: string;
42
+ /**
43
+ * The branches that have a clone under `root`. A name that is not in here
44
+ * answers the way the real service does for a branch nobody ever pushed: the
45
+ * 404 the file tools answer, not an empty workspace.
46
+ */
47
+ let clones: Set<string>;
41
48
 
42
- // WorkspaceService stub: default-branch workspace lives at <root>/<wsId>, the
43
- // KB clone at <root>/<wsId>/<KB_DIR>.
49
+ // WorkspaceService stub: a branch's workspace lives at <root>/<branch's id>,
50
+ // its KB clone at <root>/<id>/<KB_DIR>.
44
51
  const workspaceService = {
45
- getOrCreateForBranch: async () => ({ id: wsId }),
52
+ getOrCreateForBranch: async (branch: string) => {
53
+ if (!clones.has(branch)) throw new BranchNotFoundError(branch);
54
+ return { id: workspaceIdForBranch(branch) };
55
+ },
46
56
  getWorkspacePath: async (id: string) => join(root, id),
47
57
  readFile: async (id: string, rel: string) => fsReadFile(join(root, id, rel), 'utf-8'),
48
58
  } as unknown as WorkspaceService;
@@ -58,6 +68,7 @@ describe('SkillService', () => {
58
68
 
59
69
  beforeEach(async () => {
60
70
  root = await mkdtemp(join(tmpdir(), 'skills-'));
71
+ clones = new Set([DEFAULT_BRANCH]);
61
72
  const skills = join(root, wsId, KB_DIR, 'Plugins');
62
73
  await mkdir(join(skills, 'rfi', 'scripts'), { recursive: true });
63
74
  await writeFile(join(skills, 'rfi', 'SKILL.md'), RFI_SKILL);
@@ -480,4 +491,217 @@ describe('SkillService', () => {
480
491
  });
481
492
  });
482
493
  });
494
+
495
+ /**
496
+ * Reading a DRAFT branch: what an agent needs to try the skill it has just
497
+ * written, with the answer saying plainly that nobody approved it.
498
+ *
499
+ * The draft starts as a copy of the released tree — which is what a clone of
500
+ * a branch with no skill changes on it is — and each test changes it.
501
+ */
502
+ describe('reading a branch', () => {
503
+ const DRAFT = 'juan/skill-deck';
504
+ const draftWsId = workspaceIdForBranch(DRAFT);
505
+ const user = 'user@x.eu';
506
+ const draftKb = () => join(root, draftWsId, KB_DIR);
507
+ const named = async (service = svc()) =>
508
+ Object.fromEntries((await service.listSkills(user, { branch: DRAFT })).map((s) => [s.name, s]));
509
+
510
+ const writeDeck = (body = '# Deck\n') =>
511
+ mkdir(join(draftKb(), 'Plugins', 'make-deck'), { recursive: true }).then(() =>
512
+ writeFile(join(draftKb(), 'Plugins', 'make-deck', 'SKILL.md'), `---\ndescription: Decks.\n---\n\n${body}`),
513
+ );
514
+
515
+ beforeEach(async () => {
516
+ await cp(join(root, wsId), join(root, draftWsId), { recursive: true });
517
+ clones.add(DRAFT);
518
+ });
519
+
520
+ test('a skill that exists only on the branch is listed as unmerged; the released ones are not marked', async () => {
521
+ await writeDeck();
522
+ const list = await named();
523
+ expect(Object.keys(list).sort()).toEqual(['coding-guidelines', 'make-deck', 'rfi']);
524
+ expect(list['make-deck']).toMatchObject({ unmerged: true, branch: DRAFT, description: 'Decks.' });
525
+ // The skills the branch shares with the default branch are the released
526
+ // ones, read through a draft's clone: nothing about them is unapproved.
527
+ expect(list['rfi'].unmerged).toBeUndefined();
528
+ expect(list['rfi'].branch).toBeUndefined();
529
+ expect(list['coding-guidelines'].unmerged).toBeUndefined();
530
+ });
531
+
532
+ test('without a branch the draft-only skill is absent, and nothing carries a mark', async () => {
533
+ await writeDeck();
534
+ const list = await svc().listSkills(user);
535
+ expect(list.map((s) => s.name)).toEqual(['coding-guidelines', 'rfi']);
536
+ expect(list.some((s) => s.unmerged !== undefined || s.branch !== undefined)).toBe(false);
537
+ });
538
+
539
+ test('get_skill loads the draft-only skill, body starting with the unmerged line', async () => {
540
+ await writeDeck();
541
+ const res = await svc().getSkill(user, 'make-deck', undefined, { branch: DRAFT });
542
+ expect(res.ok && res.kind === 'skill').toBe(true);
543
+ if (!res.ok || res.kind !== 'skill') return;
544
+ expect(res.skill.body.split('\n')[0]).toBe(
545
+ `This skill is read from the unmerged branch "${DRAFT}" and is not approved.`,
546
+ );
547
+ expect(res.skill.body.split('\n')[0]).toBe(unmergedSkillNotice(DRAFT));
548
+ expect(res.skill.body).toContain('# Deck');
549
+ expect(res.skill).toMatchObject({ unmerged: true, branch: DRAFT, path: 'Plugins/make-deck' });
550
+ // Without the branch there is no such skill at all.
551
+ expect(await svc().getSkill(user, 'make-deck')).toEqual({ ok: false, error: 'not_found' });
552
+ });
553
+
554
+ test('a skill changed on the branch loads changed there and released without the branch', async () => {
555
+ await writeFile(
556
+ join(draftKb(), 'Plugins', 'rfi', 'SKILL.md'),
557
+ RFI_SKILL.replace('You answer RFIs.', 'You answer RFIs, in three paragraphs.'),
558
+ );
559
+ const service = svc();
560
+ const draft = await service.getSkill(user, 'rfi', undefined, { branch: DRAFT });
561
+ expect(draft.ok && draft.kind === 'skill' && draft.skill.body).toContain('in three paragraphs');
562
+ expect(draft.ok && draft.kind === 'skill' && draft.skill.body.split('\n')[0]).toBe(unmergedSkillNotice(DRAFT));
563
+ expect(draft.ok && draft.kind === 'skill' && draft.skill.unmerged).toBe(true);
564
+ expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
565
+
566
+ const released = await service.getSkill(user, 'rfi');
567
+ expect(released.ok && released.kind === 'skill' && released.skill.body).not.toContain('three paragraphs');
568
+ expect(released.ok && released.kind === 'skill' && released.skill.body).not.toContain('unmerged');
569
+ expect(released.ok && released.kind === 'skill' && released.skill.unmerged).toBeUndefined();
570
+ });
571
+
572
+ test('a skill unchanged on the branch carries no mark and no line — it is the released answer', async () => {
573
+ const service = svc();
574
+ expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual(
575
+ await service.getSkill(user, 'rfi'),
576
+ );
577
+ expect(await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT })).toEqual(
578
+ await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py'),
579
+ );
580
+ });
581
+
582
+ test('a bundled file changed on the branch makes the skill unmerged, and rides beside its content', async () => {
583
+ // The SKILL.md is byte-identical: the difference is in the folder.
584
+ await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("xlsx v2")\n');
585
+ expect((await named())['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
586
+ const file = await svc().getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT });
587
+ expect(file).toEqual({
588
+ ok: true,
589
+ kind: 'file',
590
+ file: {
591
+ name: 'rfi',
592
+ file: 'scripts/build_xlsx.py',
593
+ path: 'Plugins/rfi/scripts/build_xlsx.py',
594
+ // The notice never goes INSIDE a script.
595
+ content: 'print("xlsx v2")\n',
596
+ unmerged: true,
597
+ branch: DRAFT,
598
+ },
599
+ });
600
+ });
601
+
602
+ test('an asset added on the branch is a difference too', async () => {
603
+ await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'extra.py'), 'print("new")\n');
604
+ expect((await named())['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
605
+ });
606
+
607
+ test('a skill deleted on the branch is absent there and still released without it', async () => {
608
+ await rm(join(draftKb(), 'Plugins', 'rfi'), { recursive: true });
609
+ const service = svc();
610
+ expect(Object.keys(await named(service))).toEqual(['coding-guidelines']);
611
+ expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual({
612
+ ok: false,
613
+ error: 'not_found',
614
+ });
615
+ expect((await service.listSkills(user)).map((s) => s.name)).toContain('rfi');
616
+ });
617
+
618
+ test("access is judged on the branch, with that branch's rules", async () => {
619
+ // The same caller, the same skill: readable on the default branch,
620
+ // denied in the draft's own workspace.
621
+ const perBranch: IAccessControl = {
622
+ canRead: async () => true,
623
+ canReadBatch: async (w: string, _e: string, paths: string[]) =>
624
+ new Map(paths.map((p) => [p, !(w === draftWsId && p.includes('/rfi/'))])),
625
+ } as unknown as IAccessControl;
626
+ const service = svc(perBranch);
627
+ expect(Object.keys(await named(service))).toEqual(['coding-guidelines']);
628
+ // Not `forbidden`: a draft skill the caller may not read there is one
629
+ // they must not learn exists.
630
+ expect(await service.getSkill(user, 'rfi', undefined, { branch: DRAFT })).toEqual({
631
+ ok: false,
632
+ error: 'not_found',
633
+ });
634
+ expect(await service.getSkill(user, 'rfi', 'scripts/build_xlsx.py', { branch: DRAFT })).toEqual({
635
+ ok: false,
636
+ error: 'not_found',
637
+ });
638
+ // The released answer is untouched by the draft's rules.
639
+ expect((await service.listSkills(user)).map((s) => s.name)).toContain('rfi');
640
+ expect((await service.getSkill(user, 'rfi')).ok).toBe(true);
641
+ });
642
+
643
+ test('a branch that does not exist is a 404 naming it, as on the file tools', async () => {
644
+ const service = svc();
645
+ await expect(service.listSkills(user, { branch: 'juan/typo' })).rejects.toMatchObject({
646
+ status: 404,
647
+ kind: 'branch-not-found',
648
+ branch: 'juan/typo',
649
+ message: 'There is no branch named juan/typo.',
650
+ });
651
+ await expect(service.getSkill(user, 'rfi', undefined, { branch: 'juan/typo' })).rejects.toMatchObject({
652
+ status: 404,
653
+ kind: 'branch-not-found',
654
+ });
655
+ });
656
+
657
+ test('naming the default branch answers exactly as naming nothing', async () => {
658
+ await writeDeck();
659
+ const service = svc();
660
+ expect(await service.listSkills(user, { branch: DEFAULT_BRANCH })).toEqual(await service.listSkills(user));
661
+ expect(await service.getSkill(user, 'rfi', undefined, { branch: DEFAULT_BRANCH })).toEqual(
662
+ await service.getSkill(user, 'rfi'),
663
+ );
664
+ });
665
+
666
+ test('a branch read is never cached: the next call sees what the agent just wrote', async () => {
667
+ const service = svc();
668
+ expect(Object.keys(await named(service))).not.toContain('make-deck');
669
+ await writeDeck();
670
+ // Same instance, no `invalidate()`: the released catalog is cached, a
671
+ // draft is not.
672
+ expect(Object.keys(await named(service))).toContain('make-deck');
673
+ });
674
+
675
+ // The `unmerged` verdict compares two folder digests, and the released
676
+ // side of that comparison is memoized per cached catalog (it used to be
677
+ // re-hashed once per skill per call). These two pin what the memo may and
678
+ // may not do.
679
+ test('the memo never makes a draft stale: an edit on the branch is seen on the next call', async () => {
680
+ const service = svc();
681
+ expect((await named(service))['rfi'].unmerged).toBeUndefined();
682
+ await writeFile(join(draftKb(), 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("on the branch")\n');
683
+ // Same instance, no `invalidate()`: the draft's digests are built per
684
+ // call along with its catalog, so the agent's own last write shows.
685
+ expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
686
+ });
687
+
688
+ test('the released digests live and die with the released catalog', async () => {
689
+ const service = svc();
690
+ expect((await named(service))['rfi'].unmerged).toBeUndefined();
691
+ // An asset of the RELEASED copy changes. The catalog caches the file
692
+ // list, not the contents, so this is visible to a digest the moment one
693
+ // is taken — and the memo holds the pre-change one for the same TTL the
694
+ // rest of the released answer is held for.
695
+ await writeFile(join(root, wsId, KB_DIR, 'Plugins', 'rfi', 'scripts', 'build_xlsx.py'), 'print("released")\n');
696
+ expect((await named(service))['rfi'].unmerged).toBeUndefined();
697
+ service.invalidate();
698
+ expect((await named(service))['rfi']).toMatchObject({ unmerged: true, branch: DRAFT });
699
+ });
700
+
701
+ test('the global set (no caller) can be read from a branch too', async () => {
702
+ await writeDeck();
703
+ const list = await svc().listSkills(undefined, { branch: DRAFT });
704
+ expect(list.find((s) => s.name === 'make-deck')).toMatchObject({ unmerged: true, branch: DRAFT });
705
+ });
706
+ });
483
707
  });
@@ -2,7 +2,10 @@
2
2
  * Skills are reusable specialist instructions living under `Plugins/<plugin>/<name>/SKILL.md`
3
3
  * in the KB repo. Discovery/loading is pinned to the DEFAULT branch — the catalog
4
4
  * is a single global, released set (a skill on a draft isn't discoverable until
5
- * merged). Progressive disclosure: `listSkills` = name + description (level 1),
5
+ * merged) — UNLESS the caller names a branch, which reads the skills as they are
6
+ * on that draft and marks the ones the default branch does not have yet (see
7
+ * {@link ListSkillsOptions} and `unmerged` below). Progressive disclosure:
8
+ * `listSkills` = name + description (level 1),
6
9
  * `getSkill` = full body + bundled-file paths (level 2), `getSkill(file)` = a
7
10
  * bundled file's content (level 3).
8
11
  */
@@ -38,6 +41,17 @@ export interface SkillSummary {
38
41
  * surfaces, which load skills by name and never by plugin.
39
42
  */
40
43
  plugins?: PluginMembership[];
44
+ /**
45
+ * Set only on an answer read from a branch OTHER than the default, and only
46
+ * on a skill whose folder DIFFERS there: it exists only on that branch, or
47
+ * its content was changed on it. Nobody has approved what such a skill says
48
+ * — it is not merged. A skill the branch shares byte-for-byte with the
49
+ * default branch carries no mark, because there is nothing unapproved about
50
+ * it.
51
+ */
52
+ unmerged?: true;
53
+ /** With `unmerged`: the branch the skill was read from. */
54
+ branch?: string;
41
55
  }
42
56
 
43
57
  export interface Skill extends SkillSummary {
@@ -57,6 +71,15 @@ export interface SkillFileContent {
57
71
  /** Repo-root-relative path of the file. */
58
72
  path: string;
59
73
  content: string;
74
+ /**
75
+ * As on {@link SkillSummary} — the file came off a branch whose copy of this
76
+ * skill differs from the default branch's. A note BESIDE the content, never
77
+ * inside it: a bundled file is a script or a data file, and a sentence
78
+ * prepended to one is a syntax error rather than a warning. A skill's body,
79
+ * which is prose an agent reads, carries the line instead.
80
+ */
81
+ unmerged?: true;
82
+ branch?: string;
60
83
  }
61
84
 
62
85
  export type GetSkillResult =
@@ -64,32 +87,76 @@ export type GetSkillResult =
64
87
  | { ok: true; kind: 'file'; file: SkillFileContent }
65
88
  | { ok: false; error: 'not_found' | 'forbidden' | 'invalid_file' }
66
89
  /**
67
- * A `version` was asked for that no commit of the skill's `SKILL.md` on the
68
- * default branch declared. `versions` is every version the history did
69
- * declare, newest first — the caller picks from those or asks for the
70
- * latest by leaving `version` out.
90
+ * A `version` was asked for that no commit of the skill's `SKILL.md`
91
+ * declared in the history searched — the default branch's, or `branch`'s own
92
+ * when a branch was named. `versions` is every version that history did
93
+ * declare, newest first, so it too is that branch's answer: the caller picks
94
+ * from those or asks for the latest by leaving `version` out.
71
95
  */
72
96
  | { ok: false; error: 'version_not_found'; versions: string[] };
73
97
 
98
+ /**
99
+ * What `listSkills` may be asked beyond a caller.
100
+ *
101
+ * The branch is the ONE thing that moves the catalog off the default branch.
102
+ * Omitted (or naming the default branch itself), every answer is exactly the
103
+ * released one — the browser menu and the MCP prompts take that path and are
104
+ * unaffected by this option existing.
105
+ */
106
+ export interface ListSkillsOptions {
107
+ /**
108
+ * Read the skills as they are on this branch instead of the default one: a
109
+ * skill that exists only there is listed, one changed there is listed with
110
+ * its changed description and version, one deleted there is absent. Access
111
+ * is judged on THAT branch, with that branch's access rules. A branch the
112
+ * platform has never heard of is a 404 naming it, as on the file tools.
113
+ *
114
+ * Everything listed from a non-default branch that differs from the default
115
+ * carries `unmerged: true` and this branch name: nobody approved it.
116
+ */
117
+ branch?: string;
118
+ }
119
+
74
120
  /** What `getSkill` may be asked beyond a name and a file. */
75
121
  export interface GetSkillOptions {
76
122
  /**
77
123
  * The version the skill declared in the copy to load — its
78
124
  * `metadata.version`, else a top-level `version`, else `lifecycle.version`.
79
125
  * Omitted, the skill is loaded as it is now — the latest. Given, the
80
- * default branch's history of the skill's `SKILL.md` is searched newest
81
- * first for the most recent commit that declared exactly this version, and
82
- * the skill (body, bundled files, or the one `file` asked for) is served as
83
- * it was at that commit. Read access is the caller's access to the skill as
84
- * it is now: a skill you may read, you may read the history of.
126
+ * history of the skill's `SKILL.md` is searched newest first for the most
127
+ * recent commit that declared exactly this version, and the skill (body,
128
+ * bundled files, or the one `file` asked for) is served as it was at that
129
+ * commit. Read access is the caller's access to the skill as it is now: a
130
+ * skill you may read, you may read the history of.
131
+ *
132
+ * The history searched is the one belonging to the branch being read: the
133
+ * default branch's, or — with `branch` — that branch's own, including the
134
+ * `versions` a `version_not_found` lists.
85
135
  */
86
136
  version?: string;
137
+ /**
138
+ * Load the skill as it is on this branch instead of the default one — see
139
+ * {@link ListSkillsOptions.branch}, which it mirrors exactly: same branch
140
+ * resolution, same 404, same access rules read on that branch. When the
141
+ * skill differs from the default branch's copy, the body returned BEGINS
142
+ * with one line saying so (a bundled file carries `unmerged` beside its
143
+ * content instead).
144
+ *
145
+ * With `version`, the history searched is that branch's own. The agent
146
+ * tools refuse the two together rather than make a caller reason about which
147
+ * branch a version came from; this service answers both, because the
148
+ * combination has exactly one sensible meaning.
149
+ */
150
+ branch?: string;
87
151
  }
88
152
 
89
153
  /**
90
- * The slice of git the skill service reads history through — the default
91
- * branch's own clone, never a caller's draft. Narrow on purpose: the catalog
92
- * is a disk scan and needs none of this; only a `version` asks for history.
154
+ * The slice of git the skill service reads history through. Every call names
155
+ * the workspace to read in, which is always the clone the skill itself was
156
+ * scanned out of — the default branch's, or the draft a `branch` named, so a
157
+ * version asked for on a branch is answered from that branch's history.
158
+ * Narrow on purpose: the catalog is a disk scan and needs none of this; only
159
+ * a `version` asks for history.
93
160
  */
94
161
  export interface SkillHistorySource {
95
162
  /** The commits (newest first) on `ref` that touched `repoRelativePath`, at most `limit`. */
@@ -133,17 +200,23 @@ export interface IPendingSkillService {
133
200
 
134
201
  export interface ISkillService {
135
202
  /**
136
- * The default-branch skill catalog. When `userEmail` is given, filtered to
137
- * skills that user may read (`canRead`); omit it for the global set (used to
138
- * compose the tool descriptions in the manual).
203
+ * The default-branch skill catalog — or `options.branch`'s, when one is
204
+ * named. When `userEmail` is given, filtered to skills that user may read
205
+ * (`canRead`, judged on the branch being read); omit it for the global set
206
+ * (used to compose the tool descriptions in the manual, which always
207
+ * describe the default branch).
139
208
  */
140
- listSkills(userEmail?: string): Promise<SkillSummary[]>;
209
+ listSkills(userEmail?: string, options?: ListSkillsOptions): Promise<SkillSummary[]>;
141
210
  /**
142
211
  * Load a skill's body (+ files), or a bundled file's content when `file` is
143
212
  * given — as it is now, or as it was at the commit that declared
144
213
  * `options.version` (see {@link GetSkillOptions}).
145
214
  */
146
215
  getSkill(userEmail: string, name: string, file?: string, options?: GetSkillOptions): Promise<GetSkillResult>;
147
- /** Drop the cached catalog (call after a merge to the default branch). */
216
+ /**
217
+ * Drop the cached DEFAULT-branch catalog (call after a merge to it). A
218
+ * branch read is never cached, so an agent that writes a skill on its draft
219
+ * and lists it right after sees what it just wrote.
220
+ */
148
221
  invalidate(): void;
149
222
  }