@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,54 @@
|
|
|
1
|
+
/** `repo persist <kind> [--workspace dir]`: save the stdin body as a plan, roadmap, handoff, implementation or tmp file. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { processHookIo, readAllText, type HookIo } from "./hook-io.ts";
|
|
4
|
+
import { persistHandoff } from "./persist-handoff.ts";
|
|
5
|
+
import { persistImplementation } from "./persist-implementation.ts";
|
|
6
|
+
import { persistPlan } from "./persist-plan.ts";
|
|
7
|
+
import { persistRoadmap } from "./persist-roadmap.ts";
|
|
8
|
+
import { persistTmp } from "./persist-tmp.ts";
|
|
9
|
+
import { resolveRoots } from "./workspace-root.ts";
|
|
10
|
+
|
|
11
|
+
type Saved = { ok: true; serial: number; path: string; filename: string } | { ok: false; error: string };
|
|
12
|
+
type Saver = (opts: { workspaceRoot: string; content: string }) => Saved;
|
|
13
|
+
|
|
14
|
+
const SAVERS: Readonly<Record<string, Saver>> = {
|
|
15
|
+
plan: persistPlan,
|
|
16
|
+
roadmap: persistRoadmap,
|
|
17
|
+
handoff: persistHandoff,
|
|
18
|
+
implementation: persistImplementation,
|
|
19
|
+
tmp: persistTmp,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const KINDS = "use plan, roadmap, handoff, implementation or tmp";
|
|
23
|
+
|
|
24
|
+
const fail = (message: string): number => {
|
|
25
|
+
process.stderr.write(`${message}\n`);
|
|
26
|
+
return 1;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** `--workspace dir`, else the repo root the working directory sits in. */
|
|
30
|
+
function workspaceOf(argv: string[]): string {
|
|
31
|
+
const at = argv.indexOf("--workspace");
|
|
32
|
+
return (at >= 0 ? argv[at + 1] : undefined) ?? resolveRoots().root;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function saveBody(save: Saver, argv: string[], io: HookIo): number {
|
|
36
|
+
try {
|
|
37
|
+
const workspaceRoot = workspaceOf(argv);
|
|
38
|
+
const result = save({ workspaceRoot, content: readAllText(io) });
|
|
39
|
+
if (!result.ok) return fail(result.error);
|
|
40
|
+
const { serial, path: saved, filename } = result;
|
|
41
|
+
io.write(JSON.stringify({ serial, path: saved, absPath: path.resolve(workspaceRoot, saved), filename }) + "\n");
|
|
42
|
+
return 0;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function main(argv: string[] = [], io: HookIo = processHookIo()): number {
|
|
49
|
+
const kind = argv[0];
|
|
50
|
+
if (kind === undefined) return fail(`repo persist: missing kind; ${KINDS}`);
|
|
51
|
+
const save = Object.hasOwn(SAVERS, kind) ? SAVERS[kind] : undefined;
|
|
52
|
+
if (!save) return fail(`repo persist: unknown kind "${kind}"; ${KINDS}`);
|
|
53
|
+
return saveBody(save, argv, io);
|
|
54
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** `repo phase-files <plan> <phases>`: map a parallel group's unstaged changes to its phases. */
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { PLAN_DIR, parallelFiles, planBoxes, planPhases } from "./persist-plan.ts";
|
|
6
|
+
import { canonicalPath, resolveRoots } from "./workspace-root.ts";
|
|
7
|
+
|
|
8
|
+
const USAGE = "usage: repo phase-files <plan serial|path> <phases, e.g. 2,3>\n";
|
|
9
|
+
|
|
10
|
+
/** Paths with unstaged or untracked changes, as git prints them; the new side of a rename. */
|
|
11
|
+
function unstagedPaths(cwd: string): string[] {
|
|
12
|
+
const raw = execFileSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
|
|
13
|
+
cwd,
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
16
|
+
maxBuffer: 1 << 28,
|
|
17
|
+
});
|
|
18
|
+
const fields = raw.split("\0")[Symbol.iterator]();
|
|
19
|
+
const paths: string[] = [];
|
|
20
|
+
for (let next = fields.next(); !next.done; next = fields.next()) {
|
|
21
|
+
const entry = next.value;
|
|
22
|
+
if (entry.length < 4) continue;
|
|
23
|
+
const x = entry[0];
|
|
24
|
+
const y = entry[1];
|
|
25
|
+
// A rename or copy carries its old path in the next field.
|
|
26
|
+
if (x === "R" || x === "C" || y === "R" || y === "C") fields.next();
|
|
27
|
+
if (y !== " " || entry.startsWith("??")) paths.push(entry.slice(3).replace(/\\/g, "/"));
|
|
28
|
+
}
|
|
29
|
+
return [...new Set(paths)].sort();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findPlan(arg: string, cwd: string): { file: string; label: string } | { error: string } {
|
|
33
|
+
if (!/^\d+$/.test(arg)) {
|
|
34
|
+
const file = path.resolve(cwd, arg);
|
|
35
|
+
return fs.existsSync(file)
|
|
36
|
+
? { file, label: path.basename(file).split("-", 1).join("") }
|
|
37
|
+
: { error: `no plan ${arg}\n` };
|
|
38
|
+
}
|
|
39
|
+
const dir = path.join(resolveRoots({}, { cwd, env: {} }).root, PLAN_DIR);
|
|
40
|
+
const serial = Number(arg);
|
|
41
|
+
const names = fs.existsSync(dir)
|
|
42
|
+
? fs.readdirSync(dir).filter((b) => b.endsWith(".md") && Number(/^(\d+)-/.exec(b)?.[1]) === serial)
|
|
43
|
+
: [];
|
|
44
|
+
const name = names.sort().at(-1);
|
|
45
|
+
return name ? { file: path.join(dir, name), label: arg } : { error: `no plan ${arg} in ${dir}\n` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function main(argv: string[], cwd: string = canonicalPath(process.cwd())): number {
|
|
49
|
+
const [planArg, phaseArg, ...extra] = argv;
|
|
50
|
+
if (!planArg || !phaseArg || extra.length) return fail(USAGE);
|
|
51
|
+
const plan = findPlan(planArg, cwd);
|
|
52
|
+
if ("error" in plan) return fail(plan.error);
|
|
53
|
+
|
|
54
|
+
const phases = planPhases(planBoxes(fs.readFileSync(plan.file, "utf8")));
|
|
55
|
+
const ids = [
|
|
56
|
+
...new Set(
|
|
57
|
+
phaseArg
|
|
58
|
+
.split(",")
|
|
59
|
+
.map((s) => s.trim())
|
|
60
|
+
.filter(Boolean),
|
|
61
|
+
),
|
|
62
|
+
];
|
|
63
|
+
if (!ids.length) return fail(USAGE);
|
|
64
|
+
const wanted = [];
|
|
65
|
+
const declaredBy = new Map<string, string[]>();
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
const phase = phases.find((p) => p.id === id);
|
|
68
|
+
if (!phase) return fail(`plan ${plan.label} has no phase ${id}\n`);
|
|
69
|
+
wanted.push(phase);
|
|
70
|
+
}
|
|
71
|
+
for (const p of wanted) {
|
|
72
|
+
const declared = parallelFiles(p);
|
|
73
|
+
if (!declared) return fail(`phase ${p.id} is not parallel; name phases of one parallel group\n`);
|
|
74
|
+
declaredBy.set(p.id, declared);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Adjacent parallel phases are one group.
|
|
78
|
+
const groupOf = (id: string) => {
|
|
79
|
+
const at = phases.findIndex((p) => p.id === id);
|
|
80
|
+
let from = at;
|
|
81
|
+
let to = at;
|
|
82
|
+
while (from > 0 && parallelFiles(phases[from - 1]!)) from--;
|
|
83
|
+
while (to < phases.length - 1 && parallelFiles(phases[to + 1]!)) to++;
|
|
84
|
+
return from;
|
|
85
|
+
};
|
|
86
|
+
const first = wanted[0]!;
|
|
87
|
+
const other = wanted.find((p) => groupOf(p.id) !== groupOf(first.id));
|
|
88
|
+
if (other)
|
|
89
|
+
return fail(`phases ${first.id} and ${other.id} are in different parallel groups; name phases of one group\n`);
|
|
90
|
+
|
|
91
|
+
let changed: string[];
|
|
92
|
+
try {
|
|
93
|
+
changed = unstagedPaths(cwd);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
const detail = String((e as { stderr?: unknown }).stderr || (e as Error).message).trim();
|
|
96
|
+
return fail(`git status failed in ${cwd}: ${detail}\n`);
|
|
97
|
+
}
|
|
98
|
+
if (!changed.length) {
|
|
99
|
+
process.stdout.write("no unstaged changes\n");
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const owned = new Set<string>();
|
|
104
|
+
for (const [id, declared] of declaredBy) {
|
|
105
|
+
const mine = changed.filter((f) => declared.includes(f));
|
|
106
|
+
mine.forEach((f) => owned.add(f));
|
|
107
|
+
process.stdout.write(`phase ${id}${mine.map((f) => ` ${f}`).join("")}\n`);
|
|
108
|
+
}
|
|
109
|
+
const unowned = changed.filter((f) => !owned.has(f));
|
|
110
|
+
for (const f of unowned) {
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
`unowned ${f} (no phase in ${wanted.map((p) => p.id).join(",")} lists it); send it back as a finding\n`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return unowned.length ? 1 : 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function fail(message: string): number {
|
|
119
|
+
process.stderr.write(message);
|
|
120
|
+
return 2;
|
|
121
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** TTY prompts. Copied into consumers. */
|
|
2
|
+
import * as clack from "@clack/prompts";
|
|
3
|
+
|
|
4
|
+
type SelectOptions<Value> = Parameters<typeof clack.select<Value>>[0];
|
|
5
|
+
|
|
6
|
+
export { isInteractive } from "./interactive.ts";
|
|
7
|
+
|
|
8
|
+
export function bailIfCancel<T>(value: T | symbol): T {
|
|
9
|
+
if (clack.isCancel(value)) {
|
|
10
|
+
clack.cancel("Aborted");
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
return value as T; // isCancel above exits on the symbol, so only T is left
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function select<Value>(opts: SelectOptions<Value>): Promise<Value> {
|
|
17
|
+
return bailIfCancel(await clack.select(opts));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function confirm(opts: Parameters<typeof clack.confirm>[0]): Promise<boolean> {
|
|
21
|
+
return bailIfCancel(await clack.confirm(opts));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function text(opts: Parameters<typeof clack.text>[0]): Promise<string> {
|
|
25
|
+
return bailIfCancel(await clack.text(opts));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function password(opts: Parameters<typeof clack.password>[0]): Promise<string> {
|
|
29
|
+
return bailIfCancel(await clack.password(opts));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function spinner(): {
|
|
33
|
+
start: (msg?: string) => void;
|
|
34
|
+
stop: (msg?: string, code?: number) => void;
|
|
35
|
+
message: (msg?: string) => void;
|
|
36
|
+
} {
|
|
37
|
+
return clack.spinner();
|
|
38
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Purge stubborn global MCP sources under $HOME (dry-run default). Never project MCP. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { isRecord } from "./hook-input.ts";
|
|
7
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
8
|
+
import { readEnv } from "../env.ts";
|
|
9
|
+
import type { Env } from "../env.ts";
|
|
10
|
+
|
|
11
|
+
type JsonObject = Record<string, unknown>;
|
|
12
|
+
export type ParsedArgv = { ok: true; apply: boolean } | { ok: false; error: string };
|
|
13
|
+
|
|
14
|
+
/** What one dest needs: skip it, or write `obj` (TOML) / `json` as the new text. */
|
|
15
|
+
interface PlanBase {
|
|
16
|
+
abs: string;
|
|
17
|
+
skip: boolean;
|
|
18
|
+
detail: string;
|
|
19
|
+
changed?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface GrokPlan extends PlanBase {
|
|
22
|
+
obj: JsonObject | null;
|
|
23
|
+
}
|
|
24
|
+
interface JsonPlan extends PlanBase {
|
|
25
|
+
json: JsonObject | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ReportRow {
|
|
29
|
+
action: string;
|
|
30
|
+
path: string;
|
|
31
|
+
detail: string;
|
|
32
|
+
line: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PurgeResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
report: ReportRow[];
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const errorText = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
42
|
+
|
|
43
|
+
const PLUGIN_IDS = ["cloudflare", "stripe"];
|
|
44
|
+
const DISABLE_MCP = ["cloudflare-docs", "MCP_DOCKER", "stripe"];
|
|
45
|
+
const CURSOR_SERVER = "MCP_DOCKER";
|
|
46
|
+
const STRIPE_PLUGIN = "stripe";
|
|
47
|
+
|
|
48
|
+
function isPlainObject(v: unknown): v is JsonObject {
|
|
49
|
+
return v != null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** HOME, else USERPROFILE (Windows / env sim), else os.homedir(). */
|
|
53
|
+
export function resolveHome(env: Env = readEnv()): string {
|
|
54
|
+
return env.HOME || env.USERPROFILE || os.homedir();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseArgv(argv: unknown): ParsedArgv {
|
|
58
|
+
const args: unknown[] = Array.isArray(argv) ? argv : [];
|
|
59
|
+
let apply = false;
|
|
60
|
+
for (const a of args) {
|
|
61
|
+
if (a === "--apply") {
|
|
62
|
+
apply = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, apply };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function grokConfigPath(home: string): string {
|
|
71
|
+
return path.join(path.resolve(home), ".grok", "config.toml");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function cursorMcpPath(home: string): string {
|
|
75
|
+
return path.join(path.resolve(home), ".cursor", "mcp.json");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function claudePluginsPath(home: string): string {
|
|
79
|
+
return path.join(path.resolve(home), ".claude", "plugins", "installed_plugins.json");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isStripePluginKey(k: unknown): boolean {
|
|
83
|
+
return String(k).split("@")[0] === STRIPE_PLUGIN;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function strList(v: unknown, label: string): string[] {
|
|
87
|
+
if (v == null) return [];
|
|
88
|
+
if (!Array.isArray(v)) throw new Error(`unreadable dest: ${label}`);
|
|
89
|
+
return v.map((x) => String(x));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function dedupe(list: string[]): string[] {
|
|
93
|
+
const out: string[] = [];
|
|
94
|
+
const seen = new Set<string>();
|
|
95
|
+
for (const x of list) {
|
|
96
|
+
if (seen.has(x)) continue;
|
|
97
|
+
seen.add(x);
|
|
98
|
+
out.push(x);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeText(abs: string, body: string): void {
|
|
104
|
+
const text = body.endsWith("\n") ? body : body + "\n";
|
|
105
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
106
|
+
fs.writeFileSync(abs, text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function reportLine(row: { action: string; path: string; detail: string }): string {
|
|
110
|
+
return `${row.action} ${row.path} ${row.detail}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function planGrok(home: string): GrokPlan {
|
|
114
|
+
const abs = grokConfigPath(home);
|
|
115
|
+
let raw: string;
|
|
116
|
+
try {
|
|
117
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (isRecord(err) && err.code === "ENOENT") {
|
|
120
|
+
return { abs, skip: true, detail: "noop (missing)", obj: null };
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
123
|
+
}
|
|
124
|
+
let obj: JsonObject;
|
|
125
|
+
try {
|
|
126
|
+
obj = parseToml(raw);
|
|
127
|
+
} catch {
|
|
128
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const next = { ...obj };
|
|
132
|
+
const details: string[] = [];
|
|
133
|
+
let changed = false;
|
|
134
|
+
|
|
135
|
+
const hadPlugins = isPlainObject(obj.plugins);
|
|
136
|
+
const plugins: JsonObject = isPlainObject(obj.plugins) ? { ...obj.plugins } : {};
|
|
137
|
+
if (obj.plugins != null && !hadPlugins) {
|
|
138
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
139
|
+
}
|
|
140
|
+
const enabled = strList(plugins.enabled, abs);
|
|
141
|
+
const disabled = strList(plugins.disabled, abs);
|
|
142
|
+
const nextEnabled = enabled.filter((n) => !PLUGIN_IDS.includes(n));
|
|
143
|
+
const nextDisabled = dedupe([...disabled, ...PLUGIN_IDS]);
|
|
144
|
+
const enabledChanged = nextEnabled.join("\0") !== enabled.join("\0");
|
|
145
|
+
const disabledChanged = nextDisabled.join("\0") !== disabled.join("\0");
|
|
146
|
+
if (enabledChanged) {
|
|
147
|
+
const removed = enabled.filter((n) => PLUGIN_IDS.includes(n));
|
|
148
|
+
details.push(`remove ${removed.join(", ")} from [plugins].enabled`);
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
if (disabledChanged) {
|
|
152
|
+
const added = PLUGIN_IDS.filter((id) => !disabled.includes(id));
|
|
153
|
+
details.push(`add ${added.join(", ")} to [plugins].disabled`);
|
|
154
|
+
changed = true;
|
|
155
|
+
}
|
|
156
|
+
if (enabledChanged || disabledChanged) {
|
|
157
|
+
if ("enabled" in plugins || enabledChanged) plugins.enabled = nextEnabled;
|
|
158
|
+
plugins.disabled = nextDisabled;
|
|
159
|
+
next.plugins = plugins;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const mcpKey = "disabled_mcp_servers";
|
|
163
|
+
if (next[mcpKey] == null || Array.isArray(next[mcpKey])) {
|
|
164
|
+
const cur = strList(next[mcpKey], abs);
|
|
165
|
+
const mcpNext = dedupe([...cur, ...DISABLE_MCP]);
|
|
166
|
+
if (mcpNext.join("\0") !== cur.join("\0")) {
|
|
167
|
+
const added = mcpNext.filter((x) => !cur.includes(x));
|
|
168
|
+
next[mcpKey] = mcpNext;
|
|
169
|
+
details.push(`add ${added.join(", ")} to ${mcpKey}`);
|
|
170
|
+
changed = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
abs,
|
|
176
|
+
skip: !changed,
|
|
177
|
+
detail: changed ? details.join("; ") : "noop (already clean)",
|
|
178
|
+
obj: next,
|
|
179
|
+
changed,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function planClaudePlugins(home: string): JsonPlan {
|
|
184
|
+
const abs = claudePluginsPath(home);
|
|
185
|
+
let raw: string;
|
|
186
|
+
try {
|
|
187
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (isRecord(err) && err.code === "ENOENT") {
|
|
190
|
+
return { abs, skip: true, detail: "noop (missing)", json: null };
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
193
|
+
}
|
|
194
|
+
let parsed: unknown;
|
|
195
|
+
try {
|
|
196
|
+
parsed = JSON.parse(raw);
|
|
197
|
+
} catch {
|
|
198
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
199
|
+
}
|
|
200
|
+
if (!isPlainObject(parsed)) throw new Error(`unreadable dest: ${abs}`);
|
|
201
|
+
const obj = parsed;
|
|
202
|
+
const plugins = obj.plugins;
|
|
203
|
+
if (plugins == null) {
|
|
204
|
+
return { abs, skip: true, detail: "noop (already clean)", json: null };
|
|
205
|
+
}
|
|
206
|
+
if (!isPlainObject(plugins)) throw new Error(`unreadable dest: ${abs}`);
|
|
207
|
+
const keys = Object.keys(plugins).filter(isStripePluginKey);
|
|
208
|
+
if (!keys.length) {
|
|
209
|
+
return { abs, skip: true, detail: "noop (already clean)", json: null };
|
|
210
|
+
}
|
|
211
|
+
const nextPlugins = { ...plugins };
|
|
212
|
+
for (const k of keys) delete nextPlugins[k];
|
|
213
|
+
return {
|
|
214
|
+
abs,
|
|
215
|
+
skip: false,
|
|
216
|
+
detail: `remove ${keys.join(", ")} from plugins`,
|
|
217
|
+
json: { ...obj, plugins: nextPlugins },
|
|
218
|
+
changed: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function planCursor(home: string): JsonPlan {
|
|
223
|
+
const abs = cursorMcpPath(home);
|
|
224
|
+
let raw: string;
|
|
225
|
+
try {
|
|
226
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
227
|
+
} catch (err) {
|
|
228
|
+
if (isRecord(err) && err.code === "ENOENT") {
|
|
229
|
+
return { abs, skip: true, detail: "noop (missing)", json: null };
|
|
230
|
+
}
|
|
231
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
232
|
+
}
|
|
233
|
+
let parsed: unknown;
|
|
234
|
+
try {
|
|
235
|
+
parsed = JSON.parse(raw);
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error(`unreadable dest: ${abs}`);
|
|
238
|
+
}
|
|
239
|
+
if (!isPlainObject(parsed)) throw new Error(`unreadable dest: ${abs}`);
|
|
240
|
+
const obj = parsed;
|
|
241
|
+
const servers = obj.mcpServers;
|
|
242
|
+
if (servers == null) {
|
|
243
|
+
return { abs, skip: true, detail: "noop (already clean)", json: null };
|
|
244
|
+
}
|
|
245
|
+
if (!isPlainObject(servers)) throw new Error(`unreadable dest: ${abs}`);
|
|
246
|
+
if (!Object.prototype.hasOwnProperty.call(servers, CURSOR_SERVER)) {
|
|
247
|
+
return { abs, skip: true, detail: "noop (already clean)", json: null };
|
|
248
|
+
}
|
|
249
|
+
const nextServers = { ...servers };
|
|
250
|
+
delete nextServers[CURSOR_SERVER];
|
|
251
|
+
const json = { ...obj, mcpServers: nextServers };
|
|
252
|
+
return {
|
|
253
|
+
abs,
|
|
254
|
+
skip: false,
|
|
255
|
+
detail: `remove ${CURSOR_SERVER} from mcpServers`,
|
|
256
|
+
json,
|
|
257
|
+
changed: true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function purgeGlobalMcps({ home, apply = false }: { home?: string; apply?: boolean } = {}): PurgeResult {
|
|
262
|
+
const report: ReportRow[] = [];
|
|
263
|
+
const action = apply ? "apply" : "dry-run";
|
|
264
|
+
try {
|
|
265
|
+
if (!home) throw new Error("HOME required");
|
|
266
|
+
const grok = planGrok(home);
|
|
267
|
+
const cursor = planCursor(home);
|
|
268
|
+
const claude = planClaudePlugins(home);
|
|
269
|
+
if (apply) {
|
|
270
|
+
if (!grok.skip && grok.obj) {
|
|
271
|
+
writeText(grok.abs, stringifyToml(grok.obj));
|
|
272
|
+
}
|
|
273
|
+
if (!cursor.skip && cursor.json) {
|
|
274
|
+
writeText(cursor.abs, JSON.stringify(cursor.json, null, 2));
|
|
275
|
+
}
|
|
276
|
+
if (!claude.skip && claude.json) {
|
|
277
|
+
writeText(claude.abs, JSON.stringify(claude.json, null, 2));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const row of [
|
|
281
|
+
{ action, path: grok.abs, detail: grok.detail },
|
|
282
|
+
{ action, path: cursor.abs, detail: cursor.detail },
|
|
283
|
+
{ action, path: claude.abs, detail: claude.detail },
|
|
284
|
+
]) {
|
|
285
|
+
report.push({ ...row, line: reportLine(row) });
|
|
286
|
+
}
|
|
287
|
+
return { ok: true, report };
|
|
288
|
+
} catch (err) {
|
|
289
|
+
return { ok: false, report, error: errorText(err) };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function main(argv: string[]): number {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = parseArgv(argv);
|
|
296
|
+
if (!parsed.ok) {
|
|
297
|
+
process.stderr.write(parsed.error + "\n");
|
|
298
|
+
return 1;
|
|
299
|
+
}
|
|
300
|
+
const result = purgeGlobalMcps({ home: resolveHome(), apply: parsed.apply });
|
|
301
|
+
for (const row of result.report) {
|
|
302
|
+
process.stdout.write(row.line + "\n");
|
|
303
|
+
}
|
|
304
|
+
if (!result.ok) {
|
|
305
|
+
process.stderr.write(result.error + "\n");
|
|
306
|
+
return 1;
|
|
307
|
+
}
|
|
308
|
+
return 0;
|
|
309
|
+
} catch (err) {
|
|
310
|
+
process.stderr.write(errorText(err) + "\n");
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rejections step of `repo turn`: every reject file of each package the change set touches, since a change to a
|
|
3
|
+
* source can widen a type its reject files test. Absent when those packages hold none.
|
|
4
|
+
*/
|
|
5
|
+
import { sourceFiles } from "../check-comments.ts";
|
|
6
|
+
import { rejectionProblems } from "../check-rejections.ts";
|
|
7
|
+
import { ownerOf } from "./change-set.ts";
|
|
8
|
+
import { failed, owners, passed, type Step } from "./turn-steps.ts";
|
|
9
|
+
|
|
10
|
+
const REJECT_FILE = /\.reject\.tsx?$/;
|
|
11
|
+
|
|
12
|
+
/** The reject files of the touched packages, or why they could not be listed. */
|
|
13
|
+
function rejectFiles(root: string, packages: readonly string[], touched: readonly string[]) {
|
|
14
|
+
const listed = sourceFiles(root);
|
|
15
|
+
if (!listed.ok) return listed;
|
|
16
|
+
const files = listed.files.filter((rel) => REJECT_FILE.test(rel) && touched.includes(ownerOf(rel, packages)));
|
|
17
|
+
return { ok: true as const, files };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `rejections ok`, or `rejections N failed` naming the failing files' count, each problem printed. */
|
|
21
|
+
export const rejectionsStep: Step = (turn) => {
|
|
22
|
+
const listed = rejectFiles(turn.root, turn.packages, owners(turn));
|
|
23
|
+
if (!listed.ok)
|
|
24
|
+
return failed(
|
|
25
|
+
"rejections failed",
|
|
26
|
+
listed.errors.map((line) => `repo turn: ${line}\n`),
|
|
27
|
+
);
|
|
28
|
+
if (listed.files.length === 0) return null;
|
|
29
|
+
const problems = rejectionProblems(turn.root, listed.files);
|
|
30
|
+
if (problems.length === 0) return passed("rejections ok");
|
|
31
|
+
const failing = listed.files.filter((rel) => problems.some((line) => line.startsWith(`${rel}: `)));
|
|
32
|
+
const label = failing.length === 0 ? "rejections failed" : `rejections ${failing.length} failed`;
|
|
33
|
+
return failed(
|
|
34
|
+
label,
|
|
35
|
+
problems.map((line) => `repo turn: rejections: ${line}\n`),
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Grok PreToolUse: rewrite Bash/run_terminal_command through `rtk rewrite`.
|
|
3
|
+
|
|
4
|
+
Fail-open: parse error, missing rtk, timeout, no rewrite → empty stdout, exit 0.
|
|
5
|
+
Keep the full toolInput (Grok needs `description`); only replace `command`.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "../spawn.ts";
|
|
8
|
+
import { readEnv } from "../env.ts";
|
|
9
|
+
import { processHookIo, readHookInput, type HookIo } from "./hook-io.ts";
|
|
10
|
+
|
|
11
|
+
const RTK_TIMEOUT_MS = 3000;
|
|
12
|
+
|
|
13
|
+
const ULTRA_SUBS = new Set([
|
|
14
|
+
"grep",
|
|
15
|
+
"rg",
|
|
16
|
+
"curl",
|
|
17
|
+
"wget",
|
|
18
|
+
"vitest",
|
|
19
|
+
"jest",
|
|
20
|
+
"pytest",
|
|
21
|
+
"test",
|
|
22
|
+
"lint",
|
|
23
|
+
"tsc",
|
|
24
|
+
"npm",
|
|
25
|
+
"npx",
|
|
26
|
+
"read",
|
|
27
|
+
"find",
|
|
28
|
+
"diff",
|
|
29
|
+
"json",
|
|
30
|
+
"log",
|
|
31
|
+
"playwright",
|
|
32
|
+
"cargo",
|
|
33
|
+
"ruff",
|
|
34
|
+
"prettier",
|
|
35
|
+
"format",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export function main(_argv: string[] = [], io: HookIo = processHookIo()): number {
|
|
39
|
+
try {
|
|
40
|
+
const data = readHookInput(io);
|
|
41
|
+
if (!data) return 0;
|
|
42
|
+
const toolInput = data.toolInput ?? data.tool_input;
|
|
43
|
+
if (!toolInput) return 0;
|
|
44
|
+
const cmdRaw = toolInput.command;
|
|
45
|
+
if (typeof cmdRaw !== "string") return 0;
|
|
46
|
+
const cmd = cmdRaw.trim();
|
|
47
|
+
if (!cmd) return 0;
|
|
48
|
+
|
|
49
|
+
const first = cmd.slice(0, cmd.search(/\s|$/));
|
|
50
|
+
const already = first === "rtk" || first.endsWith("/rtk");
|
|
51
|
+
let rewritten: string | null;
|
|
52
|
+
if (already) {
|
|
53
|
+
rewritten = injectUltra(cmd);
|
|
54
|
+
if (rewritten === cmd) return 0;
|
|
55
|
+
} else {
|
|
56
|
+
rewritten = rewrite(cmd);
|
|
57
|
+
if (!rewritten) return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const updated = Object.assign({}, toolInput, { command: rewritten });
|
|
61
|
+
io.write(
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
hookSpecificOutput: {
|
|
64
|
+
hookEventName: "PreToolUse",
|
|
65
|
+
permissionDecisionReason: "RTK auto-rewrite",
|
|
66
|
+
updatedInput: updated,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
return 0;
|
|
71
|
+
} catch {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function injectUltra(rewritten: string): string {
|
|
77
|
+
if (rewritten.includes("--ultra-compact")) return rewritten;
|
|
78
|
+
const m = rewritten.match(/^(\S+)\s+(\S+)(?:\s+([\s\S]*))?$/);
|
|
79
|
+
if (!m || m[1] !== "rtk") return rewritten;
|
|
80
|
+
// Groups 1 and 2 are required, so they are strings once the match succeeded.
|
|
81
|
+
const sub = m[2]!;
|
|
82
|
+
if (!ULTRA_SUBS.has(sub)) return rewritten;
|
|
83
|
+
const rest = m[3] || "";
|
|
84
|
+
return "rtk " + sub + " --ultra-compact" + (rest ? " " + rest : "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rewrite(cmd: string): string | null {
|
|
88
|
+
// Bun.which caches the PATH it started with, so pass the current one.
|
|
89
|
+
const rtk = Bun.which("rtk", { PATH: readEnv().PATH ?? "" });
|
|
90
|
+
if (!rtk) return null;
|
|
91
|
+
try {
|
|
92
|
+
const proc = spawnSync(rtk, ["rewrite", "--ultra-compact", cmd], {
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
timeout: RTK_TIMEOUT_MS,
|
|
95
|
+
});
|
|
96
|
+
const out = (proc.stdout || "").trim();
|
|
97
|
+
if (!out || out === cmd) return null;
|
|
98
|
+
return injectUltra(out);
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|