@bevel-software/platform-core-backend 0.14.0 → 0.15.1
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/THIRD-PARTY-NOTICES.md +6 -6
- package/dist/core/catalog-cache-invalidation.d.ts +29 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +71 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +99 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +18 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +96 -43
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +18 -6
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +26 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +213 -71
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +59 -7
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +51 -1
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +95 -1
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +4 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/access-model/plugin-principals.d.ts +49 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +125 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +326 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +52 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +6 -0
- package/dist/modules/kb-sync/index.d.ts.map +1 -0
- package/dist/modules/kb-sync/index.js +5 -0
- package/dist/modules/kb-sync/index.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
- package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.js +135 -0
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.js +183 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
- package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-auth.js +62 -0
- package/dist/modules/kb-sync/sync-auth.js.map +1 -0
- package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
- package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-payload.js +105 -0
- package/dist/modules/kb-sync/sync-payload.js.map +1 -0
- package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/git-http.routes.js +190 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +23 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +99 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +147 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +228 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +109 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js +172 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/index.js +6 -0
- package/dist/modules/marketplace/github-facade/index.js.map +1 -0
- package/dist/modules/marketplace/index.d.ts +4 -0
- package/dist/modules/marketplace/index.d.ts.map +1 -0
- package/dist/modules/marketplace/index.js +4 -0
- package/dist/modules/marketplace/index.js.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +122 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +300 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +13 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.js +3 -1
- package/dist/modules/mcp/oauth/oauth-state.js.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +97 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +240 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +66 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +132 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +135 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +45 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +147 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +37 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +107 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +15 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +102 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +96 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.js +2 -0
- package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +10 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +8 -0
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +66 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +160 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +114 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +304 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +39 -3
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +152 -52
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +69 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +245 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +27 -1
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +7 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +120 -12
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +50 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +108 -47
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +9 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +82 -2
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +26 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +9 -1
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +1 -0
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +9 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
- package/dist/modules/skills/skill-access-requests.routes.js +168 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +31 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +28 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +42 -3
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +4 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +83 -22
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +18 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +13 -4
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +26 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +36 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +130 -92
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +18 -25
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +40 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +227 -54
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +6 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +8 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +52 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +242 -9
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +35 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +121 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.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 +46 -2
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +112 -25
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +9 -9
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +33 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +110 -16
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +20 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +29 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/fs-errors.d.ts +8 -0
- package/dist/shared/fs-errors.d.ts.map +1 -0
- package/dist/shared/fs-errors.js +11 -0
- package/dist/shared/fs-errors.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +20 -6
- package/dist/shared/fs-walk.d.ts.map +1 -1
- package/dist/shared/fs-walk.js +35 -27
- package/dist/shared/fs-walk.js.map +1 -1
- package/dist/shared/kb-walk.d.ts +52 -0
- package/dist/shared/kb-walk.d.ts.map +1 -0
- package/dist/shared/kb-walk.js +76 -0
- package/dist/shared/kb-walk.js.map +1 -0
- package/dist/shared/path-order.d.ts +18 -0
- package/dist/shared/path-order.d.ts.map +1 -0
- package/dist/shared/path-order.js +35 -0
- package/dist/shared/path-order.js.map +1 -0
- package/dist/shared/ttl-cache.d.ts +28 -1
- package/dist/shared/ttl-cache.d.ts.map +1 -1
- package/dist/shared/ttl-cache.js +33 -1
- package/dist/shared/ttl-cache.js.map +1 -1
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +95 -30
- package/kb-template/Skills/.gitkeep +0 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/__tests__/kb-layout-config.test.ts +110 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +146 -0
- package/src/core/catalog-cache-invalidation.ts +92 -0
- package/src/core/create-core-server.ts +123 -7
- package/src/core/create-core-services.ts +162 -41
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +86 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -3
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +368 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +8 -0
- package/src/modules/access/access-control.interface.ts +20 -9
- package/src/modules/access/access-control.service.ts +240 -78
- package/src/modules/access/access.routes.ts +96 -14
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/access-grammar.ts +114 -2
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +137 -0
- package/src/modules/database/core-schema.ts +54 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +297 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
- package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
- package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
- package/src/modules/kb-sync/index.ts +19 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
- package/src/modules/kb-sync/kb-sync.routes.ts +197 -0
- package/src/modules/kb-sync/kb-sync.service.ts +201 -0
- package/src/modules/kb-sync/sync-auth.ts +118 -0
- package/src/modules/kb-sync/sync-payload.ts +143 -0
- package/src/modules/marketplace/__tests__/github-facade.test.ts +443 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +179 -0
- package/src/modules/marketplace/git-http.routes.ts +193 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +78 -0
- package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +208 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +268 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +221 -0
- package/src/modules/marketplace/github-facade/index.ts +25 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +337 -0
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -0
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +24 -2
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +163 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +294 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +56 -12
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +287 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +205 -5
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +394 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +80 -33
- package/src/modules/plugins/compile/compile-marketplace.ts +403 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +150 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +329 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +140 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +175 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +114 -0
- package/src/modules/plugins/discovery/native.source.ts +122 -0
- package/src/modules/plugins/discovery/plugin-source.ts +99 -0
- package/src/modules/plugins/index.ts +10 -0
- package/src/modules/plugins/plugin-links.service.ts +363 -0
- package/src/modules/plugins/plugin-links.ts +184 -0
- package/src/modules/plugins/plugin-provision.service.ts +160 -54
- package/src/modules/plugins/plugin-rename.service.ts +298 -0
- package/src/modules/plugins/plugins.contract.ts +28 -1
- package/src/modules/plugins/plugins.routes.ts +128 -11
- package/src/modules/plugins/plugins.service.ts +106 -53
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +57 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/deployment-settings.service.ts +95 -2
- package/src/modules/settings/setup.routes.ts +24 -0
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +85 -0
- package/src/modules/skills/index.ts +1 -0
- package/src/modules/skills/pending-skills.service.ts +7 -5
- package/src/modules/skills/skill-access-requests.routes.ts +199 -0
- package/src/modules/skills/skills.contract.ts +32 -0
- package/src/modules/skills/skills.routes.ts +59 -3
- package/src/modules/skills/skills.service.ts +84 -22
- package/src/modules/tool-auth/external-api-key.interface.ts +20 -1
- package/src/modules/tool-auth/external-api-key.service.ts +31 -13
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -88
- package/src/modules/tool-manuals/tool-manuals.service.ts +14 -23
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +2 -2
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +119 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +437 -0
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/git.service.ts +239 -56
- package/src/modules/workflow/git/pull-request.service.ts +9 -0
- package/src/modules/workflow/workflow.service.ts +260 -9
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +6 -7
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +93 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +42 -12
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +219 -4
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +129 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +44 -2
- package/src/modules/workspace/startup/steps/template-files.step.ts +131 -27
- package/src/modules/workspace/workspace.routes.ts +9 -17
- package/src/modules/workspace/workspace.service.ts +107 -16
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/fs-walk.test.ts +22 -1
- package/src/shared/__tests__/kb-walk.test.ts +107 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/domain-errors.ts +29 -0
- package/src/shared/fs-errors.ts +10 -0
- package/src/shared/fs-walk.ts +38 -23
- package/src/shared/kb-walk.ts +95 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/ttl-cache.ts +33 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
6
|
+
|
|
7
|
+
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
8
|
+
import { AccessControlService } from '../../access/access-control.service.js';
|
|
9
|
+
import { SkillService } from '../../skills/skills.service.js';
|
|
10
|
+
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
11
|
+
import { PluginLinkIndex } from '../plugin-links.js';
|
|
12
|
+
import { MarketplaceCompilerService } from '../compile/marketplace-compiler.service.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Source in, distribution out — for one caller. The real resolver decides
|
|
16
|
+
* what is readable, the real catalog and link index say what is where; the
|
|
17
|
+
* assertions are on the produced tree.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const KB_DIR = 'knowledge-base';
|
|
21
|
+
const wsId = workspaceIdForBranch(DEFAULT_BRANCH);
|
|
22
|
+
|
|
23
|
+
describe('compileMarketplace', () => {
|
|
24
|
+
let root: string;
|
|
25
|
+
let repo: string;
|
|
26
|
+
let compiler: MarketplaceCompilerService;
|
|
27
|
+
|
|
28
|
+
const write = async (rel: string, text: string) => {
|
|
29
|
+
const abs = path.join(repo, rel);
|
|
30
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
31
|
+
await fs.writeFile(abs, text);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-compile-'));
|
|
36
|
+
repo = path.join(root, wsId, KB_DIR);
|
|
37
|
+
const workspaceService = {
|
|
38
|
+
getOrCreateForBranch: async () => ({ id: wsId }),
|
|
39
|
+
getWorkspacePath: async (id: string) => path.join(root, id),
|
|
40
|
+
readFile: async (id: string, rel: string) => fs.readFile(path.join(root, id, rel), 'utf-8'),
|
|
41
|
+
ensureRemotesFetched: async () => undefined,
|
|
42
|
+
} as unknown as WorkspaceService;
|
|
43
|
+
|
|
44
|
+
await write('roles.yaml', 'roles:\n Admin:\n - admin@x.io\n');
|
|
45
|
+
await write('access.md', '---\nwrite:\n - Admin\n---\n');
|
|
46
|
+
// GTM: Sam is a member. One inline skill, one MCP server with a vault header.
|
|
47
|
+
await write(
|
|
48
|
+
'Plugins/GTM/access.md',
|
|
49
|
+
'---\nread:\n - everyone\n---\nread:\n - Sam <sam@x.io>\nwrite:\n - Mia <mia@x.io>\n',
|
|
50
|
+
);
|
|
51
|
+
await write(
|
|
52
|
+
'Plugins/GTM/plugin.json',
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
name: 'gtm',
|
|
55
|
+
version: '2.1.0',
|
|
56
|
+
description: 'Go to market',
|
|
57
|
+
extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy'] } },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
await write('Plugins/GTM/skills/outreach/SKILL.md', '---\ndescription: Reach out.\n---\nBody\n');
|
|
61
|
+
await write('Plugins/GTM/skills/outreach/scripts/run.py', 'print(1)\n');
|
|
62
|
+
await write('Plugins/GTM/software.bevel.hexis/tools/web.tool', 'not shipped');
|
|
63
|
+
await write(
|
|
64
|
+
'Plugins/GTM/mcp.json',
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
mcpServers: {
|
|
67
|
+
notion: { type: 'streamable-http', url: 'https://mcp.notion.com', headers: { Authorization: 'Bearer ${NOTION_TOKEN}', 'X-Client': 'hexis' } },
|
|
68
|
+
local: { type: 'stdio', command: 'npx', args: ['-y', 'x'] },
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
// Shared skills: Eng/deploy linked into GTM and readable by its members;
|
|
73
|
+
// Eng/secret readable by nobody but admins; Sales/pitch public.
|
|
74
|
+
await write('Skills/Eng/deploy/access.md', '---\n---\nread:\n - plugin/GTM/read\n');
|
|
75
|
+
await write('Skills/Eng/deploy/SKILL.md', '---\ndescription: Ship it.\n---\n');
|
|
76
|
+
await write('Skills/Eng/deploy/references/notes.md', 'notes');
|
|
77
|
+
await write('Skills/Eng/secret/SKILL.md', '---\ndescription: Hush.\n---\n');
|
|
78
|
+
await write('Skills/Sales/access.md', '---\n---\nread:\n - everyone\n');
|
|
79
|
+
await write('Skills/Sales/pitch/SKILL.md', '---\ndescription: Pitch.\n---\n');
|
|
80
|
+
|
|
81
|
+
const access = new AccessControlService(workspaceService, KB_DIR);
|
|
82
|
+
const skills = new SkillService(workspaceService, access, KB_DIR);
|
|
83
|
+
const links = new PluginLinkIndex(workspaceService, skills, access, KB_DIR);
|
|
84
|
+
compiler = new MarketplaceCompilerService(workspaceService, access, skills, links, KB_DIR, {
|
|
85
|
+
name: 'acme-hexis',
|
|
86
|
+
owner: 'Acme',
|
|
87
|
+
knowledgeBaseMcp: { name: 'hexis', url: 'https://kb.acme.com/api/mcp' },
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
afterEach(() => fs.rm(root, { recursive: true, force: true }));
|
|
91
|
+
|
|
92
|
+
const text = (tree: { files: Map<string, Buffer> }, rel: string) => tree.files.get(rel)?.toString('utf-8');
|
|
93
|
+
const json = (tree: { files: Map<string, Buffer> }, rel: string) => JSON.parse(text(tree, rel) ?? 'null');
|
|
94
|
+
|
|
95
|
+
it('compiles exactly what the caller may read, in the layouts the clients want', async () => {
|
|
96
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
97
|
+
const paths = [...tree.files.keys()].sort();
|
|
98
|
+
|
|
99
|
+
// GTM: inline + linked skills copied in, tools and access files left out.
|
|
100
|
+
expect(paths).toContain('plugins/gtm/skills/outreach/SKILL.md');
|
|
101
|
+
expect(paths).toContain('plugins/gtm/skills/outreach/scripts/run.py');
|
|
102
|
+
expect(paths).toContain('plugins/gtm/skills/deploy/SKILL.md');
|
|
103
|
+
expect(paths).toContain('plugins/gtm/skills/deploy/references/notes.md');
|
|
104
|
+
expect(paths.some((p) => p.includes('.tool') || p.endsWith('access.md'))).toBe(false);
|
|
105
|
+
// The unreadable skill is nowhere — not in a plugin, not in the flat copy.
|
|
106
|
+
expect(paths.some((p) => p.includes('secret'))).toBe(false);
|
|
107
|
+
|
|
108
|
+
// Three manifests per plugin, with the source manifest's identity.
|
|
109
|
+
expect(json(tree, 'plugins/gtm/plugin.json')).toMatchObject({ name: 'gtm', version: '2.1.0', description: 'Go to market' });
|
|
110
|
+
expect(json(tree, 'plugins/gtm/.claude-plugin/plugin.json')).toMatchObject({ name: 'gtm', version: '2.1.0' });
|
|
111
|
+
expect(json(tree, 'plugins/gtm/.codex-plugin/plugin.json')).toMatchObject({ name: 'gtm' });
|
|
112
|
+
|
|
113
|
+
// The portable half of mcp.json: the vault header is gone, the literal stays.
|
|
114
|
+
const mcp = json(tree, 'plugins/gtm/.mcp.json');
|
|
115
|
+
expect(mcp.mcpServers.notion).toEqual({ type: 'streamable-http', url: 'https://mcp.notion.com', headers: { 'X-Client': 'hexis' } });
|
|
116
|
+
expect(mcp.mcpServers.local).toEqual({ type: 'stdio', command: 'npx', args: ['-y', 'x'] });
|
|
117
|
+
|
|
118
|
+
// The leftovers plugin holds what no real plugin ships (pitch), never what
|
|
119
|
+
// one does (deploy is GTM's); the flat copy carries everything once.
|
|
120
|
+
expect(paths).toContain('plugins/skills-and-knowledge/skills/pitch/SKILL.md');
|
|
121
|
+
expect(paths).not.toContain('plugins/skills-and-knowledge/skills/deploy/SKILL.md');
|
|
122
|
+
// …and the knowledge base itself as an MCP server, URL only.
|
|
123
|
+
expect(json(tree, 'plugins/skills-and-knowledge/.mcp.json')).toEqual({
|
|
124
|
+
mcpServers: { hexis: { type: 'streamable-http', url: 'https://kb.acme.com/api/mcp' } },
|
|
125
|
+
});
|
|
126
|
+
expect(paths).toContain('skills/deploy/SKILL.md');
|
|
127
|
+
expect(paths).toContain('skills/pitch/SKILL.md');
|
|
128
|
+
expect(paths).toContain('skills/outreach/SKILL.md');
|
|
129
|
+
|
|
130
|
+
// The bundle depends on every other plugin; the catalogues list them all.
|
|
131
|
+
expect(json(tree, 'plugins/hexis-all/.claude-plugin/plugin.json').dependencies.sort()).toEqual(['gtm', 'skills-and-knowledge']);
|
|
132
|
+
const claude = json(tree, '.claude-plugin/marketplace.json');
|
|
133
|
+
expect(claude.name).toBe('acme-hexis');
|
|
134
|
+
expect(claude.plugins.map((p: { name: string; source: string }) => [p.name, p.source])).toEqual([
|
|
135
|
+
['gtm', './plugins/gtm'],
|
|
136
|
+
['skills-and-knowledge', './plugins/skills-and-knowledge'],
|
|
137
|
+
['hexis-all', './plugins/hexis-all'],
|
|
138
|
+
]);
|
|
139
|
+
const codex = json(tree, '.agents/plugins/marketplace.json');
|
|
140
|
+
expect(codex.plugins.every((p: { policy: { installation: string } }) => p.policy.installation === 'INSTALLED_BY_DEFAULT')).toBe(true);
|
|
141
|
+
expect(codex.plugins[0].source).toEqual({ source: 'local', path: './plugins/gtm' });
|
|
142
|
+
expect(text(tree, 'README.md')).toContain('Compiled from Acme');
|
|
143
|
+
expect(tree.warnings).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('ships only mcp servers a client could run, and names each one it leaves out', async () => {
|
|
147
|
+
// Written as text, not from an object literal: a literal `__proto__` key
|
|
148
|
+
// sets the prototype instead of an own property, and the point is a
|
|
149
|
+
// repository file that CARRIES that key.
|
|
150
|
+
await write(
|
|
151
|
+
'Plugins/GTM/mcp.json',
|
|
152
|
+
`{"mcpServers": {
|
|
153
|
+
"notion": { "type": "streamable-http", "url": "https://mcp.notion.com" },
|
|
154
|
+
"events": { "type": "sse", "url": "https://events.example" },
|
|
155
|
+
"Bad Name": { "type": "stdio", "command": "x" },
|
|
156
|
+
"empty": { "type": "stdio" },
|
|
157
|
+
"creds": { "type": "streamable-http", "url": "https://user:pw@mcp.example" },
|
|
158
|
+
"badenv": { "type": "stdio", "command": "x", "env": { "A": 1 } },
|
|
159
|
+
"badargs": { "type": "stdio", "command": "x", "args": [1, "b"] },
|
|
160
|
+
"badheaders": { "type": "streamable-http", "url": "https://x.example", "headers": { "A": 1 } },
|
|
161
|
+
"__proto__": { "type": "stdio", "command": "x" }
|
|
162
|
+
}}`,
|
|
163
|
+
);
|
|
164
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
165
|
+
const mcp = json(tree, 'plugins/gtm/mcp.json');
|
|
166
|
+
expect(Object.keys(mcp.mcpServers)).toEqual(['notion']);
|
|
167
|
+
const left = (name: string) => tree.warnings.find((w) => w.includes(`"${name}" not shipped`)) ?? '';
|
|
168
|
+
expect(left('events')).toContain('sse');
|
|
169
|
+
expect(left('Bad Name')).toContain('name');
|
|
170
|
+
expect(left('empty')).toContain('no command');
|
|
171
|
+
expect(left('creds')).toContain('credentials');
|
|
172
|
+
expect(left('badenv')).toContain('env');
|
|
173
|
+
expect(left('badargs')).toContain('args');
|
|
174
|
+
expect(left('badheaders')).toContain('headers');
|
|
175
|
+
expect(left('__proto__')).toContain('name');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('the manifest name is the identity: a folder called something else publishes, and is granted, under the name', async () => {
|
|
179
|
+
await write(
|
|
180
|
+
'Plugins/GTM/plugin.json',
|
|
181
|
+
JSON.stringify({ name: 'go-to-market', version: '2.1.0', extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy'] } } }),
|
|
182
|
+
);
|
|
183
|
+
// The linked skill's grant spelled by the name reaches Sam…
|
|
184
|
+
await write('Skills/Eng/deploy/access.md', '---\n---\nread:\n - plugin/go-to-market/read\n');
|
|
185
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
186
|
+
expect(tree.plugins).toContain('go-to-market');
|
|
187
|
+
expect(tree.plugins).not.toContain('gtm');
|
|
188
|
+
expect([...tree.files.keys()]).toContain('plugins/go-to-market/skills/deploy/SKILL.md');
|
|
189
|
+
// …and a name that differs from the folder is nothing to warn about.
|
|
190
|
+
expect(tree.warnings).toEqual([]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('a grant spelled by the folder names no plugin once the manifest says otherwise', async () => {
|
|
194
|
+
await write('Plugins/GTM/plugin.json', JSON.stringify({ name: 'go-to-market', extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy'] } } }));
|
|
195
|
+
// The fixture's grant on Skills/Eng/deploy still says plugin/GTM/read.
|
|
196
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
197
|
+
expect([...tree.files.keys()].some((p) => p.includes('deploy'))).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('for a stranger the plugin keeps only its MCP servers, and the public scope is all that ships', async () => {
|
|
201
|
+
const tree = await compiler.compileFor({ userEmail: 'nobody@elsewhere.io' });
|
|
202
|
+
const paths = [...tree.files.keys()].sort();
|
|
203
|
+
expect(paths.some((p) => p.startsWith('plugins/gtm/skills/'))).toBe(false);
|
|
204
|
+
expect(paths).toContain('plugins/gtm/.mcp.json'); // portable servers are not access-gated content
|
|
205
|
+
expect(paths).toContain('plugins/skills-and-knowledge/skills/pitch/SKILL.md');
|
|
206
|
+
expect(paths.some((p) => p.includes('deploy'))).toBe(false);
|
|
207
|
+
expect(tree.plugins).toEqual(['gtm', 'skills-and-knowledge', 'hexis-all']);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('the everyone audience carries the public subset only', async () => {
|
|
211
|
+
const tree = await compiler.compileFor({ everyone: true });
|
|
212
|
+
const paths = [...tree.files.keys()];
|
|
213
|
+
expect(paths).toContain('skills/pitch/SKILL.md');
|
|
214
|
+
expect(paths.some((p) => p.includes('deploy') || p.includes('outreach') || p.includes('secret'))).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('with the endpoint configured, the skills plugin exists even for a caller with no leftover skill', async () => {
|
|
218
|
+
// Make the one public skill part of GTM, so nothing is left over.
|
|
219
|
+
await write(
|
|
220
|
+
'Plugins/GTM/plugin.json',
|
|
221
|
+
JSON.stringify({ name: 'gtm', extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy', 'Skills/Sales'] } } }),
|
|
222
|
+
);
|
|
223
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
224
|
+
const paths = [...tree.files.keys()];
|
|
225
|
+
expect(paths.some((p) => p.startsWith('plugins/skills-and-knowledge/skills/'))).toBe(false);
|
|
226
|
+
expect(paths).toContain('plugins/skills-and-knowledge/.mcp.json');
|
|
227
|
+
expect(tree.plugins).toEqual(['gtm', 'skills-and-knowledge', 'hexis-all']);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('a manifest name that is no identifier is ignored, with a warning — the folder names the plugin', async () => {
|
|
231
|
+
// A hostile or mistaken manifest name must never become a path segment
|
|
232
|
+
// that escapes the compiled tree, nor an identity grants could spell.
|
|
233
|
+
await write('Plugins/GTM/plugin.json', JSON.stringify({ name: '../../etc/passwd' }));
|
|
234
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
235
|
+
const paths = [...tree.files.keys()];
|
|
236
|
+
expect(paths.every((p) => !p.includes('..') && !p.startsWith('/'))).toBe(true);
|
|
237
|
+
expect(paths).toContain('plugins/gtm/skills/outreach/SKILL.md');
|
|
238
|
+
expect(tree.warnings.some((w) => w.includes('Plugins/GTM') && w.includes('not a plugin identifier'))).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('two folders claiming one identity: the first by path keeps it, the second is skipped and named', async () => {
|
|
242
|
+
await write('Plugins/Twin/plugin.json', JSON.stringify({ name: 'gtm' }));
|
|
243
|
+
await write('Plugins/Twin/access.md', '---\nread:\n - everyone\n---\nread:\n - Sam <sam@x.io>\n');
|
|
244
|
+
await write('Plugins/Twin/skills/twin-skill/SKILL.md', '---\ndescription: t\n---\n');
|
|
245
|
+
|
|
246
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
247
|
+
const paths = [...tree.files.keys()];
|
|
248
|
+
expect(paths).toContain('plugins/gtm/skills/outreach/SKILL.md');
|
|
249
|
+
expect(paths).not.toContain('plugins/gtm/skills/twin-skill/SKILL.md');
|
|
250
|
+
expect(tree.warnings.some((w) => w.includes('Plugins/Twin') && w.includes('already used'))).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('a compile reads the checkout as it is now, not the catalogs a moment ago', async () => {
|
|
254
|
+
// Warm every cache: the skill catalog, the link index, the access model.
|
|
255
|
+
const before = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
256
|
+
expect([...before.files.keys()]).not.toContain('skills/late/SKILL.md');
|
|
257
|
+
expect([...before.files.keys()].some((p) => p.includes('secret'))).toBe(false);
|
|
258
|
+
|
|
259
|
+
// Then the knowledge base moves under them, the way an MCP `write_files`
|
|
260
|
+
// moves it: a new public skill, linked into GTM by its manifest, and a
|
|
261
|
+
// grant that opens the secret skill — all within every cache's TTL.
|
|
262
|
+
await write('Skills/Sales/late/SKILL.md', '---\ndescription: Just landed.\n---\n');
|
|
263
|
+
await write(
|
|
264
|
+
'Plugins/GTM/plugin.json',
|
|
265
|
+
JSON.stringify({
|
|
266
|
+
name: 'gtm',
|
|
267
|
+
version: '2.1.0',
|
|
268
|
+
description: 'Go to market',
|
|
269
|
+
extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy', 'Skills/Sales/late'] } },
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
await write('Skills/Eng/secret/access.md', '---\n---\nread:\n - everyone\n');
|
|
273
|
+
|
|
274
|
+
const after = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
275
|
+
const paths = [...after.files.keys()];
|
|
276
|
+
expect(paths).toContain('skills/late/SKILL.md');
|
|
277
|
+
expect(paths).toContain('plugins/gtm/skills/late/SKILL.md');
|
|
278
|
+
expect(paths).toContain('skills/secret/SKILL.md');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('a name clash inside one plugin keeps the first by path and says so', async () => {
|
|
282
|
+
// A second `deploy` skill, linked from the same plugin via a folder root.
|
|
283
|
+
await write('Skills/Ops/deploy/SKILL.md', '---\ndescription: Ops deploy.\n---\n');
|
|
284
|
+
await write('Skills/Ops/access.md', '---\n---\nread:\n - plugin/GTM/read\n');
|
|
285
|
+
await write(
|
|
286
|
+
'Plugins/GTM/plugin.json',
|
|
287
|
+
JSON.stringify({ name: 'gtm', extensions: { 'software.bevel.hexis': { skills: ['Skills/Eng/deploy', 'Skills/Ops'] } } }),
|
|
288
|
+
);
|
|
289
|
+
// Catalog-level dedup already keeps ONE `deploy` (Skills/Eng wins by path),
|
|
290
|
+
// so the compiled plugin has one and the tree stays coherent.
|
|
291
|
+
const tree = await compiler.compileFor({ userEmail: 'sam@x.io' });
|
|
292
|
+
expect(text(tree, 'plugins/gtm/skills/deploy/SKILL.md')).toContain('Ship it.');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
6
6
|
import { PluginIndexService } from '../plugins.service.js';
|
|
7
|
+
import type { PluginLinkIndex } from '../plugin-links.js';
|
|
7
8
|
import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
|
|
8
9
|
import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
9
10
|
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
@@ -62,9 +63,14 @@ describe('PluginIndexService', () => {
|
|
|
62
63
|
|
|
63
64
|
const kb = () => join(root, wsId, KB_DIR);
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* A REAL plugin: a folder carrying the manifest discovery reads AND the
|
|
68
|
+
* access.md that makes it exist to the index (a legacy folder without a
|
|
69
|
+
* manifest gets one from the boot step before the index ever runs).
|
|
70
|
+
*/
|
|
66
71
|
const pluginDir = async (name: string) => {
|
|
67
72
|
await mkdir(join(kb(), 'Plugins', name), { recursive: true });
|
|
73
|
+
await writeFile(join(kb(), 'Plugins', name, 'plugin.json'), `{"name":"${name.toLowerCase()}"}`);
|
|
68
74
|
await writeFile(join(kb(), 'Plugins', name, 'access.md'), '---\nread:\n - everyone\n---\n');
|
|
69
75
|
};
|
|
70
76
|
|
|
@@ -79,8 +85,9 @@ describe('PluginIndexService', () => {
|
|
|
79
85
|
await pluginDir('Engineering');
|
|
80
86
|
|
|
81
87
|
const catalog = await svc().catalog();
|
|
82
|
-
|
|
83
|
-
expect(catalog
|
|
88
|
+
// The manifest name is the identity; the folder is what people see.
|
|
89
|
+
expect(catalog.map((g) => g.name)).toEqual(['engineering', 'gtm']);
|
|
90
|
+
expect(catalog[1]).toMatchObject({ displayName: 'GTM', folders: ['Plugins/GTM'] });
|
|
84
91
|
});
|
|
85
92
|
|
|
86
93
|
test('a folder without an access.md is not a plugin', async () => {
|
|
@@ -90,10 +97,14 @@ describe('PluginIndexService', () => {
|
|
|
90
97
|
await mkdir(join(kb(), 'Plugins', 'Ghost'), { recursive: true });
|
|
91
98
|
await mkdir(join(kb(), 'Plugins', 'Residue'), { recursive: true });
|
|
92
99
|
await writeFile(join(kb(), 'Plugins', 'Residue', 'notes.md'), 'leftover');
|
|
100
|
+
// A manifest alone is a plugin to discovery but not to the index: the
|
|
101
|
+
// access.md is what a deleted plugin's residue lacks.
|
|
102
|
+
await mkdir(join(kb(), 'Plugins', 'Manifest-only'), { recursive: true });
|
|
103
|
+
await writeFile(join(kb(), 'Plugins', 'Manifest-only', 'plugin.json'), '{"name":"manifest-only"}');
|
|
93
104
|
await pluginDir('Real');
|
|
94
105
|
|
|
95
106
|
const catalog = await svc().catalog();
|
|
96
|
-
expect(catalog.map((g) => g.name)).toEqual(['
|
|
107
|
+
expect(catalog.map((g) => g.name)).toEqual(['real']);
|
|
97
108
|
});
|
|
98
109
|
|
|
99
110
|
test('the retired Skills/ and Tools/ roots are NOT plugin roots', async () => {
|
|
@@ -102,7 +113,7 @@ describe('PluginIndexService', () => {
|
|
|
102
113
|
await pluginDir('Engineering');
|
|
103
114
|
|
|
104
115
|
const catalog = await svc().catalog();
|
|
105
|
-
expect(catalog.map((g) => g.name)).toEqual(['
|
|
116
|
+
expect(catalog.map((g) => g.name)).toEqual(['engineering']);
|
|
106
117
|
});
|
|
107
118
|
|
|
108
119
|
test('ignores loose files and dot-dirs under the plugin root', async () => {
|
|
@@ -113,7 +124,7 @@ describe('PluginIndexService', () => {
|
|
|
113
124
|
await writeFile(join(kb(), 'Plugins', 'slack.tool'), '{}');
|
|
114
125
|
|
|
115
126
|
const catalog = await svc().catalog();
|
|
116
|
-
expect(catalog.map((g) => g.name)).toEqual(['
|
|
127
|
+
expect(catalog.map((g) => g.name)).toEqual(['gtm']);
|
|
117
128
|
});
|
|
118
129
|
|
|
119
130
|
test('counts skills and tools from the global catalogs by pluginOfPath', async () => {
|
|
@@ -126,14 +137,47 @@ describe('PluginIndexService', () => {
|
|
|
126
137
|
tools: tools('Plugins/GTM/heyreach.tool', 'Plugins/slack.tool'),
|
|
127
138
|
}).catalog();
|
|
128
139
|
|
|
129
|
-
const gtm = catalog.find((g) => g.name === '
|
|
140
|
+
const gtm = catalog.find((g) => g.name === 'gtm')!;
|
|
130
141
|
expect(gtm.skillCount).toBe(2);
|
|
131
142
|
expect(gtm.toolCount).toBe(1);
|
|
132
|
-
const product = catalog.find((g) => g.name === '
|
|
143
|
+
const product = catalog.find((g) => g.name === 'product')!;
|
|
133
144
|
expect(product.skillCount).toBe(1);
|
|
134
145
|
expect(product.toolCount).toBe(0);
|
|
135
146
|
});
|
|
136
147
|
|
|
148
|
+
test("counts the linked skills a plugin's members cannot read, from the unfiltered link index", async () => {
|
|
149
|
+
await pluginDir('GTM');
|
|
150
|
+
await pluginDir('Product');
|
|
151
|
+
// The link index's view: GTM links two shared skills, one of which lost
|
|
152
|
+
// its grant; Product's inline skill is granted by definition.
|
|
153
|
+
const membership = vi.fn(async () => ({
|
|
154
|
+
bySkill: new Map([
|
|
155
|
+
['Skills/Eng/deploy', [{ name: 'gtm', linked: true, granted: false }]],
|
|
156
|
+
['Skills/Eng/rollback', [{ name: 'gtm', linked: true, granted: true }]],
|
|
157
|
+
['Plugins/Product/roadmap', [{ name: 'product', linked: false, granted: true }]],
|
|
158
|
+
]),
|
|
159
|
+
byPlugin: new Map(),
|
|
160
|
+
}));
|
|
161
|
+
const links = { membership } as unknown as PluginLinkIndex;
|
|
162
|
+
const catalog = await new PluginIndexService(
|
|
163
|
+
workspaceService,
|
|
164
|
+
principals,
|
|
165
|
+
skillService(skills('Skills/Eng/deploy', 'Skills/Eng/rollback', 'Plugins/Product/roadmap')),
|
|
166
|
+
toolService(),
|
|
167
|
+
KB_DIR,
|
|
168
|
+
Date.now,
|
|
169
|
+
links,
|
|
170
|
+
).catalog();
|
|
171
|
+
|
|
172
|
+
expect(catalog.find((g) => g.name === 'gtm')).toMatchObject({ skillCount: 2, brokenLinks: 1 });
|
|
173
|
+
expect(catalog.find((g) => g.name === 'product')).toMatchObject({ skillCount: 1, brokenLinks: 0 });
|
|
174
|
+
// Both counts from ONE read of the index: its cache has no single-flight,
|
|
175
|
+
// so two concurrent reads of a cold catalog would build the tree twice.
|
|
176
|
+
expect(membership).toHaveBeenCalledTimes(1);
|
|
177
|
+
// Without a link index there are no links to be broken.
|
|
178
|
+
expect((await svc().catalog()).every((g) => g.brokenLinks === 0)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
137
181
|
test('resolves principals on the plugin folder', async () => {
|
|
138
182
|
await pluginDir('GTM');
|
|
139
183
|
|
|
@@ -190,7 +234,7 @@ describe('PluginIndexService', () => {
|
|
|
190
234
|
await expect(service.catalog()).resolves.toEqual([]);
|
|
191
235
|
|
|
192
236
|
fail = false;
|
|
193
|
-
expect((await service.catalog()).map((g) => g.name)).toEqual(['
|
|
237
|
+
expect((await service.catalog()).map((g) => g.name)).toEqual(['gtm']);
|
|
194
238
|
warn.mockRestore();
|
|
195
239
|
});
|
|
196
240
|
|
|
@@ -214,12 +258,12 @@ describe('PluginIndexService', () => {
|
|
|
214
258
|
await pluginDir('GTM');
|
|
215
259
|
const service = svc();
|
|
216
260
|
|
|
217
|
-
expect((await service.catalog()).map((g) => g.name)).toEqual(['
|
|
261
|
+
expect((await service.catalog()).map((g) => g.name)).toEqual(['gtm']);
|
|
218
262
|
// A plugin added out of band is NOT seen while the cache holds…
|
|
219
263
|
await pluginDir('Finance');
|
|
220
|
-
expect((await service.catalog()).map((g) => g.name)).toEqual(['
|
|
264
|
+
expect((await service.catalog()).map((g) => g.name)).toEqual(['gtm']);
|
|
221
265
|
// …and IS seen once the file-change subscriber drops it.
|
|
222
266
|
service.invalidate();
|
|
223
|
-
expect((await service.catalog()).map((g) => g.name)).toEqual(['
|
|
267
|
+
expect((await service.catalog()).map((g) => g.name)).toEqual(['finance', 'gtm']);
|
|
224
268
|
});
|
|
225
269
|
});
|