@bevel-software/platform-core-backend 0.14.0 → 0.19.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/THIRD-PARTY-NOTICES.md +8 -8
- package/dist/core/catalog-cache-invalidation.d.ts +45 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +76 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +279 -64
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +66 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +290 -111
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +231 -15
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +83 -12
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +815 -212
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +312 -84
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +100 -25
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +162 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- 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 +51 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +119 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.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 +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +672 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +171 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +79 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/bevel-ignore.d.ts +35 -0
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/kb-fs/bevel-ignore.js +71 -0
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +37 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +104 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.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 +137 -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 +185 -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 +192 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +33 -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 +88 -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 +126 -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 +192 -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 +252 -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 +121 -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 +213 -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 +135 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +318 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +9 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +24 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +83 -179
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +218 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +713 -159
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +8 -2
- 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 +16 -2
- 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/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +104 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +323 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +73 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +135 -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 +158 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +65 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +204 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +39 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +110 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +16 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +120 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +110 -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 +15 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +13 -2
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +69 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +163 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +111 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +295 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +129 -21
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +320 -75
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +71 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +254 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +78 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +44 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +297 -115
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +82 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +178 -48
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +157 -2
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +412 -35
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +57 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +408 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +4 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +2 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +24 -71
- 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 +171 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +67 -2
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +35 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +57 -4
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -3
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +236 -65
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +66 -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 +23 -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 +79 -19
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +5 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +5 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +7 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +8 -5
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.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 +136 -96
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +121 -2
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +415 -92
- package/dist/modules/tool-manuals/tool-manuals.service.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 +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +303 -6
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +753 -170
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +57 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +165 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +217 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +1142 -109
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +41 -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 +85 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +174 -54
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +521 -126
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +657 -246
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +323 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +1006 -257
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1590 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +128 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +166 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- 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/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +17 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +123 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- 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/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.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/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.js.map +1 -1
- 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/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +364 -54
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/_journal.json +49 -0
- package/package.json +8 -7
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +295 -0
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +226 -0
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +111 -0
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +358 -63
- package/src/core/create-core-services.ts +449 -107
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +88 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +142 -81
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +385 -0
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +14 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +269 -18
- package/src/modules/access/access-control.service.ts +943 -226
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +385 -105
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +198 -49
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +130 -0
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +177 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +75 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +123 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- 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 +200 -0
- package/src/modules/kb-sync/kb-sync.service.ts +204 -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 +575 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +180 -0
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +196 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +110 -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 +263 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +294 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +282 -0
- package/src/modules/marketplace/github-facade/index.ts +26 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +352 -0
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +8 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +274 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +25 -16
- package/src/modules/mcp/mcp.routes.ts +376 -482
- package/src/modules/mcp/mcp.service.ts +846 -176
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +8 -2
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +28 -6
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +166 -0
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +423 -0
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +201 -16
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +284 -0
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +330 -20
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +456 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +441 -43
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +500 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +151 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +365 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +161 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +250 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +117 -0
- package/src/modules/plugins/discovery/native.source.ts +140 -0
- package/src/modules/plugins/discovery/plugin-source.ts +113 -0
- package/src/modules/plugins/index.ts +25 -2
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +350 -0
- package/src/modules/plugins/plugin-links.ts +187 -0
- package/src/modules/plugins/plugin-provision.service.ts +356 -78
- package/src/modules/plugins/plugin-rename.service.ts +310 -0
- package/src/modules/plugins/plugins.contract.ts +79 -4
- package/src/modules/plugins/plugins.routes.ts +320 -123
- package/src/modules/plugins/plugins.service.ts +184 -57
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +240 -1
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +478 -39
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +480 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +308 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +5 -0
- package/src/modules/skills/pending-skills.service.ts +26 -85
- package/src/modules/skills/skill-access-requests.routes.ts +203 -0
- package/src/modules/skills/skills.contract.ts +67 -3
- package/src/modules/skills/skills.routes.ts +75 -4
- package/src/modules/skills/skills.service.ts +258 -66
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +124 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +73 -1
- package/src/modules/tool-auth/external-api-key.service.ts +95 -29
- package/src/modules/tool-auth/internal-token.service.ts +5 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +16 -7
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +11 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +164 -95
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +440 -104
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +3 -1
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +3 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +296 -27
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +133 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +442 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -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.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +922 -199
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +188 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1341 -174
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +792 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +31 -13
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +94 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +98 -13
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1405 -48
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +90 -0
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +177 -54
- package/src/modules/workspace/startup/steps/template-files.step.ts +581 -126
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +691 -269
- package/src/modules/workspace/workspace.service.ts +1071 -292
- package/src/modules/workspace/workspace.tools.ts +1787 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +115 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +178 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +124 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/shared/ttl-cache.ts +33 -1
- package/src/version.ts +10 -6
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -42
- package/src/shared/fs-walk.ts +0 -30
- package/src/shared/hash-email.ts +0 -14
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { NodeFs } from '../../../../kb-fs/node-fs.js';
|
|
2
3
|
import { execFile } from 'node:child_process';
|
|
3
4
|
import { promisify } from 'node:util';
|
|
4
5
|
import fs from 'node:fs/promises';
|
|
5
6
|
import os from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { KbStartupRunner } from '../../kb-startup-runner.js';
|
|
9
|
+
import { NodeGitRunner } from '../../../../workflow/git/node-git-runner.js';
|
|
8
10
|
import type { OnServerStart, ServerStartContext, StepResult } from '../../on-server-start.js';
|
|
9
11
|
import { GroupsToPluginsStep } from '../groups-to-plugins.step.js';
|
|
12
|
+
import { PluginDisplayNamesStep } from '../plugin-display-names.step.js';
|
|
13
|
+
import { PluginManifestsStep } from '../plugin-manifests.step.js';
|
|
14
|
+
import { PersonalSpacesStep } from '../personal-spaces.step.js';
|
|
10
15
|
import { RolesYamlStep } from '../roles-yaml.step.js';
|
|
11
16
|
import { renderRolesYaml } from '../../../../access-model/render-roles-yaml.js';
|
|
17
|
+
import { mergeGroupsIntoRoles, parseRolesYaml } from '../../../../access-model/access-grammar.js';
|
|
12
18
|
import { TemplateFilesStep } from '../template-files.step.js';
|
|
13
19
|
import { buildSeedTree } from '../seed-tree.js';
|
|
14
20
|
import { defaultKbTemplateDir } from '../../../../../assets.js';
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_KB_LAYOUT,
|
|
23
|
+
agentsFilePointerSentence,
|
|
24
|
+
configureKbLayout,
|
|
25
|
+
renderKbLayoutPlaceholders,
|
|
26
|
+
} from '@bevel-software/platform-shared';
|
|
27
|
+
import { PLATFORM_HEADER, TOOL_PREFIX_LINE, composeAgentInstructions } from '../../../../agent-instructions/index.js';
|
|
15
28
|
|
|
16
29
|
const execFileAsync = promisify(execFile);
|
|
17
30
|
|
|
@@ -54,11 +67,16 @@ beforeEach(async () => {
|
|
|
54
67
|
|
|
55
68
|
afterEach(async () => {
|
|
56
69
|
vi.restoreAllMocks();
|
|
70
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT });
|
|
57
71
|
await fs.rm(root, { recursive: true, force: true });
|
|
58
72
|
});
|
|
59
73
|
|
|
60
74
|
/** A populated upstream carrying `files`: one commit, both protected refs. Returns the seed clone. */
|
|
61
|
-
async function seedUpstream(
|
|
75
|
+
async function seedUpstream(
|
|
76
|
+
files: Record<string, string>,
|
|
77
|
+
/** Symlinks to seed beside them: path in the repo → the target it points at. */
|
|
78
|
+
symlinks: Record<string, string> = {},
|
|
79
|
+
): Promise<string> {
|
|
62
80
|
const seed = path.join(root, '.seed');
|
|
63
81
|
await fs.mkdir(seed, { recursive: true });
|
|
64
82
|
await git(seed, ['init', '-b', DEFAULT_BRANCH]);
|
|
@@ -67,6 +85,11 @@ async function seedUpstream(files: Record<string, string>): Promise<string> {
|
|
|
67
85
|
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
68
86
|
await fs.writeFile(abs, content, 'utf8');
|
|
69
87
|
}
|
|
88
|
+
for (const [rel, target] of Object.entries(symlinks)) {
|
|
89
|
+
const abs = path.join(seed, rel);
|
|
90
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
91
|
+
await fs.symlink(target, abs);
|
|
92
|
+
}
|
|
70
93
|
await git(seed, ['add', '-A']);
|
|
71
94
|
await git(seed, ['commit', '-m', 'init']);
|
|
72
95
|
await git(seed, ['branch', PROTECTED[1]!]);
|
|
@@ -77,6 +100,7 @@ async function seedUpstream(files: Record<string, string>): Promise<string> {
|
|
|
77
100
|
|
|
78
101
|
function makeRunner(steps: OnServerStart[], templateDir: string = TEMPLATE_DIR) {
|
|
79
102
|
return new KbStartupRunner({
|
|
103
|
+
gitRunner: new NodeGitRunner(),
|
|
80
104
|
kbRepoUrl: () => upstream,
|
|
81
105
|
gitUsername: () => 'x-access-token',
|
|
82
106
|
workspacesRoot,
|
|
@@ -107,10 +131,18 @@ async function exists(dir: string, rel: string): Promise<boolean> {
|
|
|
107
131
|
return fs.access(path.join(dir, rel)).then(() => true, () => false);
|
|
108
132
|
}
|
|
109
133
|
|
|
134
|
+
async function readJson(dir: string, rel: string): Promise<Record<string, any>> {
|
|
135
|
+
return JSON.parse(await fs.readFile(path.join(dir, rel), 'utf8'));
|
|
136
|
+
}
|
|
137
|
+
|
|
110
138
|
const norm = (text: string) => text.replace(/\r\n?/g, '\n');
|
|
139
|
+
const PREAMBLE_IGNORE_BLOCK =
|
|
140
|
+
'\n# Added by the platform: agent instructions are edited from External agent access.\n' +
|
|
141
|
+
'/mcp-description.md\n';
|
|
111
142
|
|
|
143
|
+
/** The template as the step writes it under the default layout — placeholders rendered. */
|
|
112
144
|
async function template(name: string): Promise<string> {
|
|
113
|
-
return fs.readFile(path.join(TEMPLATE_DIR, name), 'utf8');
|
|
145
|
+
return renderKbLayoutPlaceholders(await fs.readFile(path.join(TEMPLATE_DIR, name), 'utf8'), DEFAULT_KB_LAYOUT);
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
/** Every required file + reserved root already present, from the real template. */
|
|
@@ -120,19 +152,21 @@ async function fullScaffold(): Promise<Record<string, string>> {
|
|
|
120
152
|
'AGENTS.md': await template('AGENTS.md'),
|
|
121
153
|
'.bevelignore': await template('.bevelignore'),
|
|
122
154
|
'.gitignore': await template('gitignore.template'),
|
|
155
|
+
'mcp-description.md': await template('mcp-description.md'),
|
|
123
156
|
'KnowledgeBase/.gitkeep': '',
|
|
124
157
|
'Plugins/.gitkeep': '',
|
|
158
|
+
'Skills/.gitkeep': '',
|
|
125
159
|
};
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
describe('TemplateFilesStep', () => {
|
|
129
163
|
it('adds the missing scaffolding — .gitignore arriving from its packable template spelling', async () => {
|
|
130
164
|
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
131
|
-
await makeRunner([new TemplateFilesStep()]).runAll();
|
|
165
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
132
166
|
|
|
133
167
|
for (const b of PROTECTED) {
|
|
134
168
|
const dir = await checkout(b);
|
|
135
|
-
for (const rel of ['access.md', 'AGENTS.md', '.bevelignore', '.gitignore']) {
|
|
169
|
+
for (const rel of ['access.md', 'AGENTS.md', '.bevelignore', '.gitignore', 'mcp-description.md']) {
|
|
136
170
|
expect(await exists(dir, rel), `${b}: ${rel}`).toBe(true);
|
|
137
171
|
}
|
|
138
172
|
// The packaged template cannot ship a literal .gitignore (npm strips
|
|
@@ -144,15 +178,112 @@ describe('TemplateFilesStep', () => {
|
|
|
144
178
|
// Reserved roots materialize as <dir>/.gitkeep.
|
|
145
179
|
expect(await exists(dir, 'KnowledgeBase/.gitkeep')).toBe(true);
|
|
146
180
|
expect(await exists(dir, 'Plugins/.gitkeep')).toBe(true);
|
|
181
|
+
expect(await exists(dir, 'Skills/.gitkeep')).toBe(true);
|
|
147
182
|
const subject = (await git(dir, ['log', '--format=%s', '-1'])).trim();
|
|
148
183
|
expect(subject).toMatch(/^Add missing KB scaffolding: /);
|
|
149
184
|
expect(subject).toContain('.gitignore');
|
|
150
185
|
}
|
|
151
186
|
});
|
|
152
187
|
|
|
188
|
+
it('renders the managed files with the deployment\'s own root names', async () => {
|
|
189
|
+
// A deployment that renamed its roots must hand the agent a guide naming
|
|
190
|
+
// the folders it will find, and an ignore file hiding the real ones.
|
|
191
|
+
configureKbLayout({ knowledgeBaseDir: 'docs', skillsDir: 'skills', pluginsDir: 'plugins' });
|
|
192
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
193
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
194
|
+
|
|
195
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
196
|
+
const agents = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
|
|
197
|
+
expect(agents).toContain('plugins/<Plugin>/plugin.json');
|
|
198
|
+
expect(agents).toContain('`docs/`');
|
|
199
|
+
expect(agents).not.toContain('{{');
|
|
200
|
+
expect(agents).not.toContain('KnowledgeBase/');
|
|
201
|
+
const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
|
|
202
|
+
// Both roots stay VISIBLE, whatever they are called: the Skills & Tools
|
|
203
|
+
// sidebar reads each from the workspace tree.
|
|
204
|
+
expect(ignore).not.toContain('plugins/');
|
|
205
|
+
expect(ignore).not.toContain('skills/');
|
|
206
|
+
expect(ignore).not.toContain('Plugins/');
|
|
207
|
+
expect(ignore).toContain('roles.yaml');
|
|
208
|
+
expect(await exists(dir, 'docs/.gitkeep')).toBe(true);
|
|
209
|
+
|
|
210
|
+
// And a second boot sees the rendered guide as current: no churn commit.
|
|
211
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
212
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
213
|
+
expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + scaffolding
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The placement rule. A tester's agent filed a ticket into a plugin folder
|
|
218
|
+
* and explained itself: it had found no convention saying where tickets go,
|
|
219
|
+
* and Plugins was where it already held write rights. The guide named the
|
|
220
|
+
* three roots but never said which KIND of file belongs in which — so the
|
|
221
|
+
* section has to survive into the guide agents actually read, which is this
|
|
222
|
+
* written file (an MCP session reads the branch's copy of it), under the
|
|
223
|
+
* deployment's own root names rather than the placeholders.
|
|
224
|
+
*/
|
|
225
|
+
it('writes the "Where a new file goes" placement rule, named for the deployment\'s roots', async () => {
|
|
226
|
+
configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'Abilities', pluginsDir: 'Extensions' });
|
|
227
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
228
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
229
|
+
|
|
230
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
231
|
+
const agents = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
|
|
232
|
+
const start = agents.indexOf('## Where a new file goes\n');
|
|
233
|
+
expect(start, 'the written guide has no placement section').toBeGreaterThan(-1);
|
|
234
|
+
// The section alone, so a phrase matched elsewhere in the guide cannot
|
|
235
|
+
// stand in for a rule that is missing here.
|
|
236
|
+
const section = agents.slice(start).split('\n## ')[0]!;
|
|
237
|
+
|
|
238
|
+
// A document goes to the knowledge root, a shared skill to the skills
|
|
239
|
+
// root or the plugin's own skills folder, machinery inside a plugin.
|
|
240
|
+
expect(section).toContain('`Docs/`');
|
|
241
|
+
expect(section).toContain('`Abilities/`');
|
|
242
|
+
expect(section).toContain('Extensions/<Plugin>/skills/<skill>/SKILL.md');
|
|
243
|
+
expect(section).toContain('Extensions/<Plugin>/software.bevel.hexis/tools/');
|
|
244
|
+
for (const kind of ['notes', 'reports', 'tickets', 'mcp.json', 'plugin.json']) {
|
|
245
|
+
expect(section, kind).toContain(kind);
|
|
246
|
+
}
|
|
247
|
+
// And the two rules the tester's agent broke.
|
|
248
|
+
expect(section).toMatch(/never holds a document/);
|
|
249
|
+
expect(section).toMatch(/\bask\b/);
|
|
250
|
+
|
|
251
|
+
// Rendered, not raw: a leftover placeholder would name a folder that is
|
|
252
|
+
// not there in a section whose whole job is naming the right folder.
|
|
253
|
+
expect(section).not.toContain('{{');
|
|
254
|
+
expect(section).not.toContain('KnowledgeBase/');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The step is built at boot, while the process still runs the defaults; the
|
|
259
|
+
* save that completes first-run setup applies the admin's names afterwards.
|
|
260
|
+
* A root list snapshotted at construction scaffolded `Skills/` beside the
|
|
261
|
+
* `skills/` they had just chosen — the very bug the names exist to avoid.
|
|
262
|
+
*/
|
|
263
|
+
it('scaffolds the layout in effect when it RUNS, not the one it was built under', async () => {
|
|
264
|
+
const builtAtBoot = new TemplateFilesStep(new NodeFs());
|
|
265
|
+
await seedUpstream({ 'skills/deploy/SKILL.md': '# deploy\n' });
|
|
266
|
+
configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'skills', pluginsDir: 'Plugins' });
|
|
267
|
+
await makeRunner([builtAtBoot]).runAll();
|
|
268
|
+
|
|
269
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
270
|
+
// Listed rather than probed: a probe for `Skills` answers yes on a
|
|
271
|
+
// case-insensitive filesystem that holds `skills`.
|
|
272
|
+
const roots = (await fs.readdir(dir, { withFileTypes: true }))
|
|
273
|
+
.filter((e) => e.isDirectory() && e.name !== '.git')
|
|
274
|
+
.map((e) => e.name)
|
|
275
|
+
.sort();
|
|
276
|
+
expect(roots).toEqual(['Docs', 'Plugins', 'skills']);
|
|
277
|
+
expect(await exists(dir, 'Docs/.gitkeep')).toBe(true);
|
|
278
|
+
const subject = (await git(dir, ['log', '--format=%s', '-1'])).trim();
|
|
279
|
+
expect(subject).toContain('Docs/.gitkeep');
|
|
280
|
+
expect(subject).not.toContain('KnowledgeBase/');
|
|
281
|
+
expect(subject).not.toContain('Skills/');
|
|
282
|
+
});
|
|
283
|
+
|
|
153
284
|
it('replaces a drifted AGENTS.md, and says so when that is the only change', async () => {
|
|
154
285
|
await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': 'stale conventions\n' });
|
|
155
|
-
await makeRunner([new TemplateFilesStep()]).runAll();
|
|
286
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
156
287
|
|
|
157
288
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
158
289
|
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(norm(await template('AGENTS.md')));
|
|
@@ -160,13 +291,79 @@ describe('TemplateFilesStep', () => {
|
|
|
160
291
|
expect(subject).toBe('Update AGENTS.md to the current platform template');
|
|
161
292
|
});
|
|
162
293
|
|
|
163
|
-
it('
|
|
164
|
-
|
|
165
|
-
|
|
294
|
+
it('ships the roles-vs-groups guide, and a deployment on an older template receives it at boot', async () => {
|
|
295
|
+
const guide = await template('AGENTS.md');
|
|
296
|
+
// The section runs from its own heading to the next heading of any level (or the end).
|
|
297
|
+
const section = guide.split(/\n(?=#{1,6} )/).find((s) => s.startsWith('### Roles are pre-set')) ?? '';
|
|
298
|
+
const prose = section.replace(/\s+/g, ' ');
|
|
299
|
+
// What roles are, that agents never create them, the group test, what to do instead.
|
|
300
|
+
expect(prose).toContain('A role in `roles.yaml` is an app role');
|
|
301
|
+
expect(prose).toContain('**Agents never create roles.**');
|
|
302
|
+
expect(prose).toContain('**Is it really a group?**');
|
|
303
|
+
expect(prose).toContain('**What to do instead.**');
|
|
304
|
+
expect(prose).toContain('add people to existing roles, and use a GROUP for a task- or team-scoped set of people');
|
|
305
|
+
|
|
306
|
+
// An existing deployment's AGENTS.md from before the guide existed.
|
|
307
|
+
await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': guide.replace(section, '') });
|
|
308
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
309
|
+
|
|
310
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
311
|
+
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toContain('**Agents never create roles.**');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('documents giving a role to a group, with a valid example, in the guide the step writes', async () => {
|
|
315
|
+
const guide = await template('AGENTS.md');
|
|
316
|
+
const section = guide.split(/\n(?=#{2,3} )/).find((s) => s.startsWith('### Giving a role to a group')) ?? '';
|
|
317
|
+
const prose = section.replace(/\s+/g, ' ');
|
|
318
|
+
expect(prose).toContain('`- group:<Name>`');
|
|
319
|
+
expect(prose).toContain('case- and whitespace-insensitively against the active group source');
|
|
320
|
+
expect(prose).toContain('validation error');
|
|
321
|
+
expect(prose).toContain('names the entry');
|
|
322
|
+
expect(prose).toContain("removes the role's contribution for everyone in the group");
|
|
323
|
+
// A bare name resolves to a same-named group first, so role denials use `role/`.
|
|
324
|
+
expect(prose).toContain('`deny role/Reviewer`');
|
|
325
|
+
// Closeness first: a person's own entry wins only inside the same access.md.
|
|
326
|
+
expect(prose).toContain('The nearest `access.md` that says anything about the person decides');
|
|
327
|
+
expect(prose).toContain('in the SAME `access.md` as the denial keeps that access');
|
|
328
|
+
expect(prose).toContain('A group under `Admin` makes every member a full admin');
|
|
329
|
+
// A change request cannot carry a roles.yaml edit — the merge restores the
|
|
330
|
+
// base file — so the guide says who changes roles and where, and never
|
|
331
|
+
// walks an agent through drafting one.
|
|
332
|
+
expect(prose).toContain('Only an Admin changes `roles.yaml`');
|
|
333
|
+
expect(prose).toContain('A change request cannot carry the edit');
|
|
334
|
+
expect(prose).toContain('Do not propose one');
|
|
335
|
+
expect(prose).toContain('`edit_file`');
|
|
336
|
+
for (const tool of ['create_branch', 'commit_change', 'open_change_request']) {
|
|
337
|
+
expect(prose).not.toContain(`\`${tool}\``);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// The example parses as a roles.yaml whose group entry names a real group.
|
|
341
|
+
const example = /```yaml\n([\s\S]*?)```/.exec(section)?.[1] ?? '';
|
|
342
|
+
expect(example).toContain('- group:Platform Team');
|
|
343
|
+
const parsed = parseRolesYaml(example);
|
|
344
|
+
expect(parsed.ok).toBe(true);
|
|
345
|
+
if (!parsed.ok) return;
|
|
346
|
+
const groups = new Map([['platform team', { displayName: 'Platform Team', emails: new Set(['pat@example.com']) }]]);
|
|
347
|
+
expect(mergeGroupsIntoRoles(parsed.index, groups, 'groups.yaml')).toEqual([]);
|
|
348
|
+
// The example hands out an ordinary role, never Admin.
|
|
349
|
+
expect(parsed.index.byEmail.get('pat@example.com')?.has('reviewer')).toBe(true);
|
|
350
|
+
expect(parsed.index.byEmail.get('pat@example.com')?.has('role/admin')).toBe(false);
|
|
351
|
+
|
|
352
|
+
// What the step writes — the file agents read through the MCP server — carries it.
|
|
353
|
+
await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': guide.replace(section, '') });
|
|
354
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
355
|
+
const written = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'));
|
|
356
|
+
expect(written).toContain('### Giving a role to a group');
|
|
357
|
+
expect(written).toContain(example);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('rejects the git folder — in any spelling — as a reserved root name', async () => {
|
|
361
|
+
for (const bad of ['.git', '.GIT', '.Git', '.git.', '%2egit']) {
|
|
362
|
+
expect(() => new TemplateFilesStep(new NodeFs(), [bad]), bad).toThrow(/must not name the git folder/);
|
|
166
363
|
}
|
|
167
364
|
});
|
|
168
365
|
|
|
169
|
-
it('appends the
|
|
366
|
+
it('appends the managed root-file rules to a custom template that lacks them', async () => {
|
|
170
367
|
// A distribution's own template whose ignore file does not carry the rule
|
|
171
368
|
// the on-disk merge assumes: the merge only runs against an EXISTING file,
|
|
172
369
|
// so the declared content itself must arrive with the rule in it.
|
|
@@ -178,44 +375,689 @@ describe('TemplateFilesStep', () => {
|
|
|
178
375
|
delete scaffold['.bevelignore']; // the one file the step will declare from the template
|
|
179
376
|
await seedUpstream(scaffold);
|
|
180
377
|
|
|
181
|
-
await makeRunner([new TemplateFilesStep()], customTemplate).runAll();
|
|
378
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
|
|
182
379
|
|
|
183
380
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
184
381
|
const lines = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
|
|
185
382
|
expect(lines).toContain('MyStuff/'); // the operator's rules survive
|
|
186
383
|
expect(lines).toContain('AGENTS.md'); // the platform's rule was appended
|
|
384
|
+
expect(lines).toContain('/mcp-description.md');
|
|
385
|
+
expect(lines).not.toContain('Skills/'); // never the skills root — the Library's tree needs it
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('drops the Skills/ rule an earlier release appended, and its comment, keeping every other rule', async () => {
|
|
389
|
+
// A knowledge base whose ignore file was topped up by the release that
|
|
390
|
+
// hid the skills root: the platform's comment + line sit at the end. The
|
|
391
|
+
// Skills & Tools sidebar reads that root from the workspace tree now, so
|
|
392
|
+
// the rule comes out exactly as it went in — nothing of the operator's moves.
|
|
393
|
+
const scaffold = await fullScaffold();
|
|
394
|
+
scaffold['.bevelignore'] =
|
|
395
|
+
'# mine\n.git/\nAGENTS.md\nPlugins/\n\n# Added by the platform: the conventions doc is not node content.\nSkills/\n';
|
|
396
|
+
await seedUpstream(scaffold);
|
|
397
|
+
|
|
398
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
399
|
+
|
|
400
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
401
|
+
const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
|
|
402
|
+
// The plugins-root rule goes with it — the same sidebar draws that root now.
|
|
403
|
+
expect(text).toBe('# mine\n.git/\nAGENTS.md\n' + PREAMBLE_IGNORE_BLOCK);
|
|
404
|
+
// Idempotent: a second boot has nothing to change.
|
|
405
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
406
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
407
|
+
expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(text);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("drops the template's own Skills/ rule from a KB seeded by that release, comment included — whatever root the comment named", async () => {
|
|
411
|
+
// The previous template listed the rule under its own explanatory line,
|
|
412
|
+
// ending with the plugins root's name of the day; a deployment may have
|
|
413
|
+
// renamed that root since, so the line is known by its opening.
|
|
414
|
+
const scaffold = await fullScaffold();
|
|
415
|
+
scaffold['.bevelignore'] =
|
|
416
|
+
'AGENTS.md\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like Groups/.\nSkills/\n';
|
|
417
|
+
await seedUpstream(scaffold);
|
|
418
|
+
|
|
419
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
420
|
+
|
|
421
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
422
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
423
|
+
'AGENTS.md\n' + PREAMBLE_IGNORE_BLOCK,
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('recognises the legacy comment for a renamed plugins root with a space in its name', async () => {
|
|
428
|
+
// The name between the fixed opening and closing is judged by the one
|
|
429
|
+
// root-name rule the platform has, so every name it could have rendered
|
|
430
|
+
// there is recognised — and nothing a root cannot be called is.
|
|
431
|
+
const scaffold = await fullScaffold();
|
|
432
|
+
scaffold['.bevelignore'] =
|
|
433
|
+
'AGENTS.md\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like My Plugins/.\nSkills/\n';
|
|
434
|
+
await seedUpstream(scaffold);
|
|
435
|
+
|
|
436
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
437
|
+
|
|
438
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
439
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
440
|
+
'AGENTS.md\n' + PREAMBLE_IGNORE_BLOCK,
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("keeps an operator's Skills/ rule whose own comment merely opens like the platform's", async () => {
|
|
445
|
+
// Provenance is the platform's EXACT comment. A comment that begins the
|
|
446
|
+
// same way and goes on differently was never written by the platform.
|
|
447
|
+
const scaffold = await fullScaffold();
|
|
448
|
+
const text =
|
|
449
|
+
'AGENTS.md\n# The shared-skills root is rendered by the Skills & Tools app, and I hide it anyway\nSkills/\n';
|
|
450
|
+
scaffold['.bevelignore'] = text;
|
|
451
|
+
await seedUpstream(scaffold);
|
|
452
|
+
|
|
453
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
454
|
+
|
|
455
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
456
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
457
|
+
text + PREAMBLE_IGNORE_BLOCK,
|
|
458
|
+
);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("keeps a Skills/ rule the operator wrote themselves — provenance is the platform's comment", async () => {
|
|
462
|
+
const scaffold = await fullScaffold();
|
|
463
|
+
scaffold['.bevelignore'] = 'AGENTS.md\n# I hide skills on purpose\nSkills/\n';
|
|
464
|
+
await seedUpstream(scaffold);
|
|
465
|
+
|
|
466
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
467
|
+
|
|
468
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
469
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
470
|
+
'AGENTS.md\n# I hide skills on purpose\nSkills/\n' + PREAMBLE_IGNORE_BLOCK,
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('drops EVERY Plugins/ rule, whoever wrote it — the sidebar draws that root now, and the seed left no comment to know it by', async () => {
|
|
475
|
+
// The first template ever seeded hid the plugins root as a bare line at
|
|
476
|
+
// the end of the file, so provenance cannot tell the platform's copy
|
|
477
|
+
// from an operator's; both would empty the Plugins tree, and both go.
|
|
478
|
+
// One under the platform's own comment loses the comment with it; a
|
|
479
|
+
// `!Plugins/` negation is not the rule and stays.
|
|
480
|
+
const scaffold = await fullScaffold();
|
|
481
|
+
scaffold['.bevelignore'] =
|
|
482
|
+
'# mine\nAGENTS.md\nPlugins/\nMy-Own-Rule/\n# Added by the platform: the conventions doc is not node content.\nPlugins/\n!Plugins/\n';
|
|
483
|
+
await seedUpstream(scaffold);
|
|
484
|
+
|
|
485
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
486
|
+
|
|
487
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
488
|
+
const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
|
|
489
|
+
expect(text).toBe('# mine\nAGENTS.md\nMy-Own-Rule/\n!Plugins/\n' + PREAMBLE_IGNORE_BLOCK);
|
|
490
|
+
// Idempotent: a second boot has nothing to change.
|
|
491
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
492
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
493
|
+
expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(text);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("declares a custom template's ignore file without the Plugins/ rule it still ships", async () => {
|
|
497
|
+
// A distribution's own template may still carry the rule the packaged
|
|
498
|
+
// one dropped; a KB seeded from it must not start out hiding the root.
|
|
499
|
+
const customTemplate = path.join(root, 'custom-template-plugins-rule');
|
|
500
|
+
await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
|
|
501
|
+
await fs.writeFile(path.join(customTemplate, '.bevelignore'), '.git/\nAGENTS.md\n\nPlugins/\n');
|
|
502
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
503
|
+
|
|
504
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
|
|
505
|
+
|
|
506
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
507
|
+
const lines = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
|
|
508
|
+
expect(lines).not.toContain('Plugins/');
|
|
509
|
+
expect(lines).toContain('AGENTS.md');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('never seeds a template entry that links into a git folder', async () => {
|
|
513
|
+
// A template that IS a working tree (this repo in a Docker build) can hold
|
|
514
|
+
// a link into its own .git. The name rule cannot see it; the target can.
|
|
515
|
+
const customTemplate = path.join(root, 'custom-template-gitlink');
|
|
516
|
+
await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
|
|
517
|
+
await fs.mkdir(path.join(customTemplate, '.git'), { recursive: true });
|
|
518
|
+
await fs.writeFile(path.join(customTemplate, '.git', 'config'), '[credential]\n\thelper = store\n');
|
|
519
|
+
await fs.symlink(path.join('.git', 'config'), path.join(customTemplate, 'notes.md'));
|
|
520
|
+
|
|
521
|
+
const dir = path.join(root, 'seeded-gitlink');
|
|
522
|
+
await fs.mkdir(dir, { recursive: true });
|
|
523
|
+
await buildSeedTree(new NodeFs(), customTemplate, [], ['admin@example.com'])(dir);
|
|
524
|
+
|
|
525
|
+
await expect(fs.readFile(path.join(dir, 'notes.md'), 'utf8')).rejects.toMatchObject({ code: 'ENOENT' });
|
|
526
|
+
await expect(fs.readdir(path.join(dir, '.git'))).rejects.toMatchObject({ code: 'ENOENT' });
|
|
527
|
+
// The rest of the template still seeded.
|
|
528
|
+
expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8')).toContain('#');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('seeds a binary template file byte for byte and keeps a script executable — text is what decodes', async () => {
|
|
532
|
+
const customTemplate = path.join(root, 'custom-template-bytes');
|
|
533
|
+
// The packaged template as a base, plus two files it does not ship.
|
|
534
|
+
await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
|
|
535
|
+
await fs.mkdir(path.join(customTemplate, 'assets'), { recursive: true });
|
|
536
|
+
await fs.mkdir(path.join(customTemplate, 'scripts'), { recursive: true });
|
|
537
|
+
// Not UTF-8, and holding a NUL: text by no reading of the bytes. A
|
|
538
|
+
// name-based rule once sent this through the decoder and changed it.
|
|
539
|
+
const binary = Buffer.from([0x89, 0x50, 0x00, 0xff, 0xfe, 0x7b, 0x7b, 0x7d, 0x7d]);
|
|
540
|
+
await fs.writeFile(path.join(customTemplate, 'assets', '.logo.bin'), binary);
|
|
541
|
+
await fs.writeFile(path.join(customTemplate, 'scripts', 'run.sh'), '#!/bin/sh\necho {{skillsDir}}\n', { mode: 0o755 });
|
|
542
|
+
// The empty-remote seed is the one path that copies a whole template.
|
|
543
|
+
const dir = path.join(root, 'seeded-bytes');
|
|
544
|
+
await fs.mkdir(dir, { recursive: true });
|
|
545
|
+
await buildSeedTree(new NodeFs(),customTemplate, [], ['admin@example.com'])(dir);
|
|
546
|
+
|
|
547
|
+
expect(await fs.readFile(path.join(dir, 'assets', '.logo.bin'))).toEqual(binary);
|
|
548
|
+
expect(norm(await fs.readFile(path.join(dir, 'scripts', 'run.sh'), 'utf8'))).toBe('#!/bin/sh\necho Skills\n');
|
|
549
|
+
if (process.platform !== 'win32') {
|
|
550
|
+
expect((await fs.stat(path.join(dir, 'scripts', 'run.sh'))).mode & 0o111).not.toBe(0);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("declares a custom template's ignore file without the stale Skills/ rule it still ships", async () => {
|
|
555
|
+
// A KB with NO ignore file gets the template's copy — and a distribution's
|
|
556
|
+
// template may still carry the rule the previous release had. The on-disk
|
|
557
|
+
// reconciliation never runs on an absent file, so the declared content
|
|
558
|
+
// must arrive already reconciled.
|
|
559
|
+
const customTemplate = path.join(root, 'custom-template-stale');
|
|
560
|
+
await fs.mkdir(customTemplate, { recursive: true });
|
|
561
|
+
await fs.writeFile(path.join(customTemplate, 'AGENTS.md'), await template('AGENTS.md'), 'utf8');
|
|
562
|
+
await fs.writeFile(
|
|
563
|
+
path.join(customTemplate, '.bevelignore'),
|
|
564
|
+
'# custom\nMyStuff/\n# The shared-skills root is rendered by the Skills & Tools app, like {{pluginsDir}}/.\n{{skillsDir}}/\n',
|
|
565
|
+
'utf8',
|
|
566
|
+
);
|
|
567
|
+
const scaffold = await fullScaffold();
|
|
568
|
+
delete scaffold['.bevelignore'];
|
|
569
|
+
await seedUpstream(scaffold);
|
|
570
|
+
|
|
571
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
|
|
572
|
+
|
|
573
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
574
|
+
const text = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
|
|
575
|
+
expect(text).toContain('MyStuff/');
|
|
576
|
+
expect(text.split('\n').map((l) => l.trim())).not.toContain('Skills/');
|
|
577
|
+
expect(text).not.toContain('shared-skills root');
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it("leaves an operator's !Skills/ negation alone — there is nothing of the platform's to remove", async () => {
|
|
581
|
+
const scaffold = await fullScaffold();
|
|
582
|
+
scaffold['.bevelignore'] = 'AGENTS.md\n!Skills/\n';
|
|
583
|
+
await seedUpstream(scaffold);
|
|
584
|
+
|
|
585
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
586
|
+
|
|
587
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
588
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
589
|
+
'AGENTS.md\n!Skills/\n' + PREAMBLE_IGNORE_BLOCK,
|
|
590
|
+
);
|
|
187
591
|
});
|
|
188
592
|
|
|
189
593
|
it('respects an explicit !AGENTS.md negation — hiding the doc is a default, not a mandate', async () => {
|
|
190
594
|
// Appending the positive rule after the negation would WIN under ordered
|
|
191
595
|
// matching and silently defeat the operator's stated choice to show it.
|
|
192
596
|
const scaffold = await fullScaffold();
|
|
193
|
-
scaffold['.bevelignore'] = '# operator wants the doc visible\n!AGENTS.md\n';
|
|
597
|
+
scaffold['.bevelignore'] = '# operator wants the doc visible\n!AGENTS.md\nMyStuff/\n';
|
|
598
|
+
await seedUpstream(scaffold);
|
|
599
|
+
|
|
600
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
601
|
+
|
|
602
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
603
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
604
|
+
'# operator wants the doc visible\n!AGENTS.md\nMyStuff/\n' + PREAMBLE_IGNORE_BLOCK,
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('respects an explicit !mcp-description.md negation', async () => {
|
|
609
|
+
const scaffold = await fullScaffold();
|
|
610
|
+
scaffold['.bevelignore'] = 'AGENTS.md\n!mcp-description.md\n';
|
|
611
|
+
await seedUpstream(scaffold);
|
|
612
|
+
|
|
613
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
614
|
+
|
|
615
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
616
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
617
|
+
'AGENTS.md\n!mcp-description.md\n',
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("respells the platform's OWN unanchored preamble rule instead of leaving it standing", async () => {
|
|
622
|
+
// What the release that shipped the unanchored spelling wrote. The bare
|
|
623
|
+
// name hides a nested `mcp-description.md` page too, so the platform's own
|
|
624
|
+
// line is corrected rather than treated as somebody's choice.
|
|
625
|
+
const scaffold = await fullScaffold();
|
|
626
|
+
scaffold['.bevelignore'] =
|
|
627
|
+
'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\nmcp-description.md\n';
|
|
628
|
+
await seedUpstream(scaffold);
|
|
629
|
+
|
|
630
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
631
|
+
|
|
632
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
633
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
634
|
+
'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\n/mcp-description.md\n',
|
|
635
|
+
);
|
|
636
|
+
// Idempotent: a second boot has nothing to change.
|
|
637
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
638
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
639
|
+
expect(norm(await fs.readFile(path.join(again, '.bevelignore'), 'utf8'))).toBe(
|
|
640
|
+
'AGENTS.md\n# Added by the platform: agent instructions are edited from External agent access.\n/mcp-description.md\n',
|
|
641
|
+
);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("keeps an unanchored preamble rule the operator wrote themselves, and adds nothing beside it", async () => {
|
|
645
|
+
const scaffold = await fullScaffold();
|
|
646
|
+
scaffold['.bevelignore'] = 'AGENTS.md\n# I hide it everywhere on purpose\nmcp-description.md\n';
|
|
194
647
|
await seedUpstream(scaffold);
|
|
195
648
|
|
|
196
|
-
await makeRunner([new TemplateFilesStep()]).runAll();
|
|
649
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
197
650
|
|
|
198
651
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
199
652
|
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe(
|
|
200
|
-
'#
|
|
653
|
+
'AGENTS.md\n# I hide it everywhere on purpose\nmcp-description.md\n',
|
|
201
654
|
);
|
|
202
655
|
});
|
|
203
656
|
|
|
657
|
+
it('seeds mcp-description.md on every protected branch, from the template, when it is missing', async () => {
|
|
658
|
+
const scaffold = await fullScaffold();
|
|
659
|
+
delete scaffold['mcp-description.md'];
|
|
660
|
+
await seedUpstream(scaffold);
|
|
661
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
662
|
+
|
|
663
|
+
for (const b of PROTECTED) {
|
|
664
|
+
const dir = await checkout(b);
|
|
665
|
+
expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(
|
|
666
|
+
norm(await template('mcp-description.md')),
|
|
667
|
+
);
|
|
668
|
+
const subject = (await git(dir, ['log', '--format=%s', '-1'])).trim();
|
|
669
|
+
expect(subject).toBe('Add missing KB scaffolding: mcp-description.md');
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('leaves an existing mcp-description.md untouched: seeded once, never refreshed', async () => {
|
|
674
|
+
const mine = 'Acme builds solar farms.\n\n## What is where\n\n- Projects/\n';
|
|
675
|
+
await seedUpstream({ ...(await fullScaffold()), 'mcp-description.md': mine });
|
|
676
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
677
|
+
|
|
678
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
679
|
+
expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(mine);
|
|
680
|
+
expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1'); // init only
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('keeps a deliberately emptied mcp-description.md empty: the top-up restores only a MISSING file', async () => {
|
|
684
|
+
await seedUpstream({ ...(await fullScaffold()), 'mcp-description.md': '' });
|
|
685
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
686
|
+
|
|
687
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
688
|
+
expect(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8')).toBe('');
|
|
689
|
+
expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1');
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it('ships a template that composes to the platform header alone: one comment, nothing broadcast', async () => {
|
|
693
|
+
const shipped = await template('mcp-description.md');
|
|
694
|
+
expect(shipped.trimStart().startsWith('<!--')).toBe(true);
|
|
695
|
+
expect(shipped.trimEnd().endsWith('-->')).toBe(true);
|
|
696
|
+
// The first line says how to turn the text on; the caps are stated inside.
|
|
697
|
+
expect(shipped.split('\n')[0]).toMatch(/Remove this comment wrapper/);
|
|
698
|
+
expect(shipped).toContain('6,000 characters');
|
|
699
|
+
expect(shipped).toContain('first paragraph under about 220');
|
|
700
|
+
expect(shipped).not.toContain('{{');
|
|
701
|
+
const composed = composeAgentInstructions(shipped);
|
|
702
|
+
expect(composed.instructions).toBe(PLATFORM_HEADER);
|
|
703
|
+
expect(composed.toolPrefix).toBe(TOOL_PREFIX_LINE);
|
|
704
|
+
expect(composed.unterminatedComment).toBe(false);
|
|
705
|
+
expect(composed.preambleChars).toBe(0);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('seeds mcp-description.md from the packaged template when a custom template predates it, instead of failing the boot', async () => {
|
|
709
|
+
// A distribution's own KB_TEMPLATE_DIR forked before this file existed:
|
|
710
|
+
// the upgrade must not stop startup over a file whose content is one
|
|
711
|
+
// comment. The packaged copy stands in, and the log says so once.
|
|
712
|
+
const customTemplate = path.join(root, 'custom-template-old');
|
|
713
|
+
await fs.mkdir(customTemplate, { recursive: true });
|
|
714
|
+
await fs.writeFile(path.join(customTemplate, 'AGENTS.md'), await template('AGENTS.md'), 'utf8');
|
|
715
|
+
await fs.writeFile(path.join(customTemplate, 'access.md'), await template('access.md'), 'utf8');
|
|
716
|
+
const scaffold = await fullScaffold();
|
|
717
|
+
delete scaffold['mcp-description.md'];
|
|
718
|
+
await seedUpstream(scaffold);
|
|
719
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
720
|
+
|
|
721
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())], customTemplate).runAll();
|
|
722
|
+
|
|
723
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
724
|
+
expect(norm(await fs.readFile(path.join(dir, 'mcp-description.md'), 'utf8'))).toBe(
|
|
725
|
+
norm(await template('mcp-description.md')),
|
|
726
|
+
);
|
|
727
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('no "mcp-description.md"'));
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it('gives the stand-in to that file only: a custom template missing access.md still fails loudly', async () => {
|
|
731
|
+
const stricter = path.join(root, 'custom-template-broken');
|
|
732
|
+
await fs.mkdir(stricter, { recursive: true });
|
|
733
|
+
await fs.writeFile(path.join(stricter, 'AGENTS.md'), await template('AGENTS.md'), 'utf8');
|
|
734
|
+
const scaffold = await fullScaffold();
|
|
735
|
+
delete scaffold['access.md'];
|
|
736
|
+
await seedUpstream(scaffold);
|
|
737
|
+
await expect(makeRunner([new TemplateFilesStep(new NodeFs())], stricter).runAll()).rejects.toThrow(/ENOENT/);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
it('points a clone at mcp-description.md from the managed AGENTS.md, before the platform mechanics', async () => {
|
|
741
|
+
const agents = await template('AGENTS.md');
|
|
742
|
+
const pointer = agents.indexOf('mcp-description.md');
|
|
743
|
+
expect(pointer).toBeGreaterThan(-1);
|
|
744
|
+
expect(pointer).toBeLessThan(agents.indexOf('## Directory Structure'));
|
|
745
|
+
expect(agents).toMatch(/default branch's copy inline/);
|
|
746
|
+
});
|
|
747
|
+
|
|
204
748
|
it('treats a CRLF checkout of identical AGENTS.md content as current — no churn commit', async () => {
|
|
205
749
|
const scaffold = await fullScaffold();
|
|
206
750
|
scaffold['AGENTS.md'] = norm(scaffold['AGENTS.md']!).replace(/\n/g, '\r\n');
|
|
207
751
|
await seedUpstream(scaffold);
|
|
208
|
-
await makeRunner([new TemplateFilesStep()]).runAll();
|
|
752
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
209
753
|
|
|
210
754
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
211
755
|
expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1'); // init only
|
|
212
756
|
});
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* The guide under a name of the deployment's own — and what becomes of the
|
|
760
|
+
* `AGENTS.md` beside it, which on such a deployment is the CUSTOMER'S file.
|
|
761
|
+
*/
|
|
762
|
+
describe('a renamed agent guide', () => {
|
|
763
|
+
/** The scaffold minus the guide, so each test says for itself what `AGENTS.md` holds. */
|
|
764
|
+
async function scaffoldWithoutGuide(): Promise<Record<string, string>> {
|
|
765
|
+
const scaffold = await fullScaffold();
|
|
766
|
+
delete scaffold['AGENTS.md'];
|
|
767
|
+
return scaffold;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const CUSTOMER_GUIDE = '# Acme conventions\n\nWrite tickets in the present tense.\n';
|
|
771
|
+
|
|
772
|
+
it('writes and refreshes the guide under the configured name, on every protected branch', async () => {
|
|
773
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
774
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
775
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
776
|
+
|
|
777
|
+
for (const branch of PROTECTED) {
|
|
778
|
+
const dir = await checkout(branch);
|
|
779
|
+
const guide = norm(await fs.readFile(path.join(dir, 'HEXIS.md'), 'utf8'));
|
|
780
|
+
// The header names the file it lives in, not the name it ships under.
|
|
781
|
+
expect(guide).toContain('`HEXIS.md`');
|
|
782
|
+
expect(guide).not.toContain('{{');
|
|
783
|
+
expect(await exists(dir, 'AGENTS.md')).toBe(false);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// A second boot sees the rendered guide as current: no churn commit…
|
|
787
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
788
|
+
expect((await git(await checkout(DEFAULT_BRANCH), ['rev-list', '--count', 'HEAD'])).trim()).toBe('2');
|
|
789
|
+
|
|
790
|
+
// …and a drifted copy is replaced, exactly as `AGENTS.md` is by default.
|
|
791
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
792
|
+
await fs.writeFile(path.join(dir, 'HEXIS.md'), 'stale conventions\n', 'utf8');
|
|
793
|
+
await git(dir, ['commit', '-am', 'drift']);
|
|
794
|
+
await git(dir, ['push', 'origin', DEFAULT_BRANCH]);
|
|
795
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
796
|
+
const after = await checkout(DEFAULT_BRANCH);
|
|
797
|
+
expect(norm(await fs.readFile(path.join(after, 'HEXIS.md'), 'utf8'))).toContain('# Knowledge base');
|
|
798
|
+
expect((await git(after, ['log', '--format=%s', '-1'])).trim()).toBe(
|
|
799
|
+
'Update HEXIS.md to the current platform template',
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('leaves a customer AGENTS.md byte for byte alone, and shows it in the tree', async () => {
|
|
804
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
805
|
+
// Their file already names the guide, so the pointer has nothing to add
|
|
806
|
+
// — this test is about the bytes and the ignore rules alone.
|
|
807
|
+
const theirs = `${CUSTOMER_GUIDE}\nSee HEXIS.md for the platform.\n`;
|
|
808
|
+
await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': theirs });
|
|
809
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
810
|
+
|
|
811
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
812
|
+
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(theirs);
|
|
813
|
+
expect(await exists(dir, 'HEXIS.md')).toBe(true);
|
|
814
|
+
const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))
|
|
815
|
+
.split('\n')
|
|
816
|
+
.map((l) => l.trim());
|
|
817
|
+
// The configured file is hidden; the customer's shows.
|
|
818
|
+
expect(ignore).toContain('HEXIS.md');
|
|
819
|
+
expect(ignore).not.toContain('AGENTS.md');
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('removes the platform-written AGENTS.md — only its own header proves it ours', async () => {
|
|
823
|
+
// A knowledge base the platform seeded, whose admin then renames the guide.
|
|
824
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
825
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
826
|
+
expect(await exists(await checkout(DEFAULT_BRANCH), 'AGENTS.md')).toBe(true);
|
|
827
|
+
|
|
828
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
829
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
830
|
+
|
|
831
|
+
// On EVERY protected branch: the step tops up each one, and a rename
|
|
832
|
+
// that only reached the default branch would leave the others carrying a
|
|
833
|
+
// stale platform guide under the customer's name.
|
|
834
|
+
for (const branch of PROTECTED) {
|
|
835
|
+
const dir = await checkout(branch);
|
|
836
|
+
expect(await exists(dir, 'AGENTS.md'), branch).toBe(false);
|
|
837
|
+
expect(await exists(dir, 'HEXIS.md'), branch).toBe(true);
|
|
838
|
+
const message = await git(dir, ['log', '--format=%B', '-1']);
|
|
839
|
+
expect(message, branch).toContain(
|
|
840
|
+
'Remove the platform-written AGENTS.md — the agent guide is now HEXIS.md',
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it('keeps an AGENTS.md it cannot prove it wrote, and says so', async () => {
|
|
846
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
847
|
+
// Edited by the customer: the managed header is gone, so it is theirs.
|
|
848
|
+
const edited = `${CUSTOMER_GUIDE}\nMentions HEXIS.md already.\n`;
|
|
849
|
+
await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': edited });
|
|
850
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
851
|
+
|
|
852
|
+
for (const branch of PROTECTED) {
|
|
853
|
+
const dir = await checkout(branch);
|
|
854
|
+
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8')), branch).toBe(edited);
|
|
855
|
+
expect(await git(dir, ['log', '--format=%B', '-1']), branch).toContain('Keep AGENTS.md');
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('leaves a root AGENTS.md that is a symbolic link exactly where it points', async () => {
|
|
860
|
+
// Following the link would read the managed header at the other end and
|
|
861
|
+
// delete the customer's entry — or, with the pointer on, write our
|
|
862
|
+
// sentence into a file nobody asked us to edit.
|
|
863
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
864
|
+
await seedUpstream({ ...(await scaffoldWithoutGuide()), 'conventions/ours.md': await template('AGENTS.md') });
|
|
865
|
+
// Committed as a link, so every checkout the step makes carries one.
|
|
866
|
+
const seeding = await checkout(DEFAULT_BRANCH);
|
|
867
|
+
await fs.symlink('conventions/ours.md', path.join(seeding, 'AGENTS.md'));
|
|
868
|
+
await git(seeding, ['add', '-A']);
|
|
869
|
+
await git(seeding, ['commit', '-m', 'link AGENTS.md at our own file']);
|
|
870
|
+
await git(seeding, ['push', 'origin', DEFAULT_BRANCH]);
|
|
871
|
+
|
|
872
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
873
|
+
|
|
874
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
875
|
+
expect((await fs.lstat(path.join(dir, 'AGENTS.md'))).isSymbolicLink()).toBe(true);
|
|
876
|
+
expect(await fs.readlink(path.join(dir, 'AGENTS.md'))).toBe('conventions/ours.md');
|
|
877
|
+
// The file at the other end is untouched — neither removed nor appended to.
|
|
878
|
+
expect(norm(await fs.readFile(path.join(dir, 'conventions/ours.md'), 'utf8'))).toBe(
|
|
879
|
+
norm(await template('AGENTS.md')),
|
|
880
|
+
);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it("keeps an operator's own AGENTS.md ignore rule while dropping the platform's", async () => {
|
|
884
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
885
|
+
const scaffold = await scaffoldWithoutGuide();
|
|
886
|
+
// Their own rule, written by hand, nowhere near the template's slot.
|
|
887
|
+
scaffold['.bevelignore'] = '# mine\nAGENTS.md\nMyStuff/\n';
|
|
888
|
+
scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
|
|
889
|
+
await seedUpstream(scaffold);
|
|
890
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
891
|
+
|
|
892
|
+
const text = norm(
|
|
893
|
+
await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
|
|
894
|
+
);
|
|
895
|
+
expect(text).toContain('# mine\nAGENTS.md\nMyStuff/');
|
|
896
|
+
expect(text.split('\n').map((l) => l.trim())).toContain('HEXIS.md');
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it("keeps an operator's rule that merely happens to sit under .gitattributes", async () => {
|
|
900
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
901
|
+
const scaffold = await scaffoldWithoutGuide();
|
|
902
|
+
// Hand-written, and by coincidence in the position the template's own
|
|
903
|
+
// rule used to occupy. One matching line above is not provenance — the
|
|
904
|
+
// block the template wrote is not there, so the rule is theirs.
|
|
905
|
+
scaffold['.bevelignore'] = '# ours\n.gitattributes\nAGENTS.md\nMyStuff/\n';
|
|
906
|
+
scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
|
|
907
|
+
await seedUpstream(scaffold);
|
|
908
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
909
|
+
|
|
910
|
+
const text = norm(
|
|
911
|
+
await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
|
|
912
|
+
);
|
|
913
|
+
expect(text).toContain('# ours\n.gitattributes\nAGENTS.md\nMyStuff/');
|
|
914
|
+
expect(text.split('\n').map((l) => l.trim())).toContain('HEXIS.md');
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
it("drops the bare rule an earlier release seeded in the template's hygiene block", async () => {
|
|
918
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
919
|
+
const scaffold = await scaffoldWithoutGuide();
|
|
920
|
+
// The `.bevelignore` of a knowledge base seeded before the rule had a
|
|
921
|
+
// comment above it: bare, at the end of the template's own block.
|
|
922
|
+
scaffold['.bevelignore'] =
|
|
923
|
+
'# Repo hygiene files that clutter the tree without being node content.\n' +
|
|
924
|
+
'.gitignore\n.gitattributes\nAGENTS.md\nroles.yaml\n';
|
|
925
|
+
scaffold['AGENTS.md'] = `${CUSTOMER_GUIDE}\nSee HEXIS.md.\n`;
|
|
926
|
+
await seedUpstream(scaffold);
|
|
927
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
928
|
+
|
|
929
|
+
const lines = norm(
|
|
930
|
+
await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'),
|
|
931
|
+
)
|
|
932
|
+
.split('\n')
|
|
933
|
+
.map((l) => l.trim());
|
|
934
|
+
// The platform's line goes; the block above it is content and stays.
|
|
935
|
+
expect(lines).not.toContain('AGENTS.md');
|
|
936
|
+
expect(lines).toContain('.gitattributes');
|
|
937
|
+
expect(lines).toContain('.gitignore');
|
|
938
|
+
expect(lines).toContain('HEXIS.md');
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
describe('the pointer in the customer\'s own AGENTS.md', () => {
|
|
942
|
+
/** Boot once with the guide renamed, against a root `AGENTS.md` holding `theirs`. */
|
|
943
|
+
async function bootWith(theirs: string | null, link = true): Promise<string> {
|
|
944
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
945
|
+
const scaffold = await scaffoldWithoutGuide();
|
|
946
|
+
if (theirs !== null) scaffold['AGENTS.md'] = theirs;
|
|
947
|
+
await seedUpstream(scaffold);
|
|
948
|
+
await makeRunner([new TemplateFilesStep(new NodeFs(), [], () => link)]).runAll();
|
|
949
|
+
return checkout(DEFAULT_BRANCH);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const SENTENCE = agentsFilePointerSentence('HEXIS.md');
|
|
953
|
+
|
|
954
|
+
it('appends the sentence when the guide is not mentioned, and says so', async () => {
|
|
955
|
+
const dir = await bootWith(CUSTOMER_GUIDE);
|
|
956
|
+
const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
|
|
957
|
+
// Their bytes, then a blank line, then exactly the shared sentence.
|
|
958
|
+
expect(text).toBe(`${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${SENTENCE}\n`);
|
|
959
|
+
expect(await git(dir, ['log', '--format=%B', '-1'])).toContain(
|
|
960
|
+
'Add a pointer to HEXIS.md at the end of AGENTS.md',
|
|
961
|
+
);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* The sentence spells a punctuated name twice and neither spelling is
|
|
966
|
+
* the raw one — the label is escaped, the destination percent-encoded.
|
|
967
|
+
* A second boot must still see the guide as mentioned, or it appends
|
|
968
|
+
* another copy, and the boot after that a third.
|
|
969
|
+
*/
|
|
970
|
+
it('appends once and only once, even when the name is nothing but punctuation', async () => {
|
|
971
|
+
const guide = 'Our [Agent] Guide (v2).md';
|
|
972
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: guide });
|
|
973
|
+
await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': CUSTOMER_GUIDE });
|
|
974
|
+
const boot = () => makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
975
|
+
|
|
976
|
+
await boot();
|
|
977
|
+
const once = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'));
|
|
978
|
+
expect(once).toBe(
|
|
979
|
+
`${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${agentsFilePointerSentence(guide)}\n`,
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
const head = (await git(await checkout(DEFAULT_BRANCH), ['rev-parse', 'HEAD'])).trim();
|
|
983
|
+
await boot();
|
|
984
|
+
const after = await checkout(DEFAULT_BRANCH);
|
|
985
|
+
expect(norm(await fs.readFile(path.join(after, 'AGENTS.md'), 'utf8'))).toBe(once);
|
|
986
|
+
// And the second boot committed nothing at all.
|
|
987
|
+
expect((await git(after, ['rev-parse', 'HEAD'])).trim()).toBe(head);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* The guide's name is a setting, and a setting can be changed twice. The
|
|
992
|
+
* sentence the last rename wrote points at a file that is no longer
|
|
993
|
+
* there, and the new name is nowhere in the text — so "is it mentioned?"
|
|
994
|
+
* says no and an unaimed sentence would be joined by a second one.
|
|
995
|
+
*/
|
|
996
|
+
it('aims the sentence it already wrote at the new guide, rather than adding another', async () => {
|
|
997
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
998
|
+
await seedUpstream({ ...(await scaffoldWithoutGuide()), 'AGENTS.md': CUSTOMER_GUIDE });
|
|
999
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
1000
|
+
expect(norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), 'AGENTS.md'), 'utf8'))).toContain(
|
|
1001
|
+
agentsFilePointerSentence('HEXIS.md'),
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
// The admin renames the guide a second time.
|
|
1005
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'GUIDE.md' });
|
|
1006
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
1007
|
+
|
|
1008
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1009
|
+
const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
|
|
1010
|
+
// Their bytes, then ONE sentence, pointing at the guide that exists.
|
|
1011
|
+
expect(text).toBe(
|
|
1012
|
+
`${CUSTOMER_GUIDE.replace(/\n+$/, '')}\n\n${agentsFilePointerSentence('GUIDE.md')}\n`,
|
|
1013
|
+
);
|
|
1014
|
+
expect(text).not.toContain('HEXIS.md');
|
|
1015
|
+
expect(await git(dir, ['log', '--format=%B', '-1'])).toContain(
|
|
1016
|
+
'Point the sentence in AGENTS.md at GUIDE.md',
|
|
1017
|
+
);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('writes nothing when the name appears anywhere in the text, in any wording', async () => {
|
|
1021
|
+
const theirs = `${CUSTOMER_GUIDE}\nThe platform keeps its own notes in HEXIS.md; read those too.\n`;
|
|
1022
|
+
const dir = await bootWith(theirs);
|
|
1023
|
+
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(theirs);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
it('writes nothing while the setting is off', async () => {
|
|
1027
|
+
const dir = await bootWith(CUSTOMER_GUIDE, false);
|
|
1028
|
+
expect(norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'))).toBe(CUSTOMER_GUIDE);
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
it('never creates an AGENTS.md for the pointer', async () => {
|
|
1032
|
+
const dir = await bootWith(null);
|
|
1033
|
+
expect(await exists(dir, 'AGENTS.md')).toBe(false);
|
|
1034
|
+
expect(await exists(dir, 'HEXIS.md')).toBe(true);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it('never appends to a file the platform wrote — it removes that one instead', async () => {
|
|
1038
|
+
const dir = await bootWith(await template('AGENTS.md'));
|
|
1039
|
+
expect(await exists(dir, 'AGENTS.md')).toBe(false);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('does nothing at all under the default name', async () => {
|
|
1043
|
+
const theirs = `${CUSTOMER_GUIDE}`;
|
|
1044
|
+
// The default guide IS AGENTS.md, so the platform owns that file and
|
|
1045
|
+
// the pointer question never arises: the drifted copy is replaced.
|
|
1046
|
+
await seedUpstream({ ...(await fullScaffold()), 'AGENTS.md': theirs });
|
|
1047
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
1048
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1049
|
+
const text = norm(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf8'));
|
|
1050
|
+
expect(text).toBe(norm(await template('AGENTS.md')));
|
|
1051
|
+
expect(text).not.toContain(agentsFilePointerSentence('AGENTS.md'));
|
|
1052
|
+
});
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
213
1055
|
});
|
|
214
1056
|
|
|
215
1057
|
describe('RolesYamlStep', () => {
|
|
216
1058
|
it('generates roles.yaml with the configured admins on every protected branch', async () => {
|
|
217
1059
|
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
218
|
-
await makeRunner([new RolesYamlStep(['admin@example.com'])]).runAll();
|
|
1060
|
+
await makeRunner([new RolesYamlStep(new NodeFs(), ['admin@example.com'])]).runAll();
|
|
219
1061
|
|
|
220
1062
|
for (const b of PROTECTED) {
|
|
221
1063
|
const dir = await checkout(b);
|
|
@@ -233,7 +1075,7 @@ describe('RolesYamlStep', () => {
|
|
|
233
1075
|
// skip-if-present check, reporting success over a KB whose access roster
|
|
234
1076
|
// cannot be read. Fail closed instead.
|
|
235
1077
|
await seedUpstream({ 'roles.yaml/placeholder.txt': 'squatter' });
|
|
236
|
-
await expect(makeRunner([new RolesYamlStep(['admin@example.com'])]).runAll()).rejects.toThrow(
|
|
1078
|
+
await expect(makeRunner([new RolesYamlStep(new NodeFs(), ['admin@example.com'])]).runAll()).rejects.toThrow(
|
|
237
1079
|
/"roles\.yaml" on branch "current-company-state" exists but is not a regular file \(directory\)/,
|
|
238
1080
|
);
|
|
239
1081
|
});
|
|
@@ -241,7 +1083,7 @@ describe('RolesYamlStep', () => {
|
|
|
241
1083
|
it('declares a skip when the file is missing and no admins are configured', async () => {
|
|
242
1084
|
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
243
1085
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
244
|
-
await makeRunner([new RolesYamlStep([])]).runAll(); // resolves — a declared skip, not a failure
|
|
1086
|
+
await makeRunner([new RolesYamlStep(new NodeFs(), [])]).runAll(); // resolves — a declared skip, not a failure
|
|
245
1087
|
|
|
246
1088
|
expect(warn.mock.calls.some((c) => String(c[0]).includes('roles-yaml: skipped'))).toBe(true);
|
|
247
1089
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
@@ -287,16 +1129,437 @@ describe('buildSeedTree', () => {
|
|
|
287
1129
|
|
|
288
1130
|
const dest = path.join(root, 'seed-dest');
|
|
289
1131
|
await fs.mkdir(dest, { recursive: true });
|
|
290
|
-
const generated = await buildSeedTree(templateDir, [], ['admin@example.com'])(dest);
|
|
1132
|
+
const generated = await buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest);
|
|
291
1133
|
|
|
292
1134
|
expect(await exists(dest, '.git')).toBe(false);
|
|
293
1135
|
expect(await exists(dest, 'docs/.git')).toBe(false);
|
|
294
1136
|
expect(await fs.readFile(path.join(dest, 'docs/guide.md'), 'utf8')).toBe('guide');
|
|
295
1137
|
expect(await fs.readFile(path.join(dest, 'access.md'), 'utf8')).toBe('policy');
|
|
296
1138
|
// The generated paths — what the runner force-adds past a template .gitignore.
|
|
297
|
-
expect(generated.sort()).toEqual(['KnowledgeBase/.gitkeep', 'Plugins/.gitkeep', 'roles.yaml']);
|
|
1139
|
+
expect(generated.sort()).toEqual(['KnowledgeBase/.gitkeep', 'Plugins/.gitkeep', 'Skills/.gitkeep', 'roles.yaml']);
|
|
298
1140
|
expect(await exists(dest, 'roles.yaml')).toBe(true);
|
|
299
1141
|
});
|
|
1142
|
+
|
|
1143
|
+
it('seeds the layout in effect when it seeds, not the one it was composed under', async () => {
|
|
1144
|
+
const templateDir = path.join(root, 'seed-template-layout');
|
|
1145
|
+
await fs.mkdir(templateDir, { recursive: true });
|
|
1146
|
+
await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
|
|
1147
|
+
// Composed at boot, under the defaults…
|
|
1148
|
+
const seed = buildSeedTree(new NodeFs(), templateDir, [], ['admin@example.com']);
|
|
1149
|
+
// …and run after the completing save applied the admin's names.
|
|
1150
|
+
configureKbLayout({ knowledgeBaseDir: 'Docs', skillsDir: 'skills', pluginsDir: 'Plugins' });
|
|
1151
|
+
const dest = path.join(root, 'seed-dest-layout');
|
|
1152
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1153
|
+
const generated = await seed(dest);
|
|
1154
|
+
expect(generated.sort()).toEqual(['Docs/.gitkeep', 'Plugins/.gitkeep', 'roles.yaml', 'skills/.gitkeep']);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* An empty remote seeded by a deployment that renamed its guide. Done in the
|
|
1159
|
+
* SEED rather than left to the top-up step, which would otherwise lay down
|
|
1160
|
+
* `AGENTS.md` and delete it one commit later — two commits saying opposite
|
|
1161
|
+
* things about a knowledge base nobody has used yet.
|
|
1162
|
+
*/
|
|
1163
|
+
it('seeds the packaged guide under the configured name, and no AGENTS.md beside it', async () => {
|
|
1164
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
1165
|
+
const dest = path.join(root, 'seed-dest-guide');
|
|
1166
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1167
|
+
await buildSeedTree(new NodeFs(), TEMPLATE_DIR, [], ['admin@example.com'])(dest);
|
|
1168
|
+
expect(await exists(dest, 'AGENTS.md')).toBe(false);
|
|
1169
|
+
const guide = norm(await fs.readFile(path.join(dest, 'HEXIS.md'), 'utf8'));
|
|
1170
|
+
expect(guide).toContain('`HEXIS.md`');
|
|
1171
|
+
expect(guide).not.toContain('{{');
|
|
1172
|
+
// And the ignore file hides the name it actually seeded.
|
|
1173
|
+
const ignore = norm(await fs.readFile(path.join(dest, '.bevelignore'), 'utf8'))
|
|
1174
|
+
.split('\n')
|
|
1175
|
+
.map((l) => l.trim());
|
|
1176
|
+
expect(ignore).toContain('HEXIS.md');
|
|
1177
|
+
expect(ignore).not.toContain('AGENTS.md');
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
it('lets the managed guide win a template that carries a root file of the same name', async () => {
|
|
1181
|
+
// A custom KB_TEMPLATE_DIR that happens to ship `HEXIS.md` at its root, on
|
|
1182
|
+
// a deployment that named its guide `HEXIS.md`: two walk entries, one
|
|
1183
|
+
// destination. Whichever the walk reached last used to decide what an
|
|
1184
|
+
// empty deployment was seeded with. The managed guide decides.
|
|
1185
|
+
const customTemplate = path.join(root, 'custom-template-guide-clash');
|
|
1186
|
+
await fs.cp(TEMPLATE_DIR, customTemplate, { recursive: true });
|
|
1187
|
+
await fs.writeFile(path.join(customTemplate, 'HEXIS.md'), '# Not the platform guide\n', 'utf8');
|
|
1188
|
+
|
|
1189
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: 'HEXIS.md' });
|
|
1190
|
+
const dest = path.join(root, 'seed-dest-guide-clash');
|
|
1191
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1192
|
+
await buildSeedTree(new NodeFs(), customTemplate, [], ['admin@example.com'])(dest);
|
|
1193
|
+
|
|
1194
|
+
const guide = norm(await fs.readFile(path.join(dest, 'HEXIS.md'), 'utf8'));
|
|
1195
|
+
expect(guide).toContain('`HEXIS.md`');
|
|
1196
|
+
expect(guide).not.toContain('Not the platform guide');
|
|
1197
|
+
expect(await exists(dest, 'AGENTS.md')).toBe(false);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
it('names a template that is not there — a missing directory is a broken build, not an empty seed', async () => {
|
|
1201
|
+
const dest = path.join(root, 'seed-dest-missing');
|
|
1202
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1203
|
+
await expect(buildSeedTree(new NodeFs(),path.join(root, 'no-such-template'), [], ['admin@example.com'])(dest)).rejects.toThrow(
|
|
1204
|
+
/KB template ".*no-such-template" is not a directory/,
|
|
1205
|
+
);
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it('seeds a file the template links to, and refuses a link to anything else', async () => {
|
|
1209
|
+
const templateDir = path.join(root, 'seed-template-links');
|
|
1210
|
+
const elsewhere = path.join(root, 'elsewhere-template');
|
|
1211
|
+
await fs.mkdir(path.join(templateDir, 'docs'), { recursive: true });
|
|
1212
|
+
await fs.mkdir(elsewhere, { recursive: true });
|
|
1213
|
+
await fs.writeFile(path.join(elsewhere, 'shared.md'), 'shared', 'utf8');
|
|
1214
|
+
await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
|
|
1215
|
+
await fs.symlink(path.join(elsewhere, 'shared.md'), path.join(templateDir, 'docs', 'linked.md'), 'file');
|
|
1216
|
+
|
|
1217
|
+
const dest = path.join(root, 'seed-dest-links');
|
|
1218
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1219
|
+
await buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest);
|
|
1220
|
+
// The link's CONTENT, under the link's own name, as a real file.
|
|
1221
|
+
expect(await fs.readFile(path.join(dest, 'docs/linked.md'), 'utf8')).toBe('shared');
|
|
1222
|
+
expect((await fs.lstat(path.join(dest, 'docs/linked.md'))).isSymbolicLink()).toBe(false);
|
|
1223
|
+
|
|
1224
|
+
// A link to nothing is a broken template, named as such…
|
|
1225
|
+
await fs.symlink(path.join(root, 'nowhere.md'), path.join(templateDir, 'dangling.md'), 'file');
|
|
1226
|
+
const dest2 = path.join(root, 'seed-dest-links-2');
|
|
1227
|
+
await fs.mkdir(dest2, { recursive: true });
|
|
1228
|
+
await expect(buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest2)).rejects.toThrow(
|
|
1229
|
+
/KB template entry "dangling.md" links to nothing/,
|
|
1230
|
+
);
|
|
1231
|
+
// …and so is a link to a folder, named for what it is.
|
|
1232
|
+
await fs.rm(path.join(templateDir, 'dangling.md'));
|
|
1233
|
+
await fs.symlink(elsewhere, path.join(templateDir, 'dirlink'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
1234
|
+
const dest3 = path.join(root, 'seed-dest-links-3');
|
|
1235
|
+
await fs.mkdir(dest3, { recursive: true });
|
|
1236
|
+
await expect(buildSeedTree(new NodeFs(),templateDir, [], ['admin@example.com'])(dest3)).rejects.toThrow(
|
|
1237
|
+
/KB template entry "dirlink" links to a directory/,
|
|
1238
|
+
);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it('seeds a template file whose name collides with Object.prototype', async () => {
|
|
1242
|
+
// The packable-spelling lookup is keyed by FILENAME. Asked about a file
|
|
1243
|
+
// called `constructor` an object map answers from the prototype and hands
|
|
1244
|
+
// a function to path.join, failing the seed of a whole knowledge base
|
|
1245
|
+
// with a type error that names nothing.
|
|
1246
|
+
const templateDir = path.join(root, 'proto-template');
|
|
1247
|
+
await fs.mkdir(templateDir, { recursive: true });
|
|
1248
|
+
await fs.writeFile(path.join(templateDir, 'access.md'), 'policy', 'utf8');
|
|
1249
|
+
await fs.writeFile(path.join(templateDir, 'constructor'), 'not a function', 'utf8');
|
|
1250
|
+
await fs.writeFile(path.join(templateDir, 'toString'), 'nor this', 'utf8');
|
|
1251
|
+
|
|
1252
|
+
const dest = path.join(root, 'proto-dest');
|
|
1253
|
+
await fs.mkdir(dest, { recursive: true });
|
|
1254
|
+
await buildSeedTree(new NodeFs(), templateDir, [], ['admin@example.com'])(dest);
|
|
1255
|
+
expect(await fs.readFile(path.join(dest, 'constructor'), 'utf8')).toBe('not a function');
|
|
1256
|
+
expect(await fs.readFile(path.join(dest, 'toString'), 'utf8')).toBe('nor this');
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
describe('PluginManifestsStep', () => {
|
|
1261
|
+
it('writes plugin.json into legacy plugin folders on every branch, and leaves scopes and real plugins alone', async () => {
|
|
1262
|
+
const scaffold = await fullScaffold();
|
|
1263
|
+
await seedUpstream({
|
|
1264
|
+
...scaffold,
|
|
1265
|
+
// Legacy shapes: access.md only; mcp.json only; a bare skill tree.
|
|
1266
|
+
'Plugins/GTM/access.md': '---\n---\nread:\n - everyone\n',
|
|
1267
|
+
'Plugins/GTM/outreach/SKILL.md': '---\ndescription: x\n---\n',
|
|
1268
|
+
'Plugins/Servers/mcp.json': '{"mcpServers":{}}',
|
|
1269
|
+
'Plugins/Bare/deploy/SKILL.md': '---\ndescription: y\n---\n',
|
|
1270
|
+
// Already a plugin, both shapes.
|
|
1271
|
+
'Plugins/Modern/plugin.json': '{"name":"modern"}',
|
|
1272
|
+
'Plugins/functional/cluster/example/plugin.bundle.json': '{"name":"example"}',
|
|
1273
|
+
// A scope with rules of its own above a bundle: not a plugin.
|
|
1274
|
+
'Plugins/functional/access.md': '---\n---\nread:\n - everyone\n',
|
|
1275
|
+
// Nothing plugin-shaped at all.
|
|
1276
|
+
'Plugins/notes/README.md': 'just a folder',
|
|
1277
|
+
// A legacy plugin whose vendored dependency ships a manifest: the walk
|
|
1278
|
+
// does not enter node_modules, so the folder is the plugin, not a
|
|
1279
|
+
// grouping folder over one.
|
|
1280
|
+
'Plugins/Vendored/access.md': '---\n---\nread:\n - everyone\n',
|
|
1281
|
+
'Plugins/Vendored/node_modules/some-pkg/plugin.json': '{"name":"some-pkg"}',
|
|
1282
|
+
// A plain folder whose only SKILL.md is vendored: not a plugin either.
|
|
1283
|
+
'Plugins/deps/node_modules/some-pkg/SKILL.md': '---\ndescription: z\n---\n',
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
await makeRunner([new PluginManifestsStep(new NodeFs())]).runAll();
|
|
1287
|
+
|
|
1288
|
+
for (const branch of PROTECTED) {
|
|
1289
|
+
const dir = await checkout(branch);
|
|
1290
|
+
for (const legacy of ['GTM', 'Servers', 'Bare', 'Vendored']) {
|
|
1291
|
+
const manifest = JSON.parse(await fs.readFile(path.join(dir, `Plugins/${legacy}/plugin.json`), 'utf8'));
|
|
1292
|
+
expect(manifest.name).toBe(legacy.toLowerCase());
|
|
1293
|
+
}
|
|
1294
|
+
expect(await exists(dir, 'Plugins/functional/plugin.json')).toBe(false);
|
|
1295
|
+
expect(await exists(dir, 'Plugins/notes/plugin.json')).toBe(false);
|
|
1296
|
+
expect(await exists(dir, 'Plugins/deps/plugin.json')).toBe(false);
|
|
1297
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/Modern/plugin.json'), 'utf8')).toBe('{"name":"modern"}');
|
|
1298
|
+
}
|
|
1299
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1300
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1301
|
+
expect(log).toContain('Add plugin manifests to 4 legacy plugin folders');
|
|
1302
|
+
expect(log).toContain('Plugins/GTM: plugin.json written');
|
|
1303
|
+
|
|
1304
|
+
// Idempotent: nothing left to write on the next boot.
|
|
1305
|
+
await makeRunner([new PluginManifestsStep(new NodeFs())]).runAll();
|
|
1306
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
1307
|
+
expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one migration commit
|
|
1308
|
+
});
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
describe('PluginDisplayNamesStep', () => {
|
|
1312
|
+
/**
|
|
1313
|
+
* The one-time repair behind the display-name rule: a manifest is now the
|
|
1314
|
+
* only thing any reader consults for what a plugin is called, so every
|
|
1315
|
+
* manifest written under the old rule — which stored the field only when
|
|
1316
|
+
* the folder was spelled differently from the identifier — has to be told
|
|
1317
|
+
* what its plugin was already called, or it would appear to rename itself
|
|
1318
|
+
* on the boot that introduces the rule.
|
|
1319
|
+
*/
|
|
1320
|
+
it('writes the folder\'s spelling into the manifests that lack the field, and touches nothing else', async () => {
|
|
1321
|
+
const scaffold = await fullScaffold();
|
|
1322
|
+
await seedUpstream({
|
|
1323
|
+
...scaffold,
|
|
1324
|
+
// Spelled differently from its identifier and saying nothing about it:
|
|
1325
|
+
// exactly the plugin that would start showing `sales-team`.
|
|
1326
|
+
'Plugins/Sales Team/plugin.json': '{\n "name": "sales-team",\n "version": "1.2.0"\n}\n',
|
|
1327
|
+
'Plugins/Sales Team/access.md': '---\n---\nread:\n - everyone\n',
|
|
1328
|
+
// Nested just as deep as discovery looks.
|
|
1329
|
+
'Plugins/Teams/EU Field/plugin.json': '{"name":"eu-field"}',
|
|
1330
|
+
// Already spelled as its identifier: called the same thing under both
|
|
1331
|
+
// rules, so there is nothing the folder knows that the manifest does not.
|
|
1332
|
+
'Plugins/imported-tools/plugin.json': '{"name":"imported-tools"}',
|
|
1333
|
+
// Already carries the field — the author's own answer, never overwritten,
|
|
1334
|
+
// even though the folder disagrees with both of its names.
|
|
1335
|
+
'Plugins/Ops Desk/plugin.json': '{"name":"ops","displayName":"Operations"}',
|
|
1336
|
+
// A bundle: a foreign repository's file this platform reads, never writes.
|
|
1337
|
+
'Plugins/functional/cluster/example/plugin.bundle.json': '{"name":"example"}',
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
|
|
1341
|
+
|
|
1342
|
+
for (const branch of PROTECTED) {
|
|
1343
|
+
const dir = await checkout(branch);
|
|
1344
|
+
// Backfilled from the folder — what the plugin was called the moment
|
|
1345
|
+
// before the boot — with everything else in the manifest kept, and the
|
|
1346
|
+
// field where the renderer puts it.
|
|
1347
|
+
const sales = await readJson(dir, 'Plugins/Sales Team/plugin.json');
|
|
1348
|
+
expect(sales).toEqual({ name: 'sales-team', displayName: 'Sales Team', version: '1.2.0' });
|
|
1349
|
+
expect(Object.keys(sales)).toEqual(['name', 'displayName', 'version']);
|
|
1350
|
+
expect(await readJson(dir, 'Plugins/Teams/EU Field/plugin.json')).toEqual({
|
|
1351
|
+
name: 'eu-field',
|
|
1352
|
+
displayName: 'EU Field',
|
|
1353
|
+
});
|
|
1354
|
+
// Untouched, byte for byte.
|
|
1355
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/imported-tools/plugin.json'), 'utf8')).toBe(
|
|
1356
|
+
'{"name":"imported-tools"}',
|
|
1357
|
+
);
|
|
1358
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/Ops Desk/plugin.json'), 'utf8')).toBe(
|
|
1359
|
+
'{"name":"ops","displayName":"Operations"}',
|
|
1360
|
+
);
|
|
1361
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/functional/cluster/example/plugin.bundle.json'), 'utf8')).toBe(
|
|
1362
|
+
'{"name":"example"}',
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1367
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1368
|
+
expect(log).toContain('Record the display names of 2 plugins in their manifests');
|
|
1369
|
+
expect(log).toContain('Plugins/Sales Team: displayName "Sales Team"');
|
|
1370
|
+
|
|
1371
|
+
// Idempotent: every manifest it would touch now carries the field.
|
|
1372
|
+
await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
|
|
1373
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
1374
|
+
expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one backfill commit
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* The three shapes the plain "lacks the field, folder differs" reading gets
|
|
1379
|
+
* wrong: a field that is present and says nothing, a manifest naming the
|
|
1380
|
+
* folder's own spelling without being an identifier, and a `plugin.json`
|
|
1381
|
+
* that is not this platform's file to write.
|
|
1382
|
+
*/
|
|
1383
|
+
it('repairs a blank field, leaves a manifest that already shows its folder, and never writes through a link', async () => {
|
|
1384
|
+
const scaffold = await fullScaffold();
|
|
1385
|
+
await seedUpstream(
|
|
1386
|
+
{
|
|
1387
|
+
...scaffold,
|
|
1388
|
+
// Present but BLANK. The new reader falls through it to `name` where
|
|
1389
|
+
// the old one fell through to the folder, so leaving it be is leaving
|
|
1390
|
+
// the rename this step exists to prevent: blank is absent, and repaired.
|
|
1391
|
+
'Plugins/Growth Team/plugin.json': '{"name":"growth-team","displayName":" "}',
|
|
1392
|
+
// A `name` that is no identifier but IS the folder's spelling: the
|
|
1393
|
+
// identity is the slug `my-plugin`, and what people see is already
|
|
1394
|
+
// "My Plugin". The folder knows nothing the manifest does not.
|
|
1395
|
+
'Plugins/My Plugin/plugin.json': '{"name":"My Plugin"}',
|
|
1396
|
+
// An own `__proto__` key — `JSON.parse` makes it one. Copied as DATA:
|
|
1397
|
+
// assignment would have set the object's prototype instead, dropping
|
|
1398
|
+
// the field and letting a prototype `displayName` stand in for the
|
|
1399
|
+
// one being written.
|
|
1400
|
+
'Plugins/Odd One/plugin.json': '{"name":"odd-one","__proto__":{"displayName":"from the prototype"}}',
|
|
1401
|
+
// A bundle makes this folder a plugin; the `plugin.json` beside it is
|
|
1402
|
+
// a LINK, which discovery does not count as a native manifest.
|
|
1403
|
+
'Plugins/Kit Pro/plugin.bundle.json': '{"name":"kit"}',
|
|
1404
|
+
'vendor-manifest.json': '{"name":"kit"}',
|
|
1405
|
+
},
|
|
1406
|
+
{ 'Plugins/Kit Pro/plugin.json': '../../vendor-manifest.json' },
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
|
|
1410
|
+
|
|
1411
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1412
|
+
expect(await readJson(dir, 'Plugins/Growth Team/plugin.json')).toEqual({
|
|
1413
|
+
name: 'growth-team',
|
|
1414
|
+
displayName: 'Growth Team',
|
|
1415
|
+
});
|
|
1416
|
+
// Untouched, byte for byte.
|
|
1417
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/My Plugin/plugin.json'), 'utf8')).toBe('{"name":"My Plugin"}');
|
|
1418
|
+
expect(await fs.readFile(path.join(dir, 'Plugins/Odd One/plugin.json'), 'utf8')).toBe(
|
|
1419
|
+
'{\n "name": "odd-one",\n "displayName": "Odd One",\n "__proto__": {\n "displayName": "from the prototype"\n }\n}\n',
|
|
1420
|
+
);
|
|
1421
|
+
// Still a link, and the file it points at is still the vendor's own.
|
|
1422
|
+
expect((await fs.lstat(path.join(dir, 'Plugins/Kit Pro/plugin.json'))).isSymbolicLink()).toBe(true);
|
|
1423
|
+
expect(await fs.readFile(path.join(dir, 'vendor-manifest.json'), 'utf8')).toBe('{"name":"kit"}');
|
|
1424
|
+
|
|
1425
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1426
|
+
expect(log).toContain('Record the display names of 2 plugins in their manifests');
|
|
1427
|
+
expect(log).toContain('Plugins/Growth Team: displayName "Growth Team"');
|
|
1428
|
+
expect(log).not.toContain('Kit Pro');
|
|
1429
|
+
|
|
1430
|
+
// Idempotent over all of it: the next boot finds nothing to write.
|
|
1431
|
+
await makeRunner([new PluginDisplayNamesStep(new NodeFs())]).runAll();
|
|
1432
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
1433
|
+
expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2'); // init + one backfill commit
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
it('leaves a plugin the manifests step just made alone — that manifest already says it', async () => {
|
|
1437
|
+
const scaffold = await fullScaffold();
|
|
1438
|
+
await seedUpstream({
|
|
1439
|
+
...scaffold,
|
|
1440
|
+
// Legacy: no manifest at all. The manifests step renders one, which the
|
|
1441
|
+
// renderer has already given both names; the backfill then finds nothing.
|
|
1442
|
+
'Plugins/Sales Team/access.md': '---\n---\nread:\n - everyone\n',
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
await makeRunner([new PluginManifestsStep(new NodeFs()), new PluginDisplayNamesStep(new NodeFs())]).runAll();
|
|
1446
|
+
|
|
1447
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1448
|
+
expect(await readJson(dir, 'Plugins/Sales Team/plugin.json')).toEqual({
|
|
1449
|
+
$schema: 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json',
|
|
1450
|
+
name: 'sales-team',
|
|
1451
|
+
displayName: 'Sales Team',
|
|
1452
|
+
});
|
|
1453
|
+
// ONE commit — the backfill declared nothing of its own to caption.
|
|
1454
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1455
|
+
expect(log).toContain('Add plugin manifests to a legacy plugin folder');
|
|
1456
|
+
expect(log).not.toContain('Record the display name');
|
|
1457
|
+
});
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
describe('PersonalSpacesStep', () => {
|
|
1461
|
+
// A personal folder as the previous template seeded it: the owner's grants
|
|
1462
|
+
// and nothing else — private only while nothing above grants read.
|
|
1463
|
+
const LEGACY_PERSONAL =
|
|
1464
|
+
'---\nread:\n - Ali Vega <ali@x.io>\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>\n---\nread: []\n';
|
|
1465
|
+
|
|
1466
|
+
it("adds `deny everyone` to every personal space's read rules on every branch, keeping the rest, once", async () => {
|
|
1467
|
+
const scaffold = await fullScaffold();
|
|
1468
|
+
const seed = await seedUpstream({
|
|
1469
|
+
...scaffold,
|
|
1470
|
+
'Plugins/personal-u1/plugin.json': '{"name":"personal-u1"}',
|
|
1471
|
+
'Plugins/personal-u1/access.md': LEGACY_PERSONAL,
|
|
1472
|
+
// A space someone already closed (its folder rules stay; only the
|
|
1473
|
+
// file's own block, which said nothing, gets the same statement), one
|
|
1474
|
+
// its owner opened on purpose (a denial beside that grant would be
|
|
1475
|
+
// overruled and read as a contradiction — and the file stays open
|
|
1476
|
+
// with the folder), and a shared plugin: untouched.
|
|
1477
|
+
'Plugins/personal-u2/access.md': '---\nread: []\n---\nread:\n - deny everyone\n - Bo <bo@x.io>\n',
|
|
1478
|
+
'Plugins/personal-u3/access.md': '---\nread: []\n---\nread:\n - everyone\nowner:\n - Cy <cy@x.io>\n',
|
|
1479
|
+
// Entries that say nothing about READ — a denial under write — leave the
|
|
1480
|
+
// space open to an inherited `read: everyone`, so it is closed like any
|
|
1481
|
+
// other.
|
|
1482
|
+
'Plugins/personal-u4/access.md': '---\nread: []\n---\nwrite:\n - deny everyone\nowner:\n - Di <di@x.io>\n',
|
|
1483
|
+
// The old seed's frontmatter grants are carried into the body ONLY where
|
|
1484
|
+
// the body has no word on that person: a denial someone wrote there
|
|
1485
|
+
// stands, and is not overridden by the older grant.
|
|
1486
|
+
'Plugins/personal-u5/access.md': '---\nread:\n - Ed <ed@x.io>\nowner:\n - Ed <ed@x.io>\n---\nread:\n - deny Ed <ed@x.io>\n',
|
|
1487
|
+
// A folder whose read denial is overruled by `write: everyone` (a
|
|
1488
|
+
// same-scope grant wins, and write folds into read) is OPEN: the file
|
|
1489
|
+
// stays open with it, and nothing is written.
|
|
1490
|
+
'Plugins/personal-u6/access.md': '---\nread: []\n---\nread:\n - deny everyone\nwrite:\n - everyone\nowner:\n - Fy <fy@x.io>\n',
|
|
1491
|
+
// `download: everyone` is a deliberate opening now that download folds
|
|
1492
|
+
// into read: the space is already settled OPEN, so nothing is written —
|
|
1493
|
+
// the same treatment `write: everyone` gets above.
|
|
1494
|
+
'Plugins/personal-u7/access.md': '---\nread: []\n---\ndownload:\n - everyone\nowner:\n - Gi <gi@x.io>\n',
|
|
1495
|
+
'Plugins/GTM/plugin.json': '{"name":"gtm"}',
|
|
1496
|
+
'Plugins/GTM/access.md': '---\nread:\n - everyone\n---\nread:\n - Ali Vega <ali@x.io>\n',
|
|
1497
|
+
});
|
|
1498
|
+
// A draft carrying a personal space too: the step visits every branch,
|
|
1499
|
+
// not only the protected ones.
|
|
1500
|
+
await git(seed, ['checkout', '-b', 'ali/draft']);
|
|
1501
|
+
await git(seed, ['push', 'origin', 'ali/draft']);
|
|
1502
|
+
|
|
1503
|
+
await makeRunner([new PersonalSpacesStep(new NodeFs())]).runAll();
|
|
1504
|
+
|
|
1505
|
+
for (const branch of [...PROTECTED, 'ali/draft']) {
|
|
1506
|
+
const dir = await checkout(branch);
|
|
1507
|
+
const closed = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u1/access.md'), 'utf8'));
|
|
1508
|
+
// The body — which governs the folder — now denies everyone (Admin
|
|
1509
|
+
// included: nobody is named) and carries the owner's grants so they
|
|
1510
|
+
// can still read, write and own their space; the old seed's
|
|
1511
|
+
// frontmatter grants stay, joined by the same denial, so the file says
|
|
1512
|
+
// of itself what the folder says.
|
|
1513
|
+
expect(closed).toBe(
|
|
1514
|
+
'---\nread:\n - Ali Vega <ali@x.io>\n - deny everyone\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>\n---\n' +
|
|
1515
|
+
'read:\n - Ali Vega <ali@x.io>\n - deny everyone\n\nwrite:\n - Ali Vega <ali@x.io>\nowner:\n - Ali Vega <ali@x.io>',
|
|
1516
|
+
);
|
|
1517
|
+
// Already closed: the folder rules are untouched, the empty file block
|
|
1518
|
+
// now states the same — the denial, then the people the folder admits.
|
|
1519
|
+
expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u2/access.md'), 'utf8'))).toBe(
|
|
1520
|
+
'---\nread:\n - deny everyone\n - Bo <bo@x.io>\n---\nread:\n - deny everyone\n - Bo <bo@x.io>\n',
|
|
1521
|
+
);
|
|
1522
|
+
expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u3/access.md'), 'utf8'))).toBe(
|
|
1523
|
+
'---\nread: []\n---\nread:\n - everyone\nowner:\n - Cy <cy@x.io>\n',
|
|
1524
|
+
);
|
|
1525
|
+
const u4 = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u4/access.md'), 'utf8'));
|
|
1526
|
+
expect(u4.slice(0, u4.indexOf('\n---\n', 4))).toBe('---\nread:\n - deny everyone\n - Di <di@x.io>');
|
|
1527
|
+
expect(u4.trimEnd().endsWith('\nread:\n - deny everyone')).toBe(true);
|
|
1528
|
+
expect(u4).toContain('write:\n - deny everyone');
|
|
1529
|
+
expect(u4).toContain('owner:\n - Di <di@x.io>');
|
|
1530
|
+
const u5 = norm(await fs.readFile(path.join(dir, 'Plugins/personal-u5/access.md'), 'utf8'));
|
|
1531
|
+
const u5Body = u5.slice(u5.indexOf('\n---\n', 4) + 5);
|
|
1532
|
+
// Ed's read denial stands — the frontmatter's read grant is NOT carried
|
|
1533
|
+
// beside it — while the owner grant, which the body said nothing about, is.
|
|
1534
|
+
expect(u5Body.match(/read:\n((?: - [^\n]*\n)*)/)![1]).toBe(' - deny Ed <ed@x.io>\n - deny everyone\n');
|
|
1535
|
+
expect(u5Body).toMatch(/owner:\n - Ed <ed@x.io>/);
|
|
1536
|
+
// The file block keeps Ed's grant as written and gains the denial.
|
|
1537
|
+
expect(u5.slice(0, u5.indexOf('\n---\n', 4))).toBe('---\nread:\n - Ed <ed@x.io>\n - deny everyone\nowner:\n - Ed <ed@x.io>');
|
|
1538
|
+
expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u6/access.md'), 'utf8'))).toBe(
|
|
1539
|
+
'---\nread: []\n---\nread:\n - deny everyone\nwrite:\n - everyone\nowner:\n - Fy <fy@x.io>\n',
|
|
1540
|
+
);
|
|
1541
|
+
// Untouched: `download: everyone` settles read as GRANTED (download folds
|
|
1542
|
+
// into read), so the step reads the space as opened on purpose.
|
|
1543
|
+
expect(norm(await fs.readFile(path.join(dir, 'Plugins/personal-u7/access.md'), 'utf8'))).toBe(
|
|
1544
|
+
'---\nread: []\n---\ndownload:\n - everyone\nowner:\n - Gi <gi@x.io>\n',
|
|
1545
|
+
);
|
|
1546
|
+
expect(norm(await fs.readFile(path.join(dir, 'Plugins/GTM/access.md'), 'utf8'))).toBe(
|
|
1547
|
+
'---\nread:\n - everyone\n---\nread:\n - Ali Vega <ali@x.io>\n',
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1551
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1552
|
+
expect(log).toContain('Keep 4 personal spaces private');
|
|
1553
|
+
expect(log).toContain('Plugins/personal-u1/access.md: read denies everyone');
|
|
1554
|
+
expect(log).toContain('Plugins/personal-u2/access.md: read denies everyone');
|
|
1555
|
+
expect(log).toContain('Plugins/personal-u4/access.md: read denies everyone');
|
|
1556
|
+
expect(log).toContain('Plugins/personal-u5/access.md: read denies everyone');
|
|
1557
|
+
|
|
1558
|
+
// Idempotent: the next boot finds every space already closed.
|
|
1559
|
+
await makeRunner([new PersonalSpacesStep(new NodeFs())]).runAll();
|
|
1560
|
+
const again = await checkout(DEFAULT_BRANCH);
|
|
1561
|
+
expect((await git(again, ['rev-list', '--count', 'HEAD'])).trim()).toBe('2');
|
|
1562
|
+
});
|
|
300
1563
|
});
|
|
301
1564
|
|
|
302
1565
|
describe('GroupsToPluginsStep', () => {
|
|
@@ -322,7 +1585,7 @@ describe('GroupsToPluginsStep', () => {
|
|
|
322
1585
|
await git(seed, ['checkout', '-b', 'alice/draft']);
|
|
323
1586
|
await git(seed, ['push', 'origin', 'alice/draft']);
|
|
324
1587
|
|
|
325
|
-
await makeRunner([new GroupsToPluginsStep()]).runAll();
|
|
1588
|
+
await makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll();
|
|
326
1589
|
|
|
327
1590
|
for (const b of [DEFAULT_BRANCH, 'alice/draft']) {
|
|
328
1591
|
const dir = await checkout(b);
|
|
@@ -337,10 +1600,11 @@ describe('GroupsToPluginsStep', () => {
|
|
|
337
1600
|
expect(mcp.mcpServers.notion).toEqual({ type: 'streamable-http', url: 'https://mcp.notion.com/mcp' });
|
|
338
1601
|
const manifest = JSON.parse(await fs.readFile(path.join(dir, 'Plugins/GTM/plugin.json'), 'utf8'));
|
|
339
1602
|
expect(manifest.name).toBe('gtm');
|
|
340
|
-
// The rename's companion edit: the stale ignore rule
|
|
1603
|
+
// The rename's companion edit: the stale ignore rule is retired — and
|
|
1604
|
+
// not replaced, since the plugins root is drawn by the sidebar now.
|
|
341
1605
|
const ignore = await fs.readFile(path.join(dir, '.bevelignore'), 'utf8');
|
|
342
|
-
expect(ignore).toContain('Plugins/');
|
|
343
1606
|
expect(ignore).not.toContain('Groups/');
|
|
1607
|
+
expect(ignore.split('\n').map((l) => l.trim())).not.toContain('Plugins/');
|
|
344
1608
|
}
|
|
345
1609
|
|
|
346
1610
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
@@ -356,7 +1620,7 @@ describe('GroupsToPluginsStep', () => {
|
|
|
356
1620
|
// silently skipping a branch whose reserved root cannot be a plugin tree
|
|
357
1621
|
// (and on a draft nothing later would ever report it).
|
|
358
1622
|
await seedUpstream({ Plugins: 'i am a file, not a folder' });
|
|
359
|
-
await expect(makeRunner([new GroupsToPluginsStep()]).runAll()).rejects.toThrow(
|
|
1623
|
+
await expect(makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll()).rejects.toThrow(
|
|
360
1624
|
/"Plugins" exists but is not a directory/,
|
|
361
1625
|
);
|
|
362
1626
|
});
|
|
@@ -368,7 +1632,7 @@ describe('GroupsToPluginsStep', () => {
|
|
|
368
1632
|
'Plugins/B/y.md': 'new',
|
|
369
1633
|
});
|
|
370
1634
|
await makeRunner([
|
|
371
|
-
new GroupsToPluginsStep(),
|
|
1635
|
+
new GroupsToPluginsStep(new NodeFs()),
|
|
372
1636
|
// A later step dirties the branch so the refusal note surfaces in the commit.
|
|
373
1637
|
step('dirty', async (ctx) => {
|
|
374
1638
|
for (const b of await ctx.protectedBranches()) {
|
|
@@ -398,11 +1662,7 @@ describe('GroupsToPluginsStep', () => {
|
|
|
398
1662
|
*/
|
|
399
1663
|
describe('GroupsToPluginsStep — migration edge cases', () => {
|
|
400
1664
|
async function migrate(): Promise<void> {
|
|
401
|
-
await makeRunner([new GroupsToPluginsStep()]).runAll();
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async function readJson(dir: string, rel: string): Promise<Record<string, any>> {
|
|
405
|
-
return JSON.parse(await fs.readFile(path.join(dir, rel), 'utf8'));
|
|
1665
|
+
await makeRunner([new GroupsToPluginsStep(new NodeFs())]).runAll();
|
|
406
1666
|
}
|
|
407
1667
|
|
|
408
1668
|
/** What the runner's `partial` warning carried — the named refusals. */
|
|
@@ -570,6 +1830,36 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
|
|
|
570
1830
|
});
|
|
571
1831
|
});
|
|
572
1832
|
|
|
1833
|
+
it('leaves a plain folder under the root alone — a .gitkeep is not a plugin, and a grouping folder holding one is not either', async () => {
|
|
1834
|
+
await seedUpstream({
|
|
1835
|
+
'Plugins/GTM/access.md': 'write:\n - Admin\n',
|
|
1836
|
+
// Made with "New folder" in the tree: nothing plugin-shaped in it.
|
|
1837
|
+
'Plugins/TestFolder/.gitkeep': '',
|
|
1838
|
+
// A grouping folder with a plugin INSIDE: a manifest on the folder
|
|
1839
|
+
// would hide the plugin beneath it from every catalog.
|
|
1840
|
+
'Plugins/Teams/Agent Made/plugin.json': '{"name":"agent-made"}',
|
|
1841
|
+
// A legacy plugin whose vendored dependency happens to ship a
|
|
1842
|
+
// manifest: `node_modules` is nothing to the walk, so the folder is
|
|
1843
|
+
// still the plugin it was.
|
|
1844
|
+
'Plugins/Vendored/access.md': 'write:\n - Admin\n',
|
|
1845
|
+
'Plugins/Vendored/node_modules/some-pkg/plugin.json': '{"name":"some-pkg"}',
|
|
1846
|
+
// A folder whose ONLY plugin-shaped entry is one the walk skips. The
|
|
1847
|
+
// migration's own sweep never moves a dot-entry, so counting it as
|
|
1848
|
+
// legacy content wrote a manifest for a folder and then moved nothing
|
|
1849
|
+
// into it — and the manifests step, reading a walk that had already
|
|
1850
|
+
// dropped it, disagreed about the same folder.
|
|
1851
|
+
'Plugins/HiddenOnly/.hidden.tool': 'name: hidden\n',
|
|
1852
|
+
});
|
|
1853
|
+
await migrate();
|
|
1854
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1855
|
+
expect(await exists(dir, 'Plugins/GTM/plugin.json')).toBe(true);
|
|
1856
|
+
expect(await exists(dir, 'Plugins/TestFolder/plugin.json')).toBe(false);
|
|
1857
|
+
expect(await exists(dir, 'Plugins/Teams/plugin.json')).toBe(false);
|
|
1858
|
+
expect(await exists(dir, 'Plugins/Teams/Agent Made/plugin.json')).toBe(true);
|
|
1859
|
+
expect(await exists(dir, 'Plugins/Vendored/plugin.json')).toBe(true);
|
|
1860
|
+
expect(await exists(dir, 'Plugins/HiddenOnly/plugin.json')).toBe(false);
|
|
1861
|
+
});
|
|
1862
|
+
|
|
573
1863
|
it('leaves a personal folder a valid plugin', async () => {
|
|
574
1864
|
await seedUpstream({
|
|
575
1865
|
'Groups/personal-u-123/access.md': 'write:\n - Ali <ali@x.com>\n',
|
|
@@ -581,7 +1871,7 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
|
|
|
581
1871
|
});
|
|
582
1872
|
|
|
583
1873
|
describe('the .bevelignore root rule', () => {
|
|
584
|
-
it('
|
|
1874
|
+
it('retires the rule with the rename — nothing takes its place — preserving every other line', async () => {
|
|
585
1875
|
await seedUpstream({
|
|
586
1876
|
'Groups/GTM/access.md': 'write:\n - Admin\n',
|
|
587
1877
|
'.bevelignore': '# mine\nGroups/\nMy-Own-Rule/\n',
|
|
@@ -589,48 +1879,115 @@ describe('GroupsToPluginsStep — migration edge cases', () => {
|
|
|
589
1879
|
await migrate();
|
|
590
1880
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
591
1881
|
const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'));
|
|
592
|
-
expect(ignore.
|
|
593
|
-
expect(ignore).not.toContain('Groups/');
|
|
594
|
-
expect(ignore).toContain('# mine');
|
|
595
|
-
expect(ignore).toContain('My-Own-Rule/');
|
|
1882
|
+
expect(ignore).toBe('# mine\nMy-Own-Rule/\n');
|
|
596
1883
|
});
|
|
597
1884
|
|
|
598
|
-
it('
|
|
1885
|
+
it('drops EVERY exact Groups/ line, keeping the lines between them', async () => {
|
|
599
1886
|
await seedUpstream({
|
|
600
1887
|
'Groups/GTM/access.md': 'write:\n - Admin\n',
|
|
601
1888
|
'.bevelignore': 'Groups/\n# keep\nGroups/\n',
|
|
602
1889
|
});
|
|
603
1890
|
await migrate();
|
|
604
1891
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
605
|
-
|
|
606
|
-
expect(lines.filter((l) => l.trim() === 'Plugins/')).toHaveLength(1);
|
|
607
|
-
expect(lines.filter((l) => l.trim() === 'Groups/')).toHaveLength(0);
|
|
608
|
-
expect(lines).toContain('# keep');
|
|
1892
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# keep\n');
|
|
609
1893
|
});
|
|
610
1894
|
|
|
611
|
-
it('
|
|
1895
|
+
it('retires a Plugins/ line beside it too — both root rules are stale for the same reason', async () => {
|
|
612
1896
|
await seedUpstream({
|
|
613
1897
|
'Groups/GTM/access.md': 'write:\n - Admin\n',
|
|
614
|
-
'.bevelignore': '
|
|
1898
|
+
'.bevelignore': '# mine\nGroups/\nPlugins/\n!Plugins/\n',
|
|
1899
|
+
});
|
|
1900
|
+
await migrate();
|
|
1901
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1902
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# mine\n!Plugins/\n');
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1905
|
+
it('retires the rules on a branch with neither root — nothing to migrate there, and the rules are as stale', async () => {
|
|
1906
|
+
await seedUpstream({
|
|
1907
|
+
'marker.txt': 'seeded',
|
|
1908
|
+
'.bevelignore': 'Plugins/\nGroups/\nkeep/\n',
|
|
1909
|
+
});
|
|
1910
|
+
await migrate();
|
|
1911
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1912
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('keep/\n');
|
|
1913
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1914
|
+
expect(log).toContain('.bevelignore: Groups/, Plugins/ dropped');
|
|
1915
|
+
});
|
|
1916
|
+
|
|
1917
|
+
it('names the commit for what happened: a migrated branch that only lost the rules was not reorganised', async () => {
|
|
1918
|
+
await seedUpstream({
|
|
1919
|
+
'Plugins/GTM/plugin.json': '{"name":"gtm"}',
|
|
1920
|
+
'Plugins/GTM/access.md': 'write:\n - Admin\n',
|
|
1921
|
+
'.bevelignore': 'Plugins/\n',
|
|
615
1922
|
});
|
|
616
1923
|
await migrate();
|
|
617
1924
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
expect(
|
|
1925
|
+
const log = (await git(dir, ['log', '-1', '--format=%B'])).trim();
|
|
1926
|
+
expect(log.split('\n')[0]).toBe('Retire the stale Groups/ and Plugins/ ignore rules');
|
|
1927
|
+
expect(log).not.toContain('Reorganise');
|
|
621
1928
|
});
|
|
622
1929
|
|
|
623
|
-
it('
|
|
1930
|
+
it('retires the rules on a run that does not rename — a branch migrated by an earlier release, a draft included', async () => {
|
|
1931
|
+
// An earlier release renamed `Groups/` to `Plugins/` in the ignore file
|
|
1932
|
+
// of every branch it migrated; the template step retires that line on
|
|
1933
|
+
// the protected branches only, so this step does it wherever it goes.
|
|
624
1934
|
await seedUpstream({
|
|
625
1935
|
'Plugins/GTM/access.md': 'write:\n - Admin\n',
|
|
626
1936
|
'Plugins/GTM/outreach/SKILL.md': '# Outreach\n',
|
|
627
|
-
'.bevelignore': '
|
|
1937
|
+
'.bevelignore': 'AGENTS.md\nPlugins/\n',
|
|
628
1938
|
});
|
|
629
1939
|
await migrate();
|
|
630
1940
|
const dir = await checkout(DEFAULT_BRANCH);
|
|
631
|
-
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('
|
|
632
|
-
// The run still reorganised the folder — the rule alone was off-limits.
|
|
1941
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('AGENTS.md\n');
|
|
633
1942
|
expect(await exists(dir, 'Plugins/GTM/skills/outreach/SKILL.md')).toBe(true);
|
|
1943
|
+
// Idempotent: a second run declares nothing.
|
|
1944
|
+
await migrate();
|
|
1945
|
+
expect(norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'))).toBe('AGENTS.md\n');
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
it("takes a platform comment above a stale rule with it, and the blank line that opened the block — the template step's own tidy-up, on a branch it never visits", async () => {
|
|
1949
|
+
await seedUpstream({
|
|
1950
|
+
'Plugins/GTM/access.md': 'write:\n - Admin\n',
|
|
1951
|
+
'.bevelignore':
|
|
1952
|
+
'# mine\n.git/\n\n# Added by the platform: the conventions doc is not node content.\nPlugins/\n# The shared-skills root is rendered by the Skills & Tools app, like Groups/.\nGroups/\n',
|
|
1953
|
+
});
|
|
1954
|
+
await migrate();
|
|
1955
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1956
|
+
expect(norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8'))).toBe('# mine\n.git/\n');
|
|
634
1957
|
});
|
|
1958
|
+
|
|
1959
|
+
it('fails the run when the ignore file cannot be read — a hole is not "no file"', async () => {
|
|
1960
|
+
// A directory where the file should be: readable as neither. Treating
|
|
1961
|
+
// that as absence would leave a possible `Plugins/` rule in place and
|
|
1962
|
+
// report the migration done.
|
|
1963
|
+
await seedUpstream({
|
|
1964
|
+
'Plugins/GTM/access.md': 'write:\n - Admin\n',
|
|
1965
|
+
'.bevelignore/keep': '',
|
|
1966
|
+
});
|
|
1967
|
+
await expect(migrate()).rejects.toThrow(/EISDIR/);
|
|
1968
|
+
});
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
/**
|
|
1973
|
+
* The guide's rule in `.bevelignore` is a gitignore PATTERN, and the name rules
|
|
1974
|
+
* admit characters gitignore reads as syntax: written bare, `#Guide.md` is a
|
|
1975
|
+
* comment and hides nothing. Rendered escaped, the rule names exactly the file.
|
|
1976
|
+
*/
|
|
1977
|
+
describe('TemplateFilesStep: a guide named with gitignore syntax', () => {
|
|
1978
|
+
it('escapes the name in the ignore rule it writes, and keeps it escaped on the next boot', async () => {
|
|
1979
|
+
configureKbLayout({ ...DEFAULT_KB_LAYOUT, agentsFile: '#Guide[1].md' });
|
|
1980
|
+
await seedUpstream({ 'marker.txt': 'seeded' });
|
|
1981
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
1982
|
+
const dir = await checkout(DEFAULT_BRANCH);
|
|
1983
|
+
const ignore = norm(await fs.readFile(path.join(dir, '.bevelignore'), 'utf8')).split('\n').map((l) => l.trim());
|
|
1984
|
+
const escaped = String.raw`\#Guide\[1\].md`;
|
|
1985
|
+
expect(ignore).toContain(escaped);
|
|
1986
|
+
expect(ignore).not.toContain('#Guide[1].md');
|
|
1987
|
+
expect(await exists(dir, '#Guide[1].md')).toBe(true);
|
|
1988
|
+
// A second boot sees the escaped rule as present and adds no second line.
|
|
1989
|
+
await makeRunner([new TemplateFilesStep(new NodeFs())]).runAll();
|
|
1990
|
+
const again = norm(await fs.readFile(path.join(await checkout(DEFAULT_BRANCH), '.bevelignore'), 'utf8'));
|
|
1991
|
+
expect(again.split('\n').filter((l) => l.trim() === escaped)).toHaveLength(1);
|
|
635
1992
|
});
|
|
636
1993
|
});
|