@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
|
@@ -11,9 +11,16 @@ import type { IReviewWorkflowService } from '../review-workflow/review-workflow.
|
|
|
11
11
|
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
12
12
|
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
13
13
|
import { FileLockService } from '../file-lock.service.js';
|
|
14
|
-
import { PendingCommitsService } from '../pending-commits.service.js';
|
|
14
|
+
import { PendingCommitsService, type PendingCommit } from '../pending-commits.service.js';
|
|
15
|
+
import { PendingCommitsWorker } from '../pending-commits.worker.js';
|
|
16
|
+
import { HttpCommunicationProtocol } from '@utcp/http';
|
|
15
17
|
import { WorkflowService } from '../workflow.service.js';
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
PullRebaseConflictError,
|
|
20
|
+
PushNeedsAgentResolutionError,
|
|
21
|
+
WorkflowDomainError,
|
|
22
|
+
} from '../../../shared/domain-errors.js';
|
|
23
|
+
import type { WorkflowEventBus } from '../event-bus.js';
|
|
17
24
|
import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
18
25
|
import type { Database } from '../../database/connection.js';
|
|
19
26
|
import { openChangeGate } from '../../../__tests__/open-change-gate.js';
|
|
@@ -121,6 +128,7 @@ function makePendingCommits(): PendingCommitsService {
|
|
|
121
128
|
markNeedsAttention: vi.fn().mockResolvedValue(undefined),
|
|
122
129
|
listNeedsAttention: vi.fn().mockResolvedValue([]),
|
|
123
130
|
countPending: vi.fn().mockResolvedValue(0),
|
|
131
|
+
queuedOnBranch: vi.fn().mockResolvedValue({ queued: 0, stuck: 0, needsAttention: null }),
|
|
124
132
|
hasAnyForWorkspace: vi.fn().mockResolvedValue(false),
|
|
125
133
|
startupReconcile: vi.fn().mockResolvedValue(undefined),
|
|
126
134
|
} as unknown as PendingCommitsService;
|
|
@@ -182,7 +190,7 @@ function makeGit(): GitService {
|
|
|
182
190
|
listBranches: vi.fn(),
|
|
183
191
|
createBranch: vi.fn(),
|
|
184
192
|
switchBranch: vi.fn(),
|
|
185
|
-
deleteBranch: vi.fn(),
|
|
193
|
+
deleteBranch: vi.fn(async () => ({ lastCommit: null })),
|
|
186
194
|
forkCurrentToDraft: vi.fn(),
|
|
187
195
|
discardChanges: vi.fn(),
|
|
188
196
|
commit: vi.fn(),
|
|
@@ -529,8 +537,8 @@ describe('WorkflowService — file lock delegation', () => {
|
|
|
529
537
|
let releaseFirst!: () => void;
|
|
530
538
|
const gate = new Promise<void>((resolve) => { releaseFirst = resolve; });
|
|
531
539
|
vi.mocked(git.deleteBranch)
|
|
532
|
-
.mockImplementationOnce(async () => { order.push('first:start'); await gate; order.push('first:end'); })
|
|
533
|
-
.mockImplementationOnce(async () => { order.push('second'); });
|
|
540
|
+
.mockImplementationOnce(async () => { order.push('first:start'); await gate; order.push('first:end'); return { lastCommit: null }; })
|
|
541
|
+
.mockImplementationOnce(async () => { order.push('second'); return { lastCommit: null }; });
|
|
534
542
|
const svc = new WorkflowService(makeDb(), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), testKbContext(), openChangeGate());
|
|
535
543
|
const first = svc.deleteBranch('w1', 'feat/x', makeUser());
|
|
536
544
|
const second = svc.deleteBranch('w1', 'feat/x', makeUser());
|
|
@@ -581,6 +589,7 @@ describe('WorkflowService — revertChangeRequestFile / closeEmptyChangeRequest'
|
|
|
581
589
|
canWriteAtRef?: boolean | null;
|
|
582
590
|
prState?: string;
|
|
583
591
|
updateRows?: unknown[];
|
|
592
|
+
events?: WorkflowEventBus;
|
|
584
593
|
} = {}) {
|
|
585
594
|
const git = opts.git ?? makeRevertGit();
|
|
586
595
|
const prs = makePrs();
|
|
@@ -607,7 +616,7 @@ describe('WorkflowService — revertChangeRequestFile / closeEmptyChangeRequest'
|
|
|
607
616
|
returning: vi.fn(async () => updateRows),
|
|
608
617
|
});
|
|
609
618
|
const db = chain as unknown as Database;
|
|
610
|
-
const svc = new WorkflowService(db, git, prs, makeReviewWorkflow(), makeWorkspaceService(), access, fileLocks, makePendingCommits(), testKbContext(), openChangeGate());
|
|
619
|
+
const svc = new WorkflowService(db, git, prs, makeReviewWorkflow(), makeWorkspaceService(), access, fileLocks, makePendingCommits(), testKbContext(), openChangeGate(), opts.events);
|
|
611
620
|
return { svc, git, prs, access, fileLocks, db: chain };
|
|
612
621
|
}
|
|
613
622
|
|
|
@@ -744,6 +753,130 @@ describe('WorkflowService — revertChangeRequestFile / closeEmptyChangeRequest'
|
|
|
744
753
|
expect(fileLocks.release).toHaveBeenCalledTimes(2);
|
|
745
754
|
});
|
|
746
755
|
|
|
756
|
+
describe('when the repository host refuses the push', () => {
|
|
757
|
+
// What GitHub answered during its 2026-10-07 incident, credential and all.
|
|
758
|
+
const REFUSED = new Error(
|
|
759
|
+
"git push failed: remote: Internal Server Error\nTo https://x-access-token:ghp_abc123@github.com/acme/kb.git\n ! [remote rejected] ali/x -> ali/x (Internal Server Error)",
|
|
760
|
+
);
|
|
761
|
+
const kindsOf = (emit: ReturnType<typeof vi.fn>) =>
|
|
762
|
+
emit.mock.calls
|
|
763
|
+
.map((c) => (c[0] as { kind: string }).kind)
|
|
764
|
+
.filter((k) => k.startsWith('git-sync-'));
|
|
765
|
+
|
|
766
|
+
it('keeps the revert committed locally, answers 409 with the saved-locally sentence, and raises the banner', async () => {
|
|
767
|
+
const emit = vi.fn();
|
|
768
|
+
const git = makeRevertGit({
|
|
769
|
+
push: vi.fn().mockRejectedValue(REFUSED),
|
|
770
|
+
// A refused push is not a divergence — but "rejected" reads like one,
|
|
771
|
+
// so the cooperative pull runs first; it changes nothing here.
|
|
772
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: false }),
|
|
773
|
+
});
|
|
774
|
+
const { svc, fileLocks, prs, db } = makeHarness({ git, events: { emit } as unknown as WorkflowEventBus });
|
|
775
|
+
|
|
776
|
+
const err = await svc.revertChangeRequestFile(7, makeUser(), 'Docs/a.md').catch((e: unknown) => e);
|
|
777
|
+
expect(err).toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
778
|
+
const refusal = err as PushNeedsAgentResolutionError;
|
|
779
|
+
expect(refusal.status).toBe(409);
|
|
780
|
+
expect(refusal.message).toContain('Saved locally on "ali/x"');
|
|
781
|
+
expect(refusal.message).toContain('the repository host refused the push');
|
|
782
|
+
// The browser receives message + payload: neither carries git output.
|
|
783
|
+
const body = JSON.stringify({ ...refusal.payload, error: refusal.message });
|
|
784
|
+
expect(body).not.toMatch(/Internal Server Error|remote rejected|ghp_|github\.com/);
|
|
785
|
+
|
|
786
|
+
// The local result stays: restored, committed, locks dropped, caches fresh.
|
|
787
|
+
expect(git.restorePathFromRef).toHaveBeenCalledWith('ali%2Fx', 'mb-sha', 'Docs/a.md');
|
|
788
|
+
expect(git.commitFile).toHaveBeenCalled();
|
|
789
|
+
expect(fileLocks.release).toHaveBeenCalled();
|
|
790
|
+
expect(prs.invalidateDetailCache).toHaveBeenCalledWith(7);
|
|
791
|
+
// Nothing is closed on the strength of a branch the host never received:
|
|
792
|
+
// the remaining files (read from the published refs) are not even asked.
|
|
793
|
+
expect(git.changedPathsForPr).toHaveBeenCalledTimes(1);
|
|
794
|
+
// Nor is a recorded apply failure erased: the published head it
|
|
795
|
+
// describes did not move.
|
|
796
|
+
expect(db.update).not.toHaveBeenCalled();
|
|
797
|
+
|
|
798
|
+
// The banner, on the request's source branch, in words — not git's.
|
|
799
|
+
const failed = emit.mock.calls.map((c) => c[0] as Record<string, unknown>).find((e) => e.kind === 'git-sync-failed');
|
|
800
|
+
expect(failed).toMatchObject({ workspaceId: 'ali/x', branch: 'ali/x' });
|
|
801
|
+
expect(String(failed?.reason)).not.toMatch(/Internal Server Error|remote|ghp_/);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('clears the banner on the next push of that branch that lands', async () => {
|
|
805
|
+
const emit = vi.fn();
|
|
806
|
+
const push = vi.fn().mockRejectedValueOnce(REFUSED).mockRejectedValueOnce(REFUSED).mockResolvedValue(undefined);
|
|
807
|
+
const git = makeRevertGit({ push, pull: vi.fn().mockResolvedValue({ treeChanged: false }) });
|
|
808
|
+
const { svc } = makeHarness({ git, events: { emit } as unknown as WorkflowEventBus });
|
|
809
|
+
|
|
810
|
+
await expect(svc.revertChangeRequestFile(7, makeUser(), 'Docs/a.md')).rejects.toBeInstanceOf(
|
|
811
|
+
PushNeedsAgentResolutionError,
|
|
812
|
+
);
|
|
813
|
+
// The host is back; the next push of the branch carries the revert.
|
|
814
|
+
await svc.shareCurrentBranch('ali%2Fx', makeUser());
|
|
815
|
+
expect(kindsOf(emit)).toEqual(['git-sync-failed', 'git-sync-recovered']);
|
|
816
|
+
expect(emit).toHaveBeenLastCalledWith(
|
|
817
|
+
expect.objectContaining({ kind: 'git-sync-recovered', workspaceId: 'ali/x' }),
|
|
818
|
+
);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it('a divergence whose retry push the host then refuses is answered as a refusal, not a divergence', async () => {
|
|
822
|
+
const push = vi
|
|
823
|
+
.fn()
|
|
824
|
+
.mockRejectedValueOnce(new Error('! [rejected] ali/x -> ali/x (non-fast-forward)'))
|
|
825
|
+
.mockRejectedValueOnce(REFUSED);
|
|
826
|
+
const git = makeRevertGit({ push, pull: vi.fn().mockResolvedValue({ treeChanged: true }) });
|
|
827
|
+
const { svc } = makeHarness({ git });
|
|
828
|
+
|
|
829
|
+
const err = await svc.revertChangeRequestFile(7, makeUser(), 'Docs/a.md').catch((e: unknown) => e);
|
|
830
|
+
expect(err).toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
831
|
+
expect((err as PushNeedsAgentResolutionError).message).toContain('the repository host refused the push');
|
|
832
|
+
expect(push).toHaveBeenCalledTimes(2);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
it('a recovery whose PULL the host refuses says the credentials cannot reach the repository, not "push"', async () => {
|
|
836
|
+
const emit = vi.fn();
|
|
837
|
+
const push = vi.fn().mockRejectedValue(new Error('! [rejected] ali/x -> ali/x (non-fast-forward)'));
|
|
838
|
+
const pull = vi
|
|
839
|
+
.fn()
|
|
840
|
+
// The freshen pull is best-effort; the recovery pull is the one that counts.
|
|
841
|
+
.mockResolvedValueOnce({ treeChanged: false })
|
|
842
|
+
.mockRejectedValue(
|
|
843
|
+
new Error("fatal: unable to access 'https://github.com/acme/kb.git/': The requested URL returned error: 403"),
|
|
844
|
+
);
|
|
845
|
+
const git = makeRevertGit({ push, pull });
|
|
846
|
+
const { svc } = makeHarness({ git, events: { emit } as unknown as WorkflowEventBus });
|
|
847
|
+
|
|
848
|
+
await expect(svc.revertChangeRequestFile(7, makeUser(), 'Docs/a.md')).rejects.toBeInstanceOf(
|
|
849
|
+
PushNeedsAgentResolutionError,
|
|
850
|
+
);
|
|
851
|
+
const failed = emit.mock.calls.map((c) => c[0] as Record<string, unknown>).find((e) => e.kind === 'git-sync-failed');
|
|
852
|
+
expect(failed?.reason).toBe(
|
|
853
|
+
"The repository host did not give this server's credentials access to this repository.",
|
|
854
|
+
);
|
|
855
|
+
expect(push).toHaveBeenCalledTimes(1);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it('a non-fast-forward still takes the cooperative pull-rebase, then lands', async () => {
|
|
859
|
+
const emit = vi.fn();
|
|
860
|
+
const push = vi
|
|
861
|
+
.fn()
|
|
862
|
+
.mockRejectedValueOnce(new Error('! [rejected] ali/x -> ali/x (non-fast-forward)'))
|
|
863
|
+
.mockResolvedValue(undefined);
|
|
864
|
+
const pull = vi.fn().mockResolvedValue({ treeChanged: true });
|
|
865
|
+
const git = makeRevertGit({ push, pull });
|
|
866
|
+
const { svc } = makeHarness({ git, events: { emit } as unknown as WorkflowEventBus });
|
|
867
|
+
|
|
868
|
+
await expect(svc.revertChangeRequestFile(7, makeUser(), 'Docs/a.md')).resolves.toMatchObject({ closed: false });
|
|
869
|
+
expect(push).toHaveBeenCalledTimes(2);
|
|
870
|
+
// Once to freshen the checkout, once as the cooperative recovery —
|
|
871
|
+
// both replay merges as merges, so a merge a refused open or update
|
|
872
|
+
// stranded on the branch is not flattened before this push publishes it.
|
|
873
|
+
expect(pull).toHaveBeenCalledTimes(2);
|
|
874
|
+
expect(pull).toHaveBeenNthCalledWith(1, 'ali%2Fx', { preserveMerges: true });
|
|
875
|
+
expect(pull).toHaveBeenNthCalledWith(2, 'ali%2Fx', { preserveMerges: true });
|
|
876
|
+
expect(kindsOf(emit)).not.toContain('git-sync-failed');
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
747
880
|
it('never reverts a placeholder when that would leave the folder with nothing', async () => {
|
|
748
881
|
const git = makeRevertGit({
|
|
749
882
|
changedPathsForPr: vi
|
|
@@ -1019,7 +1152,7 @@ describe('WorkflowService.mergeBranch — an agent merges branches, never an ope
|
|
|
1019
1152
|
// being published.
|
|
1020
1153
|
const TARGET_TIP = 'target-tip-sha';
|
|
1021
1154
|
|
|
1022
|
-
function harness(opts: { canWrite?: Map<string, boolean> | null } = {}) {
|
|
1155
|
+
function harness(opts: { canWrite?: Map<string, boolean> | null; pending?: PendingCommitsService } = {}) {
|
|
1023
1156
|
const git = makeGit();
|
|
1024
1157
|
(git as unknown as Record<string, unknown>).remoteBranchExists = vi.fn().mockResolvedValue(true);
|
|
1025
1158
|
(git as unknown as Record<string, unknown>).hasUnpushedCommits = vi.fn().mockResolvedValue(false);
|
|
@@ -1062,15 +1195,17 @@ describe('WorkflowService.mergeBranch — an agent merges branches, never an ope
|
|
|
1062
1195
|
).changedPathsForPr(workspaceId, target, source, { forAccessCheck: true });
|
|
1063
1196
|
await mergeOpts.authorize({ sha: TARGET_TIP, changedPaths });
|
|
1064
1197
|
}
|
|
1065
|
-
|
|
1198
|
+
// The real contract: `mergeCommit` is the commit this merge made, null when it made none.
|
|
1199
|
+
return { kind: 'merged', sha: 'merge-sha', mergeCommit: 'merge-sha' };
|
|
1066
1200
|
},
|
|
1067
1201
|
);
|
|
1068
1202
|
const access = makeAccessControl();
|
|
1069
1203
|
(access.canWriteBatchAtRef as ReturnType<typeof vi.fn>).mockResolvedValue(opts.canWrite === undefined ? null : opts.canWrite);
|
|
1070
1204
|
const workspaceService = makeWorkspaceService();
|
|
1071
|
-
const
|
|
1205
|
+
const pending = opts.pending ?? makePendingCommits();
|
|
1206
|
+
const svc = new WorkflowService(makeDb([OPEN_REQUEST]), git, makePrs(), makeReviewWorkflow(), workspaceService, access, makeFileLockService(), pending, testKbContext(), openChangeGate());
|
|
1072
1207
|
const merge = (git as unknown as { mergeChangeRequest: ReturnType<typeof vi.fn> }).mergeChangeRequest;
|
|
1073
|
-
return { svc, git, access, merge };
|
|
1208
|
+
return { svc, git, access, merge, pending };
|
|
1074
1209
|
}
|
|
1075
1210
|
|
|
1076
1211
|
it('refuses to merge a source into the target of its open change request, naming the request', async () => {
|
|
@@ -1187,3 +1322,326 @@ describe('WorkflowService.mergeBranch — an agent merges branches, never an ope
|
|
|
1187
1322
|
expect(merge.mock.calls[0][5]).toMatchObject({ requireCleanTarget: true });
|
|
1188
1323
|
});
|
|
1189
1324
|
});
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Opening a request pushes its source after the auto-merge. A push the host
|
|
1328
|
+
* refuses must not undo the open: the merge is committed locally, the request
|
|
1329
|
+
* is a DB row, and the next push of the branch carries the commits.
|
|
1330
|
+
*/
|
|
1331
|
+
describe('WorkflowService.openChangeRequest — when the repository host refuses the push', () => {
|
|
1332
|
+
const INPUT = { sourceBranch: 'ali/x', targetBranch: 'main', title: 'Tidy the glossary' };
|
|
1333
|
+
|
|
1334
|
+
function makeOpenHarness(push: ReturnType<typeof vi.fn>) {
|
|
1335
|
+
const emit = vi.fn();
|
|
1336
|
+
const git = Object.assign(makeGit(), {
|
|
1337
|
+
mergeFromOrigin: vi.fn().mockResolvedValue({ kind: 'clean', alreadyUpToDate: false }),
|
|
1338
|
+
push,
|
|
1339
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: false }),
|
|
1340
|
+
}) as unknown as GitService;
|
|
1341
|
+
const prs = makePrs();
|
|
1342
|
+
(prs.listOpenPrs as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
1343
|
+
(prs.getPrDetail as ReturnType<typeof vi.fn>).mockResolvedValue({ number: 12, branch: 'ali/x', base: 'main' });
|
|
1344
|
+
const values = vi.fn(() => ({ returning: vi.fn(async () => [{ number: 12 }]) }));
|
|
1345
|
+
const db = { insert: vi.fn(() => ({ values })) } as unknown as Database;
|
|
1346
|
+
const svc = new WorkflowService(
|
|
1347
|
+
db, git, prs, makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(),
|
|
1348
|
+
makeFileLockService(), makePendingCommits(), testKbContext(), openChangeGate(),
|
|
1349
|
+
{ emit } as unknown as WorkflowEventBus,
|
|
1350
|
+
);
|
|
1351
|
+
return { svc, git, prs, values, emit };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
it('creates the request, then answers 409 with the saved-locally sentence and raises the banner', async () => {
|
|
1355
|
+
const refused = new Error(
|
|
1356
|
+
'git push failed: remote: Internal Server Error\n ! [remote rejected] ali/x -> ali/x (Internal Server Error)',
|
|
1357
|
+
);
|
|
1358
|
+
const h = makeOpenHarness(vi.fn().mockRejectedValue(refused));
|
|
1359
|
+
|
|
1360
|
+
const err = await h.svc.openChangeRequest('ali%2Fx', makeUser(), INPUT).catch((e: unknown) => e);
|
|
1361
|
+
expect(err).toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
1362
|
+
const refusal = err as PushNeedsAgentResolutionError;
|
|
1363
|
+
expect(refusal.status).toBe(409);
|
|
1364
|
+
expect(refusal.message).toContain('Saved locally on "ali/x"');
|
|
1365
|
+
expect(JSON.stringify({ ...refusal.payload, error: refusal.message })).not.toMatch(/Internal Server Error|remote rejected/);
|
|
1366
|
+
|
|
1367
|
+
// The request exists — the row was inserted and announced.
|
|
1368
|
+
expect(h.values).toHaveBeenCalledWith(expect.objectContaining({ sourceBranch: 'ali/x', targetBranch: 'main' }));
|
|
1369
|
+
expect(h.prs.invalidateDetailCache).toHaveBeenCalledWith(12);
|
|
1370
|
+
const kinds = h.emit.mock.calls.map((c) => (c[0] as { kind: string }).kind);
|
|
1371
|
+
expect(kinds).toContain('change-request-opened');
|
|
1372
|
+
expect(h.emit).toHaveBeenCalledWith(
|
|
1373
|
+
expect.objectContaining({ kind: 'git-sync-failed', workspaceId: 'ali/x', branch: 'ali/x' }),
|
|
1374
|
+
);
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
it('a push that lands answers the detail as before, with no banner', async () => {
|
|
1378
|
+
const h = makeOpenHarness(vi.fn().mockResolvedValue(undefined));
|
|
1379
|
+
await expect(h.svc.openChangeRequest('ali%2Fx', makeUser(), INPUT)).resolves.toMatchObject({ number: 12 });
|
|
1380
|
+
expect(h.git.push).toHaveBeenCalledWith('ali%2Fx', expect.objectContaining({ email: 'alice@example.com' }));
|
|
1381
|
+
const kinds = h.emit.mock.calls.map((c) => (c[0] as { kind: string }).kind);
|
|
1382
|
+
expect(kinds).not.toContain('git-sync-failed');
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
it('a non-fast-forward recovers with a merge-preserving pull, and the request opens', async () => {
|
|
1386
|
+
const push = vi
|
|
1387
|
+
.fn()
|
|
1388
|
+
.mockRejectedValueOnce(new Error('! [rejected] ali/x -> ali/x (fetch first)'))
|
|
1389
|
+
.mockResolvedValue(undefined);
|
|
1390
|
+
const h = makeOpenHarness(push);
|
|
1391
|
+
await expect(h.svc.openChangeRequest('ali%2Fx', makeUser(), INPUT)).resolves.toMatchObject({ number: 12 });
|
|
1392
|
+
expect(h.git.pull).toHaveBeenCalledWith('ali%2Fx', { preserveMerges: true });
|
|
1393
|
+
expect(push).toHaveBeenCalledTimes(2);
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Writes are committed asynchronously: a write tool answers once the bytes are
|
|
1399
|
+
* on disk, and the pending-commits worker commits and pushes them seconds
|
|
1400
|
+
* later. `mergeBranch` merges what the REMOTE holds of the source, so it waits
|
|
1401
|
+
* for the source's queue first — bounded, outside the lifecycle locks — and
|
|
1402
|
+
* says plainly when there was nothing to merge.
|
|
1403
|
+
*/
|
|
1404
|
+
describe('WorkflowService.mergeBranch — waits for the source branch\'s pending commits', () => {
|
|
1405
|
+
const SOURCE = 'alice/other';
|
|
1406
|
+
const TARGET = 'alice/feat';
|
|
1407
|
+
|
|
1408
|
+
function row(over: Partial<PendingCommit> = {}): PendingCommit {
|
|
1409
|
+
return {
|
|
1410
|
+
id: `row-${Math.random()}`,
|
|
1411
|
+
workspaceId: encodeURIComponent(SOURCE),
|
|
1412
|
+
branch: SOURCE,
|
|
1413
|
+
path: 'knowledge-base/Notes/New.md',
|
|
1414
|
+
authorEmail: 'alice@example.com',
|
|
1415
|
+
authorName: 'Alice',
|
|
1416
|
+
queuedAt: new Date(),
|
|
1417
|
+
status: 'pending',
|
|
1418
|
+
attempts: 0,
|
|
1419
|
+
recoveryAgentRuns: 0,
|
|
1420
|
+
lastAttemptedAt: null,
|
|
1421
|
+
lastError: null,
|
|
1422
|
+
...over,
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* An in-memory `pending_commits` table with the methods the worker and
|
|
1428
|
+
* `mergeBranch` use — the same rules as the SQL: `markSucceeded` deletes the
|
|
1429
|
+
* row, `queuedOnBranch` counts `pending` + `running` and reports the first
|
|
1430
|
+
* `needs_attention` row's message.
|
|
1431
|
+
*/
|
|
1432
|
+
function memoryQueue(rows: PendingCommit[] = []) {
|
|
1433
|
+
const table = [...rows];
|
|
1434
|
+
const service = {
|
|
1435
|
+
enqueue: vi.fn(async (input: { workspaceId: string; branch: string; path: string; authorEmail: string; authorName: string }) => {
|
|
1436
|
+
table.push(row({ ...input }));
|
|
1437
|
+
}),
|
|
1438
|
+
claimNext: vi.fn(async (workspaceId: string) => {
|
|
1439
|
+
const next = table.find((r) => r.workspaceId === workspaceId && r.status === 'pending');
|
|
1440
|
+
if (next) next.status = 'running';
|
|
1441
|
+
return next ?? null;
|
|
1442
|
+
}),
|
|
1443
|
+
hasReadyRow: vi.fn(async (workspaceId: string) => table.some((r) => r.workspaceId === workspaceId && r.status === 'pending')),
|
|
1444
|
+
markSucceeded: vi.fn(async (id: string) => {
|
|
1445
|
+
table.splice(table.findIndex((r) => r.id === id), 1);
|
|
1446
|
+
}),
|
|
1447
|
+
markTransientFailure: vi.fn(async () => undefined),
|
|
1448
|
+
markRecoveryStarted: vi.fn(async () => undefined),
|
|
1449
|
+
markNeedsAttention: vi.fn(async () => undefined),
|
|
1450
|
+
queuedOnBranch: vi.fn(async (branch: string) => {
|
|
1451
|
+
const mine = table.filter((r) => r.branch === branch);
|
|
1452
|
+
const stuck = mine.filter((r) => r.status === 'needs_attention');
|
|
1453
|
+
return {
|
|
1454
|
+
queued: mine.filter((r) => r.status === 'pending' || r.status === 'running').length,
|
|
1455
|
+
stuck: stuck.length,
|
|
1456
|
+
needsAttention: stuck[0] ? stuck[0].lastError : null,
|
|
1457
|
+
};
|
|
1458
|
+
}),
|
|
1459
|
+
};
|
|
1460
|
+
return { service: service as unknown as PendingCommitsService & typeof service, table };
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function harness(pending: PendingCommitsService, wait = { timeoutMs: 20_000, pollMs: 20 }) {
|
|
1464
|
+
const git = makeGit();
|
|
1465
|
+
(git as unknown as Record<string, unknown>).remoteBranchExists = vi.fn().mockResolvedValue(true);
|
|
1466
|
+
const merge = vi.fn(async (): Promise<unknown> => ({ kind: 'merged', sha: 'merge-sha', mergeCommit: 'merge-sha' }));
|
|
1467
|
+
(git as unknown as Record<string, unknown>).mergeChangeRequest = merge;
|
|
1468
|
+
const svc = new WorkflowService(
|
|
1469
|
+
makeDb([{ number: 12, sourceBranch: 'alice/feat', targetBranch: DEFAULT_BRANCH, state: 'open' }]),
|
|
1470
|
+
git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(),
|
|
1471
|
+
pending, testKbContext(), openChangeGate(),
|
|
1472
|
+
);
|
|
1473
|
+
svc.mergeWaitForPendingCommits = wait;
|
|
1474
|
+
return { svc, git, merge };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
it('lands a write still queued on the source: the worker commits it while the merge waits, no retry', async () => {
|
|
1478
|
+
// The shared remote, as `mergeChangeRequest` sees it: what each branch has
|
|
1479
|
+
// been PUSHED with. The write below is on disk, not here yet.
|
|
1480
|
+
const remote = new Map<string, Set<string>>([[SOURCE, new Set()], [TARGET, new Set()]]);
|
|
1481
|
+
const { service } = memoryQueue();
|
|
1482
|
+
const { svc, merge } = harness(service);
|
|
1483
|
+
merge.mockImplementation(async () => {
|
|
1484
|
+
const missing = [...remote.get(SOURCE)!].filter((p) => !remote.get(TARGET)!.has(p));
|
|
1485
|
+
if (missing.length === 0) return { kind: 'merged', sha: 'target-tip', mergeCommit: null };
|
|
1486
|
+
missing.forEach((p) => remote.get(TARGET)!.add(p));
|
|
1487
|
+
return { kind: 'merged', sha: 'merge-commit', mergeCommit: 'merge-commit' };
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
// The REAL worker drains the queue; its commit driver pushes to the remote
|
|
1491
|
+
// after a moment, the way a commit + push takes a few seconds.
|
|
1492
|
+
const worker = new PendingCommitsWorker({
|
|
1493
|
+
service,
|
|
1494
|
+
workflow: {
|
|
1495
|
+
runPendingCommit: async (_ws: string, branch: string, path: string) => {
|
|
1496
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
1497
|
+
remote.get(branch)!.add(path);
|
|
1498
|
+
},
|
|
1499
|
+
},
|
|
1500
|
+
recoveryAgent: { run: vi.fn() } as never,
|
|
1501
|
+
feedback: { notify: vi.fn() } as never,
|
|
1502
|
+
workspaces: { knownWorkspaces: () => [{ id: encodeURIComponent(SOURCE), branch: SOURCE }] },
|
|
1503
|
+
recoveryBot: { id: 'bot', email: 'bot@example.com', name: 'Bot' },
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
// The write tool answered: the bytes are on disk and a row is queued.
|
|
1507
|
+
await service.enqueue({
|
|
1508
|
+
workspaceId: encodeURIComponent(SOURCE),
|
|
1509
|
+
branch: SOURCE,
|
|
1510
|
+
path: 'knowledge-base/Notes/New.md',
|
|
1511
|
+
authorEmail: 'alice@example.com',
|
|
1512
|
+
authorName: 'Alice',
|
|
1513
|
+
});
|
|
1514
|
+
worker.start();
|
|
1515
|
+
try {
|
|
1516
|
+
// …and the agent merges in the same breath.
|
|
1517
|
+
const outcome = await svc.mergeBranch(makeUser(), SOURCE, TARGET);
|
|
1518
|
+
expect(outcome).toEqual({ kind: 'merged', sha: 'merge-commit' });
|
|
1519
|
+
expect(remote.get(TARGET)!.has('knowledge-base/Notes/New.md')).toBe(true);
|
|
1520
|
+
// One merge, after the commit landed — not an empty one first.
|
|
1521
|
+
expect(merge).toHaveBeenCalledTimes(1);
|
|
1522
|
+
expect(service.queuedOnBranch.mock.calls.length).toBeGreaterThan(1);
|
|
1523
|
+
} finally {
|
|
1524
|
+
await worker.stop();
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it('answers pending-commits after the bound when the source\'s commit cannot land, and merges nothing', async () => {
|
|
1529
|
+
// The worker is stopped: the row stays queued.
|
|
1530
|
+
const { service } = memoryQueue([row()]);
|
|
1531
|
+
const { svc, merge, git } = harness(service, { timeoutMs: 200, pollMs: 20 });
|
|
1532
|
+
const started = Date.now();
|
|
1533
|
+
const outcome = await svc.mergeBranch(makeUser(), SOURCE, TARGET);
|
|
1534
|
+
expect(Date.now() - started).toBeGreaterThanOrEqual(190);
|
|
1535
|
+
expect(outcome).toEqual({
|
|
1536
|
+
kind: 'pending-commits',
|
|
1537
|
+
branch: SOURCE,
|
|
1538
|
+
pending: 1,
|
|
1539
|
+
message: expect.stringMatching(/still being committed.*Nothing was merged.*Retry the merge shortly/),
|
|
1540
|
+
});
|
|
1541
|
+
expect(merge).not.toHaveBeenCalled();
|
|
1542
|
+
expect(git.pull).not.toHaveBeenCalled();
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
it('bounds the wait well inside the timeout an agent\'s call is forwarded under', () => {
|
|
1546
|
+
const svc = new WorkflowService(
|
|
1547
|
+
makeDb(), makeGit(), makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(),
|
|
1548
|
+
makeFileLockService(), makePendingCommits(), testKbContext(), openChangeGate(),
|
|
1549
|
+
);
|
|
1550
|
+
// Over `/api/mcp` the call reaches the tool through `@utcp/http`, whose
|
|
1551
|
+
// request timeout cannot be set per template. A wait that ran to that
|
|
1552
|
+
// ceiling answered "timeout of 30000ms exceeded" instead of
|
|
1553
|
+
// `pending-commits` (Local Testing, attempt 1). The wait and the merge after
|
|
1554
|
+
// it must both fit, so the bound leaves ten seconds for the merge.
|
|
1555
|
+
const forwarding = (new HttpCommunicationProtocol() as unknown as { _axiosInstance: { defaults: { timeout: number } } })
|
|
1556
|
+
._axiosInstance.defaults.timeout;
|
|
1557
|
+
expect(forwarding).toBe(30_000);
|
|
1558
|
+
expect(svc.mergeWaitForPendingCommits.timeoutMs).toBe(20_000);
|
|
1559
|
+
expect(forwarding - svc.mergeWaitForPendingCommits.timeoutMs).toBeGreaterThanOrEqual(10_000);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it('answers pending-commits at once, with the worker\'s message, when a queued commit needs attention', async () => {
|
|
1563
|
+
const { service } = memoryQueue([
|
|
1564
|
+
row({ status: 'needs_attention', lastError: 'push rejected: protected ref' }),
|
|
1565
|
+
row(),
|
|
1566
|
+
]);
|
|
1567
|
+
// The default 20-second bound: an answer well inside it shows no wait.
|
|
1568
|
+
const { svc, merge } = harness(service, { timeoutMs: 20_000, pollMs: 20 });
|
|
1569
|
+
const started = Date.now();
|
|
1570
|
+
const outcome = await svc.mergeBranch(makeUser(), SOURCE, TARGET);
|
|
1571
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
1572
|
+
expect(outcome).toEqual({
|
|
1573
|
+
kind: 'pending-commits',
|
|
1574
|
+
branch: SOURCE,
|
|
1575
|
+
pending: 2,
|
|
1576
|
+
needsAttention: 'push rejected: protected ref',
|
|
1577
|
+
message: expect.stringContaining('push rejected: protected ref'),
|
|
1578
|
+
});
|
|
1579
|
+
expect(service.queuedOnBranch).toHaveBeenCalledTimes(1);
|
|
1580
|
+
expect(merge).not.toHaveBeenCalled();
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
it('answers nothing-to-merge with the target\'s tip when the source has nothing the target lacks', async () => {
|
|
1584
|
+
const { svc, merge } = harness(memoryQueue().service);
|
|
1585
|
+
merge.mockResolvedValue({ kind: 'merged', sha: 'target-tip', mergeCommit: null });
|
|
1586
|
+
expect(await svc.mergeBranch(makeUser(), SOURCE, TARGET)).toEqual({ kind: 'nothing-to-merge', sha: 'target-tip' });
|
|
1587
|
+
expect(merge).toHaveBeenCalledTimes(1);
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
it('answers merged with the merge commit when the source has commits the target lacks and nothing is pending', async () => {
|
|
1591
|
+
const { svc } = harness(memoryQueue().service);
|
|
1592
|
+
expect(await svc.mergeBranch(makeUser(), SOURCE, TARGET)).toEqual({ kind: 'merged', sha: 'merge-sha' });
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
it('answers conflicts-need-resolution with the paths, as before', async () => {
|
|
1596
|
+
const { svc, merge } = harness(memoryQueue().service);
|
|
1597
|
+
merge.mockResolvedValue({ kind: 'conflicts', paths: ['A.md'] });
|
|
1598
|
+
expect(await svc.mergeBranch(makeUser(), SOURCE, TARGET)).toEqual({
|
|
1599
|
+
kind: 'conflicts-need-resolution',
|
|
1600
|
+
conflictedPaths: ['A.md'],
|
|
1601
|
+
});
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
it('refuses a merge an open change request proposes before any waiting', async () => {
|
|
1605
|
+
// A queued commit that would hold the merge for the full bound.
|
|
1606
|
+
const { service } = memoryQueue([row({ branch: 'alice/feat' })]);
|
|
1607
|
+
const { svc, merge } = harness(service, { timeoutMs: 20_000, pollMs: 20 });
|
|
1608
|
+
const started = Date.now();
|
|
1609
|
+
const err = await svc.mergeBranch(makeUser(), 'alice/feat', DEFAULT_BRANCH).catch((e: unknown) => e);
|
|
1610
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
1611
|
+
expect(err).toMatchObject({ status: 409, payload: { kind: 'open-change-request-blocks-merge', number: 12 } });
|
|
1612
|
+
expect(service.queuedOnBranch).not.toHaveBeenCalled();
|
|
1613
|
+
expect(merge).not.toHaveBeenCalled();
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
it('holds neither branch\'s lifecycle lock while it waits', async () => {
|
|
1617
|
+
const { service, table } = memoryQueue([row()]);
|
|
1618
|
+
const { svc, git } = harness(service, { timeoutMs: 5_000, pollMs: 20 });
|
|
1619
|
+
(git.createBranch as ReturnType<typeof vi.fn>).mockResolvedValue({ name: 'x' });
|
|
1620
|
+
const merging = svc.mergeBranch(makeUser(), SOURCE, TARGET);
|
|
1621
|
+
let mergeDone = false;
|
|
1622
|
+
void merging.then(() => (mergeDone = true));
|
|
1623
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1624
|
+
// Both keys `runOnBranchPair` takes: work under either completes while
|
|
1625
|
+
// the merge is still waiting on the source's queue.
|
|
1626
|
+
await svc.createBranch(encodeURIComponent(TARGET), TARGET);
|
|
1627
|
+
await svc.createBranch(encodeURIComponent(SOURCE), SOURCE);
|
|
1628
|
+
expect(git.createBranch).toHaveBeenCalledTimes(2);
|
|
1629
|
+
expect(mergeDone).toBe(false);
|
|
1630
|
+
// The commit lands; the merge goes on.
|
|
1631
|
+
table.splice(0, table.length);
|
|
1632
|
+
expect(await merging).toEqual({ kind: 'merged', sha: 'merge-sha' });
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
it('leaves the app\'s change-request apply as it was: no wait, same outcome', async () => {
|
|
1636
|
+
const { service } = memoryQueue([row({ branch: 'alice/feat' })]);
|
|
1637
|
+
const prs = makePrs();
|
|
1638
|
+
const reviewWorkflow = makeReviewWorkflow();
|
|
1639
|
+
const mergeResult = { prNumber: 4, sha: 'abc', mergedAt: 't' };
|
|
1640
|
+
(reviewWorkflow.mergePr as ReturnType<typeof vi.fn>).mockResolvedValue(mergeResult);
|
|
1641
|
+
(prs.getPr as ReturnType<typeof vi.fn>).mockResolvedValue({ branch: 'alice/feat', base: 'main' });
|
|
1642
|
+
const svc = new WorkflowService(makeDb(), makeGit(), prs, reviewWorkflow, makeWorkspaceService(), makeAccessControl(), makeFileLockService(), service, testKbContext(), openChangeGate());
|
|
1643
|
+
const outcome = await svc.mergeChangeRequest(4, makeUser(), 'sha', [], 'open', 'PR title', 'main', 'w1', { bypass: true });
|
|
1644
|
+
expect(outcome).toEqual({ kind: 'merged', result: mergeResult });
|
|
1645
|
+
expect(service.queuedOnBranch).not.toHaveBeenCalled();
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
@@ -281,6 +281,10 @@ describe('WorkflowService.removeFolderFromChangeRequests', () => {
|
|
|
281
281
|
expect(closed).toEqual([40]);
|
|
282
282
|
expect([...changed.get(elsewhere.branch)!]).toEqual(['Data/ReportsArchive/old.md']);
|
|
283
283
|
|
|
284
|
+
// Each source checkout is brought up to date replaying merges as merges:
|
|
285
|
+
// a merge a refused open or update stranded there must not be flattened.
|
|
286
|
+
expect(git.pull).toHaveBeenCalledWith(aliceWs, { preserveMerges: true });
|
|
287
|
+
expect(git.pull).toHaveBeenCalledWith(bobWs, { preserveMerges: true });
|
|
284
288
|
// Every checkout goes to git in ONE call, each file reverted to its merge base.
|
|
285
289
|
expect(git.revertPathsAndPush).toHaveBeenCalledTimes(1);
|
|
286
290
|
expect(plansOf(git)).toEqual([
|
|
@@ -599,7 +599,7 @@ describe('WorkflowService — git-sync visibility events', () => {
|
|
|
599
599
|
expect(kinds()).toEqual(['file-changed', 'file-changed']);
|
|
600
600
|
});
|
|
601
601
|
|
|
602
|
-
it('carries the branch and a
|
|
602
|
+
it('carries the branch and a reason in words — never the git output — for the banner', async () => {
|
|
603
603
|
const svc = makeFacade(
|
|
604
604
|
makeGit({ pushBehavior: 'auth-fail' }),
|
|
605
605
|
makeFileLocks(USER.id),
|
|
@@ -615,9 +615,10 @@ describe('WorkflowService — git-sync visibility events', () => {
|
|
|
615
615
|
workspaceId: 'ws-1',
|
|
616
616
|
branch: 'feat/x',
|
|
617
617
|
});
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
);
|
|
618
|
+
// The kind of failure survives; git's own text stays in the server log.
|
|
619
|
+
const { reason } = emitSpy.mock.calls[0][0] as { reason: string };
|
|
620
|
+
expect(reason).toBe("The repository host did not accept this server's credentials.");
|
|
621
|
+
expect(reason).not.toContain('Authentication failed');
|
|
621
622
|
});
|
|
622
623
|
|
|
623
624
|
it('re-announces on every failure, so a session joining mid-outage still learns of it', async () => {
|