@bevel-software/platform-core-backend 0.33.0 → 0.34.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 (497) hide show
  1. package/agent-guide/access-control.md +19 -8
  2. package/agent-guide/html-views.md +231 -5
  3. package/agent-guide/tool-manuals.md +1 -1
  4. package/dist/core/catalog-revision.d.ts +12 -1
  5. package/dist/core/catalog-revision.d.ts.map +1 -1
  6. package/dist/core/catalog-revision.js +23 -3
  7. package/dist/core/catalog-revision.js.map +1 -1
  8. package/dist/core/core-ports.d.ts +41 -0
  9. package/dist/core/core-ports.d.ts.map +1 -1
  10. package/dist/core/core-ports.js.map +1 -1
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +14 -7
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +16 -5
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +37 -12
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +16 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -1
  20. package/dist/core/lifecycle.js +12 -2
  21. package/dist/core/lifecycle.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/modules/access/access-control.interface.d.ts +17 -9
  26. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  27. package/dist/modules/access/access-control.service.d.ts +12 -2
  28. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  29. package/dist/modules/access/access-control.service.js +164 -75
  30. package/dist/modules/access/access-control.service.js.map +1 -1
  31. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  32. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  33. package/dist/modules/access/access-mutation.service.js +48 -51
  34. package/dist/modules/access/access-mutation.service.js.map +1 -1
  35. package/dist/modules/access/access-view.d.ts.map +1 -1
  36. package/dist/modules/access/access-view.js +12 -11
  37. package/dist/modules/access/access-view.js.map +1 -1
  38. package/dist/modules/access/access.routes.d.ts +3 -0
  39. package/dist/modules/access/access.routes.d.ts.map +1 -1
  40. package/dist/modules/access/access.routes.js +35 -29
  41. package/dist/modules/access/access.routes.js.map +1 -1
  42. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  43. package/dist/modules/access/groups-admin.routes.js +4 -2
  44. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  45. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  46. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  47. package/dist/modules/access/groups-admin.service.js +80 -38
  48. package/dist/modules/access/groups-admin.service.js.map +1 -1
  49. package/dist/modules/access/groups-edit.d.ts +1 -1
  50. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  51. package/dist/modules/access/groups-edit.js +1 -1
  52. package/dist/modules/access/groups-edit.js.map +1 -1
  53. package/dist/modules/access/reference-scan.d.ts +116 -24
  54. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  55. package/dist/modules/access/reference-scan.js +305 -44
  56. package/dist/modules/access/reference-scan.js.map +1 -1
  57. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  58. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  59. package/dist/modules/access/roles-admin.service.js +32 -23
  60. package/dist/modules/access/roles-admin.service.js.map +1 -1
  61. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  62. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  63. package/dist/modules/access-model/access-grammar.js +60 -20
  64. package/dist/modules/access-model/access-grammar.js.map +1 -1
  65. package/dist/modules/access-model/access-splice.d.ts +22 -18
  66. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  67. package/dist/modules/access-model/access-splice.js +20 -14
  68. package/dist/modules/access-model/access-splice.js.map +1 -1
  69. package/dist/modules/access-model/group-files.d.ts +1 -0
  70. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  71. package/dist/modules/access-model/group-files.js +5 -1
  72. package/dist/modules/access-model/group-files.js.map +1 -1
  73. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  74. package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
  75. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  76. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  77. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  78. package/dist/modules/agent-instructions/compose.js +3 -1
  79. package/dist/modules/agent-instructions/compose.js.map +1 -1
  80. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  81. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  82. package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
  83. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  84. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  85. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  86. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  87. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  88. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  89. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  90. package/dist/modules/agent-scope/connection-scope.js +165 -0
  91. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  92. package/dist/modules/agent-scope/index.d.ts +3 -0
  93. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  94. package/dist/modules/agent-scope/index.js +3 -0
  95. package/dist/modules/agent-scope/index.js.map +1 -0
  96. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  97. package/dist/modules/audit/agent-audit.service.js +2 -0
  98. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  99. package/dist/modules/audit/audit.contract.d.ts +6 -0
  100. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  101. package/dist/modules/audit/audit.contract.js.map +1 -1
  102. package/dist/modules/auth/account-admission.d.ts +12 -0
  103. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  104. package/dist/modules/auth/account-admission.js +15 -0
  105. package/dist/modules/auth/account-admission.js.map +1 -1
  106. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  107. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  108. package/dist/modules/auth/auth.middleware.js +42 -4
  109. package/dist/modules/auth/auth.middleware.js.map +1 -1
  110. package/dist/modules/auth/auth.routes.d.ts +13 -9
  111. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  112. package/dist/modules/auth/auth.routes.js +35 -29
  113. package/dist/modules/auth/auth.routes.js.map +1 -1
  114. package/dist/modules/auth/auth.service.d.ts +55 -6
  115. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  116. package/dist/modules/auth/auth.service.js +128 -34
  117. package/dist/modules/auth/auth.service.js.map +1 -1
  118. package/dist/modules/auth/index.d.ts +1 -1
  119. package/dist/modules/auth/index.d.ts.map +1 -1
  120. package/dist/modules/auth/index.js +1 -1
  121. package/dist/modules/auth/index.js.map +1 -1
  122. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  123. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  124. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  125. package/dist/modules/database/core-schema.d.ts +68 -0
  126. package/dist/modules/database/core-schema.d.ts.map +1 -1
  127. package/dist/modules/database/core-schema.js +33 -0
  128. package/dist/modules/database/core-schema.js.map +1 -1
  129. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  130. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  131. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  132. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  133. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  134. package/dist/modules/embed/embed.tools.js +5 -0
  135. package/dist/modules/embed/embed.tools.js.map +1 -1
  136. package/dist/modules/kb-fs/index.d.ts +2 -0
  137. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  138. package/dist/modules/kb-fs/index.js +1 -0
  139. package/dist/modules/kb-fs/index.js.map +1 -1
  140. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  141. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  142. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  143. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  144. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  145. package/dist/modules/kb-fs/repo-path.js +22 -7
  146. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  147. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  148. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  149. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  150. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  151. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  152. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  153. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  154. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  155. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  156. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp.routes.js +39 -4
  158. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +68 -13
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  168. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  169. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  170. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  171. package/dist/modules/plugins/index.d.ts +1 -1
  172. package/dist/modules/plugins/index.d.ts.map +1 -1
  173. package/dist/modules/plugins/index.js.map +1 -1
  174. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  175. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  176. package/dist/modules/plugins/plugin-links.js.map +1 -1
  177. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  178. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  179. package/dist/modules/plugins/plugins.routes.js +32 -5
  180. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  181. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  182. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  183. package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
  184. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  185. package/dist/modules/skills/index.d.ts +3 -3
  186. package/dist/modules/skills/index.d.ts.map +1 -1
  187. package/dist/modules/skills/index.js +0 -2
  188. package/dist/modules/skills/index.js.map +1 -1
  189. package/dist/modules/skills/skills.contract.d.ts +16 -0
  190. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  191. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  192. package/dist/modules/skills/skills.service.js +6 -1
  193. package/dist/modules/skills/skills.service.js.map +1 -1
  194. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  195. package/dist/modules/skills/skills.tools.js +24 -12
  196. package/dist/modules/skills/skills.tools.js.map +1 -1
  197. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  198. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  199. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  200. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  201. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  202. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  203. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  204. package/dist/modules/tool-auth/index.d.ts +1 -1
  205. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  206. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  207. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  208. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  209. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  210. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  211. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  212. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  213. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  214. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  215. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  216. package/dist/modules/tool-helpers/tool-context.js +3 -0
  217. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  218. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  219. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  220. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  221. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  222. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  223. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  224. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  225. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  226. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  227. package/dist/modules/tool-helpers/validate-token.js +1 -1
  228. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  229. package/dist/modules/tool-manuals/index.d.ts +3 -3
  230. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  231. package/dist/modules/tool-manuals/index.js +0 -2
  232. package/dist/modules/tool-manuals/index.js.map +1 -1
  233. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
  234. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  235. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  236. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  238. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  239. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
  240. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
  242. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  244. package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
  245. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  246. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  247. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  248. package/dist/modules/tool-registry/manual.routes.js +27 -7
  249. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  250. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  251. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  253. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  254. package/dist/modules/workflow/agent-tools/workflow.tools.js +14 -9
  255. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  256. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  257. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  258. package/dist/modules/workflow/git/git.service.js +20 -35
  259. package/dist/modules/workflow/git/git.service.js.map +1 -1
  260. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  261. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/workflow.service.js +3 -0
  263. package/dist/modules/workflow/workflow.service.js.map +1 -1
  264. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  265. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  266. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  267. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  268. package/dist/modules/workspace/index.d.ts +8 -0
  269. package/dist/modules/workspace/index.d.ts.map +1 -1
  270. package/dist/modules/workspace/index.js +8 -0
  271. package/dist/modules/workspace/index.js.map +1 -1
  272. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  273. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  274. package/dist/modules/workspace/nul-scan.js +55 -0
  275. package/dist/modules/workspace/nul-scan.js.map +1 -0
  276. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  277. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  278. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  279. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  280. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  281. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  282. package/dist/modules/workspace/ripgrep-search.js +657 -0
  283. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  284. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  285. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  286. package/dist/modules/workspace/ripgrep-version.js +31 -0
  287. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  288. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  289. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  290. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  291. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  292. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  293. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  294. package/dist/modules/workspace/worker-pool.js.map +1 -1
  295. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  296. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  297. package/dist/modules/workspace/workspace-tmp.js +435 -0
  298. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  299. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  300. package/dist/modules/workspace/workspace.routes.js +5 -26
  301. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  302. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  303. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  304. package/dist/modules/workspace/workspace.service.js +86 -6
  305. package/dist/modules/workspace/workspace.service.js.map +1 -1
  306. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  307. package/dist/modules/workspace/workspace.tools.js +460 -116
  308. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  309. package/dist/shared/path-order.d.ts +28 -8
  310. package/dist/shared/path-order.d.ts.map +1 -1
  311. package/dist/shared/path-order.js +37 -8
  312. package/dist/shared/path-order.js.map +1 -1
  313. package/migrations/0022_session_version.sql +6 -0
  314. package/migrations/0023_agent_path.sql +3 -0
  315. package/migrations/meta/0022_snapshot.json +2360 -0
  316. package/migrations/meta/0023_snapshot.json +2378 -0
  317. package/migrations/meta/_journal.json +14 -0
  318. package/package.json +3 -3
  319. package/src/__tests__/pg-scratch.test.ts +69 -0
  320. package/src/__tests__/pg-scratch.ts +31 -0
  321. package/src/__tests__/slow-ripgrep.ts +65 -0
  322. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  323. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  324. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  325. package/src/core/__tests__/gated-boot.test.ts +8 -1
  326. package/src/core/__tests__/lifecycle.test.ts +46 -13
  327. package/src/core/catalog-revision.ts +24 -2
  328. package/src/core/core-ports.ts +42 -0
  329. package/src/core/create-core-server.ts +13 -0
  330. package/src/core/create-core-services.ts +49 -12
  331. package/src/core/lifecycle.ts +26 -2
  332. package/src/index.ts +1 -1
  333. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  334. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  335. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  336. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  337. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  338. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  339. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  340. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  341. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  342. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  343. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  344. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  345. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  346. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  347. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  348. package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
  349. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  350. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  351. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  352. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  353. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  354. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  355. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  356. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  357. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  358. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  359. package/src/modules/access/access-control.interface.ts +17 -10
  360. package/src/modules/access/access-control.service.ts +158 -72
  361. package/src/modules/access/access-mutation.service.ts +48 -53
  362. package/src/modules/access/access-view.ts +12 -10
  363. package/src/modules/access/access.routes.ts +43 -33
  364. package/src/modules/access/groups-admin.routes.ts +4 -2
  365. package/src/modules/access/groups-admin.service.ts +114 -44
  366. package/src/modules/access/groups-edit.ts +1 -1
  367. package/src/modules/access/reference-scan.ts +319 -46
  368. package/src/modules/access/roles-admin.service.ts +43 -26
  369. package/src/modules/access/synced-groups-writer.ts +1 -1
  370. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  371. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  372. package/src/modules/access-model/access-grammar.ts +65 -20
  373. package/src/modules/access-model/access-splice.ts +31 -23
  374. package/src/modules/access-model/group-files.ts +5 -0
  375. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
  376. package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
  377. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
  378. package/src/modules/agent-instructions/compose.ts +3 -1
  379. package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
  380. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  381. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  382. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  383. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  384. package/src/modules/agent-scope/connection-scope.ts +199 -0
  385. package/src/modules/agent-scope/index.ts +15 -0
  386. package/src/modules/audit/agent-audit.service.ts +2 -0
  387. package/src/modules/audit/audit.contract.ts +6 -0
  388. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  389. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  390. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  391. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  392. package/src/modules/auth/account-admission.ts +19 -0
  393. package/src/modules/auth/auth.middleware.ts +45 -4
  394. package/src/modules/auth/auth.routes.ts +46 -29
  395. package/src/modules/auth/auth.service.ts +135 -34
  396. package/src/modules/auth/index.ts +2 -0
  397. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  398. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  399. package/src/modules/database/core-schema.ts +33 -0
  400. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  401. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  402. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  403. package/src/modules/embed/embed.tools.ts +5 -0
  404. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  405. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  406. package/src/modules/kb-fs/index.ts +2 -0
  407. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  408. package/src/modules/kb-fs/repo-path.ts +33 -7
  409. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  410. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  411. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  412. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  413. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  414. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  415. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  416. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  417. package/src/modules/mcp/mcp.routes.ts +39 -3
  418. package/src/modules/mcp/mcp.service.ts +107 -17
  419. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  420. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  421. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  422. package/src/modules/plugins/index.ts +1 -1
  423. package/src/modules/plugins/plugin-links.ts +19 -2
  424. package/src/modules/plugins/plugins.routes.ts +35 -4
  425. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  426. package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
  427. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  428. package/src/modules/skills/index.ts +7 -2
  429. package/src/modules/skills/skills.contract.ts +17 -0
  430. package/src/modules/skills/skills.service.ts +5 -1
  431. package/src/modules/skills/skills.tools.ts +44 -13
  432. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  433. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  434. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  435. package/src/modules/tool-auth/index.ts +1 -1
  436. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  437. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  438. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  439. package/src/modules/tool-helpers/tool-context.ts +10 -0
  440. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  441. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  442. package/src/modules/tool-helpers/validate-token.ts +4 -2
  443. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  444. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  445. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  446. package/src/modules/tool-manuals/index.ts +6 -2
  447. package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
  448. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  449. package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
  450. package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
  451. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
  452. package/src/modules/tool-registry/manual.routes.ts +26 -5
  453. package/src/modules/tool-registry/tool.contract.ts +7 -0
  454. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  455. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  457. package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
  458. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  459. package/src/modules/workflow/git/git.service.ts +29 -48
  460. package/src/modules/workflow/workflow.service.ts +5 -0
  461. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  462. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  463. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  464. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  465. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  466. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  467. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  468. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  469. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
  470. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  471. package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
  472. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  473. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  474. package/src/modules/workspace/index.ts +20 -0
  475. package/src/modules/workspace/nul-scan.ts +50 -0
  476. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  477. package/src/modules/workspace/ripgrep-search.ts +715 -0
  478. package/src/modules/workspace/ripgrep-version.ts +35 -0
  479. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  480. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  481. package/src/modules/workspace/worker-pool.ts +3 -4
  482. package/src/modules/workspace/workspace-tmp.ts +457 -0
  483. package/src/modules/workspace/workspace.routes.ts +6 -27
  484. package/src/modules/workspace/workspace.service.ts +92 -15
  485. package/src/modules/workspace/workspace.tools.ts +500 -155
  486. package/src/shared/path-order.ts +37 -8
  487. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  488. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  489. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  490. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  491. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  492. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  493. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  494. package/dist/modules/workspace/bounded-matcher.js +0 -200
  495. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  496. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  497. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -8,6 +8,8 @@ import os from 'node:os';
