peaks-loop 4.1.2 → 4.1.3
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/.claude-plugin/marketplace.json +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks worktree spawn` — write a lease and run `git worktree add`.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from `worktree-lease-commands.ts` (mechanical move of
|
|
5
|
+
* the `.action()` body plus its two local helpers). The command name,
|
|
6
|
+
* every option and its help text, the ok/fail envelopes and the
|
|
7
|
+
* `process.exitCode = 1` on failure are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from 'commander';
|
|
10
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
11
|
+
export declare function registerWorktreeLeaseSpawnCommand(auth: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks worktree spawn` — write a lease and run `git worktree add`.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from `worktree-lease-commands.ts` (mechanical move of
|
|
5
|
+
* the `.action()` body plus its two local helpers). The command name,
|
|
6
|
+
* every option and its help text, the ok/fail envelopes and the
|
|
7
|
+
* `process.exitCode = 1` on failure are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
12
|
+
import { atomicWriteJson } from '../../services/ide/shared/atomic-json.js';
|
|
13
|
+
import { emitLeaseEvent } from '../../services/observability/observability-service.js';
|
|
14
|
+
import { finalizeLease, generateLeaseId, leaseFilePath, ttlForRole, worktreePath } from '../../services/worktree/worktree-lease.js';
|
|
15
|
+
import { joinPathSession, resolveProjectRoot, resolveSessionId } from './worktree-lease-session.js';
|
|
16
|
+
const SPAWN_DESCRIPTION = `Spawn a worktree under .peaks/_runtime/<sid>/worktrees/<leaseId>/ with a managed lease. ` +
|
|
17
|
+
`The lease is the source of truth for the L2 hook gate (Part 2 of this slice); ` +
|
|
18
|
+
`until the hook integration ships, sub-agents may still invoke raw \`git worktree add\` ` +
|
|
19
|
+
`with a current \`peaks worktree auth grant\` token. Default TTL is role-aware ` +
|
|
20
|
+
`(rd=30m / qa=15m / ui=1h); pass --ttl <ms> to override.`;
|
|
21
|
+
const SPAWN_TTL_DESCRIPTION = `time-to-live in ms (default role-aware; override with positive number)`;
|
|
22
|
+
// Branch names must be safe for git ref-format; the cap keeps a pathological
|
|
23
|
+
// rid from producing an unusable ref.
|
|
24
|
+
const MAX_BRANCH_NAME_LENGTH = 80;
|
|
25
|
+
function resolveTtlMs(raw, role) {
|
|
26
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
27
|
+
return ttlForRole(role);
|
|
28
|
+
const parsed = Number(raw);
|
|
29
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
30
|
+
return ttlForRole(role);
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
function deriveBranch(rid) {
|
|
34
|
+
// Branch names must be safe for git ref-format. Strip leading
|
|
35
|
+
// `rid-` if present and replace any non-safe chars with `-`.
|
|
36
|
+
return rid.replace(/[^A-Za-z0-9._/-]/g, '-').slice(0, MAX_BRANCH_NAME_LENGTH);
|
|
37
|
+
}
|
|
38
|
+
function printSpawnOk(io, options, ctx) {
|
|
39
|
+
const { lease, sessionId, projectRoot, ttlMs, wtPath, branch } = ctx;
|
|
40
|
+
printResult(io, ok('worktree.spawn', {
|
|
41
|
+
lease,
|
|
42
|
+
sessionId,
|
|
43
|
+
projectRoot,
|
|
44
|
+
ttlMs,
|
|
45
|
+
nextActions: [
|
|
46
|
+
`Worktree path: ${wtPath}`,
|
|
47
|
+
`Branch: ${branch}`,
|
|
48
|
+
`Lease expires at: ${new Date(lease.expiresAt).toISOString()}`,
|
|
49
|
+
'Run `peaks worktree release --lease-id <leaseId>` when done',
|
|
50
|
+
'Hook integration (lease-aware gate) ships in Part 2'
|
|
51
|
+
]
|
|
52
|
+
}, [], []), options.json);
|
|
53
|
+
}
|
|
54
|
+
function printSpawnFailed(io, options, error, sessionId) {
|
|
55
|
+
printResult(io, fail('worktree.spawn', 'SPAWN_FAILED', getErrorMessage(error), { rid: options.rid, role: options.role, sessionId }, [
|
|
56
|
+
'Verify `git worktree add` succeeded (output above).',
|
|
57
|
+
'If the lease file was NOT written, retry; the lease directory is .peaks/_runtime/<sid>/worktree-leases/.',
|
|
58
|
+
'For an existing branch, pass --branch <name> explicitly (the spawn refuses to overwrite an active branch).'
|
|
59
|
+
]), options.json);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
function runSpawn(options, io) {
|
|
63
|
+
const projectRoot = resolveProjectRoot(options);
|
|
64
|
+
const sessionId = resolveSessionId(options, projectRoot);
|
|
65
|
+
try {
|
|
66
|
+
const leaseId = generateLeaseId();
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const ttlMs = resolveTtlMs(options.ttl, options.role);
|
|
69
|
+
const branch = options.branch ?? deriveBranch(options.rid);
|
|
70
|
+
const wtPath = worktreePath(joinPathSession(projectRoot, sessionId), leaseId);
|
|
71
|
+
const lease = finalizeLease({
|
|
72
|
+
leaseId,
|
|
73
|
+
rid: options.rid,
|
|
74
|
+
role: options.role,
|
|
75
|
+
path: wtPath,
|
|
76
|
+
branch,
|
|
77
|
+
createdAt: now,
|
|
78
|
+
expiresAt: now + ttlMs,
|
|
79
|
+
purpose: options.purpose
|
|
80
|
+
});
|
|
81
|
+
// Run `git worktree add` from the project root. The PreToolUse hook
|
|
82
|
+
// (Part 2 of this slice) will consult the lease file we just wrote
|
|
83
|
+
// and authorize this very `git worktree add`; for Part 1 we still
|
|
84
|
+
// require a current `peaks worktree auth grant` token to remain
|
|
85
|
+
// consistent with the slice-027 hard gate contract.
|
|
86
|
+
execSync(`git worktree add "${wtPath}" -b "${branch}"`, {
|
|
87
|
+
cwd: projectRoot,
|
|
88
|
+
stdio: 'pipe',
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
windowsHide: true
|
|
91
|
+
});
|
|
92
|
+
atomicWriteJson(leaseFilePath(joinPathSession(projectRoot, sessionId), leaseId), lease);
|
|
93
|
+
// Part 4.A: emit spawn metric (fire-and-forget; never blocks).
|
|
94
|
+
emitLeaseEvent({
|
|
95
|
+
sessionId,
|
|
96
|
+
projectRoot,
|
|
97
|
+
kind: 'spawn',
|
|
98
|
+
leaseId,
|
|
99
|
+
rid: options.rid,
|
|
100
|
+
role: options.role
|
|
101
|
+
});
|
|
102
|
+
printSpawnOk(io, options, { lease, sessionId, projectRoot, ttlMs, wtPath, branch });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
printSpawnFailed(io, options, error, sessionId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function registerWorktreeLeaseSpawnCommand(auth, io) {
|
|
109
|
+
addJsonOption(auth
|
|
110
|
+
.command('spawn')
|
|
111
|
+
.description(SPAWN_DESCRIPTION)
|
|
112
|
+
.requiredOption('--rid <rid>', 'peaks request id the lease is associated with')
|
|
113
|
+
.requiredOption('--role <role>', 'sub-agent role (rd | qa | ui | sc | prd | general-purpose)')
|
|
114
|
+
.requiredOption('--purpose <text>', 'why this worktree was spawned (audit log)')
|
|
115
|
+
.option('--ttl <ms>', SPAWN_TTL_DESCRIPTION)
|
|
116
|
+
.option('--branch <name>', 'git branch name (default: derived from rid)')
|
|
117
|
+
.option('--session <sid>', 'override session id')
|
|
118
|
+
.option('--project <path>', 'project root (default: findProjectRoot(cwd))')).action((options) => runSpawn(options, io));
|
|
119
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks worktree lease-status` — show one lease in detail.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from `worktree-lease-commands.ts` (mechanical move of
|
|
5
|
+
* the `.action()` body). The lease-file failures route through the shared
|
|
6
|
+
* `loadLeaseOrStop`; the computed `live` flag, the on-disk path
|
|
7
|
+
* diagnostics and the ok/fail envelopes are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from 'commander';
|
|
10
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
11
|
+
export declare function registerWorktreeLeaseStatusCommand(auth: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks worktree lease-status` — show one lease in detail.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from `worktree-lease-commands.ts` (mechanical move of
|
|
5
|
+
* the `.action()` body). The lease-file failures route through the shared
|
|
6
|
+
* `loadLeaseOrStop`; the computed `live` flag, the on-disk path
|
|
7
|
+
* diagnostics and the ok/fail envelopes are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
10
|
+
import { existsSync, statSync } from 'node:fs';
|
|
11
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
12
|
+
import { isLeaseActive, leaseFilePath } from '../../services/worktree/worktree-lease.js';
|
|
13
|
+
import { loadLeaseOrStop } from './worktree-lease-load.js';
|
|
14
|
+
import { joinPathSession, resolveProjectRoot, resolveSessionId } from './worktree-lease-session.js';
|
|
15
|
+
const LEASE_STATUS_DESCRIPTION = 'Show one lease in detail: full lease record + computed `live` flag (active AND not past ' +
|
|
16
|
+
'expiry) + path/branch/path-exists-on-disk diagnostics. Use this when triaging why a ' +
|
|
17
|
+
'sub-agent cannot write to a worktree.';
|
|
18
|
+
const STATUS_MISSING_HINTS = ['Run `peaks worktree list` to inspect available leases.'];
|
|
19
|
+
const STATUS_INVALID_HINTS = ['Delete the malformed lease file manually and re-spawn.'];
|
|
20
|
+
/** Whether the lease path is present on disk and is a directory. */
|
|
21
|
+
function inspectLeasePath(leasePath) {
|
|
22
|
+
const pathExists = existsSync(leasePath);
|
|
23
|
+
if (!pathExists)
|
|
24
|
+
return { pathExists, pathIsDirectory: false };
|
|
25
|
+
try {
|
|
26
|
+
return { pathExists, pathIsDirectory: statSync(leasePath).isDirectory() };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { pathExists, pathIsDirectory: false };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function printLeaseStatusOk(io, options, ctx) {
|
|
33
|
+
const { lease, sessionId, projectRoot } = ctx;
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const { pathExists, pathIsDirectory } = inspectLeasePath(lease.path);
|
|
36
|
+
const live = isLeaseActive(lease, now);
|
|
37
|
+
printResult(io, ok('worktree.lease-status', {
|
|
38
|
+
sessionId,
|
|
39
|
+
projectRoot,
|
|
40
|
+
file: '.peaks/_runtime/' + sessionId + '/worktree-leases/' + lease.leaseId + '.json',
|
|
41
|
+
lease,
|
|
42
|
+
live,
|
|
43
|
+
diagnostics: {
|
|
44
|
+
now,
|
|
45
|
+
remainingMs: lease.expiresAt - now,
|
|
46
|
+
pathExists,
|
|
47
|
+
pathIsDirectory
|
|
48
|
+
}
|
|
49
|
+
}, [], [
|
|
50
|
+
`Lease ${lease.leaseId} is ${live ? 'LIVE' : 'NOT LIVE'} ` +
|
|
51
|
+
`(status=${lease.status}, remaining=${lease.expiresAt - now}ms).`,
|
|
52
|
+
`Worktree path ${lease.path} ${pathExists ? (pathIsDirectory ? 'exists (dir)' : 'exists (NOT a dir)') : 'MISSING'}.`
|
|
53
|
+
]), options.json);
|
|
54
|
+
}
|
|
55
|
+
function printLeaseStatusFailed(io, options, err, sessionId) {
|
|
56
|
+
printResult(io, fail('worktree.lease-status', 'STATUS_FAILED', getErrorMessage(err), { leaseId: options.leaseId, sessionId }, ['Re-run after fixing the failure (see cause in the error message).']), options.json);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
function runLeaseStatus(options, io) {
|
|
60
|
+
const projectRoot = resolveProjectRoot(options);
|
|
61
|
+
const sessionId = resolveSessionId(options, projectRoot);
|
|
62
|
+
try {
|
|
63
|
+
const file = leaseFilePath(joinPathSession(projectRoot, sessionId), options.leaseId);
|
|
64
|
+
const loaded = loadLeaseOrStop(io, options, {
|
|
65
|
+
command: 'worktree.lease-status',
|
|
66
|
+
file,
|
|
67
|
+
hints: { missing: STATUS_MISSING_HINTS, invalid: STATUS_INVALID_HINTS }
|
|
68
|
+
});
|
|
69
|
+
if (loaded.stop) {
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
printLeaseStatusOk(io, options, { lease: loaded.lease, sessionId, projectRoot });
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
printLeaseStatusFailed(io, options, err, sessionId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function registerWorktreeLeaseStatusCommand(auth, io) {
|
|
80
|
+
addJsonOption(auth
|
|
81
|
+
.command('lease-status')
|
|
82
|
+
.description(LEASE_STATUS_DESCRIPTION)
|
|
83
|
+
.requiredOption('--lease-id <id>', 'lease id to inspect')
|
|
84
|
+
.option('--session <sid>', 'override session id')
|
|
85
|
+
.option('--project <path>', 'project root (default: findProjectRoot(cwd))')).action((options) => runLeaseStatus(options, io));
|
|
86
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared lease-store reader for the two sweep-style commands (`list`, `gc`).
|
|
3
|
+
*
|
|
4
|
+
* Both enumerate the session's lease directory through the pure
|
|
5
|
+
* `listLeasesSync` helper with the same real-fs bindings, and both render
|
|
6
|
+
* the same `Malformed lease: <file> (<error>)` warning line per unreadable
|
|
7
|
+
* file. Extracted verbatim from `worktree-lease-commands.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { type LeaseListResult } from '../../services/worktree/worktree-lease.js';
|
|
10
|
+
export interface LeaseStoreSnapshot {
|
|
11
|
+
storeDir: string;
|
|
12
|
+
result: LeaseListResult;
|
|
13
|
+
}
|
|
14
|
+
export declare function loadLeaseStore(projectRoot: string, sessionId: string): LeaseStoreSnapshot;
|
|
15
|
+
/** One warning line per malformed lease file skipped by the store reader. */
|
|
16
|
+
export declare function malformedLeaseWarnings(errors: ReadonlyArray<{
|
|
17
|
+
file: string;
|
|
18
|
+
error: string;
|
|
19
|
+
}>): string[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared lease-store reader for the two sweep-style commands (`list`, `gc`).
|
|
3
|
+
*
|
|
4
|
+
* Both enumerate the session's lease directory through the pure
|
|
5
|
+
* `listLeasesSync` helper with the same real-fs bindings, and both render
|
|
6
|
+
* the same `Malformed lease: <file> (<error>)` warning line per unreadable
|
|
7
|
+
* file. Extracted verbatim from `worktree-lease-commands.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readdirSync, readFileSync as readFileSyncNode } from 'node:fs';
|
|
10
|
+
import { leaseStoreDir, listLeasesSync } from '../../services/worktree/worktree-lease.js';
|
|
11
|
+
import { joinPathSession } from './worktree-lease-session.js';
|
|
12
|
+
export function loadLeaseStore(projectRoot, sessionId) {
|
|
13
|
+
const storeDir = leaseStoreDir(joinPathSession(projectRoot, sessionId));
|
|
14
|
+
const result = listLeasesSync(storeDir, {
|
|
15
|
+
readdir: (p) => readdirSync(p),
|
|
16
|
+
readFile: (p) => readFileSyncNode(p, 'utf8'),
|
|
17
|
+
existsSync: (p) => existsSync(p)
|
|
18
|
+
});
|
|
19
|
+
return { storeDir, result };
|
|
20
|
+
}
|
|
21
|
+
/** One warning line per malformed lease file skipped by the store reader. */
|
|
22
|
+
export function malformedLeaseWarnings(errors) {
|
|
23
|
+
return errors.map((e) => `Malformed lease: ${e.file} (${e.error})`);
|
|
24
|
+
}
|
package/dist/cli/program.js
CHANGED
|
@@ -8,6 +8,7 @@ import { registerWorkflowCommands } from './commands/workflow-commands.js';
|
|
|
8
8
|
import { registerCapabilityWorkerConfigAndSCCommands } from './commands/capability-worker-config-sc-commands.js';
|
|
9
9
|
import { registerSubAgentCommands } from './commands/sub-agent-commands.js';
|
|
10
10
|
import { registerLeaseMetricsCommand } from './commands/lease-metrics-commands.js';
|
|
11
|
+
import { registerMcpCommands } from './commands/mcp-commands.js';
|
|
11
12
|
import { registerLeaseStatsCommand } from './commands/lease-stats-commands.js';
|
|
12
13
|
import { registerContainerCommand } from './commands/container-commands.js';
|
|
13
14
|
import { registerVmCommand } from './commands/vm-commands.js';
|
|
@@ -197,6 +198,10 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
197
198
|
registerVmCommand(program, io);
|
|
198
199
|
registerCronCommand(program, io);
|
|
199
200
|
registerCronSchedulerCommand(program, io);
|
|
201
|
+
// The read-only MCP surface's command family. `serve` spawns the server
|
|
202
|
+
// module by path rather than importing it, so `src/services/mcp/` stays
|
|
203
|
+
// deletable (see the header of `mcp-commands.ts`).
|
|
204
|
+
registerMcpCommands(program, io);
|
|
200
205
|
// Auto-route the remaining 60+ commands after the orchestrators so the
|
|
201
206
|
// lazy `skill` parent (used by adapter-commands / sediment-commands)
|
|
202
207
|
// finds the existing `peaks skill` group registered by
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { McpInstallCommand, McpInstallOutcome, McpInstallRunner } from './mcp-install.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ceiling on ONE harness command. `spawnSync` without `timeout` has no ceiling
|
|
4
|
+
* at all, and the failure it lets through is measured, not hypothetical: a
|
|
5
|
+
* real harness binary on this host starts and then does not return within 25 s.
|
|
6
|
+
* Unbounded, that blocks `peaks mcp install` in the foreground forever. Same
|
|
7
|
+
* class the MCP CLI executor bounds (`cli-executor.ts`), set to twice its 15 s
|
|
8
|
+
* because the child is the harness's binary and its startup is its own to
|
|
9
|
+
* spend. A plan is at most three commands, so the install is bounded at 3x.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MCP_INSTALL_TIMEOUT_MS = 30000;
|
|
12
|
+
/**
|
|
13
|
+
* One harness command, with the deadline applied. `windowsHide` is the tree's
|
|
14
|
+
* spawn contract: a CLI that briefly runs a harness must not flash a console
|
|
15
|
+
* window on a desktop OS. `timeoutMs` is a parameter only so a test can watch
|
|
16
|
+
* the deadline fire without waiting out the production value — callers want
|
|
17
|
+
* `spawnMcpInstallRunner`, which fixes it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function spawnMcpInstallCommand(command: McpInstallCommand, timeoutMs?: number): McpInstallOutcome;
|
|
20
|
+
/** The production runner: the deadline above, and nothing else left to decide. */
|
|
21
|
+
export declare const spawnMcpInstallRunner: McpInstallRunner;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/services/distribution/mcp-install-runner.ts
|
|
2
|
+
//
|
|
3
|
+
// The production runner behind the vendor-neutral MCP registration engine
|
|
4
|
+
// (`mcp-install.ts`): it runs one harness command under a deadline and reports
|
|
5
|
+
// what the process said. It lives in its own module only so the engine keeps its
|
|
6
|
+
// size; the engine RE-EXPORTS it, so every caller still imports it from
|
|
7
|
+
// `mcp-install.js` and no import path moved.
|
|
8
|
+
//
|
|
9
|
+
// THE FIELD THIS MODULE EXISTS FOR. A run that exits non-zero ANSWERED the
|
|
10
|
+
// question — "no such registration" — and a run the deadline kills, or one that
|
|
11
|
+
// never launches, did not: `answered: false` is the only place that distinction
|
|
12
|
+
// is born, and it is what stops `peaks mcp uninstall` from reporting a hung
|
|
13
|
+
// harness's scope as "nothing was there".
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling on ONE harness command. `spawnSync` without `timeout` has no ceiling
|
|
17
|
+
* at all, and the failure it lets through is measured, not hypothetical: a
|
|
18
|
+
* real harness binary on this host starts and then does not return within 25 s.
|
|
19
|
+
* Unbounded, that blocks `peaks mcp install` in the foreground forever. Same
|
|
20
|
+
* class the MCP CLI executor bounds (`cli-executor.ts`), set to twice its 15 s
|
|
21
|
+
* because the child is the harness's binary and its startup is its own to
|
|
22
|
+
* spend. A plan is at most three commands, so the install is bounded at 3x.
|
|
23
|
+
*/
|
|
24
|
+
export const MCP_INSTALL_TIMEOUT_MS = 30_000;
|
|
25
|
+
/**
|
|
26
|
+
* One harness command, with the deadline applied. `windowsHide` is the tree's
|
|
27
|
+
* spawn contract: a CLI that briefly runs a harness must not flash a console
|
|
28
|
+
* window on a desktop OS. `timeoutMs` is a parameter only so a test can watch
|
|
29
|
+
* the deadline fire without waiting out the production value — callers want
|
|
30
|
+
* `spawnMcpInstallRunner`, which fixes it.
|
|
31
|
+
*/
|
|
32
|
+
export function spawnMcpInstallCommand(command, timeoutMs = MCP_INSTALL_TIMEOUT_MS) {
|
|
33
|
+
const result = spawnSync(command.command, [...command.args], {
|
|
34
|
+
stdio: 'pipe',
|
|
35
|
+
windowsHide: true,
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
timeout: timeoutMs
|
|
38
|
+
});
|
|
39
|
+
if (result.error !== undefined) {
|
|
40
|
+
// `spawnSync` kills the child at the deadline and reports its own timeout as
|
|
41
|
+
// ETIMEDOUT. A deadline and a launch failure are different facts, but they
|
|
42
|
+
// share the one that matters here: the harness never rendered a verdict.
|
|
43
|
+
const timedOut = result.error.code === 'ETIMEDOUT';
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
answered: false,
|
|
47
|
+
detail: timedOut
|
|
48
|
+
? `timed out after ${timeoutMs} ms and was killed: ${command.command} ${command.args.join(' ')}`
|
|
49
|
+
: result.error.message
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (result.status === 0) {
|
|
53
|
+
return { ok: true, detail: '' };
|
|
54
|
+
}
|
|
55
|
+
const stderr = (result.stderr ?? '').trim();
|
|
56
|
+
return { ok: false, detail: stderr.length > 0 ? stderr : `exit ${result.status ?? 'unknown'}` };
|
|
57
|
+
}
|
|
58
|
+
/** The production runner: the deadline above, and nothing else left to decide. */
|
|
59
|
+
export const spawnMcpInstallRunner = (command) => spawnMcpInstallCommand(command);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IdeAdapter, IdeId } from '../ide/ide-types.js';
|
|
2
|
+
import { type IdeMcpInstallProfile } from '../ide/ide-mcp-install-types.js';
|
|
3
|
+
/** One process the plan wants run, as command + args. Never a joined string. */
|
|
4
|
+
export interface McpInstallCommand {
|
|
5
|
+
readonly command: string;
|
|
6
|
+
readonly args: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
/** What one operation is for, so a report can say which step did what. */
|
|
9
|
+
export type McpInstallOperation = 'remove' | 'add';
|
|
10
|
+
export interface McpInstallStep {
|
|
11
|
+
readonly operation: McpInstallOperation;
|
|
12
|
+
/** The scope this step acts on; the target scope for an `add`. */
|
|
13
|
+
readonly scope: string;
|
|
14
|
+
readonly command: McpInstallCommand;
|
|
15
|
+
}
|
|
16
|
+
/** What a runner says happened. `detail` is surfaced verbatim when it did not. */
|
|
17
|
+
export interface McpInstallOutcome {
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly detail: string;
|
|
20
|
+
/**
|
|
21
|
+
* `false` only when the harness NEVER RENDERED A VERDICT — the deadline killed
|
|
22
|
+
* it, or it never launched. A non-zero exit answered ("no such registration"),
|
|
23
|
+
* so an absent value reads as `true`, as does every outcome that predates this
|
|
24
|
+
* field.
|
|
25
|
+
*/
|
|
26
|
+
readonly answered?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** The seam: a test supplies one, the CLI supplies the spawning one below. */
|
|
29
|
+
export type McpInstallRunner = (command: McpInstallCommand) => McpInstallOutcome;
|
|
30
|
+
export interface McpInstallReport {
|
|
31
|
+
readonly serverName: string;
|
|
32
|
+
/** Scopes where the removal reported success — i.e. there was something to remove. */
|
|
33
|
+
readonly removed: readonly string[];
|
|
34
|
+
/** Scopes where the removal reported failure — normally "nothing was there". */
|
|
35
|
+
readonly absent: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Scopes whose removal never reached a verdict — the harness timed out or never
|
|
38
|
+
* launched — so it is neither `removed` nor honestly `absent`. Optional because
|
|
39
|
+
* the install sweep tolerates a failed removal (it is about to write) and never
|
|
40
|
+
* populates it; the uninstall sweep always does.
|
|
41
|
+
*/
|
|
42
|
+
readonly unknown?: readonly string[];
|
|
43
|
+
readonly added: boolean;
|
|
44
|
+
readonly ok: boolean;
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The argv that launches THIS installation's read-only MCP server.
|
|
49
|
+
*
|
|
50
|
+
* An interpreter and an entry path, in array form: the same shape `mcp serve`
|
|
51
|
+
* spawns and the same one the read-only executor uses. A bare CLI name would be
|
|
52
|
+
* a `.cmd` shim on some platforms, and picking a spelling per platform is the
|
|
53
|
+
* branch this tree forbids — resolving the pair needs none.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveMcpServerArgv(): readonly string[];
|
|
56
|
+
/** The plan: one removal per declared scope, then the addition. Order is the contract. */
|
|
57
|
+
export declare function buildMcpInstallPlan(profile: IdeMcpInstallProfile, serverArgv: readonly string[]): readonly McpInstallStep[];
|
|
58
|
+
/** Every removal, then the addition. A failed removal is a fact, not a failure. */
|
|
59
|
+
export declare function runMcpInstallPlan(profile: IdeMcpInstallProfile, serverArgv: readonly string[], runner: McpInstallRunner): McpInstallReport;
|
|
60
|
+
/**
|
|
61
|
+
* The removal sweep on its own — what `uninstall` is, and all that it is.
|
|
62
|
+
*
|
|
63
|
+
* `exit 0` removed it, `exit != 0` answered "no" (absent, unchanged) — but a run the
|
|
64
|
+
* deadline killed, or one that never launched, ANSWERED NOTHING, and calling that
|
|
65
|
+
* "nothing was there" is the lie this sweep used to tell. Those scopes are `unknown`,
|
|
66
|
+
* and `ok` is false whenever any scope is unknown: with a scope the harness never
|
|
67
|
+
* spoke for, the removal is unconfirmed and the state is not known.
|
|
68
|
+
*/
|
|
69
|
+
export declare function runMcpUninstallPlan(profile: IdeMcpInstallProfile, serverArgv: readonly string[], runner: McpInstallRunner): McpInstallReport;
|
|
70
|
+
/**
|
|
71
|
+
* One adapter's outcome. `skipped` is a FIRST-CLASS result and not an error: the
|
|
72
|
+
* install's contract with an adapter that declares no registration entry is
|
|
73
|
+
* silence, and a caller that has to distinguish "did nothing" from
|
|
74
|
+
* "failed" needs the distinction to be in the value, not in a log line.
|
|
75
|
+
*/
|
|
76
|
+
export type AdapterMcpInstallResult = {
|
|
77
|
+
readonly ideId: IdeId;
|
|
78
|
+
readonly skipped: true;
|
|
79
|
+
readonly reason: string;
|
|
80
|
+
} | {
|
|
81
|
+
readonly ideId: IdeId;
|
|
82
|
+
readonly skipped: false;
|
|
83
|
+
readonly report: McpInstallReport;
|
|
84
|
+
};
|
|
85
|
+
export declare const NO_MCP_INSTALL_REASON = "adapter declares no mcpInstall profile";
|
|
86
|
+
/** Register this adapter's harness, or report that there is nothing to register. */
|
|
87
|
+
export declare function installMcpForAdapter(adapter: IdeAdapter, serverArgv: readonly string[], runner: McpInstallRunner): AdapterMcpInstallResult;
|
|
88
|
+
/** The same, for the removal side. */
|
|
89
|
+
export declare function uninstallMcpForAdapter(adapter: IdeAdapter, serverArgv: readonly string[], runner: McpInstallRunner): AdapterMcpInstallResult;
|
|
90
|
+
/**
|
|
91
|
+
* The production runner and its deadline live in `mcp-install-runner.ts` — split
|
|
92
|
+
* out only to keep this engine within the file-size cap. They are re-exported, so
|
|
93
|
+
* the boundary is invisible: every caller still imports them from here.
|
|
94
|
+
*/
|
|
95
|
+
export { MCP_INSTALL_TIMEOUT_MS, spawnMcpInstallCommand, spawnMcpInstallRunner } from './mcp-install-runner.js';
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/services/distribution/mcp-install.ts
|
|
2
|
+
//
|
|
3
|
+
// The vendor-neutral MCP registration engine (spec §8.3). It owns the SHAPE of
|
|
4
|
+
// an install — delete every scope, then add — and owns no harness's spelling of
|
|
5
|
+
// anything: the server key, the scopes and the argv come from the adapter's
|
|
6
|
+
// `mcpInstall` profile, and the process that runs the argv is an injected
|
|
7
|
+
// runner.
|
|
8
|
+
//
|
|
9
|
+
// WHY DELETE-THEN-ADD AND NOT "CHECK, THEN ADD IF ABSENT". The harness's own
|
|
10
|
+
// register verb is not idempotent: the same name in the same scope fails, and
|
|
11
|
+
// there is no `--force` and no `update` to reach for. A conditional add would
|
|
12
|
+
// therefore have to be right about a state it cannot see — so the engine does
|
|
13
|
+
// the one thing that is right in both states: it removes what is there (a
|
|
14
|
+
// removal that finds nothing is tolerated) and then adds. That is also what
|
|
15
|
+
// makes the install SEMANTICALLY idempotent: two runs produce the same argv in
|
|
16
|
+
// the same order, so the registration left behind has the same CONTENT, even
|
|
17
|
+
// though the second run performed operations the first one did too.
|
|
18
|
+
//
|
|
19
|
+
// WHY THE DELETE SWEEPS EVERY SCOPE. The entry can already exist in a scope the
|
|
20
|
+
// install is not writing to — an earlier install, or a user who moved it. The
|
|
21
|
+
// removal names a scope, and the harness fails on the wrong or empty one, so a
|
|
22
|
+
// targeted delete would leave the old entry behind and the add would then fail
|
|
23
|
+
// on the duplicate it left. Sweeping all declared scopes is the only version of
|
|
24
|
+
// this that is correct for a state the engine cannot enumerate.
|
|
25
|
+
//
|
|
26
|
+
// WHAT "ONLY OURS" MEANS HERE. The engine never reads or writes the
|
|
27
|
+
// harness's configuration file. It runs the harness's own removal and addition,
|
|
28
|
+
// and the only key either of them names is the adapter's `serverName` — so no
|
|
29
|
+
// other entry can be touched by construction. The honest limit of that
|
|
30
|
+
// guarantee is the name itself: an entry the USER named the same thing is
|
|
31
|
+
// indistinguishable from ours, and is the one case this cannot tell apart.
|
|
32
|
+
//
|
|
33
|
+
// NO HARNESS BRANCH, NO PLATFORM BRANCH. Every value is either the caller's or
|
|
34
|
+
// the adapter's. `resolveMcpServerArgv` returns an interpreter and an entry path
|
|
35
|
+
// for the same reason `cli-executor.ts` does: a shipped CLI name is a shim on
|
|
36
|
+
// some platforms, and choosing between the two spellings would be the platform
|
|
37
|
+
// branch this module is forbidden to contain.
|
|
38
|
+
import { dirname, join, resolve } from 'node:path';
|
|
39
|
+
import { fileURLToPath } from 'node:url';
|
|
40
|
+
import { assertMcpInstallProfile, expandMcpArgv } from '../ide/ide-mcp-install-types.js';
|
|
41
|
+
/** `<packageRoot>` — this file's tree is `<root>/src/services/distribution` or `<root>/dist/…`. */
|
|
42
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
43
|
+
/**
|
|
44
|
+
* The argv that launches THIS installation's read-only MCP server.
|
|
45
|
+
*
|
|
46
|
+
* An interpreter and an entry path, in array form: the same shape `mcp serve`
|
|
47
|
+
* spawns and the same one the read-only executor uses. A bare CLI name would be
|
|
48
|
+
* a `.cmd` shim on some platforms, and picking a spelling per platform is the
|
|
49
|
+
* branch this tree forbids — resolving the pair needs none.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveMcpServerArgv() {
|
|
52
|
+
return [process.execPath, join(PACKAGE_ROOT, 'bin', 'peaks.js'), 'mcp', 'serve'];
|
|
53
|
+
}
|
|
54
|
+
/** The plan: one removal per declared scope, then the addition. Order is the contract. */
|
|
55
|
+
export function buildMcpInstallPlan(profile, serverArgv) {
|
|
56
|
+
assertMcpInstallProfile(profile);
|
|
57
|
+
const removals = profile.scopes.map((scope) => ({
|
|
58
|
+
operation: 'remove',
|
|
59
|
+
scope,
|
|
60
|
+
command: {
|
|
61
|
+
command: profile.removeArgv[0] ?? '',
|
|
62
|
+
args: expandMcpArgv(profile.removeArgv.slice(1), {
|
|
63
|
+
serverName: profile.serverName,
|
|
64
|
+
scope,
|
|
65
|
+
serverArgv
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
const addition = {
|
|
70
|
+
operation: 'add',
|
|
71
|
+
scope: profile.targetScope,
|
|
72
|
+
command: {
|
|
73
|
+
command: profile.addArgv[0] ?? '',
|
|
74
|
+
args: expandMcpArgv(profile.addArgv.slice(1), {
|
|
75
|
+
serverName: profile.serverName,
|
|
76
|
+
scope: profile.targetScope,
|
|
77
|
+
serverArgv
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return [...removals, addition];
|
|
82
|
+
}
|
|
83
|
+
/** Every removal, then the addition. A failed removal is a fact, not a failure. */
|
|
84
|
+
export function runMcpInstallPlan(profile, serverArgv, runner) {
|
|
85
|
+
const plan = buildMcpInstallPlan(profile, serverArgv);
|
|
86
|
+
const removed = [];
|
|
87
|
+
const absent = [];
|
|
88
|
+
let detail = '';
|
|
89
|
+
for (const step of plan) {
|
|
90
|
+
const outcome = runner(step.command);
|
|
91
|
+
if (step.operation === 'remove') {
|
|
92
|
+
if (outcome.ok)
|
|
93
|
+
removed.push(step.scope);
|
|
94
|
+
else
|
|
95
|
+
absent.push(step.scope);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!outcome.ok) {
|
|
99
|
+
return {
|
|
100
|
+
serverName: profile.serverName,
|
|
101
|
+
removed,
|
|
102
|
+
absent,
|
|
103
|
+
added: false,
|
|
104
|
+
ok: false,
|
|
105
|
+
detail: `the registration was not written: ${outcome.detail}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
detail = `registered in scope '${step.scope}'`;
|
|
109
|
+
}
|
|
110
|
+
return { serverName: profile.serverName, removed, absent, added: true, ok: true, detail };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The removal sweep on its own — what `uninstall` is, and all that it is.
|
|
114
|
+
*
|
|
115
|
+
* `exit 0` removed it, `exit != 0` answered "no" (absent, unchanged) — but a run the
|
|
116
|
+
* deadline killed, or one that never launched, ANSWERED NOTHING, and calling that
|
|
117
|
+
* "nothing was there" is the lie this sweep used to tell. Those scopes are `unknown`,
|
|
118
|
+
* and `ok` is false whenever any scope is unknown: with a scope the harness never
|
|
119
|
+
* spoke for, the removal is unconfirmed and the state is not known.
|
|
120
|
+
*/
|
|
121
|
+
export function runMcpUninstallPlan(profile, serverArgv, runner) {
|
|
122
|
+
const plan = buildMcpInstallPlan(profile, serverArgv).filter((step) => step.operation === 'remove');
|
|
123
|
+
const removed = [];
|
|
124
|
+
const absent = [];
|
|
125
|
+
const unknown = [];
|
|
126
|
+
for (const step of plan) {
|
|
127
|
+
const outcome = runner(step.command);
|
|
128
|
+
if (outcome.ok)
|
|
129
|
+
removed.push(step.scope);
|
|
130
|
+
else if (outcome.answered === false)
|
|
131
|
+
unknown.push(step.scope);
|
|
132
|
+
else
|
|
133
|
+
absent.push(step.scope);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
serverName: profile.serverName,
|
|
137
|
+
removed,
|
|
138
|
+
absent,
|
|
139
|
+
unknown,
|
|
140
|
+
added: false,
|
|
141
|
+
ok: unknown.length === 0,
|
|
142
|
+
detail: unknown.length > 0
|
|
143
|
+
? `the '${profile.serverName}' removal is unconfirmed: ${unknown.length} scope(s) never answered`
|
|
144
|
+
: removed.length === 0
|
|
145
|
+
? `no '${profile.serverName}' registration was present in any declared scope`
|
|
146
|
+
: `removed '${profile.serverName}' from ${removed.length} scope(s)`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export const NO_MCP_INSTALL_REASON = 'adapter declares no mcpInstall profile';
|
|
150
|
+
/** Register this adapter's harness, or report that there is nothing to register. */
|
|
151
|
+
export function installMcpForAdapter(adapter, serverArgv, runner) {
|
|
152
|
+
const profile = adapter.mcpInstall;
|
|
153
|
+
if (profile === undefined) {
|
|
154
|
+
return { ideId: adapter.id, skipped: true, reason: NO_MCP_INSTALL_REASON };
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
ideId: adapter.id,
|
|
158
|
+
skipped: false,
|
|
159
|
+
report: runMcpInstallPlan(profile, serverArgv, runner)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** The same, for the removal side. */
|
|
163
|
+
export function uninstallMcpForAdapter(adapter, serverArgv, runner) {
|
|
164
|
+
const profile = adapter.mcpInstall;
|
|
165
|
+
if (profile === undefined) {
|
|
166
|
+
return { ideId: adapter.id, skipped: true, reason: NO_MCP_INSTALL_REASON };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
ideId: adapter.id,
|
|
170
|
+
skipped: false,
|
|
171
|
+
report: runMcpUninstallPlan(profile, serverArgv, runner)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The production runner and its deadline live in `mcp-install-runner.ts` — split
|
|
176
|
+
* out only to keep this engine within the file-size cap. They are re-exported, so
|
|
177
|
+
* the boundary is invisible: every caller still imports them from here.
|
|
178
|
+
*/
|
|
179
|
+
export { MCP_INSTALL_TIMEOUT_MS, spawnMcpInstallCommand, spawnMcpInstallRunner } from './mcp-install-runner.js';
|