@workerstack/repo 0.0.0-stage → 0.1.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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +0 -3
- package/agents/global-settings/antigravity.json +3 -0
- package/agents/global-settings/claude.json +6 -0
- package/agents/global-settings/codex.toml +2 -0
- package/agents/global-settings/cursor.json +3 -0
- package/agents/global-settings/grok.toml +36 -0
- package/agents/global-settings/manifest.json +36 -0
- package/agents/grok/hooks/orchestrate-parent.json +60 -0
- package/agents/grok/hooks/rtk.json +16 -0
- package/agents/grok/hooks/rulesync.json +3 -0
- package/agents/grok/roles/scout.toml +5 -0
- package/agents/hooks.jsonc +67 -0
- package/agents/mcp-policy.jsonc +16 -0
- package/agents/rules/overview.md +21 -0
- package/agents/settings/claude.json +5 -0
- package/agents/skills/ask/SKILL.md +13 -0
- package/agents/skills/auto/SKILL.md +13 -0
- package/agents/skills/away/SKILL.md +19 -0
- package/agents/skills/blast-radius/SKILL.md +54 -0
- package/agents/skills/bro/SKILL.md +9 -0
- package/agents/skills/browser/SKILL.md +16 -0
- package/agents/skills/clasp/SKILL.md +11 -0
- package/agents/skills/commit/SKILL.md +35 -0
- package/agents/skills/commit-and-push/SKILL.md +9 -0
- package/agents/skills/commit-push/SKILL.md +59 -0
- package/agents/skills/commit-push-deploy/SKILL.md +74 -0
- package/agents/skills/commit-push-release/SKILL.md +75 -0
- package/agents/skills/correct/SKILL.md +39 -0
- package/agents/skills/do/SKILL.md +21 -0
- package/agents/skills/explain/SKILL.md +13 -0
- package/agents/skills/google-workspace/SKILL.md +12 -0
- package/agents/skills/handoff/SKILL.md +81 -0
- package/agents/skills/implement-plan/SKILL.md +29 -0
- package/agents/skills/implement-roadmap/SKILL.md +27 -0
- package/agents/skills/jev/SKILL.md +17 -0
- package/agents/skills/listen/SKILL.md +15 -0
- package/agents/skills/pickup/SKILL.md +58 -0
- package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
- package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
- package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
- package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
- package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
- package/agents/skills/principle-experience-first/SKILL.md +22 -0
- package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
- package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
- package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
- package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
- package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
- package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
- package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
- package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
- package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
- package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
- package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
- package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
- package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
- package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
- package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
- package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
- package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
- package/agents/skills/review-plan/SKILL.md +28 -0
- package/agents/skills/review-roadmap/SKILL.md +28 -0
- package/agents/skills/show-me/SKILL.md +21 -0
- package/agents/skills/speak/SKILL.md +15 -0
- package/agents/skills/su/SKILL.md +11 -0
- package/agents/skills/teach/SKILL.md +25 -0
- package/agents/skills/tmp/SKILL.md +54 -0
- package/agents/skills/typescript-best-practices/SKILL.md +32 -0
- package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
- package/agents/skills/unslop/SKILL.md +62 -0
- package/agents/skills/write-plan/SKILL.md +33 -0
- package/agents/skills/write-roadmap/SKILL.md +66 -0
- package/agents/subagents/implementer-deep.md +57 -0
- package/agents/subagents/implementer-fast.md +57 -0
- package/agents/subagents/orchestrator.md +43 -0
- package/agents/subagents/researcher.md +47 -0
- package/agents/subagents/scout.md +44 -0
- package/bin/repo.ts +4 -0
- package/cli/actionlint-default.test.ts +38 -0
- package/cli/actionlint-download.test.ts +162 -0
- package/cli/actionlint-real.test.ts +33 -0
- package/cli/actionlint.constants.ts +43 -0
- package/cli/actionlint.test.ts +108 -0
- package/cli/actionlint.ts +178 -0
- package/cli/bin.test.ts +54 -0
- package/cli/bin.ts +40 -0
- package/cli/check-changelog.test.ts +40 -0
- package/cli/check-changelog.ts +12 -0
- package/cli/check-comments.test.ts +157 -0
- package/cli/check-comments.ts +123 -0
- package/cli/check-env-modules.test.ts +85 -0
- package/cli/check-env-modules.ts +56 -0
- package/cli/check-magic.test.ts +139 -0
- package/cli/check-magic.ts +120 -0
- package/cli/check-manifests.constants.ts +4 -0
- package/cli/check-manifests.test.ts +210 -0
- package/cli/check-manifests.ts +71 -0
- package/cli/check-nightly.test.ts +103 -0
- package/cli/check-nightly.ts +37 -0
- package/cli/check-ratchets-missing.test.ts +46 -0
- package/cli/check-ratchets-mutation.test.ts +49 -0
- package/cli/check-ratchets.constants.ts +20 -0
- package/cli/check-ratchets.test.ts +190 -0
- package/cli/check-ratchets.ts +193 -0
- package/cli/check-rejections.test.ts +220 -0
- package/cli/check-rejections.ts +153 -0
- package/cli/check-reports.ts +37 -0
- package/cli/check-tsconfig.test.ts +507 -0
- package/cli/check-tsconfig.ts +205 -0
- package/cli/commands.test.ts +114 -0
- package/cli/commands.ts +218 -0
- package/cli/commit-changelog.ts +27 -0
- package/cli/commit-msg-changelog.test.ts +93 -0
- package/cli/commit-msg-range.test.ts +126 -0
- package/cli/commit-msg-red-proof.test.ts +147 -0
- package/cli/commit-msg-repo.test.ts +153 -0
- package/cli/commit-msg.test.ts +101 -0
- package/cli/commit-msg.ts +194 -0
- package/cli/commit-range.constants.ts +17 -0
- package/cli/commit-range.ts +57 -0
- package/cli/config-knip.test.ts +70 -0
- package/cli/config-nightly.test.ts +84 -0
- package/cli/config-tdd.test.ts +46 -0
- package/cli/config.test.ts +576 -0
- package/cli/config.ts +261 -0
- package/cli/conventional.ts +9 -0
- package/cli/coverage-config.ts +62 -0
- package/cli/coverage.constants.ts +6 -0
- package/cli/coverage.test.ts +199 -0
- package/cli/coverage.ts +127 -0
- package/cli/env.constants.ts +10 -0
- package/cli/env.test.ts +56 -0
- package/cli/env.ts +72 -0
- package/cli/fixtures/base-config.ts +13 -0
- package/cli/fixtures/gate-world.ts +48 -0
- package/cli/fixtures/knip-run.ts +38 -0
- package/cli/fixtures/mutate-fakes.ts +137 -0
- package/cli/fixtures/planted-repo.ts +50 -0
- package/cli/fixtures/publish-world.ts +71 -0
- package/cli/fixtures/red-proof-repo.ts +72 -0
- package/cli/fixtures/release-context.ts +15 -0
- package/cli/fixtures/release-world.ts +111 -0
- package/cli/fixtures/stub-bin.ts +40 -0
- package/cli/guard-diagnostics.test.ts +157 -0
- package/cli/guard-diagnostics.ts +195 -0
- package/cli/init.test.ts +487 -0
- package/cli/init.ts +275 -0
- package/cli/knip-check-ignores.test.ts +106 -0
- package/cli/knip-check-real.test.ts +65 -0
- package/cli/knip-check.constants.ts +46 -0
- package/cli/knip-check.test.ts +114 -0
- package/cli/knip-check.ts +169 -0
- package/cli/knip-schema.ts +67 -0
- package/cli/known-failures.constants.ts +22 -0
- package/cli/known-failures.test.ts +138 -0
- package/cli/known-failures.ts +131 -0
- package/cli/lint/as-needs-reason.ts +41 -0
- package/cli/lint/boundaries.ts +73 -0
- package/cli/lint/class-file.ts +39 -0
- package/cli/lint/config-file.ts +32 -0
- package/cli/lint/entry-file.ts +54 -0
- package/cli/lint/env-in-config.ts +66 -0
- package/cli/lint/filename-matches-export.ts +63 -0
- package/cli/lint/index-file.ts +22 -0
- package/cli/lint/index.ts +47 -0
- package/cli/lint/is-reason.ts +6 -0
- package/cli/lint/manifest.ts +46 -0
- package/cli/lint/names.ts +59 -0
- package/cli/lint/no-commented-out-code.ts +105 -0
- package/cli/lint/no-mock-of-subject.ts +64 -0
- package/cli/lint/no-module-side-effects.ts +94 -0
- package/cli/lint/no-module-state.ts +58 -0
- package/cli/lint/no-side-effect-import.ts +17 -0
- package/cli/lint/no-v8-ignore.ts +18 -0
- package/cli/lint/script-rules.ts +9 -0
- package/cli/lint/skip-on-platform.constants.ts +8 -0
- package/cli/lint/skip-on-platform.ts +118 -0
- package/cli/lint/todo-needs-id.ts +63 -0
- package/cli/lint/types-file.ts +28 -0
- package/cli/lint-schema.ts +32 -0
- package/cli/magic/fixtures/magic-repo.ts +64 -0
- package/cli/magic/magic-hit.ts +2 -0
- package/cli/magic/magic-nodes.ts +44 -0
- package/cli/magic/magic-numbers.test.ts +99 -0
- package/cli/magic/magic-numbers.ts +117 -0
- package/cli/magic/magic-strings.test.ts +38 -0
- package/cli/magic/magic-strings.ts +81 -0
- package/cli/main.test.ts +853 -0
- package/cli/main.ts +292 -0
- package/cli/mutate-args.ts +79 -0
- package/cli/mutate-check.test.ts +147 -0
- package/cli/mutate-deps.ts +42 -0
- package/cli/mutate-edges.test.ts +150 -0
- package/cli/mutate-files.test.ts +100 -0
- package/cli/mutate-files.ts +31 -0
- package/cli/mutate-floors.test.ts +91 -0
- package/cli/mutate-floors.ts +104 -0
- package/cli/mutate-real-deps.ts +57 -0
- package/cli/mutate-real.test.ts +96 -0
- package/cli/mutate-report.ts +96 -0
- package/cli/mutate-run.ts +128 -0
- package/cli/mutate-shards.ts +64 -0
- package/cli/mutate.constants.ts +67 -0
- package/cli/mutate.test.ts +182 -0
- package/cli/mutate.ts +64 -0
- package/cli/nested-vitest-flags.ts +8 -0
- package/cli/nightly-config.constants.ts +14 -0
- package/cli/nightly-config.ts +62 -0
- package/cli/prompt.test.ts +202 -0
- package/cli/prompt.ts +1 -0
- package/cli/red-proof-edges.test.ts +78 -0
- package/cli/red-proof-failures.test.ts +82 -0
- package/cli/red-proof-tree.test.ts +157 -0
- package/cli/red-proof-verdict.test.ts +44 -0
- package/cli/red-proof.constants.ts +14 -0
- package/cli/red-proof.ts +202 -0
- package/cli/release/changelog-parse.ts +83 -0
- package/cli/release/changelog.test.ts +141 -0
- package/cli/release/changelog.ts +106 -0
- package/cli/release/gate-refusals.test.ts +85 -0
- package/cli/release/gate.test.ts +63 -0
- package/cli/release/gate.ts +70 -0
- package/cli/release/git.ts +66 -0
- package/cli/release/github.test.ts +128 -0
- package/cli/release/github.ts +46 -0
- package/cli/release/list-dir.test.ts +11 -0
- package/cli/release/list-dir.ts +6 -0
- package/cli/release/main.test.ts +48 -0
- package/cli/release/main.ts +51 -0
- package/cli/release/manifest.test.ts +24 -0
- package/cli/release/manifest.ts +34 -0
- package/cli/release/pack.test.ts +107 -0
- package/cli/release/pack.ts +76 -0
- package/cli/release/plan.ts +127 -0
- package/cli/release/publish-run.test.ts +98 -0
- package/cli/release/publish.test.ts +127 -0
- package/cli/release/publish.ts +125 -0
- package/cli/release/refuse.ts +5 -0
- package/cli/release/release-messages.constants.ts +96 -0
- package/cli/release/release-refusals.test.ts +149 -0
- package/cli/release/release.constants.ts +128 -0
- package/cli/release/release.test.ts +199 -0
- package/cli/release/release.ts +145 -0
- package/cli/release/version.test.ts +118 -0
- package/cli/release/version.ts +69 -0
- package/cli/release/wait.test.ts +101 -0
- package/cli/release/wait.ts +95 -0
- package/cli/script-diagnostics.test.ts +76 -0
- package/cli/script-diagnostics.ts +62 -0
- package/cli/scripts/board.ts +519 -0
- package/cli/scripts/browser.ts +994 -0
- package/cli/scripts/change-set.ts +105 -0
- package/cli/scripts/check-globals.ts +199 -0
- package/cli/scripts/ci-skips.constants.ts +17 -0
- package/cli/scripts/ci-skips.ts +118 -0
- package/cli/scripts/ci-wait-run.ts +28 -0
- package/cli/scripts/ci-wait.constants.ts +44 -0
- package/cli/scripts/ci-wait.ts +175 -0
- package/cli/scripts/ci.constants.ts +15 -0
- package/cli/scripts/ci.ts +208 -0
- package/cli/scripts/clean.ts +363 -0
- package/cli/scripts/comments-step.ts +15 -0
- package/cli/scripts/doctor.ts +828 -0
- package/cli/scripts/emit-agent-shell-tools.ts +91 -0
- package/cli/scripts/emit-mcp-policy.ts +402 -0
- package/cli/scripts/folder-map.ts +353 -0
- package/cli/scripts/gate-fat-tools.ts +1095 -0
- package/cli/scripts/gitignore-guard.ts +338 -0
- package/cli/scripts/google-workspace.ts +779 -0
- package/cli/scripts/hook-command.ts +72 -0
- package/cli/scripts/hook-input.ts +178 -0
- package/cli/scripts/hook-io.constants.ts +5 -0
- package/cli/scripts/hook-io.ts +46 -0
- package/cli/scripts/hooks-union.ts +469 -0
- package/cli/scripts/interactive.ts +30 -0
- package/cli/scripts/jev.ts +420 -0
- package/cli/scripts/job.ts +934 -0
- package/cli/scripts/jsonc.ts +9 -0
- package/cli/scripts/lessons.ts +564 -0
- package/cli/scripts/lint-baseline.ts +233 -0
- package/cli/scripts/lint-main.ts +24 -0
- package/cli/scripts/lint-skips.ts +139 -0
- package/cli/scripts/lint.constants.ts +6 -0
- package/cli/scripts/lint.ts +142 -0
- package/cli/scripts/listen.ts +962 -0
- package/cli/scripts/managed-text.ts +47 -0
- package/cli/scripts/orchestrate-parent.ts +1155 -0
- package/cli/scripts/parse-need.ts +81 -0
- package/cli/scripts/persist-handoff.ts +228 -0
- package/cli/scripts/persist-implementation.ts +276 -0
- package/cli/scripts/persist-plan.ts +888 -0
- package/cli/scripts/persist-roadmap.ts +478 -0
- package/cli/scripts/persist-tmp.ts +177 -0
- package/cli/scripts/persist.ts +54 -0
- package/cli/scripts/phase-files.ts +121 -0
- package/cli/scripts/prompt.ts +38 -0
- package/cli/scripts/purge-global-mcps.ts +313 -0
- package/cli/scripts/rejections-step.ts +37 -0
- package/cli/scripts/rtk-grok.ts +102 -0
- package/cli/scripts/scrub-spawn-prompt.ts +103 -0
- package/cli/scripts/scrub-text-lib.ts +58 -0
- package/cli/scripts/session-map.ts +69 -0
- package/cli/scripts/setup.ts +862 -0
- package/cli/scripts/skill-conflicts.ts +82 -0
- package/cli/scripts/speak.ts +370 -0
- package/cli/scripts/strategy.ts +168 -0
- package/cli/scripts/sync-global-settings.ts +316 -0
- package/cli/scripts/turn-steps.ts +249 -0
- package/cli/scripts/turn.ts +157 -0
- package/cli/scripts/typecheck-step.ts +43 -0
- package/cli/scripts/vscode-tasks.ts +175 -0
- package/cli/scripts/workspace-root.ts +160 -0
- package/cli/scripts/worktree.ts +806 -0
- package/cli/spawn.test.ts +34 -0
- package/cli/spawn.ts +27 -0
- package/cli/staged-change.constants.ts +11 -0
- package/cli/staged-change.ts +79 -0
- package/cli/stryker-preload.test.ts +50 -0
- package/cli/stryker-preload.ts +9 -0
- package/cli/sync/agents/frontmatter.test.ts +197 -0
- package/cli/sync/agents/frontmatter.ts +140 -0
- package/cli/sync/agents/hooks.test.ts +517 -0
- package/cli/sync/agents/hooks.ts +401 -0
- package/cli/sync/agents/rules.test.ts +229 -0
- package/cli/sync/agents/rules.ts +84 -0
- package/cli/sync/agents/sources.test.ts +375 -0
- package/cli/sync/agents/sources.ts +244 -0
- package/cli/sync/agents/subagents.test.ts +149 -0
- package/cli/sync/agents/subagents.ts +158 -0
- package/cli/sync/agents-text.test.ts +31 -0
- package/cli/sync/agents-text.ts +36 -0
- package/cli/sync/fixtures/copied.README.md +35 -0
- package/cli/sync/fixtures/copied.tgz +0 -0
- package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
- package/cli/sync/fixtures/maps-ws.tgz +0 -0
- package/cli/sync/git-files.test.ts +340 -0
- package/cli/sync/git-files.ts +197 -0
- package/cli/sync/git-hooks.test.ts +94 -0
- package/cli/sync/golden.test.ts +15 -0
- package/cli/sync/hooks-mode.test.ts +99 -0
- package/cli/sync/hosts.ts +28 -0
- package/cli/sync/knip-config.constants.ts +4 -0
- package/cli/sync/knip-config.test.ts +147 -0
- package/cli/sync/knip-config.ts +108 -0
- package/cli/sync/manifest.test.ts +78 -0
- package/cli/sync/manifest.ts +60 -0
- package/cli/sync/maps.test.ts +304 -0
- package/cli/sync/maps.ts +297 -0
- package/cli/sync/migrate.test.ts +2000 -0
- package/cli/sync/migrate.ts +1104 -0
- package/cli/sync/nightly-jobs.test.ts +63 -0
- package/cli/sync/nightly.constants.ts +48 -0
- package/cli/sync/nightly.test.ts +100 -0
- package/cli/sync/nightly.ts +75 -0
- package/cli/sync/outputs.test.ts +351 -0
- package/cli/sync/outputs.ts +168 -0
- package/cli/sync/oxlint-config.test.ts +178 -0
- package/cli/sync/oxlint-config.ts +168 -0
- package/cli/sync/release-jobs.test.ts +29 -0
- package/cli/sync/release-reports.test.ts +31 -0
- package/cli/sync/release-workflow.constants.ts +61 -0
- package/cli/sync/release-workflow.test.ts +192 -0
- package/cli/sync/release-workflow.ts +86 -0
- package/cli/sync/release.constants.ts +93 -0
- package/cli/sync/release.test.ts +187 -0
- package/cli/sync/release.ts +112 -0
- package/cli/sync/repo-dir.test.ts +371 -0
- package/cli/sync/repo-dir.ts +205 -0
- package/cli/sync/standards.constants.ts +8 -0
- package/cli/sync/standards.test.ts +206 -0
- package/cli/sync/standards.ts +179 -0
- package/cli/sync/stock-history.json +2796 -0
- package/cli/sync/stock-history.test.ts +66 -0
- package/cli/sync/stock-history.ts +84 -0
- package/cli/sync/stock.test.ts +47 -0
- package/cli/sync/stock.ts +24 -0
- package/cli/sync/trees.test.ts +621 -0
- package/cli/sync/trees.ts +280 -0
- package/cli/sync/validate.test.ts +299 -0
- package/cli/sync/validate.ts +402 -0
- package/cli/sync/workspace.test.ts +180 -0
- package/cli/sync/workspace.ts +138 -0
- package/cli/sync/write.test.ts +341 -0
- package/cli/sync/write.ts +231 -0
- package/cli/sync.test.ts +1495 -0
- package/cli/sync.ts +360 -0
- package/cli/tdd-scope.test.ts +36 -0
- package/cli/tdd-scope.ts +41 -0
- package/cli/test-files.ts +16 -0
- package/cli/ts-program.ts +69 -0
- package/cli/tsconfig.constants.ts +8 -0
- package/cli/workerstack-config.types.ts +21 -0
- package/dist/cli/env.d.ts +10 -0
- package/dist/cli/env.js +64 -0
- package/dist/presets/temp-root-env.d.ts +10 -0
- package/dist/presets/temp-root-env.js +20 -0
- package/dist/presets/temp-root.constants.d.ts +6 -0
- package/dist/presets/temp-root.constants.js +6 -0
- package/dist/presets/temp-root.d.ts +2 -0
- package/dist/presets/temp-root.js +60 -0
- package/dist/presets/vitest.constants.d.ts +16 -0
- package/dist/presets/vitest.constants.js +16 -0
- package/dist/presets/vitest.d.ts +11 -0
- package/dist/presets/vitest.js +67 -0
- package/dist/presets/workers-plugin.d.ts +10 -0
- package/dist/presets/workers-plugin.js +54 -0
- package/package.json +141 -4
- package/references/INDEX.md +100 -0
- package/references/browser.md +66 -0
- package/references/cascade.md +15 -0
- package/references/clasp.md +21 -0
- package/references/cloudflare.md +36 -0
- package/references/google-workspace.md +79 -0
- package/references/hooks.md +103 -0
- package/references/implementation-format.md +119 -0
- package/references/jev.md +56 -0
- package/references/lessons.md +73 -0
- package/references/listen.md +80 -0
- package/references/map.md +107 -0
- package/references/output.md +47 -0
- package/references/plan-format.md +264 -0
- package/references/review-loop.md +48 -0
- package/references/roadmap-format.md +104 -0
- package/references/rtk.md +27 -0
- package/references/scope.md +76 -0
- package/references/setup.md +42 -0
- package/references/speak.md +59 -0
- package/tsconfig/base.json +28 -0
- package/tsconfig/bun.d.ts +1 -0
- package/tsconfig/bun.json +8 -0
- package/tsconfig/reset.d.ts +1 -0
- package/tsconfig/strict.json +12 -0
- package/tsconfig/web.json +7 -0
- package/tsconfig/worker.d.ts +1 -0
- package/tsconfig/worker.json +8 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** After the subagent generator (`cli/sync/agents/subagents.ts`): Claude repo body uses Bash; other targets keep run_terminal_command. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { isRecord } from "./hook-input.ts";
|
|
6
|
+
|
|
7
|
+
export type Frontmatter = { ok: true; fm: string; body: string } | { ok: false; error: string };
|
|
8
|
+
|
|
9
|
+
export interface EmitResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
check: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
drift?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SSOT_REL = "agents/subagents/scout.md";
|
|
17
|
+
const CLAUDE_SCOUT_REL = ".claude/agents/scout.md";
|
|
18
|
+
|
|
19
|
+
export const GENERIC_TOOL_LIST =
|
|
20
|
+
"read_file, grep, list_dir, run_terminal_command, write, search_replace, web_search, web_fetch";
|
|
21
|
+
export const CLAUDE_TOOL_LIST = "Read, Grep, Glob, Bash, Write, Edit, Web";
|
|
22
|
+
|
|
23
|
+
export function splitFrontmatter(text: unknown): Frontmatter {
|
|
24
|
+
const src = String(text ?? "");
|
|
25
|
+
if (!src.startsWith("---")) return { ok: false, error: "missing frontmatter" };
|
|
26
|
+
const rest = src.slice(3).replace(/^\r?\n/, "");
|
|
27
|
+
const m = rest.match(/\r?\n---\s*(?:\r?\n|$)/);
|
|
28
|
+
if (!m || m.index == null) return { ok: false, error: "missing frontmatter close" };
|
|
29
|
+
return { ok: true, fm: rest.slice(0, m.index), body: rest.slice(m.index + m[0].length) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toClaudeShellBody(body: unknown): string {
|
|
33
|
+
return String(body)
|
|
34
|
+
.split(GENERIC_TOOL_LIST)
|
|
35
|
+
.join(CLAUDE_TOOL_LIST)
|
|
36
|
+
.split("web_search/web_fetch")
|
|
37
|
+
.join("WebSearch/WebFetch")
|
|
38
|
+
.split("run_terminal_command")
|
|
39
|
+
.join("Bash");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readText(abs: string): string | null {
|
|
43
|
+
try {
|
|
44
|
+
return fs.readFileSync(abs, "utf8");
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (isRecord(err) && err.code === "ENOENT") return null;
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function joinMarkdown(fm: string, body: string): string {
|
|
52
|
+
const b = body.endsWith("\n") || body === "" ? body : `${body}\n`;
|
|
53
|
+
return `---\n${fm}\n---\n${b}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The scout source is read from `sourceRoot` (default `workspaceRoot`); the generated Claude scout under `workspaceRoot` is rewritten. */
|
|
57
|
+
export function emitAgentShellTools({
|
|
58
|
+
workspaceRoot,
|
|
59
|
+
sourceRoot,
|
|
60
|
+
check = false,
|
|
61
|
+
}: { workspaceRoot?: string; sourceRoot?: string; check?: boolean } = {}): EmitResult {
|
|
62
|
+
const ws = workspaceRoot || process.cwd();
|
|
63
|
+
const ssotAbs = path.join(sourceRoot || ws, SSOT_REL);
|
|
64
|
+
const claudeAbs = path.join(ws, CLAUDE_SCOUT_REL);
|
|
65
|
+
const ssotRaw = readText(ssotAbs);
|
|
66
|
+
if (ssotRaw == null) {
|
|
67
|
+
return { ok: false, check, error: `missing ${SSOT_REL}` };
|
|
68
|
+
}
|
|
69
|
+
const ssot = splitFrontmatter(ssotRaw);
|
|
70
|
+
if (!ssot.ok) {
|
|
71
|
+
return { ok: false, check, error: `ssot: ${ssot.error}` };
|
|
72
|
+
}
|
|
73
|
+
const claudeRaw = readText(claudeAbs);
|
|
74
|
+
if (claudeRaw == null) {
|
|
75
|
+
return { ok: false, check, error: `missing ${CLAUDE_SCOUT_REL}` };
|
|
76
|
+
}
|
|
77
|
+
const claude = splitFrontmatter(claudeRaw);
|
|
78
|
+
if (!claude.ok) {
|
|
79
|
+
return { ok: false, check, error: `claude repo: ${claude.error}` };
|
|
80
|
+
}
|
|
81
|
+
const next = joinMarkdown(claude.fm, toClaudeShellBody(ssot.body));
|
|
82
|
+
if (check) {
|
|
83
|
+
if (claudeRaw !== next) {
|
|
84
|
+
return { ok: false, check: true, error: `drift: ${CLAUDE_SCOUT_REL}`, drift: [CLAUDE_SCOUT_REL] };
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, check: true, drift: [] };
|
|
87
|
+
}
|
|
88
|
+
fs.mkdirSync(path.dirname(claudeAbs), { recursive: true });
|
|
89
|
+
fs.writeFileSync(claudeAbs, next);
|
|
90
|
+
return { ok: true, check: false };
|
|
91
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Emit host MCP deny-default artifacts from agents/mcp-policy.jsonc. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
6
|
+
import { isRecord } from "./hook-input.ts";
|
|
7
|
+
import { stripJsonc } from "./jsonc.ts";
|
|
8
|
+
import { readEnv } from "../env.ts";
|
|
9
|
+
|
|
10
|
+
type JsonObject = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
/** The parts of a validated policy the emitters read. */
|
|
13
|
+
interface Policy {
|
|
14
|
+
allow: string[];
|
|
15
|
+
servers: unknown;
|
|
16
|
+
denyPlugins: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface EmitResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
check: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
drift?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const POLICY_REL = "agents/mcp-policy.jsonc";
|
|
27
|
+
const GROK_REL = ".grok/config.toml";
|
|
28
|
+
const CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
29
|
+
const MCP_JSON_REL = ".mcp.json";
|
|
30
|
+
const CODEX_REL = ".codex/config.toml";
|
|
31
|
+
const AGENTS_MCP_REL = ".agents/mcp_config.json";
|
|
32
|
+
const AGENT_IDS = ["orchestrator", "scout", "researcher", "implementer-fast", "implementer-deep"];
|
|
33
|
+
const MCP_DENY = "mcp__*";
|
|
34
|
+
/** Orchestrator is the main agent only (Grok, Antigravity); Claude has no such subagent to deny. */
|
|
35
|
+
const GROK_HEADER = "# Generated by emit-mcp-policy.ts from agents/mcp-policy.jsonc.\n";
|
|
36
|
+
|
|
37
|
+
function isPlainObject(v: unknown): v is JsonObject {
|
|
38
|
+
return v != null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function deepEqual(a: unknown, b: unknown): boolean {
|
|
42
|
+
if (Object.is(a, b)) return true;
|
|
43
|
+
if (typeof a !== typeof b) return false;
|
|
44
|
+
if (a == null || b == null) return a === b;
|
|
45
|
+
if (Array.isArray(a)) {
|
|
46
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
47
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
48
|
+
}
|
|
49
|
+
if (typeof a === "object") {
|
|
50
|
+
if (!isPlainObject(a) || !isPlainObject(b)) return false;
|
|
51
|
+
const ak = Object.keys(a);
|
|
52
|
+
const bk = Object.keys(b);
|
|
53
|
+
if (ak.length !== bk.length) return false;
|
|
54
|
+
return ak.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]));
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** desired keys must be present on disk; extra disk keys allowed. */
|
|
60
|
+
export function requiredDeepMatch(disk: unknown, desired: unknown): boolean {
|
|
61
|
+
if (Array.isArray(desired)) return deepEqual(disk, desired);
|
|
62
|
+
if (desired != null && typeof desired === "object") {
|
|
63
|
+
if (!isPlainObject(disk)) return false;
|
|
64
|
+
for (const [k, v] of Object.entries(desired)) {
|
|
65
|
+
if (!requiredDeepMatch(disk[k], v)) return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return deepEqual(disk, desired);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readText(abs: string): string | null {
|
|
73
|
+
try {
|
|
74
|
+
return fs.readFileSync(abs, "utf8");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (isRecord(err) && err.code === "ENOENT") return null;
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseJsonFile(raw: string | null, fallback: JsonObject): JsonObject {
|
|
82
|
+
if (raw != null && String(raw).trim()) {
|
|
83
|
+
const v: unknown = JSON.parse(raw);
|
|
84
|
+
return isPlainObject(v) ? v : fallback;
|
|
85
|
+
}
|
|
86
|
+
return fallback;
|
|
87
|
+
}
|
|
88
|
+
function parseTomlFile(raw: string | null, fallback: JsonObject): JsonObject {
|
|
89
|
+
if (raw == null || !String(raw).trim()) return fallback;
|
|
90
|
+
return parseToml(raw);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function jsonOut(obj: unknown): string {
|
|
94
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function tomlOut(obj: JsonObject): string {
|
|
98
|
+
return GROK_HEADER + stringifyToml(obj);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cloneJson(v: unknown): unknown {
|
|
102
|
+
return JSON.parse(JSON.stringify(v));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function pickServers(servers: unknown, allow: string[]): JsonObject {
|
|
106
|
+
const src = isPlainObject(servers) ? servers : {};
|
|
107
|
+
const out: JsonObject = {};
|
|
108
|
+
for (const name of allow) {
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(src, name)) {
|
|
110
|
+
out[name] = cloneJson(src[name]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A consumer's own policy file, deep-merged over the stock policy. */
|
|
117
|
+
const CONSUMER_POLICY_REL = ".repo/mcp-policy.jsonc";
|
|
118
|
+
|
|
119
|
+
function readPolicyFile(p: string): JsonObject {
|
|
120
|
+
const policy: unknown = JSON.parse(stripJsonc(fs.readFileSync(p, "utf8")));
|
|
121
|
+
if (!isPlainObject(policy)) {
|
|
122
|
+
throw new Error("invalid policy");
|
|
123
|
+
}
|
|
124
|
+
return policy;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** `over` onto `base`: objects merge key by key, anything else (arrays included) is replaced. */
|
|
128
|
+
function mergePolicy(base: JsonObject, over: JsonObject): JsonObject {
|
|
129
|
+
const out: JsonObject = { ...base };
|
|
130
|
+
for (const [k, v] of Object.entries(over)) {
|
|
131
|
+
const prev = out[k];
|
|
132
|
+
out[k] = isPlainObject(prev) && isPlainObject(v) ? mergePolicy(prev, v) : v;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The stock policy at `<sourceRoot>/agents/mcp-policy.jsonc`, with `<consumerRoot>/.repo/mcp-policy.jsonc` merged over it when given and present. */
|
|
138
|
+
export function loadPolicy(sourceRoot?: string, consumerRoot?: string): JsonObject {
|
|
139
|
+
const stock = readPolicyFile(path.join(sourceRoot || process.cwd(), POLICY_REL));
|
|
140
|
+
if (!consumerRoot) return stock;
|
|
141
|
+
const own = path.join(consumerRoot, CONSUMER_POLICY_REL);
|
|
142
|
+
return fs.existsSync(own) ? mergePolicy(stock, readPolicyFile(own)) : stock;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function validatePolicy(policy: unknown): { ok: true } | { ok: false; error: string } {
|
|
146
|
+
if (!isPlainObject(policy)) return { ok: false, error: "invalid policy" };
|
|
147
|
+
if (policy.default !== "deny") {
|
|
148
|
+
return { ok: false, error: 'default must be "deny"' };
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(policy.allow)) {
|
|
151
|
+
return { ok: false, error: "allow must be an array" };
|
|
152
|
+
}
|
|
153
|
+
for (const name of policy.allow) {
|
|
154
|
+
if (typeof name !== "string" || !name) {
|
|
155
|
+
return { ok: false, error: "allow names must be non-empty strings" };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const servers = policy.servers;
|
|
159
|
+
if (servers != null && !isPlainObject(servers)) {
|
|
160
|
+
return { ok: false, error: "servers must be an object" };
|
|
161
|
+
}
|
|
162
|
+
const serverKeys = isPlainObject(servers) ? Object.keys(servers) : [];
|
|
163
|
+
if (isPlainObject(servers) && serverKeys.length > 0 && policy.allow.length > 0) {
|
|
164
|
+
const missing = stringList(policy.allow).filter((n) => !Object.prototype.hasOwnProperty.call(servers, n));
|
|
165
|
+
if (missing.length) {
|
|
166
|
+
return { ok: false, error: `allow names missing from servers: ${missing.join(", ")}` };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (policy.agents != null) {
|
|
170
|
+
if (!isPlainObject(policy.agents)) {
|
|
171
|
+
return { ok: false, error: "agents must be an object" };
|
|
172
|
+
}
|
|
173
|
+
for (const id of AGENT_IDS) {
|
|
174
|
+
const row = policy.agents[id];
|
|
175
|
+
if (row != null && (!isPlainObject(row) || row.inherit !== false)) {
|
|
176
|
+
return { ok: false, error: `agents.${id}.inherit must be false` };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { ok: true };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function stringList(v: unknown): string[] {
|
|
184
|
+
return Array.isArray(v) ? v.filter((s): s is string => typeof s === "string") : [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isMcpToolRule(s: string): boolean {
|
|
188
|
+
return s === "MCPTool" || s.startsWith("MCPTool(");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isHomeGrokConfig(workspaceRoot: string | undefined, home: string | undefined): boolean {
|
|
192
|
+
if (!workspaceRoot || !home) return false;
|
|
193
|
+
return path.resolve(workspaceRoot, GROK_REL) === path.resolve(home, GROK_REL);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function desiredGrok(disk: JsonObject, policy: Policy, { keepHomeTables }: { keepHomeTables: boolean }): JsonObject {
|
|
197
|
+
const obj = { ...disk };
|
|
198
|
+
if (!keepHomeTables) delete obj.features;
|
|
199
|
+
delete obj.permissions;
|
|
200
|
+
|
|
201
|
+
const plugins = isPlainObject(obj.plugins) ? { ...obj.plugins } : {};
|
|
202
|
+
delete plugins.deny_default;
|
|
203
|
+
if (policy.denyPlugins) {
|
|
204
|
+
plugins.enabled = [];
|
|
205
|
+
obj.plugins = plugins;
|
|
206
|
+
} else if (Object.keys(plugins).length) {
|
|
207
|
+
obj.plugins = plugins;
|
|
208
|
+
} else {
|
|
209
|
+
delete obj.plugins;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const allow = policy.allow;
|
|
213
|
+
const perm = isPlainObject(obj.permission) ? { ...obj.permission } : {};
|
|
214
|
+
const denyKeep = stringList(perm.deny).filter((s) => s !== "MCPTool");
|
|
215
|
+
perm.deny = [...denyKeep, "MCPTool"];
|
|
216
|
+
const allowKeep = stringList(perm.allow).filter((s) => !isMcpToolRule(s));
|
|
217
|
+
const mcpAllows = allow.map((name) => `MCPTool(${name}__*)`);
|
|
218
|
+
const nextAllow = [...mcpAllows, ...allowKeep];
|
|
219
|
+
if (nextAllow.length) perm.allow = nextAllow;
|
|
220
|
+
else delete perm.allow;
|
|
221
|
+
obj.permission = perm;
|
|
222
|
+
|
|
223
|
+
if (isPlainObject(obj.mcp)) {
|
|
224
|
+
const mcp = { ...obj.mcp };
|
|
225
|
+
delete mcp.servers;
|
|
226
|
+
if (Object.keys(mcp).length) obj.mcp = mcp;
|
|
227
|
+
else delete obj.mcp;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const allowSet = new Set(allow);
|
|
231
|
+
const native = isPlainObject(obj.mcp_servers) ? { ...obj.mcp_servers } : {};
|
|
232
|
+
const nested = isPlainObject(disk.mcp) && isPlainObject(disk.mcp.servers) ? disk.mcp.servers : {};
|
|
233
|
+
const leftoverNames = new Set([...Object.keys(native), ...Object.keys(nested)]);
|
|
234
|
+
const servers: JsonObject = {};
|
|
235
|
+
for (const name of leftoverNames) {
|
|
236
|
+
const prev = isPlainObject(native[name]) ? native[name] : isPlainObject(nested[name]) ? nested[name] : {};
|
|
237
|
+
servers[name] = allowSet.has(name) ? { ...prev } : { ...prev, enabled: false };
|
|
238
|
+
}
|
|
239
|
+
const picked = pickServers(policy.servers, allow);
|
|
240
|
+
for (const [name, spec] of Object.entries(picked)) {
|
|
241
|
+
servers[name] = isPlainObject(spec) ? { ...spec } : spec;
|
|
242
|
+
}
|
|
243
|
+
if (Object.keys(servers).length === 0) {
|
|
244
|
+
delete obj.mcp_servers;
|
|
245
|
+
} else {
|
|
246
|
+
obj.mcp_servers = servers;
|
|
247
|
+
}
|
|
248
|
+
return obj;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function desiredClaudeSettings(disk: JsonObject, policy: Policy): JsonObject {
|
|
252
|
+
const obj = { ...disk };
|
|
253
|
+
obj.enableAllProjectMcpServers = false;
|
|
254
|
+
obj.enabledMcpjsonServers = [...policy.allow];
|
|
255
|
+
const permissions = isPlainObject(obj.permissions) ? { ...obj.permissions } : {};
|
|
256
|
+
const deny = Array.isArray(permissions.deny) ? [...permissions.deny] : [];
|
|
257
|
+
if (!deny.includes(MCP_DENY)) deny.push(MCP_DENY);
|
|
258
|
+
permissions.deny = deny;
|
|
259
|
+
obj.permissions = permissions;
|
|
260
|
+
return obj;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function desiredMcpJson(policy: Policy): JsonObject {
|
|
264
|
+
return { mcpServers: pickServers(policy.servers, policy.allow) };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function desiredCodex(disk: JsonObject, policy: Policy): JsonObject & { agents: JsonObject } {
|
|
268
|
+
const obj: JsonObject = { ...disk };
|
|
269
|
+
const agents: JsonObject = isPlainObject(obj.agents) ? { ...obj.agents } : {};
|
|
270
|
+
if (agents.max_depth == null) agents.max_depth = 1;
|
|
271
|
+
obj.agents = agents;
|
|
272
|
+
if (!policy.allow.length) {
|
|
273
|
+
delete obj.mcp_servers;
|
|
274
|
+
} else {
|
|
275
|
+
obj.mcp_servers = pickServers(policy.servers, policy.allow);
|
|
276
|
+
}
|
|
277
|
+
return { ...obj, agents };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function desiredAgentsMcp(policy: Policy): JsonObject {
|
|
281
|
+
return { mcpServers: pickServers(policy.servers, policy.allow) };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function grokCheckOk(
|
|
285
|
+
disk: JsonObject,
|
|
286
|
+
desired: JsonObject,
|
|
287
|
+
policy: Policy,
|
|
288
|
+
{ keepHomeTables }: { keepHomeTables: boolean },
|
|
289
|
+
): boolean {
|
|
290
|
+
if (!keepHomeTables && Object.prototype.hasOwnProperty.call(disk, "features")) return false;
|
|
291
|
+
if (Array.isArray(disk.permissions)) return false;
|
|
292
|
+
if (isPlainObject(disk.plugins) && Object.prototype.hasOwnProperty.call(disk.plugins, "deny_default")) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (isPlainObject(disk.mcp) && Object.prototype.hasOwnProperty.call(disk.mcp, "servers")) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const deny = isPlainObject(disk.permission) && Array.isArray(disk.permission.deny) ? disk.permission.deny : [];
|
|
299
|
+
if (!deny.includes("MCPTool")) return false;
|
|
300
|
+
const allow = isPlainObject(disk.permission) && Array.isArray(disk.permission.allow) ? disk.permission.allow : [];
|
|
301
|
+
for (const name of policy.allow) {
|
|
302
|
+
if (!allow.includes(`MCPTool(${name}__*)`)) return false;
|
|
303
|
+
}
|
|
304
|
+
if (policy.denyPlugins) {
|
|
305
|
+
if (!requiredDeepMatch(disk, { plugins: { enabled: [] } })) return false;
|
|
306
|
+
}
|
|
307
|
+
if (desired.mcp_servers && !requiredDeepMatch(disk, { mcp_servers: desired.mcp_servers })) return false;
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function claudeCheckOk(disk: JsonObject, desired: JsonObject): boolean {
|
|
312
|
+
if (disk.enableAllProjectMcpServers !== false) return false;
|
|
313
|
+
if (!deepEqual(disk.enabledMcpjsonServers, desired.enabledMcpjsonServers)) return false;
|
|
314
|
+
const deny: unknown[] =
|
|
315
|
+
isPlainObject(disk.permissions) && Array.isArray(disk.permissions.deny) ? disk.permissions.deny : [];
|
|
316
|
+
return deny.includes(MCP_DENY);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function writeText(abs: string, text: string): void {
|
|
320
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
321
|
+
fs.writeFileSync(abs, text);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Reads the policy from `sourceRoot` (default `workspaceRoot`), merges `consumerRoot`'s own over it, writes under `workspaceRoot`. */
|
|
325
|
+
export function emitMcpPolicy({
|
|
326
|
+
workspaceRoot,
|
|
327
|
+
sourceRoot,
|
|
328
|
+
consumerRoot,
|
|
329
|
+
check = false,
|
|
330
|
+
home,
|
|
331
|
+
}: {
|
|
332
|
+
workspaceRoot?: string;
|
|
333
|
+
sourceRoot?: string;
|
|
334
|
+
consumerRoot?: string;
|
|
335
|
+
check?: boolean;
|
|
336
|
+
home?: string;
|
|
337
|
+
} = {}): EmitResult {
|
|
338
|
+
const ws = workspaceRoot || process.cwd();
|
|
339
|
+
const rawPolicy = loadPolicy(sourceRoot || ws, consumerRoot);
|
|
340
|
+
const valid = validatePolicy(rawPolicy);
|
|
341
|
+
if (!valid.ok) {
|
|
342
|
+
return { ok: false, check, error: valid.error };
|
|
343
|
+
}
|
|
344
|
+
const policy: Policy = {
|
|
345
|
+
allow: stringList(rawPolicy.allow),
|
|
346
|
+
servers: rawPolicy.servers,
|
|
347
|
+
denyPlugins: isPlainObject(rawPolicy.plugins) && rawPolicy.plugins.denyDefault === true,
|
|
348
|
+
};
|
|
349
|
+
const keepHomeTables = isHomeGrokConfig(ws, home || readEnv().HOME);
|
|
350
|
+
|
|
351
|
+
const grokAbs = path.join(ws, GROK_REL);
|
|
352
|
+
const claudeAbs = path.join(ws, CLAUDE_SETTINGS_REL);
|
|
353
|
+
const mcpAbs = path.join(ws, MCP_JSON_REL);
|
|
354
|
+
const codexAbs = path.join(ws, CODEX_REL);
|
|
355
|
+
const agentsAbs = path.join(ws, AGENTS_MCP_REL);
|
|
356
|
+
|
|
357
|
+
const grokRaw = readText(grokAbs);
|
|
358
|
+
const claudeRaw = readText(claudeAbs);
|
|
359
|
+
const mcpRaw = readText(mcpAbs);
|
|
360
|
+
const codexRaw = readText(codexAbs);
|
|
361
|
+
const agentsRaw = readText(agentsAbs);
|
|
362
|
+
|
|
363
|
+
const grokDisk = parseTomlFile(grokRaw, {});
|
|
364
|
+
const claudeDisk = parseJsonFile(claudeRaw, {});
|
|
365
|
+
const mcpDisk = mcpRaw == null ? null : parseJsonFile(mcpRaw, {});
|
|
366
|
+
const codexDisk = parseTomlFile(codexRaw, {});
|
|
367
|
+
const agentsDisk = agentsRaw == null ? null : parseJsonFile(agentsRaw, {});
|
|
368
|
+
|
|
369
|
+
const grokDesired = desiredGrok(grokDisk, policy, { keepHomeTables });
|
|
370
|
+
const claudeDesired = desiredClaudeSettings(claudeDisk, policy);
|
|
371
|
+
const mcpDesired = desiredMcpJson(policy);
|
|
372
|
+
const codexDesired = desiredCodex(codexDisk, policy);
|
|
373
|
+
const agentsDesired = desiredAgentsMcp(policy);
|
|
374
|
+
|
|
375
|
+
if (check) {
|
|
376
|
+
const drift: string[] = [];
|
|
377
|
+
if (grokRaw == null || !grokCheckOk(grokDisk, grokDesired, policy, { keepHomeTables })) drift.push(GROK_REL);
|
|
378
|
+
if (claudeRaw == null || !claudeCheckOk(claudeDisk, claudeDesired)) drift.push(CLAUDE_SETTINGS_REL);
|
|
379
|
+
if (mcpDisk == null || !deepEqual(mcpDisk, mcpDesired)) drift.push(MCP_JSON_REL);
|
|
380
|
+
const codexOk =
|
|
381
|
+
codexRaw != null &&
|
|
382
|
+
requiredDeepMatch(codexDisk, {
|
|
383
|
+
agents: { max_depth: codexDesired.agents.max_depth },
|
|
384
|
+
}) &&
|
|
385
|
+
(policy.allow.length
|
|
386
|
+
? deepEqual(codexDisk.mcp_servers, codexDesired.mcp_servers)
|
|
387
|
+
: !Object.prototype.hasOwnProperty.call(codexDisk, "mcp_servers"));
|
|
388
|
+
if (!codexOk) drift.push(CODEX_REL);
|
|
389
|
+
if (agentsDisk == null || !deepEqual(agentsDisk, agentsDesired)) drift.push(AGENTS_MCP_REL);
|
|
390
|
+
if (drift.length) {
|
|
391
|
+
return { ok: false, check: true, error: `drift: ${drift.join(", ")}`, drift };
|
|
392
|
+
}
|
|
393
|
+
return { ok: true, check: true, drift: [] };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
writeText(grokAbs, tomlOut(grokDesired));
|
|
397
|
+
writeText(claudeAbs, jsonOut(claudeDesired));
|
|
398
|
+
writeText(mcpAbs, jsonOut(mcpDesired));
|
|
399
|
+
writeText(codexAbs, stringifyToml(codexDesired));
|
|
400
|
+
writeText(agentsAbs, jsonOut(agentsDesired));
|
|
401
|
+
return { ok: true, check: false };
|
|
402
|
+
}
|