8
8
  import type { WorkspaceService } from '../../../workspace/workspace.service.js';
9
9
  import { WorkflowHooks } from '../../workflow-hooks.js';
10
10
  import { GitService } from '../git.service.js';
11
+ import { NodeGitRunner } from '../node-git-runner.js';
12
+ import type { IGitRunner } from '../../../../shared/git.contract.js';
11
13
 
12
14
  const execFileAsync = promisify(execFile);
13
15
 
@@ -155,7 +157,7 @@ describe('GitService.listBranches', () => {
155
157
  expect(names).toEqual(['alice/draft-one', 'current-company-state', 'target-company-state']);
156
158
  });
157
159
 
158
- it('returns local branches before remote-only ones, order preserved through parallel ahead/behind', async () => {
160
+ it('returns local branches before remote-only ones, then alphabetically', async () => {
159
161
  await seedWorkspace(root, workspaceId);
160
162
  const svc = new GitService(
161
163
  stubWorkspaceService(workspaceId, path.join(root, workspaceId)),
@@ -165,8 +167,7 @@ describe('GitService.listBranches', () => {
165
167
 
166
168
  // current-company-state is the workspace clone's checked-out branch (the
167
169
  // only local head); the rest are remote-only. The result order must be
168
- // local-first then alphabetical — `listBranches` now computes ahead/behind
169
- // with `Promise.all`, so this pins that the mapping preserves row order.
170
+ // local-first then alphabetical.
170
171
  const branches = await svc.listBranches(workspaceId);
171
172
  expect(branches.map((b) => b.name)).toEqual([
172
173
  'current-company-state',
@@ -231,4 +232,130 @@ describe('GitService.listBranches', () => {
231
232
  expect.arrayContaining([expect.objectContaining({ name: 'alice/draft-one' })]),
232
233
  );
233
234
  });
235
+
236
+ // A listing names branches; it counts nothing. Counting cost one to three
237
+ // `rev-list` spawns a branch — about 120 git processes a listing on a
238
+ // knowledge base with 121 branches — for numbers no listing reader shows.
239
+ it('runs no git per branch: a listing over many branches spawns the same few commands as one over three', async () => {
240
+ const { upstream } = await seedWorkspace(root, workspaceId);
241
+ const pusher = path.join(root, '.pusher');
242
+ await runGit(root, ['clone', upstream, pusher]);
243
+ const drafts = Array.from({ length: 12 }, (_, i) => `team/draft-${String(i).padStart(2, '0')}`);
244
+ for (const d of drafts) {
245
+ await runGit(pusher, ['checkout', '-q', '-b', d, 'origin/current-company-state']);
246
+ await runGit(pusher, ['commit', '--allow-empty', '-m', d]);
247
+ }
248
+ await runGit(pusher, ['push', '-q', 'origin', ...drafts]);
249
+ // The workspace's own branch carries an upstream and local commits too:
250
+ // exactly what the old listing counted against.
251
+ const repo = path.join(root, workspaceId, 'knowledge-base');
252
+ await runGit(repo, ['commit', '--allow-empty', '-m', 'unshared']);
253
+
254
+ const { runner, commands } = recordingRunner();
255
+ const svc = new GitService(
256
+ stubWorkspaceService(workspaceId, path.join(root, workspaceId)),
257
+ new WorkflowHooks(),
258
+ testKbContext(),
259
+ undefined,
260
+ null,
261
+ runner,
262
+ );
263
+ const branches = await svc.listBranches(workspaceId);
264
+
265
+ expect(branches.map((b) => b.name)).toEqual([
266
+ 'current-company-state',
267
+ 'alice/draft-one',
268
+ 'target-company-state',
269
+ ...drafts,
270
+ ]);
271
+ expect(branches[0]).toEqual({ name: 'current-company-state', isProtected: true, hasRemote: true });
272
+ for (const b of branches) expect(Object.keys(b).sort()).toEqual(['hasRemote', 'isProtected', 'name']);
273
+ expect(commands.filter((c) => c === 'rev-list')).toEqual([]);
274
+ // Its fetch, and the two reads of what the repository holds — whatever
275
+ // the number of branches.
276
+ expect([...commands].sort()).toEqual(['fetch', 'for-each-ref', 'symbolic-ref']);
277
+ });
278
+ });
279
+
280
+ /** The real runner, recording the subcommand of every git it is asked to run. */
281
+ function recordingRunner(): { runner: IGitRunner; commands: string[] } {
282
+ const inner = new NodeGitRunner();
283
+ const commands: string[] = [];
284
+ const runner = {
285
+ defaultTimeoutMs: inner.defaultTimeoutMs,
286
+ credentials: inner.credentials,
287
+ run: async (cwd: string, args: string[], opts?: object) => {
288
+ // Skip `-c key=value` pairs: the subcommand is the first bare word.
289
+ let i = 0;
290
+ while (i < args.length && args[i].startsWith('-')) i += args[i] === '-c' ? 2 : 1;
291
+ commands.push(args[i] ?? '');
292
+ return inner.run(cwd, args, opts as never);
293
+ },
294
+ } as unknown as IGitRunner;
295
+ return { runner, commands };
296
+ }
297
+
298
+ describe('GitService.branchCounts', () => {
299
+ let root: string;
300
+
301
+ beforeEach(async () => {
302
+ root = await mkTmpRoot();
303
+ });
304
+
305
+ afterEach(async () => {
306
+ await fs.rm(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
307
+ });
308
+
309
+ const draftWs = encodeURIComponent('alice/draft-one');
310
+
311
+ /** The draft's own workspace, on the draft, three commits ahead of what it shared. */
312
+ async function seedDraft(): Promise<{ upstream: string; repo: string }> {
313
+ const { upstream, repo } = await seedWorkspace(root, draftWs);
314
+ await runGit(repo, ['checkout', '-q', 'alice/draft-one']);
315
+ for (const m of ['one', 'two', 'three']) await runGit(repo, ['commit', '--allow-empty', '-m', m]);
316
+ return { upstream, repo };
317
+ }
318
+
319
+ function svcFor(runner: IGitRunner = new NodeGitRunner(), kb = testKbContext()): GitService {
320
+ return new GitService(stubWorkspaceService(draftWs, path.join(root, draftWs)), new WorkflowHooks(), kb, undefined, null, runner);
321
+ }
322
+
323
+ it('counts the workspace\'s own branch against its upstream, with rev-list for that branch alone', async () => {
324
+ const { upstream, repo } = await seedDraft();
325
+ // A teammate shares one commit on the same draft, which this clone fetches.
326
+ const pusher = path.join(root, '.pusher');
327
+ await runGit(root, ['clone', '-q', '-b', 'alice/draft-one', upstream, pusher]);
328
+ await runGit(pusher, ['commit', '--allow-empty', '-m', 'teammate']);
329
+ await runGit(pusher, ['push', '-q', 'origin', 'alice/draft-one']);
330
+ await runGit(repo, ['fetch', '-q', 'origin']);
331
+
332
+ const { runner, commands } = recordingRunner();
333
+ await expect(svcFor(runner).branchCounts(draftWs)).resolves.toEqual({ ahead: 3, behind: 1 });
334
+ // The upstream resolved, so one count was enough.
335
+ expect(commands.filter((c) => c === 'rev-list')).toHaveLength(1);
336
+ });
337
+
338
+ it('falls back to the protected branch on origin it is fewest commits ahead of when it has no upstream', async () => {
339
+ const { upstream, repo } = await seedDraft();
340
+ await runGit(repo, ['branch', '--unset-upstream']);
341
+ // `target-company-state` moves on by two commits; `current-company-state` stays.
342
+ const pusher = path.join(root, '.pusher');
343
+ await runGit(root, ['clone', '-q', '-b', 'target-company-state', upstream, pusher]);
344
+ for (const m of ['t1', 't2']) await runGit(pusher, ['commit', '--allow-empty', '-m', m]);
345
+ await runGit(pusher, ['push', '-q', 'origin', 'target-company-state']);
346
+ await runGit(repo, ['fetch', '-q', 'origin']);
347
+
348
+ const { runner, commands } = recordingRunner();
349
+ // Three ahead of both; ties keep the first protected branch, which is behind by nothing.
350
+ await expect(svcFor(runner).branchCounts(draftWs)).resolves.toEqual({ ahead: 3, behind: 0 });
351
+ // The upstream it tried, then each protected branch.
352
+ expect(commands.filter((c) => c === 'rev-list')).toHaveLength(3);
353
+ });
354
+
355
+ it('answers null for a branch never shared, with no protected base to count against', async () => {
356
+ const { repo } = await seedDraft();
357
+ await runGit(repo, ['branch', '--unset-upstream']);
358
+ const kb = testKbContext({ branchModel: { defaultBranch: 'main', protectedBranches: ['main'] } });
359
+ await expect(svcFor(undefined, kb).branchCounts(draftWs)).resolves.toEqual({ ahead: null, behind: null });
360
+ });
234
361
  });
@@ -5,6 +5,7 @@ import type {
5
5
  AppliedChangeRef,
6
6
  AppliedMergeResult,
7
7
  AuthUser,
8
+ BranchCounts,
8
9
  BranchInfo,
9
10
  ChangedPathPair,
10
11
  CommitAttribution,
@@ -60,6 +61,7 @@ import {
60
61
  } from '../../../shared/git.contract.js';
61
62
  import { NodeGitRunner } from './node-git-runner.js';
62
63
  import { printable } from '../../../shared/printable.js';
64
+ import { branchForWorkspaceId } from '../../../shared/workspace-id.js';
63
65
  import { sanitizeError } from '../sanitize-error.js';
64
66
 
65
67
  const log = logger('git');
@@ -292,11 +294,6 @@ export class GitService implements IGitService {
292
294
  /** Whether the last implicit fetch per workspace SUCCEEDED — see `fetchOriginIfStale`. */
293
295
  private readonly lastImplicitFetchOk = new Map<string, boolean>();
294
296
  private static readonly IMPLICIT_FETCH_TTL_MS = 5_000;
295
- // Cap on concurrent `aheadBehindForBranch` calls inside `listBranches`. Each
296
- // call serializes up to 3 `git rev-list` spawns, so 8 workers ≈ ≤24
297
- // concurrent processes worst case — fast for typical (<8) feature-branch
298
- // counts, calm under large branch lists.
299
- private static readonly MAX_AHEAD_BEHIND_CONCURRENCY = 8;
300
297
 
301
298
  /**
302
299
  * A change request's changed entries, keyed by the two commits its diff
@@ -545,7 +542,7 @@ export class GitService implements IGitService {
545
542
  )
546
543
  ).trim();
547
544
 
548
- type Row = { shortName: string; ref: string; hasLocal: boolean; hasRemote: boolean };
545
+ type Row = { shortName: string; hasLocal: boolean; hasRemote: boolean };
549
546
  const buckets = new Map<string, Row>();
550
547
  for (const line of stdout.split('\n')) {
551
548
  const trimmed = line.trim();
@@ -573,16 +570,12 @@ export class GitService implements IGitService {
573
570
  if (!existing) {
574
571
  buckets.set(shortName, {
575
572
  shortName,
576
- ref,
577
573
  hasLocal: isLocal,
578
574
  hasRemote: !isLocal,
579
575
  });
580
576
  continue;
581
577
  }
582
- // Prefer the local entry's ref when both exist — ahead/behind against the
583
- // configured upstream is more meaningful than against origin directly.
584
578
  if (isLocal && !existing.hasLocal) {
585
- existing.ref = ref;
586
579
  existing.hasLocal = true;
587
580
  } else if (!isLocal) {
588
581
  existing.hasRemote = true;
@@ -594,36 +587,14 @@ export class GitService implements IGitService {
594
587
  return a.shortName.localeCompare(b.shortName);
595
588
  });
596
589
 
597
- // Compute ahead/behind for every branch with bounded concurrency — each
598
- // branch is 1–3 `git rev-list` spawns, and the unbounded `Promise.all`
599
- // we used before could put N×3 processes in flight on repos with many
600
- // branches. Workers pull indices off a shared counter and write into a
601
- // pre-sized array, so input order (and the sort above) is preserved.
602
- const infos: BranchInfo[] = new Array(rows.length);
603
- let nextIndex = 0;
604
- const workerCount = Math.min(GitService.MAX_AHEAD_BEHIND_CONCURRENCY, rows.length);
605
- await Promise.all(
606
- Array.from({ length: workerCount }, async () => {
607
- while (true) {
608
- const i = nextIndex++;
609
- if (i >= rows.length) return;
610
- const row = rows[i];
611
- const { ahead, behind } = await this.aheadBehindForBranch(
612
- cwd,
613
- row.shortName,
614
- row.ref,
615
- row.hasLocal,
616
- );
617
- infos[i] = {
618
- name: row.shortName,
619
- isProtected: this.kb.isProtectedBranch(row.shortName),
620
- ahead,
621
- behind,
622
- hasRemote: row.hasRemote,
623
- };
624
- }
625
- }),
626
- );
590
+ // No git per branch: commit counts are not part of a listing (they cost
591
+ // one to three `rev-list` spawns a branch, over every branch the
592
+ // repository has). The one branch that wants them asks `branchCounts`.
593
+ const infos: BranchInfo[] = rows.map((row) => ({
594
+ name: row.shortName,
595
+ isProtected: this.kb.isProtectedBranch(row.shortName),
596
+ hasRemote: row.hasRemote,
597
+ }));
627
598
  // Every name this listing showed — origin's refs and the local heads
628
599
  // alike, since a branch checked out here is one the platform has
629
600
  // plainly heard of. A listener that misbehaves must not turn a good
@@ -639,6 +610,24 @@ export class GitService implements IGitService {
639
610
  });
640
611
  }
641
612
 
613
+ async branchCounts(workspaceId: string): Promise<BranchCounts> {
614
+ return this.mutex.run(workspaceId, async () => {
615
+ const cwd = await this.repoDir(workspaceId);
616
+ const branch = branchForWorkspaceId(workspaceId);
617
+ // The workspace's own branch is its local head while it is checked out;
618
+ // otherwise only origin's ref names it — as a listing would read it.
619
+ const ownBranch = (
620
+ await this.git(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD']).then(
621
+ (r) => r.stdout,
622
+ () => '',
623
+ )
624
+ ).trim();
625
+ return ownBranch === branch
626
+ ? this.aheadBehindForBranch(cwd, branch, `refs/heads/${branch}`, true)
627
+ : this.aheadBehindForBranch(cwd, branch, `refs/remotes/origin/${branch}`, false);
628
+ });
629
+ }
630
+
642
631
  /**
643
632
  * Run `git fetch --prune origin` outside the workspace mutex, with a short
644
633
  * TTL so back-to-back callers (mount + dropdown-open + 30s poll) coalesce
@@ -757,12 +746,6 @@ export class GitService implements IGitService {
757
746
  await this.git(cwd, ['branch', '-D', name]).catch(() => undefined);
758
747
  throw err;
759
748
  }
760
- const { ahead, behind } = await this.aheadBehindForBranch(
761
- cwd,
762
- name,
763
- `refs/heads/${name}`,
764
- true,
765
- );
766
749
  // Drop the now-vestigial local head. Under the per-branch workspace model
767
750
  // this clone's identity is its OWN branch; the new draft lives in its own
768
751
  // workspace (cloned from origin on first navigation), so this clone never
@@ -782,8 +765,6 @@ export class GitService implements IGitService {
782
765
  return {
783
766
  name,
784
767
  isProtected: false,
785
- ahead,
786
- behind,
787
768
  hasRemote: true,
788
769
  };
789
770
  });
@@ -37,6 +37,7 @@ import type {
37
37
  AcquireLockResult,
38
38
  AuthUser,
39
39
  Branch,
40
+ BranchCounts,
40
41
  BranchSyncOutcome,
41
42
  BranchWorkspaceStatus,
42
43
  CancelChangeRequestResult,
@@ -932,6 +933,10 @@ export class WorkflowService implements IWorkflowService {
932
933
  return this.git.status(workspaceId);
933
934
  }
934
935
 
936
+ branchCounts(workspaceId: string): Promise<BranchCounts> {
937
+ return this.git.branchCounts(workspaceId);
938
+ }
939
+
935
940
  // `discardWorkingChanges` removed: under save=share there's nothing dirty
936
941
  // to discard.
937
942
 
@@ -36,11 +36,11 @@ import { createDiffRoutes } from '../../diff/diff.routes.js';
36
36
  import { DiffService } from '../../diff/diff.service.js';
37
37
  import { WorkspaceMutex } from '../../kb-fs/mutex.js';
38
38
  import { WorkspaceService } from '../workspace.service.js';
39
- import { WorkerPatternMatcher } from '../bounded-matcher.js';
39
+ import { RipgrepPatternMatcher } from '../index.js';
40
40
  import { newWorkspaceService } from '../../../__tests__/workspace-service.js';
41
41
 
42
42
  /** The grep matcher this file's harness wires, closed once the file is done. */
43
- const patternMatcher = new WorkerPatternMatcher();
43
+ const patternMatcher = new RipgrepPatternMatcher();
44
44
  afterAll(() => patternMatcher.close());
45
45
 
46
46
  /**
@@ -0,0 +1,66 @@
1
+ import { lstat, mkdtemp, rename, rm, symlink, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { holdsNul } from '../nul-scan.js';
6
+ import { PatternTooExpensiveError, type IPatternSearch } from '../pattern-matcher.contract.js';
7
+
8
+ /**
9
+ * The unit `grep` asks whether a named text file ripgrep answered no line from
10
+ * is binary (`nul-scan.ts`). What a caller observes — a NUL past the first
11
+ * chunk makes the answer "no searchable text" — is pinned through the grep
12
+ * tool itself in `workspace.tools.test.ts`. Two properties stay here because
13
+ * no caller can stage them: the file swapped for another, or for a link,
14
+ * between the stat that judged it and the open that reads it is a race a tool
15
+ * call cannot time; and a budget already spent is told apart from ripgrep's
16
+ * own spending only by handing the unit a search whose budget is gone.
17
+ */
18
+ let dir = '';
19
+ const free: IPatternSearch = {
20
+ files: () => (async function* () {})(),
21
+ list: async () => [],
22
+ matchingLines: async (texts) => texts.map(() => []),
23
+ charged: (work) => work(),
24
+ };
25
+
26
+ beforeEach(async () => {
27
+ dir = await mkdtemp(join(tmpdir(), 'holds-nul-'));
28
+ });
29
+ afterEach(async () => {
30
+ await rm(dir, { recursive: true, force: true });
31
+ });
32
+
33
+ describe('holdsNul', () => {
34
+ it('finds a NUL wherever it is, past the first chunk too, and answers false for text', async () => {
35
+ const text = join(dir, 'text.md');
36
+ const late = join(dir, 'late.bin');
37
+ await writeFile(text, 'plain text\n'.repeat(20_000));
38
+ await writeFile(late, Buffer.concat([Buffer.alloc(200 * 1024, 'x'), Buffer.from([0])]));
39
+ expect(await holdsNul(text, await lstat(text), free)).toBe(false);
40
+ expect(await holdsNul(late, await lstat(late), free)).toBe(true);
41
+ });
42
+
43
+ it.skipIf(process.platform === 'win32')('reads nothing from a file swapped in since it was judged, nor through a link', async () => {
44
+ const p = join(dir, 'named.md');
45
+ await writeFile(p, 'text\n');
46
+ const judged = await lstat(p);
47
+ // Another file holding a NUL, moved in under the judged name.
48
+ await writeFile(join(dir, 'other'), Buffer.from([0]));
49
+ await rename(join(dir, 'other'), p);
50
+ expect(await holdsNul(p, judged, free)).toBe(false);
51
+ // A link to a file holding a NUL, under the judged name.
52
+ await writeFile(join(dir, 'outside'), Buffer.from([0]));
53
+ await rm(p);
54
+ await symlink(join(dir, 'outside'), p);
55
+ expect(await holdsNul(p, judged, free)).toBe(false);
56
+ expect(await holdsNul(p, await lstat(join(dir, 'outside')), free)).toBe(false);
57
+ });
58
+
59
+ it('runs under the search budget: a budget already spent answers for it', async () => {
60
+ const p = join(dir, 'named.md');
61
+ await writeFile(p, 'text\n');
62
+ const spent = new PatternTooExpensiveError('needle', 5_000);
63
+ const exhausted: IPatternSearch = { ...free, charged: () => Promise.reject(spent) };
64
+ await expect(holdsNul(p, await lstat(p), exhausted)).rejects.toBe(spent);
65
+ });
66
+ });
@@ -22,10 +22,10 @@ import { AccessControlService } from '../../access/access-control.service.js';
22
22
  import { NodeFs } from '../../kb-fs/node-fs.js';
23
23
  import { BRANCH_REQUIRED_MESSAGE } from '../../../shared/domain-errors.js';
24
24
  import { callLine, compileCheck, exampleArguments } from '@bevel-software/platform-mcp-core';
25
- import { WorkerPatternMatcher } from '../bounded-matcher.js';
25
+ import { RipgrepPatternMatcher } from '../index.js';
26
26
 
27
27
  /** The grep matcher this file's harness wires, closed once the file is done. */
28
- const patternMatcher = new WorkerPatternMatcher();
28
+ const patternMatcher = new RipgrepPatternMatcher();
29
29
  afterAll(() => patternMatcher.close());
30
30
 
31
31
  /**
@@ -179,7 +179,7 @@ describe('POST /api/agent/tools/<name> checks the arguments against the tool', (
179
179
  expect(status).toBe(400);
180
180
  expect(body.kind).toBe('arguments-do-not-match');
181
181
  expect(body.error).toContain('"pattern" is required, and was not given.');
182
- expect(body.error).toContain('pattern (string, required) — JavaScript regular expression.');
182
+ expect(body.error).toContain('pattern (string, required) — Regular expression, RE2-style (ripgrep): no look-around, no backreferences.');
183
183
  // Not a 200 that matched every file, which is what this surface answered
184
184
  // before the check lived in the route.
185
185
  expect(body.matches).toBeUndefined();