@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
@@ -0,0 +1,657 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { realpath } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { MAX_TIMER_MS, sizingOption } from './worker-pool.js';
5
+ import { ToolError } from '../tool-helpers/index.js';
6
+ import { logger } from '../../shared/logging.js';
7
+ import { compareSearchPaths, PatternRefusedError, PatternTooExpensiveError, SearchAbandonedError, SearchBusyError, } from './pattern-matcher.contract.js';
8
+ const log = logger('grep');
9
+ /** The flags every run starts with: ripgrep's own configuration file is never read, and bytes are searched as they are. */
10
+ const BASE_ARGS = ['--no-config', '--encoding', 'none'];
11
+ /**
12
+ * What a tree walk covers: hidden files, ignored files (no `.gitignore`,
13
+ * `.ignore` or global excludes apply), never a symbolic link, never a `.git`
14
+ * folder in any case, files visited sorted by path.
15
+ */
16
+ const WALK_ARGS = ['--hidden', '--no-ignore', '--no-follow', '--no-messages', '--sort', 'path'];
17
+ /** Last among a walk's globs, so no extension a caller lets through can bring a `.git` folder back. */
18
+ const NO_GIT = ['--iglob', '!.git'];
19
+ /** How much of a refused run's stderr is kept. */
20
+ const STDERR_CAP = 4_096;
21
+ export class RipgrepPatternMatcher {
22
+ budgetMs;
23
+ maxRunning;
24
+ maxWaiting;
25
+ maxPatternLength;
26
+ runner;
27
+ live = new Set();
28
+ waiting = [];
29
+ running = 0;
30
+ closed = false;
31
+ constructor(opts = {}) {
32
+ this.budgetMs = option('budgetMs', opts.budgetMs ?? 5_000, 1, MAX_TIMER_MS);
33
+ // None running would leave every search waiting for a slot that never comes.
34
+ this.maxRunning = option('maxRunning', opts.maxRunning ?? 4, 1);
35
+ this.maxWaiting = option('maxWaiting', opts.maxWaiting ?? 16, 0);
36
+ this.maxPatternLength = option('maxPatternLength', opts.maxPatternLength ?? 1_000, 1);
37
+ this.runner = {
38
+ slot: (wait) => this.slot(wait),
39
+ start: (args, cwd, withInput) => this.start(args, cwd, withInput),
40
+ isClosed: () => this.closed,
41
+ };
42
+ }
43
+ async search(pattern, opts) {
44
+ if (pattern.length > this.maxPatternLength) {
45
+ throw new ToolError(`Pattern too long: ${pattern.length} characters, and a search pattern may have at most ${this.maxPatternLength}.`, 400, { code: 'pattern_too_long' });
46
+ }
47
+ // A process argument cannot carry a NUL; the escape `\x00` can.
48
+ if (pattern.includes('\0'))
49
+ throw new PatternRefusedError('a pattern cannot contain a NUL character; write it as \\x00.');
50
+ const search = new RipgrepSearch(this.runner, pattern, opts.ignoreCase ? ['-i'] : [], this.budgetMs, opts.signal);
51
+ // The engine judges the pattern before anything is searched, so a refused
52
+ // one is answered as such whatever the search would have found.
53
+ await search.matchingLines(['']);
54
+ return search;
55
+ }
56
+ async close() {
57
+ this.closed = true;
58
+ for (const w of this.waiting.splice(0))
59
+ w.reject(new SearchBusyError());
60
+ const exits = [...this.live].map((child) => exited(child));
61
+ for (const child of this.live)
62
+ child.kill('SIGKILL');
63
+ await Promise.all(exits);
64
+ }
65
+ /**
66
+ * A run's slot: given at once while fewer than `maxRunning` run, else after
67
+ * a wait bounded by the search's own limits (see {@link SlotWait}). A waiter
68
+ * that leaves — its caller gone, its budget spent, the matcher closed —
69
+ * leaves the queue too, so the slot a run gives back passes to a waiter
70
+ * that still wants it.
71
+ */
72
+ async slot(wait) {
73
+ if (this.closed)
74
+ throw new SearchBusyError();
75
+ if (this.running < this.maxRunning) {
76
+ this.running++;
77
+ }
78
+ else {
79
+ if (this.waiting.length >= this.maxWaiting)
80
+ throw new SearchBusyError();
81
+ // Resolved by a run that ends: its slot passes straight to this one.
82
+ await new Promise((resolve, reject) => {
83
+ // Every way out settles the same: the budget timer cleared, the
84
+ // abort listener removed. The closures below refer to one another
85
+ // and run only once all of them exist.
86
+ const settle = () => {
87
+ clearTimeout(timer);
88
+ wait.signal?.removeEventListener('abort', onAbort);
89
+ };
90
+ const waiter = {
91
+ resolve: () => {
92
+ settle();
93
+ resolve();
94
+ },
95
+ reject: (err) => {
96
+ settle();
97
+ reject(err);
98
+ },
99
+ };
100
+ const leave = (err) => {
101
+ const at = this.waiting.indexOf(waiter);
102
+ if (at >= 0)
103
+ this.waiting.splice(at, 1);
104
+ waiter.reject(err);
105
+ };
106
+ const onAbort = () => leave(new SearchAbandonedError());
107
+ const timer = setTimeout(() => leave(wait.expired()), wait.timeoutMs);
108
+ this.waiting.push(waiter);
109
+ if (wait.signal?.aborted) {
110
+ leave(new SearchAbandonedError());
111
+ return;
112
+ }
113
+ wait.signal?.addEventListener('abort', onAbort, { once: true });
114
+ });
115
+ }
116
+ let released = false;
117
+ return () => {
118
+ if (released)
119
+ return;
120
+ released = true;
121
+ const next = this.waiting.shift();
122
+ if (next)
123
+ next.resolve();
124
+ else
125
+ this.running--;
126
+ };
127
+ }
128
+ /** Start `rg` from the server's PATH with `args`, registered so `close()` can end it. */
129
+ start(args, cwd, withInput) {
130
+ if (this.closed)
131
+ throw new SearchBusyError();
132
+ const child = spawn('rg', args, {
133
+ cwd,
134
+ shell: false,
135
+ stdio: [withInput ? 'pipe' : 'ignore', 'pipe', 'pipe'],
136
+ windowsHide: true,
137
+ });
138
+ this.live.add(child);
139
+ child.once('close', () => this.live.delete(child));
140
+ // A spawn that fails emits 'error' and never 'close'.
141
+ child.once('error', () => this.live.delete(child));
142
+ return child;
143
+ }
144
+ }
145
+ /** One search: the pattern, its flags, and what is left of its budget. */
146
+ class RipgrepSearch {
147
+ runner;
148
+ pattern;
149
+ flags;
150
+ budgetMs;
151
+ signal;
152
+ spentMs = 0;
153
+ constructor(runner, pattern, flags, budgetMs, signal) {
154
+ this.runner = runner;
155
+ this.pattern = pattern;
156
+ this.flags = flags;
157
+ this.budgetMs = budgetMs;
158
+ this.signal = signal;
159
+ }
160
+ async *files(scope, opts) {
161
+ // Without a single whitelist glob ripgrep would search every file.
162
+ if (opts.only.length === 0)
163
+ return;
164
+ // Each file is named by a whitelist glob anchored at `cwd`, where ripgrep
165
+ // runs: the walk stays the walk (no link followed, `.git` left out), and
166
+ // a file not named is never opened. Whitelist globs filter files only, so
167
+ // the walks start at the folders holding the named files rather than at
168
+ // the root, and a folder's own files are walked without its subfolders:
169
+ // a batch of a large tree walks about its own stretch of it.
170
+ const under = scope.root === '.' ? '' : `${scope.root}/`;
171
+ // Every name is checked before the first run, so none is half-searched.
172
+ for (const p of opts.only)
173
+ exactGlob(`${under}${p}`);
174
+ let only = opts.only;
175
+ const max = opts.maxLinesPerFile;
176
+ while (only.length > 0) {
177
+ let capped = yield* this.walkFiles(scope, under, only, max, max);
178
+ if (capped === null)
179
+ return;
180
+ const path = capped.path;
181
+ // A matching line that is not UTF-8 counts toward ripgrep's cap but is
182
+ // not answered, so a file can stop short of its lines: it is searched
183
+ // again on its own, with a cap raised at least twofold, until it has
184
+ // them or ripgrep reads it to its end (and answers it from the walk).
185
+ let maxCount = max;
186
+ while (capped !== null && capped.lines.length < max) {
187
+ maxCount += Math.max(max - capped.lines.length, maxCount);
188
+ capped = yield* this.walkFiles(scope, under, [path], max, maxCount);
189
+ }
190
+ // ripgrep stopped reading the file at its cap, so a NUL past it went
191
+ // unseen: the file is read on for one, by a run of its own once the
192
+ // walk's has ended, and the walk goes on with the files after it.
193
+ if (capped !== null && !(await this.holdsNul(scope, under, path)))
194
+ yield capped;
195
+ only = only.filter((p) => compareSearchPaths(p, path) > 0);
196
+ }
197
+ }
198
+ /**
199
+ * The walks of `only`, each file answered at its `end` with at most
200
+ * `maxLinesPerFile` lines, until ripgrep stops one at `maxCount` matching
201
+ * lines (UTF-8 or not): the walk ends there, and that file is returned
202
+ * unanswered. `null` once every walk has finished.
203
+ */
204
+ async *walkFiles(scope, under, only, maxLinesPerFile, maxCount) {
205
+ // `--max-count`: a file is read no further than the lines it may answer.
206
+ const cap = ['--max-count', String(maxCount)];
207
+ for (const walk of await realWalks(scope, walkPlan(only))) {
208
+ const globs = walk.paths.flatMap((p) => ['--glob', exactGlob(`${under}${p}`)]);
209
+ const depth = walk.ownFilesOnly ? ['--max-depth', '1'] : [];
210
+ const args = [...BASE_ARGS, '--json', ...WALK_ARGS, ...depth, ...cap, ...globs, ...NO_GIT, ...this.flags, '-e', this.pattern, '--', ...walk.folders];
211
+ let current = null;
212
+ let matched = 0;
213
+ for await (const record of this.run(args, scope.cwd, undefined, '\n')) {
214
+ const event = parseEvent(record);
215
+ if (event === null)
216
+ continue;
217
+ if (event.type === 'begin') {
218
+ const path = underRoot(scope.root, pathOf(event.data?.path));
219
+ current = path === null ? null : { path, lines: [] };
220
+ matched = 0;
221
+ }
222
+ else if (event.type === 'match' && current !== null) {
223
+ matched++;
224
+ const text = textOf(event.data?.lines);
225
+ const line = event.data?.line_number;
226
+ if (text !== null && typeof line === 'number' && current.lines.length < maxLinesPerFile) {
227
+ current.lines.push({ line, text: withoutNewline(text) });
228
+ }
229
+ }
230
+ else if (event.type === 'end' && current !== null) {
231
+ // A file ripgrep found binary part-way through is not text: whatever
232
+ // it matched before the NUL is dropped with the rest. That is known
233
+ // only here, so a file is answered at its end and not before.
234
+ const binary = event.data?.binary_offset !== null && event.data?.binary_offset !== undefined;
235
+ if (!binary && matched >= maxCount)
236
+ return current;
237
+ if (!binary && current.lines.length > 0)
238
+ yield current;
239
+ current = null;
240
+ }
241
+ }
242
+ }
243
+ return null;
244
+ }
245
+ /**
246
+ * Whether `path` (relative to the root) holds a NUL anywhere, read by a walk
247
+ * of its own, which follows no link and stops at the first NUL.
248
+ */
249
+ async holdsNul(scope, under, path) {
250
+ for (const walk of await realWalks(scope, walkPlan([path]))) {
251
+ const depth = walk.ownFilesOnly ? ['--max-depth', '1'] : [];
252
+ const glob = ['--glob', exactGlob(`${under}${path}`)];
253
+ const args = [...BASE_ARGS, '--files-with-matches', ...WALK_ARGS, ...depth, ...glob, ...NO_GIT, '--text', '-e', '\\x00', '--', ...walk.folders];
254
+ for await (const record of this.run(args, scope.cwd, undefined, '\n'))
255
+ if (record.length > 0)
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ async list(scope) {
261
+ if (await rootMoved(scope))
262
+ return [];
263
+ const args = [...BASE_ARGS, '--files', '--null', ...WALK_ARGS, ...NO_GIT, '--', scope.root];
264
+ const out = [];
265
+ for await (const record of this.run(args, scope.cwd, undefined, '\0')) {
266
+ const path = underRoot(scope.root, record);
267
+ if (path !== null)
268
+ out.push(path);
269
+ }
270
+ return out;
271
+ }
272
+ async matchingLines(texts) {
273
+ const hits = texts.map(() => []);
274
+ if (texts.length === 0)
275
+ return hits;
276
+ // One run for every text: they go in one after another, and each line
277
+ // number that comes back is placed in the text it fell in. The final `\n`
278
+ // makes ripgrep's lines exactly the texts' `split('\n')`, empty last line
279
+ // included.
280
+ const starts = [];
281
+ let lines = 0;
282
+ for (const text of texts) {
283
+ starts.push(lines);
284
+ lines += countLines(text);
285
+ }
286
+ const args = [...BASE_ARGS, '--json', '--text', ...this.flags, '-e', this.pattern, '-'];
287
+ for await (const record of this.run(args, undefined, `${texts.join('\n')}\n`, '\n')) {
288
+ const event = parseEvent(record);
289
+ const n = event?.type === 'match' ? event.data?.line_number : undefined;
290
+ if (typeof n !== 'number')
291
+ continue;
292
+ const at = n - 1;
293
+ const which = lastStartAtOrBefore(starts, at);
294
+ hits[which].push(at - starts[which]);
295
+ }
296
+ return hits;
297
+ }
298
+ async charged(work) {
299
+ if (this.signal?.aborted)
300
+ throw new SearchAbandonedError();
301
+ const remaining = this.budgetMs - this.spentMs;
302
+ if (remaining <= 0)
303
+ throw new PatternTooExpensiveError(this.pattern, this.budgetMs);
304
+ const started = Date.now();
305
+ let timer;
306
+ let timedOut = false;
307
+ let onAbort;
308
+ // The work is not ours to stop; what it settles with after the budget is
309
+ // gone goes nowhere.
310
+ const pending = work();
311
+ pending.catch(() => undefined);
312
+ try {
313
+ return await Promise.race([
314
+ pending,
315
+ new Promise((_, reject) => {
316
+ timer = setTimeout(() => {
317
+ timedOut = true;
318
+ reject(new PatternTooExpensiveError(this.pattern, this.budgetMs));
319
+ }, remaining);
320
+ onAbort = () => reject(new SearchAbandonedError());
321
+ this.signal?.addEventListener('abort', onAbort, { once: true });
322
+ }),
323
+ ]);
324
+ }
325
+ finally {
326
+ if (timer)
327
+ clearTimeout(timer);
328
+ if (onAbort)
329
+ this.signal?.removeEventListener('abort', onAbort);
330
+ this.spend(started, timedOut);
331
+ }
332
+ }
333
+ /**
334
+ * Spend the wall time since `started`, or the whole budget when its timer
335
+ * fired: `Date.now()` counts whole milliseconds, so the time measured can
336
+ * fall a millisecond short of the timer's, and a search already answered
337
+ * too expensive must have nothing left for more work or another run.
338
+ */
339
+ spend(started, timedOut) {
340
+ this.spentMs = timedOut ? this.budgetMs : this.spentMs + (Date.now() - started);
341
+ }
342
+ /**
343
+ * Run ripgrep and yield its stdout split on `separator`. The run is this
344
+ * generator's: it is killed when the caller stops reading, when the
345
+ * search's budget runs out (answered with {@link PatternTooExpensiveError}),
346
+ * when the search's caller goes away (answered with
347
+ * {@link SearchAbandonedError}), and on any error, and its slot is given
348
+ * back on every one of those. The wait for the slot is the run's too: it
349
+ * counts against the budget, and a caller gone or a budget spent while
350
+ * queued is answered from the queue, without a run.
351
+ */
352
+ async *run(args, cwd, input, separator) {
353
+ let release;
354
+ let child;
355
+ let timer;
356
+ let timedOut = false;
357
+ let abandoned = false;
358
+ const onAbort = () => {
359
+ abandoned = true;
360
+ child?.kill('SIGKILL');
361
+ };
362
+ const started = Date.now();
363
+ try {
364
+ if (this.signal?.aborted)
365
+ throw new SearchAbandonedError();
366
+ const budgeted = this.budgetMs - this.spentMs;
367
+ if (budgeted <= 0)
368
+ throw new PatternTooExpensiveError(this.pattern, this.budgetMs);
369
+ release = await this.runner.slot({
370
+ signal: this.signal,
371
+ timeoutMs: budgeted,
372
+ expired: () => {
373
+ timedOut = true;
374
+ return new PatternTooExpensiveError(this.pattern, this.budgetMs);
375
+ },
376
+ });
377
+ // What the wait left of the budget is this run's.
378
+ const remaining = budgeted - (Date.now() - started);
379
+ if (remaining <= 0) {
380
+ timedOut = true;
381
+ throw new PatternTooExpensiveError(this.pattern, this.budgetMs);
382
+ }
383
+ child = this.runner.start(args, cwd, input !== undefined);
384
+ this.signal?.addEventListener('abort', onAbort, { once: true });
385
+ // Listening for a failed spawn first, so its error wins over the exit it also causes.
386
+ const failed = new Promise((_, reject) => child.once('error', reject));
387
+ failed.catch(() => undefined);
388
+ const exit = exited(child);
389
+ timer = setTimeout(() => {
390
+ timedOut = true;
391
+ child.kill('SIGKILL');
392
+ }, remaining);
393
+ let stderr = '';
394
+ child.stderr.setEncoding('utf8');
395
+ child.stderr.on('data', (chunk) => {
396
+ if (stderr.length < STDERR_CAP)
397
+ stderr += chunk;
398
+ });
399
+ if (input !== undefined) {
400
+ // ripgrep may finish (or be killed) before it reads everything.
401
+ child.stdin.on('error', () => undefined);
402
+ child.stdin.end(input);
403
+ }
404
+ child.stdout.setEncoding('utf8');
405
+ // Only each new chunk is scanned, and a record's parts are joined once,
406
+ // when it ends: a record of many chunks (a match on one long line) costs
407
+ // its length, not its length times its chunks.
408
+ const parts = [];
409
+ for await (const chunk of child.stdout) {
410
+ let from = 0;
411
+ let at;
412
+ while ((at = chunk.indexOf(separator, from)) >= 0) {
413
+ parts.push(chunk.slice(from, at));
414
+ const record = parts.join('');
415
+ parts.length = 0;
416
+ from = at + 1;
417
+ yield record;
418
+ }
419
+ if (from < chunk.length)
420
+ parts.push(from === 0 ? chunk : chunk.slice(from));
421
+ }
422
+ const rest = parts.join('');
423
+ if (rest.length > 0 && !timedOut && !abandoned)
424
+ yield rest;
425
+ const { code } = await Promise.race([exit, failed]);
426
+ if (abandoned)
427
+ throw new SearchAbandonedError();
428
+ if (timedOut)
429
+ throw new PatternTooExpensiveError(this.pattern, this.budgetMs);
430
+ if (code === null && this.runner.isClosed())
431
+ throw new SearchBusyError();
432
+ // 0: matches, 1: none. 2 with nothing on stderr is a file ripgrep could
433
+ // not read with `--no-messages` on, which is a file to skip; 2 with a
434
+ // message is the run itself refused.
435
+ if (code === 2 && stderr.trim().length > 0)
436
+ throw refusal(stderr);
437
+ if (code !== 0 && code !== 1 && code !== 2)
438
+ throw new Error(`ripgrep ended with exit code ${String(code)}`);
439
+ }
440
+ catch (err) {
441
+ if (abandoned)
442
+ throw new SearchAbandonedError();
443
+ if (timedOut)
444
+ throw new PatternTooExpensiveError(this.pattern, this.budgetMs);
445
+ throw err;
446
+ }
447
+ finally {
448
+ this.signal?.removeEventListener('abort', onAbort);
449
+ if (timer)
450
+ clearTimeout(timer);
451
+ if (child && child.exitCode === null && child.signalCode === null && child.pid !== undefined) {
452
+ const exit = exited(child);
453
+ child.kill('SIGKILL');
454
+ await exit;
455
+ }
456
+ this.spend(started, timedOut);
457
+ release?.();
458
+ }
459
+ }
460
+ }
461
+ /** Resolves when `child` has exited and its streams are closed (or it never started). */
462
+ function exited(child) {
463
+ if (child.exitCode !== null || child.signalCode !== null)
464
+ return Promise.resolve({ code: child.exitCode });
465
+ return new Promise((resolve) => {
466
+ child.once('close', (code) => resolve({ code }));
467
+ child.once('error', () => resolve({ code: null }));
468
+ });
469
+ }
470
+ /**
471
+ * The error for a run ripgrep refused. A pattern it cannot compile is the
472
+ * caller's to fix, so its own reason is quoted, without the advice to pass
473
+ * flags the caller has no way to pass. Anything else is the server's failure,
474
+ * logged rather than shown.
475
+ */
476
+ function refusal(stderr) {
477
+ const reason = stderr
478
+ .replace(/^rg: /, '')
479
+ .split(/\n\s*\n/)
480
+ .filter((part) => !/^\s*Consider enabling/.test(part))
481
+ .join('\n\n')
482
+ .trim();
483
+ if (/regex|pattern/i.test(reason))
484
+ return new PatternRefusedError(reason);
485
+ log.error(`ripgrep failed: ${reason}`);
486
+ return new Error('The search engine failed; see the server log.');
487
+ }
488
+ /** One `--json` record, or null for a line that is not one. */
489
+ function parseEvent(record) {
490
+ try {
491
+ const event = JSON.parse(record);
492
+ return event !== null && typeof event === 'object' ? event : null;
493
+ }
494
+ catch {
495
+ return null;
496
+ }
497
+ }
498
+ /** ripgrep's `{ text }` or base64 `{ bytes }` (a name that is not UTF-8) as a path. */
499
+ function pathOf(value) {
500
+ const v = value;
501
+ if (typeof v?.text === 'string')
502
+ return v.text;
503
+ if (typeof v?.bytes === 'string')
504
+ return Buffer.from(v.bytes, 'base64').toString('utf8');
505
+ return null;
506
+ }
507
+ /** A line's text, or null for one that is not UTF-8 (`{ bytes }`), which is not text. */
508
+ function textOf(value) {
509
+ const v = value;
510
+ return typeof v?.text === 'string' ? v.text : null;
511
+ }
512
+ function withoutNewline(text) {
513
+ return text.endsWith('\n') ? text.slice(0, -1) : text;
514
+ }
515
+ /** `path` as ripgrep printed it (under `root`), relative to `root`; null when it is not under it. */
516
+ function underRoot(root, path) {
517
+ if (path === null)
518
+ return null;
519
+ const prefix = `${root}/`;
520
+ return path.startsWith(prefix) && path.length > prefix.length ? path.slice(prefix.length) : null;
521
+ }
522
+ /**
523
+ * The glob that names exactly `path` (relative to where ripgrep runs):
524
+ * anchored with `/`, every character a glob reads specially escaped. A line
525
+ * break has no exact spelling in a glob, and a near one could name a sibling
526
+ * too, so such a name is refused rather than approximated.
527
+ */
528
+ function exactGlob(path) {
529
+ if (/[\r\n]/.test(path))
530
+ throw new Error(`a search cannot name ${JSON.stringify(path)} exactly: it holds a line break`);
531
+ return `/${path.replace(/[\\*?[\]{}!# ]/g, (c) => `\\${c}`)}`;
532
+ }
533
+ /**
534
+ * Runs one search makes at most for one batch of files: a folder whose own
535
+ * files and subfolders alternate costs a run per alternation, and past this
536
+ * many the batch is walked in the fewer, wider runs a folder walked whole
537
+ * gives.
538
+ */
539
+ const MAX_WALKS = 8;
540
+ /**
541
+ * The runs a walk of `paths` (in {@link compareSearchPaths} order, relative
542
+ * to the root) makes, in order. The paths are one stretch of a sorted walk,
543
+ * so in each folder only the first and the last child can hold files outside
544
+ * it: those subfolders are narrowed further, every child between is walked
545
+ * whole, and a folder's own files are walked by a run that keeps to them —
546
+ * which a subfolder sorting between two of them splits, so a folder that
547
+ * alternates costs a run per stretch. Run one after another, the walks visit
548
+ * the files in the order one walk of the root would. Past {@link MAX_WALKS},
549
+ * a folder holding any of the paths directly is walked whole instead.
550
+ */
551
+ function walkPlan(paths) {
552
+ if (paths.length === 0)
553
+ return [];
554
+ const fine = plan(paths, true);
555
+ return fine.length <= MAX_WALKS ? fine : plan(paths, false);
556
+ }
557
+ function plan(paths, ownFiles) {
558
+ const walks = [];
559
+ // Consecutive runs of one kind are one run given several folders; a
560
+ // folder's own files with nothing walked between them are one folder.
561
+ const push = (folder, ownFilesOnly, group) => {
562
+ const paths = group.map((segments) => segments.join('/'));
563
+ const last = walks[walks.length - 1];
564
+ if (last === undefined || last.ownFilesOnly !== ownFilesOnly) {
565
+ walks.push({ folders: [folder], paths, ownFilesOnly });
566
+ return;
567
+ }
568
+ if (!(ownFilesOnly && last.folders[last.folders.length - 1] === folder))
569
+ last.folders.push(folder);
570
+ last.paths.push(...paths);
571
+ };
572
+ const cover = (folder, depth, group) => {
573
+ const holdsFiles = group.some((segments) => segments.length === depth + 1);
574
+ if (holdsFiles && !ownFiles) {
575
+ push(folder, false, group);
576
+ return;
577
+ }
578
+ const children = [];
579
+ for (const segments of group) {
580
+ const last = children[children.length - 1];
581
+ if (last !== undefined && last[0][depth] === segments[depth])
582
+ last.push(segments);
583
+ else
584
+ children.push([segments]);
585
+ }
586
+ children.forEach((child, i) => {
587
+ if (child[0].length === depth + 1) {
588
+ push(folder, true, child);
589
+ return;
590
+ }
591
+ const name = child[0].slice(0, depth + 1).join('/');
592
+ if (i === 0 || i === children.length - 1)
593
+ cover(name, depth + 1, child);
594
+ else
595
+ push(name, false, child);
596
+ });
597
+ };
598
+ cover('', 0, paths.map((p) => p.split('/')));
599
+ return walks;
600
+ }
601
+ /**
602
+ * `walks` as ripgrep is given them — when each folder is still the real
603
+ * folder the walk listed. ripgrep follows a link it is GIVEN, so a folder
604
+ * that has become a link since (or is gone) sends the search back to one walk
605
+ * of the root, whose `--no-follow` never enters it. The root is checked
606
+ * again here, as where the scope was made: one that has become a link since
607
+ * is given to ripgrep not at all, and nothing is walked.
608
+ */
609
+ async function realWalks(scope, walks) {
610
+ if (walks.length === 0 || (await rootMoved(scope)))
611
+ return [];
612
+ const folders = [...new Set(walks.flatMap((w) => w.folders).filter((folder) => folder !== ''))];
613
+ const real = await Promise.all(folders.map((folder) => isRealFolder(scope.cwd, `${scope.root}/${folder}`)));
614
+ if (!real.every(Boolean))
615
+ return [{ folders: [scope.root], paths: walks.flatMap((w) => w.paths), ownFilesOnly: false }];
616
+ return walks.map((w) => ({
617
+ ...w,
618
+ folders: w.folders.map((folder) => (folder === '' ? scope.root : `${scope.root}/${folder}`)),
619
+ }));
620
+ }
621
+ /**
622
+ * Whether the scope's root now resolves elsewhere: it, or a folder above it,
623
+ * has become a link since the scope was made. A root that is gone holds
624
+ * nothing for ripgrep to find, so it is walked as it is.
625
+ */
626
+ async function rootMoved(scope) {
627
+ const absolute = join(scope.cwd, scope.root);
628
+ return (await realpath(absolute).catch(() => absolute)) !== absolute;
629
+ }
630
+ /** Whether `folder` (relative to `cwd`) is still itself: no link on the way to it, and there. */
631
+ async function isRealFolder(cwd, folder) {
632
+ const absolute = join(cwd, folder);
633
+ return (await realpath(absolute).catch(() => null)) === absolute;
634
+ }
635
+ function countLines(text) {
636
+ let n = 1;
637
+ for (let i = text.indexOf('\n'); i >= 0; i = text.indexOf('\n', i + 1))
638
+ n++;
639
+ return n;
640
+ }
641
+ /** The index of the last start at or before `at` (starts ascending, `starts[0]` is 0). */
642
+ function lastStartAtOrBefore(starts, at) {
643
+ let lo = 0;
644
+ let hi = starts.length - 1;
645
+ while (lo < hi) {
646
+ const mid = (lo + hi + 1) >> 1;
647
+ if (starts[mid] <= at)
648
+ lo = mid;
649
+ else
650
+ hi = mid - 1;
651
+ }
652
+ return lo;
653
+ }
654
+ function option(name, value, min, max = Number.MAX_SAFE_INTEGER) {
655
+ return sizingOption('RipgrepPatternMatcher', name, value, min, max);
656
+ }
657
+ //# sourceMappingURL=ripgrep-search.js.map