@bevel-software/platform-core-backend 0.33.0 → 0.35.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.
- package/agent-guide/access-control.md +146 -289
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +239 -8
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +133 -155
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +15 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +44 -16
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +39 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +15 -3
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +239 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +58 -33
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +7 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +74 -48
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +25 -13
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +531 -137
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +14 -1
- package/src/core/create-core-services.ts +62 -15
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +43 -10
- package/src/modules/access/access-control.service.ts +250 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +66 -37
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/index.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
- package/src/modules/agent-instructions/compose.ts +7 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +112 -52
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +45 -14
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +574 -176
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/path-order.ts +37 -8
- package/src/shared/read-capped.ts +40 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- package/src/modules/workspace/bounded-matcher.ts +0 -271
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { chmod, mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { delimiter, join } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
compareSearchPaths,
|
|
9
|
+
RipgrepPatternMatcher,
|
|
10
|
+
type FileMatches,
|
|
11
|
+
type IPatternSearch,
|
|
12
|
+
type RipgrepPatternMatcherOptions,
|
|
13
|
+
type TreeSearchOptions,
|
|
14
|
+
} from '../index.js';
|
|
15
|
+
import { slowRipgrep } from '../../../__tests__/slow-ripgrep.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The ripgrep runner through its contract, against the real `rg` on PATH:
|
|
19
|
+
* what a walk covers and in which order, how its JSON is read, how a pattern
|
|
20
|
+
* the engine refuses is answered, and that every run ends — at the budget, on
|
|
21
|
+
* an early stop, when the caller goes away, or on `close()`.
|
|
22
|
+
*/
|
|
23
|
+
let dir = '';
|
|
24
|
+
let matchers: RipgrepPatternMatcher[] = [];
|
|
25
|
+
|
|
26
|
+
function matcher(opts?: RipgrepPatternMatcherOptions): RipgrepPatternMatcher {
|
|
27
|
+
const m = new RipgrepPatternMatcher(opts);
|
|
28
|
+
matchers.push(m);
|
|
29
|
+
return m;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const search = (pattern: string, opts?: RipgrepPatternMatcherOptions, ignoreCase = false): Promise<IPatternSearch> =>
|
|
33
|
+
matcher(opts).search(pattern, { ignoreCase });
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every listed file searched, as grep's text pass does: the listing first,
|
|
37
|
+
* then the files it named, less those with an `exclude` extension or named in
|
|
38
|
+
* `skip`.
|
|
39
|
+
*/
|
|
40
|
+
async function all(
|
|
41
|
+
s: IPatternSearch,
|
|
42
|
+
exclude: string[] = [],
|
|
43
|
+
opts: Partial<Pick<TreeSearchOptions, 'maxLinesPerFile'>> & { root?: string; skip?: string[] } = {},
|
|
44
|
+
): Promise<FileMatches[]> {
|
|
45
|
+
const { root = 'kb', skip = [], maxLinesPerFile = 1_000 } = opts;
|
|
46
|
+
const scope = { cwd: root === '.' ? join(dir, 'kb') : dir, root };
|
|
47
|
+
const only = (await s.list(scope)).filter((p) => !exclude.some((ext) => p.toLowerCase().endsWith(ext)) && !skip.includes(p));
|
|
48
|
+
const out: FileMatches[] = [];
|
|
49
|
+
for await (const f of s.files(scope, { only, maxLinesPerFile })) out.push(f);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function put(rel: string, content: string | Buffer): Promise<void> {
|
|
54
|
+
const p = join(dir, rel);
|
|
55
|
+
await mkdir(join(p, '..'), { recursive: true });
|
|
56
|
+
await writeFile(p, content);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The real `rg`, put first on PATH behind a script that records where each
|
|
61
|
+
* tree search starts (what follows its `--`); `last()` answers the latest.
|
|
62
|
+
*/
|
|
63
|
+
async function recordWalks(): Promise<{ last(): Promise<string[]>; runs(): Promise<string[][]>; remove(): Promise<void> }> {
|
|
64
|
+
const real = (await promisify(execFile)('sh', ['-c', 'command -v rg'])).stdout.trim();
|
|
65
|
+
const shim = await mkdtemp(join(tmpdir(), 'rec-rg-'));
|
|
66
|
+
const log = join(shim, 'walks');
|
|
67
|
+
await writeFile(
|
|
68
|
+
join(shim, 'rg'),
|
|
69
|
+
[
|
|
70
|
+
'#!/bin/sh',
|
|
71
|
+
'seen=; roots=; prev=; depth=',
|
|
72
|
+
'for a in "$@"; do if [ -n "$seen" ]; then roots="$roots$a$depth|"; elif [ "$a" = "--" ]; then seen=1; elif [ "$prev" = "--max-depth" ]; then depth="@$a"; fi; prev="$a"; done',
|
|
73
|
+
`if [ -n "$seen" ]; then echo "$roots" >> '${log}'; fi`,
|
|
74
|
+
`exec '${real}' "$@"`,
|
|
75
|
+
'',
|
|
76
|
+
].join('\n'),
|
|
77
|
+
);
|
|
78
|
+
await chmod(join(shim, 'rg'), 0o755);
|
|
79
|
+
const path = process.env.PATH;
|
|
80
|
+
process.env.PATH = `${shim}${delimiter}${path ?? ''}`;
|
|
81
|
+
return {
|
|
82
|
+
async last() {
|
|
83
|
+
const lines = (await readFile(log, 'utf8')).split('\n').filter(Boolean);
|
|
84
|
+
return lines[lines.length - 1]!.split('|').filter(Boolean);
|
|
85
|
+
},
|
|
86
|
+
// Every run since the last call, each its roots (`@1`: its own files only).
|
|
87
|
+
async runs() {
|
|
88
|
+
const lines = (await readFile(log, 'utf8').catch(() => '')).split('\n').filter(Boolean);
|
|
89
|
+
await writeFile(log, '');
|
|
90
|
+
return lines.map((line) => line.split('|').filter(Boolean));
|
|
91
|
+
},
|
|
92
|
+
async remove() {
|
|
93
|
+
process.env.PATH = path;
|
|
94
|
+
await rm(shim, { recursive: true, force: true });
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
beforeEach(async () => {
|
|
100
|
+
dir = await realpath(await mkdtemp(join(tmpdir(), 'rg-search-')));
|
|
101
|
+
});
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
await Promise.all(matchers.map((m) => m.close()));
|
|
104
|
+
matchers = [];
|
|
105
|
+
await rm(dir, { recursive: true, force: true });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('RipgrepPatternMatcher', () => {
|
|
109
|
+
it('walks in path order, segment by segment, and reads each file\'s lines as ripgrep printed them', async () => {
|
|
110
|
+
await put('kb/b.md', 'x\nneedle b\r\n');
|
|
111
|
+
await put('kb/a/z.md', 'needle z\n');
|
|
112
|
+
await put('kb/a.md', 'needle a\n');
|
|
113
|
+
await put('kb/with space/ü.md', 'needle ü\n');
|
|
114
|
+
const files = await all(await search('needle'));
|
|
115
|
+
expect(files).toEqual([
|
|
116
|
+
{ path: 'a/z.md', lines: [{ line: 1, text: 'needle z' }] },
|
|
117
|
+
{ path: 'a.md', lines: [{ line: 1, text: 'needle a' }] },
|
|
118
|
+
// A CRLF line keeps its `\r`, as `split('\n')` of the file would.
|
|
119
|
+
{ path: 'b.md', lines: [{ line: 2, text: 'needle b\r' }] },
|
|
120
|
+
{ path: 'with space/ü.md', lines: [{ line: 1, text: 'needle ü' }] },
|
|
121
|
+
]);
|
|
122
|
+
const paths = files.map((f) => f.path);
|
|
123
|
+
expect([...paths].sort(compareSearchPaths)).toEqual(paths);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('leaves out binary files, lines that are not UTF-8, excluded extensions and .git in any case', async () => {
|
|
127
|
+
await put('kb/bin.dat', Buffer.concat([Buffer.from('needle\n'), Buffer.from([0])]));
|
|
128
|
+
// NUL far past the first buffer: what ripgrep matched before it is dropped too.
|
|
129
|
+
await put('kb/late-bin.dat', Buffer.concat([Buffer.from('needle\n'), Buffer.alloc(200_000, 'x'), Buffer.from([0])]));
|
|
130
|
+
await put('kb/latin.txt', Buffer.concat([Buffer.from('needle \xff\n', 'latin1'), Buffer.from('needle ok\n')]));
|
|
131
|
+
await put('kb/doc.DOCX', 'needle\n');
|
|
132
|
+
await put('kb/.git/x', 'needle\n');
|
|
133
|
+
await put('kb/sub/.Git/x', 'needle\n');
|
|
134
|
+
const files = await all(await search('needle'), ['.docx']);
|
|
135
|
+
expect(files).toEqual([{ path: 'latin.txt', lines: [{ line: 2, text: 'needle ok' }] }]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('reads a match on one very long line whole, however many chunks its record spans', async () => {
|
|
139
|
+
const long = `needle ${'é'.repeat(3_000_000)} end`;
|
|
140
|
+
await put('kb/minified.json', `${long}\nneedle short\n`);
|
|
141
|
+
const files = await all(await search('needle'));
|
|
142
|
+
expect(files).toHaveLength(1);
|
|
143
|
+
expect(files[0]!.lines).toHaveLength(2);
|
|
144
|
+
expect(files[0]!.lines[0]!.text === long).toBe(true);
|
|
145
|
+
expect(files[0]!.lines[1]).toEqual({ line: 2, text: 'needle short' });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('lists every file by the same walk, in the same order, outside .git and never through a link', async () => {
|
|
149
|
+
await put('kb/b.pdf', 'x');
|
|
150
|
+
await put('kb/a/One.PDF', 'x');
|
|
151
|
+
await put('kb/c.md', 'x');
|
|
152
|
+
await put('kb/.git/d.pdf', 'x');
|
|
153
|
+
await put('kb/.hidden/e.eml', 'x');
|
|
154
|
+
await symlink(join(dir, 'kb/c.md'), join(dir, 'kb/link.md'));
|
|
155
|
+
const s = await search('x');
|
|
156
|
+
expect(await s.list({ cwd: dir, root: 'kb' })).toEqual(['.hidden/e.eml', 'a/One.PDF', 'b.pdf', 'c.md']);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('searches exactly the files it is given, whatever their names, from any root', async () => {
|
|
160
|
+
const names = ['secret.md', 'we ird[1]/#c*.md', '!d.md', 'e.md ', '{f,g}.md', 'back\\slash.md', 'q?.md'];
|
|
161
|
+
for (const n of names) await put(`kb/${n}`, 'needle\n');
|
|
162
|
+
await put('kb/open.md', 'needle\n');
|
|
163
|
+
await put('kb/qx.md', 'needle\n');
|
|
164
|
+
for (const root of ['kb', '.']) {
|
|
165
|
+
// Each special name is given alone: it must name itself and nothing else.
|
|
166
|
+
expect((await all(await search('needle'), [], { root, skip: names })).map((f) => f.path), root).toEqual(['open.md', 'qx.md']);
|
|
167
|
+
const scope = { cwd: root === '.' ? join(dir, 'kb') : dir, root };
|
|
168
|
+
for (const n of names) {
|
|
169
|
+
const files: string[] = [];
|
|
170
|
+
for await (const f of (await search('needle')).files(scope, { only: [n], maxLinesPerFile: 10 })) files.push(f.path);
|
|
171
|
+
expect(files, `${root}: ${n}`).toEqual([n]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('never follows a given name that is a link, refuses a name it cannot spell, and runs nothing for no file', async () => {
|
|
177
|
+
await put('kb/target.md', 'needle\n');
|
|
178
|
+
await symlink(join(dir, 'kb/target.md'), join(dir, 'kb/link.md'));
|
|
179
|
+
const s = await search('needle');
|
|
180
|
+
const scope = { cwd: dir, root: 'kb' };
|
|
181
|
+
const found = async (only: string[]): Promise<string[]> => {
|
|
182
|
+
const out: string[] = [];
|
|
183
|
+
for await (const f of s.files(scope, { only, maxLinesPerFile: 10 })) out.push(f.path);
|
|
184
|
+
return out;
|
|
185
|
+
};
|
|
186
|
+
expect(await found(['link.md'])).toEqual([]);
|
|
187
|
+
expect(await found(['link.md', 'target.md'])).toEqual(['target.md']);
|
|
188
|
+
// No whitelist at all would have ripgrep search everything.
|
|
189
|
+
expect(await found([])).toEqual([]);
|
|
190
|
+
await expect(found(['a\nb.md'])).rejects.toThrow(/line break/);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('keeps at most maxLinesPerFile lines of a file and still answers the next file', async () => {
|
|
194
|
+
await put('kb/a.md', 'needle\n'.repeat(10));
|
|
195
|
+
await put('kb/b.md', 'x\nneedle b\n');
|
|
196
|
+
const files = await all(await search('needle'), [], { maxLinesPerFile: 3 });
|
|
197
|
+
expect(files).toEqual([
|
|
198
|
+
{ path: 'a.md', lines: [1, 2, 3].map((line) => ({ line, text: 'needle' })) },
|
|
199
|
+
{ path: 'b.md', lines: [{ line: 2, text: 'needle b' }] },
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('reads a file no further than its cap, yet drops it when a NUL lies past the cap', async () => {
|
|
204
|
+
// Past ripgrep's first buffer, so the walk that stops at the cap never reaches the NUL.
|
|
205
|
+
const lines = 'needle\n'.repeat(500_000);
|
|
206
|
+
await put('kb/a.md', Buffer.concat([Buffer.from(lines), Buffer.from([0]), Buffer.from('needle\n')]));
|
|
207
|
+
await put('kb/b.md', lines);
|
|
208
|
+
await put('kb/c.md', 'x\nneedle c\n');
|
|
209
|
+
const files = await all(await search('needle'), [], { maxLinesPerFile: 2 });
|
|
210
|
+
expect(files).toEqual([
|
|
211
|
+
{ path: 'b.md', lines: [1, 2].map((line) => ({ line, text: 'needle' })) },
|
|
212
|
+
{ path: 'c.md', lines: [{ line: 2, text: 'needle c' }] },
|
|
213
|
+
]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('counts matching lines that are not UTF-8 toward the cap, and reads the file on for the lines it may answer', async () => {
|
|
217
|
+
const latin = (n: number): Buffer => Buffer.from('needle \xff\n'.repeat(n), 'latin1');
|
|
218
|
+
await put('kb/latin.txt', Buffer.concat([latin(1), Buffer.from('needle ok\n')]));
|
|
219
|
+
// Many lines that are not UTF-8 before the first one that is: the cap is raised until it is reached.
|
|
220
|
+
await put('kb/many.txt', Buffer.concat([latin(1_000), Buffer.from('needle many\n'), Buffer.from('needle more\n')]));
|
|
221
|
+
// The raised cap reads on past the NUL the first run stopped short of.
|
|
222
|
+
await put('kb/nul.txt', Buffer.concat([latin(1), Buffer.alloc(200_000, 'x'), Buffer.from([0]), Buffer.from('\nneedle nul\n')]));
|
|
223
|
+
await put('kb/z.txt', 'needle z\n');
|
|
224
|
+
expect(await all(await search('needle'), [], { maxLinesPerFile: 1 })).toEqual([
|
|
225
|
+
{ path: 'latin.txt', lines: [{ line: 2, text: 'needle ok' }] },
|
|
226
|
+
{ path: 'many.txt', lines: [{ line: 1_001, text: 'needle many' }] },
|
|
227
|
+
{ path: 'z.txt', lines: [{ line: 1, text: 'needle z' }] },
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('walks only the folders holding the files it is given, a folder\'s own files without its subfolders, in the order one walk of the root would', async () => {
|
|
232
|
+
for (const p of ['A/x/1.md', 'A/y/0.md', 'B/2.md', 'C/y/3.md', 'C/z/9.md', 'T/a/1.md', 'T/a/2.md', 'T/b/3.md', 'top.md', 'U/u.md']) {
|
|
233
|
+
await put(`kb/${p}`, 'needle\n');
|
|
234
|
+
}
|
|
235
|
+
const walked = await recordWalks();
|
|
236
|
+
try {
|
|
237
|
+
const found = async (root: string, only: string[]): Promise<{ paths: string[]; runs: string[][] }> => {
|
|
238
|
+
const scope = { cwd: root === '.' ? join(dir, 'kb') : dir, root };
|
|
239
|
+
const s = await search('needle');
|
|
240
|
+
await walked.runs();
|
|
241
|
+
const paths: string[] = [];
|
|
242
|
+
for await (const f of s.files(scope, { only, maxLinesPerFile: 10 })) paths.push(f.path);
|
|
243
|
+
return { paths, runs: await walked.runs() };
|
|
244
|
+
};
|
|
245
|
+
expect(await found('kb', ['T/a/1.md', 'T/a/2.md'])).toEqual({ paths: ['T/a/1.md', 'T/a/2.md'], runs: [['kb/T/a@1']] });
|
|
246
|
+
// The first and last folder are narrowed, every one between walked whole.
|
|
247
|
+
expect(await found('kb', ['A/x/1.md', 'B/2.md', 'C/y/3.md'])).toEqual({
|
|
248
|
+
paths: ['A/x/1.md', 'B/2.md', 'C/y/3.md'],
|
|
249
|
+
runs: [['kb/A/x@1'], ['kb/B'], ['kb/C/y@1']],
|
|
250
|
+
});
|
|
251
|
+
expect(await found('.', ['T/a/2.md', 'T/b/3.md'])).toEqual({ paths: ['T/a/2.md', 'T/b/3.md'], runs: [['./T/a@1', './T/b@1']] });
|
|
252
|
+
// A file directly in a folder is reached by walking that folder's own
|
|
253
|
+
// files, not its subfolders: the root's file walks the root one deep.
|
|
254
|
+
expect(await found('kb', ['T/b/3.md', 'top.md'])).toEqual({ paths: ['T/b/3.md', 'top.md'], runs: [['kb/T/b@1', 'kb@1']] });
|
|
255
|
+
expect(await found('kb', ['top.md'])).toEqual({ paths: ['top.md'], runs: [['kb@1']] });
|
|
256
|
+
} finally {
|
|
257
|
+
await walked.remove();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('answers a folder\'s own files on both sides of a subfolder once each, in order', async () => {
|
|
262
|
+
for (const p of ['a.md', 'M/m.md', 'M/deep/d.md', 'z.md']) await put(`kb/${p}`, 'needle\n');
|
|
263
|
+
const walked = await recordWalks();
|
|
264
|
+
try {
|
|
265
|
+
const s = await search('needle');
|
|
266
|
+
await walked.runs();
|
|
267
|
+
const paths: string[] = [];
|
|
268
|
+
for await (const f of s.files({ cwd: dir, root: 'kb' }, { only: ['a.md', 'M/deep/d.md', 'M/m.md', 'z.md'], maxLinesPerFile: 10 })) paths.push(f.path);
|
|
269
|
+
expect(paths).toEqual(['a.md', 'M/deep/d.md', 'M/m.md', 'z.md']);
|
|
270
|
+
expect(await walked.runs()).toEqual([['kb@1'], ['kb/M'], ['kb@1']]);
|
|
271
|
+
} finally {
|
|
272
|
+
await walked.remove();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('walks a folder whole rather than run once per alternation when its files and subfolders alternate past the cap', async () => {
|
|
277
|
+
const only: string[] = [];
|
|
278
|
+
for (let i = 0; i < 12; i++) {
|
|
279
|
+
const name = `p${String(i).padStart(2, '0')}`;
|
|
280
|
+
only.push(`${name}.md`, `${name}/x.md`);
|
|
281
|
+
await put(`kb/${name}.md`, 'needle\n');
|
|
282
|
+
await put(`kb/${name}/x.md`, 'needle\n');
|
|
283
|
+
}
|
|
284
|
+
only.sort(compareSearchPaths);
|
|
285
|
+
const walked = await recordWalks();
|
|
286
|
+
try {
|
|
287
|
+
const s = await search('needle');
|
|
288
|
+
await walked.runs();
|
|
289
|
+
const paths: string[] = [];
|
|
290
|
+
for await (const f of s.files({ cwd: dir, root: 'kb' }, { only, maxLinesPerFile: 10 })) paths.push(f.path);
|
|
291
|
+
expect(paths).toEqual(only);
|
|
292
|
+
expect(await walked.runs()).toEqual([['kb']]);
|
|
293
|
+
} finally {
|
|
294
|
+
await walked.remove();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('walks from the root when a folder it would start at has become a link, so the link is not followed', async () => {
|
|
299
|
+
await put('kb/T/a/1.md', 'x\n');
|
|
300
|
+
await put('outside/1.md', 'needle\n');
|
|
301
|
+
const s = await search('needle');
|
|
302
|
+
const scope = { cwd: dir, root: 'kb' };
|
|
303
|
+
const only = await s.list(scope);
|
|
304
|
+
expect(only).toEqual(['T/a/1.md']);
|
|
305
|
+
// Listed as a folder, then swapped for a link out before the search runs.
|
|
306
|
+
await rm(join(dir, 'kb/T/a'), { recursive: true });
|
|
307
|
+
await symlink(join(dir, 'outside'), join(dir, 'kb/T/a'));
|
|
308
|
+
const walked = await recordWalks();
|
|
309
|
+
try {
|
|
310
|
+
const paths: string[] = [];
|
|
311
|
+
for await (const f of s.files(scope, { only, maxLinesPerFile: 10 })) paths.push(f.path);
|
|
312
|
+
expect(paths).toEqual([]);
|
|
313
|
+
expect(await walked.last()).toEqual(['kb']);
|
|
314
|
+
} finally {
|
|
315
|
+
await walked.remove();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('walks nothing when the root itself has become a link since the scope was made', async () => {
|
|
320
|
+
await put('kb/T/1.md', 'needle\n');
|
|
321
|
+
await put('outside/T/1.md', 'needle outside\n');
|
|
322
|
+
const s = await search('needle');
|
|
323
|
+
const scope = { cwd: dir, root: 'kb/T' };
|
|
324
|
+
const only = await s.list(scope);
|
|
325
|
+
expect(only).toEqual(['1.md']);
|
|
326
|
+
// The scope's root, checked when it was made, swapped for a link out.
|
|
327
|
+
await rm(join(dir, 'kb/T'), { recursive: true });
|
|
328
|
+
await symlink(join(dir, 'outside/T'), join(dir, 'kb/T'));
|
|
329
|
+
const paths: string[] = [];
|
|
330
|
+
for await (const f of s.files(scope, { only, maxLinesPerFile: 10 })) paths.push(f.path);
|
|
331
|
+
expect(paths).toEqual([]);
|
|
332
|
+
expect(await s.list(scope)).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('places each matching line in the text it came from', async () => {
|
|
336
|
+
const s = await search('^n');
|
|
337
|
+
expect(await s.matchingLines(['no\nyes\nnah', '', 'x\n', 'n'])).toEqual([[0, 2], [], [], [0]]);
|
|
338
|
+
expect(await s.matchingLines([])).toEqual([]);
|
|
339
|
+
// An empty last line is a line, as `split('\n')` has it.
|
|
340
|
+
expect(await (await search('^$')).matchingLines(['a\n'])).toEqual([[1]]);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('honours ignoreCase', async () => {
|
|
344
|
+
expect(await (await search('NEEDLE')).matchingLines(['needle'])).toEqual([[]]);
|
|
345
|
+
expect(await (await search('NEEDLE', {}, true)).matchingLines(['needle'])).toEqual([[0]]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('refuses what the engine refuses, quoting its reason without its advice to pass flags', async () => {
|
|
349
|
+
await expect(search('foo(?=bar)')).rejects.toMatchObject({ status: 400, details: { code: 'invalid_pattern' } });
|
|
350
|
+
const err = (await search('(a)\\1').catch((e: unknown) => e)) as Error;
|
|
351
|
+
expect(err.message).toMatch(/^Invalid regex: regex parse error:/);
|
|
352
|
+
expect(err.message).toContain('backreferences are not supported');
|
|
353
|
+
expect(err.message).not.toContain('PCRE2');
|
|
354
|
+
expect(err.message).toContain("grep runs ripgrep's default regex engine");
|
|
355
|
+
await expect(search('a\u0000b')).rejects.toMatchObject({ status: 400 });
|
|
356
|
+
await expect(search('x'.repeat(1_001))).rejects.toMatchObject({ status: 400, details: { code: 'pattern_too_long' } });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('reads no ripgrep configuration file', async () => {
|
|
360
|
+
const config = join(dir, 'ripgreprc');
|
|
361
|
+
await writeFile(config, '--ignore-case\n--hidden\n');
|
|
362
|
+
const before = process.env.RIPGREP_CONFIG_PATH;
|
|
363
|
+
process.env.RIPGREP_CONFIG_PATH = config;
|
|
364
|
+
try {
|
|
365
|
+
expect(await (await search('NEEDLE')).matchingLines(['needle'])).toEqual([[]]);
|
|
366
|
+
} finally {
|
|
367
|
+
if (before === undefined) delete process.env.RIPGREP_CONFIG_PATH;
|
|
368
|
+
else process.env.RIPGREP_CONFIG_PATH = before;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('reads a leading dash as the pattern, never as a flag', async () => {
|
|
373
|
+
await put('kb/f.md', 'use --files here\n');
|
|
374
|
+
const files = await all(await search('--files'));
|
|
375
|
+
expect(files).toEqual([{ path: 'f.md', lines: [{ line: 1, text: 'use --files here' }] }]);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('stops a walk at the budget with the 400, killing the run', async () => {
|
|
379
|
+
const slow = await slowRipgrep();
|
|
380
|
+
try {
|
|
381
|
+
const s = await search('needle', { budgetMs: 300 });
|
|
382
|
+
const started = Date.now();
|
|
383
|
+
await expect(all(s)).rejects.toMatchObject({ status: 400, details: { code: 'pattern_too_expensive' } });
|
|
384
|
+
expect(Date.now() - started).toBeLessThan(2_000);
|
|
385
|
+
expect(await slow.alive()).toEqual([]);
|
|
386
|
+
// The budget is the search's: spent, every later run is refused at once.
|
|
387
|
+
await expect(s.matchingLines(['needle'])).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
388
|
+
} finally {
|
|
389
|
+
await slow.remove();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('spends the budget on the work it is charged with, and answers the 400 at the budget when that work outlasts it', async () => {
|
|
394
|
+
const s = await search('needle', { budgetMs: 600 });
|
|
395
|
+
const sleep = (ms: number) => () => new Promise<string>((r) => setTimeout(() => r('done'), ms));
|
|
396
|
+
expect(await s.charged(sleep(200))).toBe('done');
|
|
397
|
+
const started = Date.now();
|
|
398
|
+
await expect(s.charged(sleep(5_000))).rejects.toMatchObject({ status: 400, details: { code: 'pattern_too_expensive' } });
|
|
399
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
400
|
+
// Spent: neither more work nor another run is started.
|
|
401
|
+
await expect(s.charged(sleep(1))).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
402
|
+
await expect(s.matchingLines(['needle'])).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
403
|
+
// A failure of the work itself is the caller's to hear.
|
|
404
|
+
const t = await search('needle');
|
|
405
|
+
await expect(t.charged(() => Promise.reject(new Error('verdict failed')))).rejects.toThrow('verdict failed');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('spends the whole budget when its timer fires, however little time the clock measured', async () => {
|
|
409
|
+
// A clock that measures no time at all: only the timer's firing can spend the budget.
|
|
410
|
+
const clock = vi.spyOn(Date, 'now').mockReturnValue(Date.now());
|
|
411
|
+
const slow = await slowRipgrep();
|
|
412
|
+
try {
|
|
413
|
+
const sleep = (ms: number) => () => new Promise<string>((r) => setTimeout(() => r('done'), ms));
|
|
414
|
+
const s = await search('needle', { budgetMs: 300 });
|
|
415
|
+
await expect(s.charged(sleep(5_000))).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
416
|
+
await expect(s.charged(sleep(1))).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
417
|
+
const t = await search('needle', { budgetMs: 300 });
|
|
418
|
+
await expect(all(t)).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
419
|
+
await expect(t.charged(sleep(1))).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
420
|
+
const refused = performance.now();
|
|
421
|
+
await expect(t.matchingLines(['needle'])).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
422
|
+
expect(performance.now() - refused).toBeLessThan(100);
|
|
423
|
+
} finally {
|
|
424
|
+
clock.mockRestore();
|
|
425
|
+
await slow.remove();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('answers charged work as abandoned when the caller goes away', async () => {
|
|
430
|
+
const gone = new AbortController();
|
|
431
|
+
const s = await matcher().search('needle', { ignoreCase: false, signal: gone.signal });
|
|
432
|
+
const waiting = s.charged(() => new Promise((r) => setTimeout(r, 5_000)));
|
|
433
|
+
gone.abort();
|
|
434
|
+
await expect(waiting).rejects.toMatchObject({ status: 499 });
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('ends the run when the caller stops reading, and gives its slot back', async () => {
|
|
438
|
+
for (let i = 0; i < 50; i++) await put(`kb/f${String(i).padStart(2, '0')}.md`, 'needle\n');
|
|
439
|
+
const m = matcher({ maxRunning: 1, maxWaiting: 0 });
|
|
440
|
+
const s = await m.search('needle', { ignoreCase: false });
|
|
441
|
+
const scope = { cwd: dir, root: 'kb' };
|
|
442
|
+
for await (const f of s.files(scope, { only: await s.list(scope), maxLinesPerFile: 1 })) {
|
|
443
|
+
expect(f.path).toBe('f00.md');
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
// With one slot and no queue, a run still holding it would make this a 503.
|
|
447
|
+
expect(await (await m.search('needle', { ignoreCase: false })).matchingLines(['needle'])).toEqual([[0]]);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('spends one budget across every run of a search, so runs that each finish can still exhaust it', async () => {
|
|
451
|
+
// Each walk takes 1 s and finishes; the third has 500 ms of a 2.5 s budget left.
|
|
452
|
+
const slow = await slowRipgrep({ walkMs: 1_000 });
|
|
453
|
+
try {
|
|
454
|
+
const s = await search('needle', { budgetMs: 2_500 });
|
|
455
|
+
expect(await all(s)).toEqual([]);
|
|
456
|
+
expect(await s.list({ cwd: dir, root: 'kb' })).toEqual([]);
|
|
457
|
+
const started = Date.now();
|
|
458
|
+
await expect(all(s)).rejects.toMatchObject({ status: 400, details: { code: 'pattern_too_expensive' } });
|
|
459
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
460
|
+
expect(await slow.alive()).toEqual([]);
|
|
461
|
+
} finally {
|
|
462
|
+
await slow.remove();
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('kills every run when the caller goes away, answers it abandoned, and gives the slot back', async () => {
|
|
467
|
+
const slow = await slowRipgrep();
|
|
468
|
+
try {
|
|
469
|
+
const m = matcher({ maxRunning: 1, maxWaiting: 0 });
|
|
470
|
+
const gone = new AbortController();
|
|
471
|
+
const s = await m.search('needle', { ignoreCase: false, signal: gone.signal });
|
|
472
|
+
const running = all(s);
|
|
473
|
+
while ((await slow.alive()).length === 0) await new Promise((r) => setTimeout(r, 20));
|
|
474
|
+
const started = Date.now();
|
|
475
|
+
gone.abort();
|
|
476
|
+
await expect(running).rejects.toMatchObject({ status: 499, details: { code: 'search_abandoned' } });
|
|
477
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
478
|
+
expect(await slow.alive()).toEqual([]);
|
|
479
|
+
// A later run of the abandoned search is refused before it starts.
|
|
480
|
+
await expect(s.matchingLines(['needle'])).rejects.toMatchObject({ details: { code: 'search_abandoned' } });
|
|
481
|
+
expect(await (await m.search('needle', { ignoreCase: false })).matchingLines(['needle'])).toEqual([[0]]);
|
|
482
|
+
} finally {
|
|
483
|
+
await slow.remove();
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('a caller gone while its run waits for a slot leaves the queue, answered abandoned, and the queue is free again', async () => {
|
|
488
|
+
const slow = await slowRipgrep();
|
|
489
|
+
try {
|
|
490
|
+
const m = matcher({ maxRunning: 1, maxWaiting: 1 });
|
|
491
|
+
const holder = await m.search('needle', { ignoreCase: false });
|
|
492
|
+
const running = all(holder);
|
|
493
|
+
while ((await slow.alive()).length === 0) await new Promise((r) => setTimeout(r, 20));
|
|
494
|
+
// The pattern check of a new search is a run: with the one slot held it queues.
|
|
495
|
+
const gone = new AbortController();
|
|
496
|
+
const queued = m.search('needle', { ignoreCase: false, signal: gone.signal });
|
|
497
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
498
|
+
gone.abort();
|
|
499
|
+
await expect(queued).rejects.toMatchObject({ status: 499, details: { code: 'search_abandoned' } });
|
|
500
|
+
// Its place in the queue went with it: the next search waits rather than being refused as one too many.
|
|
501
|
+
const next = m.search('needle', { ignoreCase: false });
|
|
502
|
+
const outcome = await Promise.race([next.then(() => 'ran', () => 'refused'), new Promise((r) => setTimeout(() => r('waiting'), 200))]);
|
|
503
|
+
expect(outcome).toBe('waiting');
|
|
504
|
+
await m.close();
|
|
505
|
+
await expect(next).rejects.toMatchObject({ status: 503 });
|
|
506
|
+
await expect(running).rejects.toMatchObject({ status: 503 });
|
|
507
|
+
expect(await slow.alive()).toEqual([]);
|
|
508
|
+
} finally {
|
|
509
|
+
await slow.remove();
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('charges the wait for a slot to the budget, and answers the 400 from the queue when the budget passes there', async () => {
|
|
514
|
+
// Every search of a matcher has the same budget, so a queued search can
|
|
515
|
+
// outwait its own only once it has spent part of it: one walk of 500 ms
|
|
516
|
+
// against a budget of 800, then a wait behind a fresh search's 500 ms
|
|
517
|
+
// walk, which outlasts the 300 ms the first has left.
|
|
518
|
+
const slow = await slowRipgrep({ walkMs: 500 });
|
|
519
|
+
try {
|
|
520
|
+
await put('kb/a.md', 'needle\n');
|
|
521
|
+
const m = matcher({ maxRunning: 1, maxWaiting: 1, budgetMs: 800 });
|
|
522
|
+
const scope = { cwd: dir, root: 'kb' };
|
|
523
|
+
const s = await m.search('needle', { ignoreCase: false });
|
|
524
|
+
expect(await s.list(scope)).toEqual([]);
|
|
525
|
+
const holder = await m.search('needle', { ignoreCase: false });
|
|
526
|
+
const holding = holder.list(scope);
|
|
527
|
+
while ((await slow.alive()).length === 0) await new Promise((r) => setTimeout(r, 20));
|
|
528
|
+
const started = Date.now();
|
|
529
|
+
const queued = s.list(scope);
|
|
530
|
+
// Refused from the queue, before the holder's walk ends — so it never ran.
|
|
531
|
+
const first = await Promise.race([
|
|
532
|
+
queued.then(
|
|
533
|
+
() => 'ran',
|
|
534
|
+
() => 'refused',
|
|
535
|
+
),
|
|
536
|
+
holding.then(() => 'the holder finished first'),
|
|
537
|
+
]);
|
|
538
|
+
expect(first).toBe('refused');
|
|
539
|
+
await expect(queued).rejects.toMatchObject({ status: 400, details: { code: 'pattern_too_expensive' } });
|
|
540
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
541
|
+
// The wait spent the budget: a later run is refused at once.
|
|
542
|
+
await expect(s.matchingLines(['needle'])).rejects.toMatchObject({ details: { code: 'pattern_too_expensive' } });
|
|
543
|
+
expect(await holding).toEqual([]);
|
|
544
|
+
} finally {
|
|
545
|
+
await slow.remove();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('refuses a run past the queue with a 503 and ends every run on close', async () => {
|
|
550
|
+
const slow = await slowRipgrep();
|
|
551
|
+
try {
|
|
552
|
+
const m = matcher({ maxRunning: 1, maxWaiting: 0 });
|
|
553
|
+
const s = await m.search('needle', { ignoreCase: false });
|
|
554
|
+
const running = all(s);
|
|
555
|
+
while ((await slow.alive()).length === 0) await new Promise((r) => setTimeout(r, 20));
|
|
556
|
+
await expect(m.search('needle', { ignoreCase: false })).rejects.toMatchObject({ status: 503, details: { code: 'search_busy' } });
|
|
557
|
+
await m.close();
|
|
558
|
+
await expect(running).rejects.toMatchObject({ status: 503 });
|
|
559
|
+
expect(await slow.alive()).toEqual([]);
|
|
560
|
+
} finally {
|
|
561
|
+
await slow.remove();
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('refuses sizes it cannot work with', () => {
|
|
566
|
+
expect(() => new RipgrepPatternMatcher({ maxRunning: 0 })).toThrow(/maxRunning must be an integer from 1 /);
|
|
567
|
+
expect(() => new RipgrepPatternMatcher({ budgetMs: 0 })).toThrow(/budgetMs/);
|
|
568
|
+
expect(() => new RipgrepPatternMatcher({ maxWaiting: -1 })).toThrow(/maxWaiting/);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { assertRipgrep } from '../index.js';
|
|
6
|
+
|
|
7
|
+
/** The boot check, against the real PATH and against one where `rg` is nowhere. */
|
|
8
|
+
describe('assertRipgrep', () => {
|
|
9
|
+
const path = process.env.PATH;
|
|
10
|
+
let empty = '';
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
process.env.PATH = path;
|
|
13
|
+
if (empty) await rm(empty, { recursive: true, force: true });
|
|
14
|
+
empty = '';
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('passes where rg is on the PATH', async () => {
|
|
18
|
+
await expect(assertRipgrep()).resolves.toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('refuses to boot without rg, naming the binary and the Dockerfile that installs it', async () => {
|
|
22
|
+
empty = await mkdtemp(join(tmpdir(), 'no-rg-'));
|
|
23
|
+
process.env.PATH = empty;
|
|
24
|
+
const err = (await assertRipgrep().catch((e: unknown) => e)) as Error;
|
|
25
|
+
expect(err).toBeInstanceOf(Error);
|
|
26
|
+
expect(err.message).toMatch(/^ripgrep \(`rg`\) is required on the server's PATH but could not be run: .*ENOENT/);
|
|
27
|
+
expect(err.message).toContain('The Hexis Dockerfile installs it');
|
|
28
|
+
});
|
|
29
|
+
});
|