@bevel-software/platform-core-backend 0.26.0 → 0.27.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 +17 -0
- package/agent-guide/conventions.md +8 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/tool-manuals.md +10 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +26 -2
- package/dist/assets.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +19 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +14 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/create-core-server.d.ts +5 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +54 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +26 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +100 -8
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +18 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +21 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +22 -1
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +11 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +1 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +8 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/index.d.ts +1 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +1 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +5 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +19 -2
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +21 -2
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +43 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
- package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
- package/dist/modules/audit/agent-connection.routes.js +38 -0
- package/dist/modules/audit/agent-connection.routes.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +24 -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/audit/index.d.ts +1 -0
- package/dist/modules/audit/index.d.ts.map +1 -1
- package/dist/modules/audit/index.js +1 -0
- package/dist/modules/audit/index.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +10 -3
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +7 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +1 -1
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +11 -3
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +96 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +32 -3
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/embed/account-link.service.d.ts +42 -0
- package/dist/modules/embed/account-link.service.d.ts.map +1 -0
- package/dist/modules/embed/account-link.service.js +50 -0
- package/dist/modules/embed/account-link.service.js.map +1 -0
- package/dist/modules/embed/embed-link.d.ts +58 -0
- package/dist/modules/embed/embed-link.d.ts.map +1 -0
- package/dist/modules/embed/embed-link.js +160 -0
- package/dist/modules/embed/embed-link.js.map +1 -0
- package/dist/modules/embed/embed.errors.d.ts +30 -0
- package/dist/modules/embed/embed.errors.d.ts.map +1 -0
- package/dist/modules/embed/embed.errors.js +42 -0
- package/dist/modules/embed/embed.errors.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +204 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -0
- package/dist/modules/embed/embed.interface.js +2 -0
- package/dist/modules/embed/embed.interface.js.map +1 -0
- package/dist/modules/embed/embed.routes.d.ts +59 -0
- package/dist/modules/embed/embed.routes.d.ts.map +1 -0
- package/dist/modules/embed/embed.routes.js +343 -0
- package/dist/modules/embed/embed.routes.js.map +1 -0
- package/dist/modules/embed/embed.service.d.ts +169 -0
- package/dist/modules/embed/embed.service.d.ts.map +1 -0
- package/dist/modules/embed/embed.service.js +627 -0
- package/dist/modules/embed/embed.service.js.map +1 -0
- package/dist/modules/embed/embed.tools.d.ts +51 -0
- package/dist/modules/embed/embed.tools.d.ts.map +1 -0
- package/dist/modules/embed/embed.tools.js +170 -0
- package/dist/modules/embed/embed.tools.js.map +1 -0
- package/dist/modules/embed/mcp-app.d.ts +64 -0
- package/dist/modules/embed/mcp-app.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.js +147 -0
- package/dist/modules/embed/mcp-app.js.map +1 -0
- package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
- package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.routes.js +36 -0
- package/dist/modules/embed/mcp-app.routes.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +15 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
- package/dist/modules/github-app/github-app.connection.js +34 -3
- package/dist/modules/github-app/github-app.connection.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +300 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +6 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +28 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +64 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
- package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.contract.js +7 -0
- package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.js +81 -0
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.js +492 -0
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
- package/dist/modules/onboarding/starter-packs.d.ts +78 -0
- package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-packs.js +156 -0
- package/dist/modules/onboarding/starter-packs.js.map +1 -0
- 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 +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +18 -1
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +0 -36
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +31 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +134 -1
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/repository-source.d.ts +1 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -1
- package/dist/modules/settings/repository-source.js +3 -3
- package/dist/modules/settings/repository-source.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +50 -32
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +59 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/index.d.ts +28 -1
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +28 -1
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
- package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-argument-check.js +132 -0
- package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +70 -6
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +157 -8
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +9 -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-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +6 -2
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +27 -5
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +4 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
- package/dist/modules/tool-registry/guide-first.js +9 -1
- package/dist/modules/tool-registry/guide-first.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +29 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +62 -5
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +72 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +172 -36
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +78 -6
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +25 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +23 -6
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +1 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +16 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +45 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +183 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +805 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
- package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.builder.js +238 -0
- package/dist/modules/workspace/agent-download.builder.js.map +1 -0
- package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.routes.js +156 -0
- package/dist/modules/workspace/agent-download.routes.js.map +1 -0
- package/dist/modules/workspace/agent-download.store.d.ts +225 -0
- package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.store.js +423 -0
- package/dist/modules/workspace/agent-download.store.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
- package/dist/modules/workspace/agent-upload.store.js +16 -6
- package/dist/modules/workspace/agent-upload.store.js.map +1 -1
- package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
- package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
- package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +5 -8
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/first-run.d.ts +111 -0
- package/dist/modules/workspace/first-run.d.ts.map +1 -0
- package/dist/modules/workspace/first-run.js +181 -0
- package/dist/modules/workspace/first-run.js.map +1 -0
- package/dist/modules/workspace/move-links.d.ts +92 -0
- package/dist/modules/workspace/move-links.d.ts.map +1 -0
- package/dist/modules/workspace/move-links.js +173 -0
- package/dist/modules/workspace/move-links.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +63 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +75 -2
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +197 -24
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +59 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +443 -65
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts.map +1 -1
- package/dist/modules/write-access/write-access.js +9 -0
- package/dist/modules/write-access/write-access.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +73 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +86 -12
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -1
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/utf8-text.d.ts +15 -0
- package/dist/shared/utf8-text.d.ts.map +1 -0
- package/dist/shared/utf8-text.js +20 -0
- package/dist/shared/utf8-text.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +2 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +9 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/mcp-app/page.html +349 -0
- package/migrations/0017_atlassian_account_links.sql +37 -0
- package/migrations/meta/0017_snapshot.json +2396 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -3
- package/src/__tests__/packaged-assets-ship.test.ts +16 -4
- package/src/assets.ts +28 -2
- package/src/core/__tests__/catalog-revision.test.ts +40 -0
- package/src/core/__tests__/gated-boot.test.ts +1 -0
- package/src/core/__tests__/lifecycle.test.ts +14 -0
- package/src/core/catalog-revision.ts +15 -2
- package/src/core/create-core-server.ts +67 -3
- package/src/core/create-core-services.ts +140 -6
- package/src/core/lifecycle.ts +28 -2
- package/src/core-config.ts +24 -1
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/access-control.interface.ts +13 -0
- package/src/modules/access/access-control.service.ts +16 -0
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
- package/src/modules/agent-guide/agent-guide.ts +9 -0
- package/src/modules/agent-guide/index.ts +1 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
- package/src/modules/agent-instructions/compose.ts +17 -2
- package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
- package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
- package/src/modules/audit/agent-audit.service.ts +58 -3
- package/src/modules/audit/agent-connection.routes.ts +56 -0
- package/src/modules/audit/audit.contract.ts +26 -0
- package/src/modules/audit/index.ts +1 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
- package/src/modules/auth/account.routes.ts +10 -3
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
- package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
- package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
- package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
- package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
- package/src/modules/embed/account-link.service.ts +76 -0
- package/src/modules/embed/embed-link.ts +180 -0
- package/src/modules/embed/embed.errors.ts +43 -0
- package/src/modules/embed/embed.interface.ts +203 -0
- package/src/modules/embed/embed.routes.ts +363 -0
- package/src/modules/embed/embed.service.ts +705 -0
- package/src/modules/embed/embed.tools.ts +206 -0
- package/src/modules/embed/mcp-app.routes.ts +41 -0
- package/src/modules/embed/mcp-app.ts +173 -0
- package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
- package/src/modules/github-app/github-app.connection.ts +38 -4
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
- package/src/modules/kb-fs/locking-filesystem.ts +287 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
- package/src/modules/kb-sync/kb-sync.service.ts +6 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
- package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +81 -12
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
- package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
- package/src/modules/onboarding/onboarding.contract.ts +85 -0
- package/src/modules/onboarding/onboarding.routes.ts +85 -0
- package/src/modules/onboarding/starter-pack.service.ts +580 -0
- package/src/modules/onboarding/starter-packs.ts +182 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
- package/src/modules/plugins/index.ts +1 -0
- package/src/modules/plugins/plugin-provision.service.ts +22 -1
- package/src/modules/plugins/plugins.routes.ts +29 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +131 -1
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
- package/src/modules/skills/skills.tools.ts +55 -35
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
- package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
- package/src/modules/tool-auth/external-api-key.service.ts +61 -4
- package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
- package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
- package/src/modules/tool-helpers/index.ts +34 -1
- package/src/modules/tool-helpers/route-argument-check.ts +149 -0
- package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
- package/src/modules/tool-helpers/tool-def.ts +132 -7
- package/src/modules/tool-helpers/tool-handler.ts +196 -8
- package/src/modules/tool-helpers/tool.contract.ts +9 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
- package/src/modules/tool-registry/description-length.ts +34 -7
- package/src/modules/tool-registry/guide-first.ts +8 -1
- package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
- package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
- package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
- package/src/modules/workflow/git/git.service.ts +176 -38
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/pending-commits.service.ts +29 -1
- package/src/modules/workflow/pending-commits.worker.ts +24 -6
- package/src/modules/workflow/recovery-bot.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.service.ts +879 -112
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
- package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
- package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
- package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
- package/src/modules/workspace/agent-download.builder.ts +308 -0
- package/src/modules/workspace/agent-download.routes.ts +183 -0
- package/src/modules/workspace/agent-download.store.ts +533 -0
- package/src/modules/workspace/agent-upload.store.ts +23 -7
- package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
- package/src/modules/workspace/file-readers/text-reader.ts +5 -8
- package/src/modules/workspace/first-run.ts +222 -0
- package/src/modules/workspace/move-links.ts +237 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
- package/src/modules/workspace/workspace.routes.ts +64 -22
- package/src/modules/workspace/workspace.service.ts +202 -25
- package/src/modules/workspace/workspace.tools.ts +492 -63
- package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
- package/src/modules/write-access/write-access.ts +9 -0
- package/src/shared/domain-errors.ts +108 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/utf8-text.ts +21 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
- package/src/tenancy/static-tenant-source.ts +10 -2
- package/starter-packs/README.md +103 -0
- package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
- package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
- package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
- package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
- package/starter-packs/engineering/pack.yaml +11 -0
- package/starter-packs/general/KnowledgeBase/About us.md +9 -0
- package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
- package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
- package/starter-packs/general/Plugins/general-starter/access.md +19 -0
- package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
- package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
- package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
- package/starter-packs/general/pack.yaml +10 -0
- package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
- package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
- package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
- package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
- package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
- package/starter-packs/operations/pack.yaml +12 -0
- package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
- package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
- package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
- package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
- package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
- package/starter-packs/sales/pack.yaml +13 -0
- package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
- package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
- package/starter-packs/support/KnowledgeBase/Products.md +9 -0
- package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
- package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
- package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
- package/starter-packs/support/Plugins/support-starter/access.md +19 -0
- package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
- package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
- package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
- package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
- package/starter-packs/support/pack.yaml +12 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Router, RequestHandler } from 'express';
|
|
2
2
|
import type { IToolRegistry, JsonSchema } from '../../tool-registry/tool.contract.js';
|
|
3
3
|
import { ToolError, type ToolContext, type ToolHandler } from '../../tool-helpers/tool.contract.js';
|
|
4
|
-
import { toolDef
|
|
4
|
+
import { toolDef } from '../../tool-helpers/tool-def.js';
|
|
5
5
|
import type { ToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
|
|
6
6
|
import { requireInternalSource } from '../../tool-auth/tool-auth.middleware.js';
|
|
7
7
|
import type { KbContext } from '../../../shared/kb-context.js';
|
|
8
|
-
import { assertBranchProvided } from '../../../shared/domain-errors.js';
|
|
9
8
|
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
10
9
|
import { assertInsideRepo, normalizePathArgs } from '../../kb-fs/repo-path.js';
|
|
11
10
|
import { RETIRED_TOOL_MESSAGES } from '@bevel-software/platform-mcp-core';
|
|
@@ -171,6 +170,12 @@ export function registerWorkflowTools(
|
|
|
171
170
|
toolHandler: ToolHandlerFactory,
|
|
172
171
|
/** The clone folder at the workspace root (`save_file` refuses a path outside it) and the branch model. */
|
|
173
172
|
kb: Pick<KbContext, 'kbDirName' | 'defaultBranch' | 'defaultWorkspaceId' | 'protectedBranches'>,
|
|
173
|
+
/**
|
|
174
|
+
* Whether the caller may see change request `number` — `get_change_request`'s
|
|
175
|
+
* own gate (`changeRequestScope(...).maySee`). `delete_branch` neither names
|
|
176
|
+
* nor links a request it answers no for.
|
|
177
|
+
*/
|
|
178
|
+
maySeeChangeRequest: (ctx: ToolContext, number: number) => Promise<boolean>,
|
|
174
179
|
): void {
|
|
175
180
|
const { kbDirName } = kb;
|
|
176
181
|
const mount = (spec: {
|
|
@@ -191,21 +196,24 @@ export function registerWorkflowTools(
|
|
|
191
196
|
* out of the check while still demanding the input.
|
|
192
197
|
*/
|
|
193
198
|
skipBranch?: boolean;
|
|
199
|
+
/** Arguments this tool's handler refuses by name itself (see `ToolDefSpec`). */
|
|
200
|
+
refusesItself?: string[];
|
|
194
201
|
handler: ToolHandler;
|
|
195
202
|
}): void => {
|
|
196
203
|
const path = `/api/agent/tools/${spec.name}`;
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// or drops the input later moves this with it.
|
|
202
|
-
const requiresBranch = ((inputs as { required?: string[] }).required ?? []).includes('branch');
|
|
204
|
+
// Every workflow tool that takes a branch REQUIRES it — the injected input
|
|
205
|
+
// and a hand-declared one (the fork base, the draft to switch to) alike.
|
|
206
|
+
// A hand-declared one is required by its own schema, which `toolDef`
|
|
207
|
+
// reads as `required` too; the tool handler refuses a call without it.
|
|
203
208
|
const def = toolDef({
|
|
204
209
|
name: spec.name,
|
|
205
210
|
description: spec.description,
|
|
206
211
|
path,
|
|
207
|
-
inputs,
|
|
212
|
+
inputs: spec.inputs,
|
|
213
|
+
branch: spec.skipBranch ? undefined : 'required',
|
|
214
|
+
write: spec.write,
|
|
208
215
|
outputs: spec.outputs,
|
|
216
|
+
refusesItself: spec.refusesItself,
|
|
209
217
|
tags: spec.write ? ['workflow', 'write'] : ['workflow'],
|
|
210
218
|
});
|
|
211
219
|
registry.registerInternalTool(def);
|
|
@@ -223,24 +231,12 @@ export function registerWorkflowTools(
|
|
|
223
231
|
// workspace path, and a path with no prefix is placed under
|
|
224
232
|
// `<kbDirName>/` rather than refused.
|
|
225
233
|
toolHandler(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// branch-less `create_branch` forks from one: it clones a branch of
|
|
233
|
-
// that name, fails, and answers `There is no branch named undefined.`
|
|
234
|
-
//
|
|
235
|
-
// Keyed on whether the SCHEMA requires `branch`, not on whether the
|
|
236
|
-
// input was injected. `create_branch` and `switch_branch` declare
|
|
237
|
-
// their own required `branch`; a check keyed on the injection skipped
|
|
238
|
-
// exactly those two, which is how the reported bug survived on
|
|
239
|
-
// `create_branch` — the one tool whose `branch` is a branch the
|
|
240
|
-
// caller must already have.
|
|
241
|
-
if (requiresBranch) assertBranchProvided(args.branch);
|
|
242
|
-
return spec.handler(normalizePathArgs(args, kbDirName), ctx);
|
|
243
|
-
},
|
|
234
|
+
// These tools hand `branch` STRAIGHT to `workspaceIdForBranch` —
|
|
235
|
+
// they address a workspace by id rather than going through
|
|
236
|
+
// `getFilesystem` — so the branch-less call the tool handler refuses
|
|
237
|
+
// before this runs would otherwise commit to, or fork from, a
|
|
238
|
+
// workspace literally named "undefined".
|
|
239
|
+
(args, ctx) => spec.handler(normalizePathArgs(args, kbDirName), ctx),
|
|
244
240
|
{ write: spec.write },
|
|
245
241
|
),
|
|
246
242
|
);
|
|
@@ -383,6 +379,9 @@ export function registerWorkflowTools(
|
|
|
383
379
|
// workspace-`branch` injection ("the branch you are currently working on"),
|
|
384
380
|
// whose wording invites filling in the draft being created instead.
|
|
385
381
|
skipBranch: true,
|
|
382
|
+
// The handler refuses a missing `name` with a message that says what the
|
|
383
|
+
// name is for (`name-required`); the generic check leaves it to that.
|
|
384
|
+
refusesItself: ['name'],
|
|
386
385
|
inputs: {
|
|
387
386
|
type: 'object',
|
|
388
387
|
properties: {
|
|
@@ -550,13 +549,16 @@ export function registerWorkflowTools(
|
|
|
550
549
|
name: 'merge_branch',
|
|
551
550
|
description:
|
|
552
551
|
'Merge branch `source` into branch `target` as you, and publish `target`. An agent proposes and syncs; ' +
|
|
553
|
-
'a person merges: this tool never lands a change request. It refuses
|
|
554
|
-
'into `target` is open (naming it)
|
|
552
|
+
'a person merges: this tool never lands a change request. It refuses while a change request from `source` ' +
|
|
553
|
+
'into `target` is open (naming it): ask the user to review it in the app. It refuses ' +
|
|
555
554
|
'when `target` is a protected branch unless you could commit every changed file directly to it. ' +
|
|
556
555
|
'SYNC: merging the target into your draft (`source` = the change request\'s target, `target` = your draft) ' +
|
|
557
|
-
'is always allowed, even with
|
|
558
|
-
'
|
|
559
|
-
'
|
|
556
|
+
'is always allowed, even with the draft\'s request open. ' +
|
|
557
|
+
'Writes are committed asynchronously; the merge first waits up to 20s for those on `source`. ' +
|
|
558
|
+
'Returns `merged` with the merge commit; `nothing-to-merge` with `target`\'s tip when it already holds ' +
|
|
559
|
+
'all of `source`; `pending-commits` (nothing merged) when writes are still committing — retry shortly — ' +
|
|
560
|
+
'or one failed (`needsAttention`: tell the user); or `conflicts-need-resolution` with the conflicting ' +
|
|
561
|
+
'paths (nothing is written; resolve them on `source` and merge again).',
|
|
560
562
|
// Names both branches itself; the merge runs in `target`'s workspace.
|
|
561
563
|
skipBranch: true,
|
|
562
564
|
inputs: {
|
|
@@ -573,11 +575,28 @@ export function registerWorkflowTools(
|
|
|
573
575
|
properties: {
|
|
574
576
|
outcome: {
|
|
575
577
|
type: 'object',
|
|
576
|
-
description:
|
|
578
|
+
description:
|
|
579
|
+
'A completed merge, a merge with nothing to do, writes on `source` not committed yet ' +
|
|
580
|
+
'(writes are committed asynchronously and the merge waits up to 20 seconds for them), ' +
|
|
581
|
+
'or a signal that conflicts must be resolved first.',
|
|
577
582
|
properties: {
|
|
578
|
-
kind: { type: 'string', enum: ['merged', 'conflicts-need-resolution'] },
|
|
579
|
-
sha: {
|
|
583
|
+
kind: { type: 'string', enum: ['merged', 'nothing-to-merge', 'pending-commits', 'conflicts-need-resolution'] },
|
|
584
|
+
sha: {
|
|
585
|
+
type: 'string',
|
|
586
|
+
description:
|
|
587
|
+
'Present when `kind` is `merged` (the merge commit, now the tip of `target`) or `nothing-to-merge` ' +
|
|
588
|
+
'(the unchanged tip of `target`, which already held everything on `source`).',
|
|
589
|
+
},
|
|
580
590
|
conflictedPaths: { type: 'array', items: { type: 'string' }, description: 'Present when `kind` is `conflicts-need-resolution`.' },
|
|
591
|
+
branch: { type: 'string', description: 'Present when `kind` is `pending-commits` — the branch whose writes are not committed yet.' },
|
|
592
|
+
pending: { type: 'integer', description: 'Present when `kind` is `pending-commits` — how many commits on that branch are still queued, plus those that failed when `needsAttention` is present.' },
|
|
593
|
+
needsAttention: {
|
|
594
|
+
type: 'string',
|
|
595
|
+
description:
|
|
596
|
+
'Present when `kind` is `pending-commits` and a queued commit failed: the worker\'s message. ' +
|
|
597
|
+
'Waiting will not help; tell the user.',
|
|
598
|
+
},
|
|
599
|
+
message: { type: 'string', description: 'Present when `kind` is `pending-commits` — what to do next.' },
|
|
581
600
|
},
|
|
582
601
|
required: ['kind'],
|
|
583
602
|
},
|
|
@@ -595,6 +614,81 @@ export function registerWorkflowTools(
|
|
|
595
614
|
},
|
|
596
615
|
});
|
|
597
616
|
|
|
617
|
+
mount({
|
|
618
|
+
name: 'delete_branch',
|
|
619
|
+
description:
|
|
620
|
+
'Delete branch `name` from the shared repository and the server. Allowed for the ' +
|
|
621
|
+
'branch\'s author (`<email-localpart>/…`, or your own `suggestions/…` bundle) or an Admin. Refuses a ' +
|
|
622
|
+
'protected branch, a name that is not a branch, a branch with saves still landing or a file held ' +
|
|
623
|
+
'(retry once they land). An open change request from or into it that proposes nothing is closed and the ' +
|
|
624
|
+
'delete goes ahead; one that still proposes something refuses it, naming and linking it: its author ' +
|
|
625
|
+
'withdraws it or an Admin declines it, in the app (one you cannot see is not named, nor in a preview). ' +
|
|
626
|
+
'Refuses a branch holding commits that are not on the ' +
|
|
627
|
+
'default branch, saying how many; `discardUnmerged: true` deletes it anyway and reports them as ' +
|
|
628
|
+
'`discardedCommits`. Refuses if the shared repository is unreachable. Preview with ' +
|
|
629
|
+
'`dryRun: true`: it changes nothing and answers `exists`, `canDelete`, `refusals`, `unmergedCommits`, ' +
|
|
630
|
+
'`openChangeRequests` and `lastCommit`. A preview grants nothing; checks rerun on delete. ' +
|
|
631
|
+
'Answers the deleted branch\'s `lastCommit`.',
|
|
632
|
+
// Names its branch itself; the deletion runs in the default branch's workspace.
|
|
633
|
+
skipBranch: true,
|
|
634
|
+
refusesItself: ['name'],
|
|
635
|
+
inputs: {
|
|
636
|
+
type: 'object',
|
|
637
|
+
properties: {
|
|
638
|
+
name: { type: 'string', minLength: 1, description: 'The branch to delete.' },
|
|
639
|
+
dryRun: { type: 'boolean', description: 'Report what deleting would do, and change nothing.' },
|
|
640
|
+
discardUnmerged: {
|
|
641
|
+
type: 'boolean',
|
|
642
|
+
description: 'Delete even though the branch holds commits that are not on the default branch. They are lost.',
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
required: ['name'],
|
|
646
|
+
additionalProperties: false,
|
|
647
|
+
},
|
|
648
|
+
outputs: {
|
|
649
|
+
type: 'object',
|
|
650
|
+
properties: {
|
|
651
|
+
kind: { type: 'string', enum: ['preview', 'deleted'] },
|
|
652
|
+
branch: { type: 'string' },
|
|
653
|
+
lastCommit: { type: ['string', 'null'], description: 'The branch\'s tip — what a restore starts from. Null in a preview of a branch that does not exist.' },
|
|
654
|
+
discardedCommits: { type: 'integer', description: 'When `kind` is `deleted`: commits not on the default branch that went with it.' },
|
|
655
|
+
exists: { type: 'boolean', description: 'When `kind` is `preview`.' },
|
|
656
|
+
canDelete: { type: 'boolean', description: 'When `kind` is `preview`: whether a delete asked for now would go through.' },
|
|
657
|
+
refusals: { type: 'array', items: { type: 'string' }, description: 'When `kind` is `preview`: why it would not.' },
|
|
658
|
+
unmergedCommits: { type: 'integer', description: 'When `kind` is `preview`: commits not on the default branch.' },
|
|
659
|
+
openChangeRequests: {
|
|
660
|
+
type: 'array',
|
|
661
|
+
description: 'When `kind` is `preview`: open change requests from (`source`) or into (`target`) the branch that you can see.',
|
|
662
|
+
items: {
|
|
663
|
+
type: 'object',
|
|
664
|
+
properties: {
|
|
665
|
+
number: { type: 'integer' },
|
|
666
|
+
end: { type: 'string', enum: ['source', 'target'] },
|
|
667
|
+
url: { type: 'string', description: 'Link to the request in the app.' },
|
|
668
|
+
proposesNothing: { type: 'boolean', description: 'True when the delete would close it rather than be refused by it.' },
|
|
669
|
+
},
|
|
670
|
+
required: ['number', 'end', 'url', 'proposesNothing'],
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
required: ['kind', 'branch', 'lastCommit'],
|
|
675
|
+
},
|
|
676
|
+
write: true,
|
|
677
|
+
handler: async (args, ctx: ToolContext) => {
|
|
678
|
+
const name = args.name;
|
|
679
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
680
|
+
throw new ToolError('`name` is required: pass the name of the branch to delete.', 400, {
|
|
681
|
+
kind: 'name-required',
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
return ctx.workflowService.deleteBranchChecked(ctx.user, name, {
|
|
685
|
+
dryRun: args.dryRun === true,
|
|
686
|
+
discardUnmerged: args.discardUnmerged === true,
|
|
687
|
+
maySee: (number) => maySeeChangeRequest(ctx, number),
|
|
688
|
+
});
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
|
|
598
692
|
// `merge_change_request` is retired: a change request is merged by a person
|
|
599
693
|
// in the app. It is in no catalog, but a caller holding an old manual still
|
|
600
694
|
// posts to its path — answer with who merges now, not a bare 404.
|
|
@@ -69,17 +69,52 @@ function rowToFileLock(row: typeof fileLocks.$inferSelect): FileLock {
|
|
|
69
69
|
export class FileLockService {
|
|
70
70
|
constructor(private readonly db: Database) {}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Branches being deleted right now, with how many deletions hold each. No
|
|
74
|
+
* lock is granted on them: a deletion checks that no save is landing and
|
|
75
|
+
* then removes the branch, and a save that took its lock in between would
|
|
76
|
+
* be lost with the checkout. See `whileNoneAcquired`.
|
|
77
|
+
*/
|
|
78
|
+
private readonly closing = new Map<string, number>();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Acquires on each branch that passed the `closing` check and have not
|
|
82
|
+
* settled yet. `whileNoneAcquired` waits for them: one whose row is still
|
|
83
|
+
* being written when a deletion starts would otherwise land after the
|
|
84
|
+
* deletion's last check.
|
|
85
|
+
*/
|
|
86
|
+
private readonly acquiring = new Map<string, Set<Promise<unknown>>>();
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Run `fn` while no lock can be acquired on `branch`, once every acquire
|
|
90
|
+
* already under way on it has settled. The deletion paths re-check for
|
|
91
|
+
* saves landing inside it, so that check sees every lock granted before it,
|
|
92
|
+
* and the deletion meets no save that started in between.
|
|
93
|
+
*/
|
|
94
|
+
async whileNoneAcquired<T>(branch: string, fn: () => Promise<T>): Promise<T> {
|
|
95
|
+
this.closing.set(branch, (this.closing.get(branch) ?? 0) + 1);
|
|
96
|
+
try {
|
|
97
|
+
await Promise.allSettled([...(this.acquiring.get(branch) ?? [])]);
|
|
98
|
+
return await fn();
|
|
99
|
+
} finally {
|
|
100
|
+
const n = (this.closing.get(branch) ?? 1) - 1;
|
|
101
|
+
if (n > 0) this.closing.set(branch, n);
|
|
102
|
+
else this.closing.delete(branch);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
/**
|
|
73
107
|
* Acquire a lock for `(workspaceId, branch, path)` on behalf of `user`.
|
|
74
108
|
*
|
|
75
109
|
* Three outcomes folded into the same return shape:
|
|
76
110
|
* - No existing row: insert + return `{ acquired: true, lock }`.
|
|
77
111
|
* - Existing row, expired: take it over (update) + `{ acquired: true }`.
|
|
78
|
-
* - Existing row, live
|
|
79
|
-
* with the current holder's lock state so the UI
|
|
80
|
-
* "Locked by X" without a follow-up call.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
112
|
+
* - Existing row, live — ANY user, the holder included: return
|
|
113
|
+
* `{ acquired: false }` with the current holder's lock state so the UI
|
|
114
|
+
* can render "Locked by X" without a follow-up call. Re-acquiring your
|
|
115
|
+
* own live lock is refused on purpose (see the comment in the body):
|
|
116
|
+
* refresh a held lock with `heartbeat()`, and ask "do I already hold
|
|
117
|
+
* it?" with `get()`.
|
|
83
118
|
*
|
|
84
119
|
* `opts.coordination` stamps the row's `mode` as `'coordination'` — a
|
|
85
120
|
* pure-mutex hold that grants no write authority (see
|
|
@@ -97,6 +132,33 @@ export class FileLockService {
|
|
|
97
132
|
opts?: { coordination?: boolean },
|
|
98
133
|
): Promise<AcquireLockResult> {
|
|
99
134
|
const targetPath = canonicalFileIdentity(rawPath);
|
|
135
|
+
if (this.closing.has(branch)) {
|
|
136
|
+
throw new WorkflowValidationError(`"${branch}" is being deleted, so "${targetPath}" cannot be held for editing.`, {
|
|
137
|
+
kind: 'branch-being-deleted',
|
|
138
|
+
branch,
|
|
139
|
+
path: targetPath,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const pending = this.acquireUnchecked(workspaceId, branch, targetPath, user, opts);
|
|
143
|
+
const inFlight = this.acquiring.get(branch) ?? new Set<Promise<unknown>>();
|
|
144
|
+
inFlight.add(pending);
|
|
145
|
+
this.acquiring.set(branch, inFlight);
|
|
146
|
+
try {
|
|
147
|
+
return await pending;
|
|
148
|
+
} finally {
|
|
149
|
+
inFlight.delete(pending);
|
|
150
|
+
if (inFlight.size === 0 && this.acquiring.get(branch) === inFlight) this.acquiring.delete(branch);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** `acquire` past the deletion gate. */
|
|
155
|
+
private async acquireUnchecked(
|
|
156
|
+
workspaceId: string,
|
|
157
|
+
branch: string,
|
|
158
|
+
targetPath: string,
|
|
159
|
+
user: AuthUser,
|
|
160
|
+
opts?: { coordination?: boolean },
|
|
161
|
+
): Promise<AcquireLockResult> {
|
|
100
162
|
const now = new Date();
|
|
101
163
|
const expiresAt = new Date(now.getTime() + LOCK_TTL_MS);
|
|
102
164
|
|
|
@@ -12,6 +12,7 @@ import type { IAccessControl } from '../../../access/access-control.interface.js
|
|
|
12
12
|
import { GitService } from '../git.service.js';
|
|
13
13
|
import {
|
|
14
14
|
BranchAuthorshipError,
|
|
15
|
+
BranchDeleteRefusedError,
|
|
15
16
|
ProtectedBranchError,
|
|
16
17
|
WorkflowValidationError,
|
|
17
18
|
} from '../../../../shared/domain-errors.js';
|
|
@@ -182,6 +183,57 @@ describe('GitService.deleteBranch — authorship + remote delete', () => {
|
|
|
182
183
|
expect(await remoteHasRef(upstream, 'alice/my-draft')).toBe(false);
|
|
183
184
|
});
|
|
184
185
|
|
|
186
|
+
it('when the host refuses the remote deletion, deletes nothing and says so (409, no git output)', async () => {
|
|
187
|
+
const { upstream, repo } = await seedWorkspace(root, workspaceId);
|
|
188
|
+
// The host turns every push away, as GitHub did during its incident.
|
|
189
|
+
const hook = path.join(upstream, 'hooks', 'pre-receive');
|
|
190
|
+
await fs.writeFile(hook, '#!/bin/sh\necho "Internal Server Error" >&2\nexit 1\n');
|
|
191
|
+
await fs.chmod(hook, 0o755);
|
|
192
|
+
const svc = makeService();
|
|
193
|
+
|
|
194
|
+
const err = await svc.deleteBranch(workspaceId, 'alice/my-draft', ALICE).catch((e: unknown) => e);
|
|
195
|
+
expect(err).toBeInstanceOf(BranchDeleteRefusedError);
|
|
196
|
+
const refusal = err as BranchDeleteRefusedError;
|
|
197
|
+
expect(refusal.status).toBe(409);
|
|
198
|
+
expect(refusal.message).toBe(
|
|
199
|
+
'The repository host refused to delete "alice/my-draft"; it is still there. Try again later.',
|
|
200
|
+
);
|
|
201
|
+
expect(JSON.stringify({ ...refusal.payload, error: refusal.message })).not.toMatch(
|
|
202
|
+
/Internal Server Error|remote rejected|pre-receive|upstream\.git/,
|
|
203
|
+
);
|
|
204
|
+
// Still there — remotely AND locally.
|
|
205
|
+
expect(await remoteHasRef(upstream, 'alice/my-draft')).toBe(true);
|
|
206
|
+
expect(
|
|
207
|
+
await gitOut(repo, ['for-each-ref', 'refs/heads/alice/my-draft', '--format', '%(refname)']),
|
|
208
|
+
).toBe('refs/heads/alice/my-draft');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('when the host cannot be reached, the local branch stays too', async () => {
|
|
212
|
+
const { repo } = await seedWorkspace(root, workspaceId);
|
|
213
|
+
await runGit(repo, ['remote', 'set-url', 'origin', path.join(root, 'gone.git')]);
|
|
214
|
+
const svc = makeService();
|
|
215
|
+
|
|
216
|
+
await expect(svc.deleteBranch(workspaceId, 'alice/my-draft', ALICE)).rejects.toBeInstanceOf(
|
|
217
|
+
BranchDeleteRefusedError,
|
|
218
|
+
);
|
|
219
|
+
expect(
|
|
220
|
+
await gitOut(repo, ['for-each-ref', 'refs/heads/alice/my-draft', '--format', '%(refname)']),
|
|
221
|
+
).toBe('refs/heads/alice/my-draft');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('deletes the remote branch even when this clone has no tracking ref for it (pushed from elsewhere since the last fetch)', async () => {
|
|
225
|
+
const { upstream, repo } = await seedWorkspace(root, workspaceId);
|
|
226
|
+
await runGit(repo, ['update-ref', '-d', 'refs/remotes/origin/alice/my-draft']);
|
|
227
|
+
const svc = makeService();
|
|
228
|
+
|
|
229
|
+
await svc.deleteBranch(workspaceId, 'alice/my-draft', ALICE);
|
|
230
|
+
|
|
231
|
+
expect(await remoteHasRef(upstream, 'alice/my-draft')).toBe(false);
|
|
232
|
+
expect(
|
|
233
|
+
await gitOut(repo, ['for-each-ref', 'refs/heads/alice/my-draft', '--format', '%(refname)']),
|
|
234
|
+
).toBe('');
|
|
235
|
+
});
|
|
236
|
+
|
|
185
237
|
it("refuses when the user isn't the author — leaves the remote ref intact", async () => {
|
|
186
238
|
const { upstream } = await seedWorkspace(root, workspaceId);
|
|
187
239
|
const svc = makeService();
|
|
@@ -318,3 +370,115 @@ describe('GitService.deleteBranch — authorship + remote delete', () => {
|
|
|
318
370
|
).rejects.toBeInstanceOf(ProtectedBranchError);
|
|
319
371
|
});
|
|
320
372
|
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The pieces an agent's `delete_branch` and the leftover cleanup decide with:
|
|
376
|
+
* where a branch stands against the default branch, whether a commit is
|
|
377
|
+
* contained in another, and a delete leased on the tip the checks saw.
|
|
378
|
+
*/
|
|
379
|
+
describe('GitService — branch state for a checked delete', () => {
|
|
380
|
+
let root: string;
|
|
381
|
+
const workspaceId = 'target-company-state';
|
|
382
|
+
|
|
383
|
+
beforeEach(async () => {
|
|
384
|
+
root = await mkTmpRoot();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
afterEach(async () => {
|
|
388
|
+
await fs.rm(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
function makeService(): GitService {
|
|
392
|
+
return new GitService(
|
|
393
|
+
stubWorkspaceService(workspaceId, path.join(root, workspaceId)),
|
|
394
|
+
new WorkflowHooks(),
|
|
395
|
+
testKbContext(),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Push `n` commits onto `branch` from a separate clone, as a colleague would. */
|
|
400
|
+
async function pushCommits(upstream: string, branch: string, n: number): Promise<string> {
|
|
401
|
+
const other = path.join(root, `.other-${Math.random().toString(36).slice(2)}`);
|
|
402
|
+
await runGit(root, ['clone', '-b', branch, upstream, other]);
|
|
403
|
+
for (let i = 0; i < n; i++) await runGit(other, ['commit', '--allow-empty', '-m', `work ${i}`]);
|
|
404
|
+
await runGit(other, ['push', 'origin', branch]);
|
|
405
|
+
return gitOut(other, ['rev-parse', 'HEAD']);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
it('counts the commits not on the default branch, and reads the tip', async () => {
|
|
409
|
+
const { upstream, repo } = await seedWorkspace(root, workspaceId);
|
|
410
|
+
const tip = await pushCommits(upstream, 'bob/other-draft', 3);
|
|
411
|
+
await runGit(repo, ['fetch', 'origin']);
|
|
412
|
+
const svc = makeService();
|
|
413
|
+
|
|
414
|
+
const state = await svc.branchState(workspaceId, 'bob/other-draft', 'target-company-state');
|
|
415
|
+
expect(state).toEqual({ exists: true, lastCommit: tip, unmergedCommits: 3 });
|
|
416
|
+
|
|
417
|
+
// A branch with nothing of its own counts none.
|
|
418
|
+
const merged = await svc.branchState(workspaceId, 'alice/my-draft', 'target-company-state');
|
|
419
|
+
expect(merged.exists).toBe(true);
|
|
420
|
+
expect(merged.unmergedCommits).toBe(0);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('a name that is not a branch does not exist', async () => {
|
|
424
|
+
await seedWorkspace(root, workspaceId);
|
|
425
|
+
const state = await makeService().branchState(workspaceId, 'alice/no-such-branch', 'target-company-state');
|
|
426
|
+
expect(state).toEqual({ exists: false, lastCommit: null, unmergedCommits: 0 });
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('isAncestor: yes for a contained commit, no for one that is not, and throws for an unknown commit', async () => {
|
|
430
|
+
const { upstream, repo } = await seedWorkspace(root, workspaceId);
|
|
431
|
+
const base = await gitOut(repo, ['rev-parse', 'HEAD']);
|
|
432
|
+
const tip = await pushCommits(upstream, 'bob/other-draft', 1);
|
|
433
|
+
await runGit(repo, ['fetch', 'origin']);
|
|
434
|
+
const svc = makeService();
|
|
435
|
+
|
|
436
|
+
expect(await svc.isAncestor(workspaceId, base, tip)).toBe(true);
|
|
437
|
+
expect(await svc.isAncestor(workspaceId, tip, base)).toBe(false);
|
|
438
|
+
await expect(svc.isAncestor(workspaceId, 'f'.repeat(40), base)).rejects.toThrow();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('removes the branch from the shared repository even when this clone never fetched it, and answers its tip', async () => {
|
|
442
|
+
const { upstream, repo } = await seedWorkspace(root, workspaceId);
|
|
443
|
+
// Created from elsewhere after this clone was made: no tracking ref here.
|
|
444
|
+
const other = path.join(root, '.creator');
|
|
445
|
+
await runGit(root, ['clone', upstream, other]);
|
|
446
|
+
await runGit(other, ['checkout', '-b', 'alice/new-elsewhere']);
|
|
447
|
+
await runGit(other, ['commit', '--allow-empty', '-m', 'elsewhere']);
|
|
448
|
+
await runGit(other, ['push', 'origin', 'alice/new-elsewhere']);
|
|
449
|
+
const tip = await gitOut(other, ['rev-parse', 'HEAD']);
|
|
450
|
+
expect(await gitOut(repo, ['for-each-ref', 'refs/remotes/origin/alice/new-elsewhere'])).toBe('');
|
|
451
|
+
|
|
452
|
+
const result = await makeService().deleteBranch(workspaceId, 'alice/new-elsewhere', ALICE);
|
|
453
|
+
expect(result).toEqual({ lastCommit: tip });
|
|
454
|
+
expect(await remoteHasRef(upstream, 'alice/new-elsewhere')).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('a delete leased on a tip refuses a branch that moved since, and leaves it in place', async () => {
|
|
458
|
+
const { upstream } = await seedWorkspace(root, workspaceId);
|
|
459
|
+
const seen = await gitOut(upstream, ['rev-parse', 'refs/heads/alice/my-draft']);
|
|
460
|
+
// A colleague pushes after the checks were made.
|
|
461
|
+
await pushCommits(upstream, 'alice/my-draft', 1);
|
|
462
|
+
|
|
463
|
+
const err = await makeService()
|
|
464
|
+
.deleteBranch(workspaceId, 'alice/my-draft', ALICE, { expectTip: seen })
|
|
465
|
+
.catch((e: unknown) => e);
|
|
466
|
+
expect(err).toBeInstanceOf(BranchDeleteRefusedError);
|
|
467
|
+
expect(await remoteHasRef(upstream, 'alice/my-draft')).toBe(true);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('a delete leased on the current tip goes through', async () => {
|
|
471
|
+
const { upstream } = await seedWorkspace(root, workspaceId);
|
|
472
|
+
const seen = await gitOut(upstream, ['rev-parse', 'refs/heads/alice/my-draft']);
|
|
473
|
+
const result = await makeService().deleteBranch(workspaceId, 'alice/my-draft', ALICE, { expectTip: seen });
|
|
474
|
+
expect(result.lastCommit).toBe(seen);
|
|
475
|
+
expect(await remoteHasRef(upstream, 'alice/my-draft')).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('mayDeleteBranch: the author yes, anyone else without the Admin role no', async () => {
|
|
479
|
+
await seedWorkspace(root, workspaceId);
|
|
480
|
+
const svc = makeService();
|
|
481
|
+
expect(await svc.mayDeleteBranch(workspaceId, 'alice/my-draft', ALICE)).toBe(true);
|
|
482
|
+
expect(await svc.mayDeleteBranch(workspaceId, 'bob/other-draft', ALICE)).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
@@ -352,6 +352,39 @@ describe('GitService.pull', () => {
|
|
|
352
352
|
await runGit(repo, ['push', 'origin', 'target-company-state']);
|
|
353
353
|
await expect(svc.hasUnpushedCommits(workspaceId)).resolves.toBe(false);
|
|
354
354
|
});
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The question the roles.yaml preservation asks before pushing a restore it
|
|
358
|
+
* finds already committed: not "is there unpushed work" but "would the push
|
|
359
|
+
* fast-forward origin to HEAD". A clone behind origin answers no, and one
|
|
360
|
+
* whose tracking ref git cannot even resolve answers with an error — the
|
|
361
|
+
* guard that asks must fail closed on it, so the probe may not swallow it.
|
|
362
|
+
*/
|
|
363
|
+
it('headContainsOrigin: true in sync and ahead, false behind origin, an error without a tracking ref', async () => {
|
|
364
|
+
const { repo } = await seedWorkspace(root, workspaceId);
|
|
365
|
+
const svc = new GitService(
|
|
366
|
+
stubWorkspaceService({ [workspaceId]: path.join(root, workspaceId) }),
|
|
367
|
+
stubWorkflowHooks(),
|
|
368
|
+
testKbContext(),
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
await expect(svc.headContainsOrigin(workspaceId)).resolves.toBe(true);
|
|
372
|
+
|
|
373
|
+
// Ahead: a local commit on top of origin's tip.
|
|
374
|
+
await fs.writeFile(path.join(repo, 'new.txt'), 'x\n');
|
|
375
|
+
await runGit(repo, ['add', '.']);
|
|
376
|
+
await runGit(repo, ['commit', '-m', 'unpushed']);
|
|
377
|
+
await expect(svc.headContainsOrigin(workspaceId)).resolves.toBe(true);
|
|
378
|
+
|
|
379
|
+
// Behind: publish it, then step the clone back before it.
|
|
380
|
+
await runGit(repo, ['push', 'origin', 'target-company-state']);
|
|
381
|
+
await runGit(repo, ['reset', '--hard', 'HEAD~1']);
|
|
382
|
+
await expect(svc.headContainsOrigin(workspaceId)).resolves.toBe(false);
|
|
383
|
+
|
|
384
|
+
// No tracking ref at all: not "false", an error.
|
|
385
|
+
await runGit(repo, ['checkout', '-b', 'never-pushed']);
|
|
386
|
+
await expect(svc.headContainsOrigin(workspaceId)).rejects.toThrow();
|
|
387
|
+
});
|
|
355
388
|
});
|
|
356
389
|
|
|
357
390
|
/**
|
|
@@ -411,7 +444,10 @@ describe('GitService.pull — an unpushed merge commit', () => {
|
|
|
411
444
|
const workspaceDir = path.join(root, workspaceId);
|
|
412
445
|
const repo = path.join(workspaceDir, 'knowledge-base');
|
|
413
446
|
await fs.mkdir(workspaceDir, { recursive: true });
|
|
414
|
-
|
|
447
|
+
// `core.autocrlf=false` on the clone itself, not only afterwards: a
|
|
448
|
+
// Windows git would otherwise check the files out with CRLF, and the
|
|
449
|
+
// rebase's autostash of that "dirty" tree conflicts on reapply.
|
|
450
|
+
await runGit(root, ['-c', 'core.autocrlf=false', 'clone', '-b', 'alice/deal', upstream, repo]);
|
|
415
451
|
await runGit(repo, ['config', 'user.email', 'workspace@bevel.test']);
|
|
416
452
|
await runGit(repo, ['config', 'user.name', 'bevel Workspace']);
|
|
417
453
|
await runGit(repo, ['config', 'core.autocrlf', 'false']);
|
|
@@ -456,6 +492,17 @@ describe('GitService.pull — an unpushed merge commit', () => {
|
|
|
456
492
|
expect(contains).toBe(false);
|
|
457
493
|
});
|
|
458
494
|
|
|
495
|
+
it('survives the background sync, which may reach the branch before any push does', async () => {
|
|
496
|
+
const { repo, targetTip } = await seedUnpushedMerge();
|
|
497
|
+
await svcFor().syncFromRemote(workspaceId);
|
|
498
|
+
|
|
499
|
+
expect(await gitOut(repo, ['rev-list', '--count', '--merges', 'HEAD'])).toBe('1');
|
|
500
|
+
const contains = await gitOut(repo, [
|
|
501
|
+
'merge-base', '--is-ancestor', targetTip, 'HEAD',
|
|
502
|
+
]).then(() => true, () => false);
|
|
503
|
+
expect(contains).toBe(true);
|
|
504
|
+
});
|
|
505
|
+
|
|
459
506
|
it('survives `preserveMerges`, and the branch still contains the target', async () => {
|
|
460
507
|
const { repo, targetTip } = await seedUnpushedMerge();
|
|
461
508
|
await svcFor().pull(workspaceId, { preserveMerges: true });
|