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.
Files changed (323) hide show
  1. package/.claude-plugin/marketplace.json +136 -47
  2. package/CHANGELOG.md +56 -0
  3. package/README-en.md +1 -1
  4. package/README.md +1 -1
  5. package/contracts/readonly-argv-whitelist.json +211 -0
  6. package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
  7. package/dist/cli/commands/audit-artifact-runners.js +170 -0
  8. package/dist/cli/commands/audit-command-shared.d.ts +97 -0
  9. package/dist/cli/commands/audit-command-shared.js +60 -0
  10. package/dist/cli/commands/audit-commands.d.ts +7 -31
  11. package/dist/cli/commands/audit-commands.js +64 -473
  12. package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
  13. package/dist/cli/commands/audit-goal-runners.js +114 -0
  14. package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
  15. package/dist/cli/commands/audit-red-lines-runners.js +43 -0
  16. package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
  17. package/dist/cli/commands/audit-refusal-data.js +69 -0
  18. package/dist/cli/commands/audit-static-runners.d.ts +13 -0
  19. package/dist/cli/commands/audit-static-runners.js +139 -0
  20. package/dist/cli/commands/await-runners.d.ts +11 -0
  21. package/dist/cli/commands/await-runners.js +148 -0
  22. package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
  23. package/dist/cli/commands/code-auto-compact-command.js +124 -0
  24. package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
  25. package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
  26. package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
  27. package/dist/cli/commands/code-context-audit-command.js +68 -0
  28. package/dist/cli/commands/code-context-now-command.d.ts +3 -0
  29. package/dist/cli/commands/code-context-now-command.js +124 -0
  30. package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
  31. package/dist/cli/commands/code-context-now-envelope.js +136 -0
  32. package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
  33. package/dist/cli/commands/code-context-now-mapping.js +43 -0
  34. package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
  35. package/dist/cli/commands/code-emit-handoff-command.js +99 -0
  36. package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
  37. package/dist/cli/commands/code-gate-step-08-command.js +139 -0
  38. package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
  39. package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
  40. package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
  41. package/dist/cli/commands/code-runtime-commands.js +20 -672
  42. package/dist/cli/commands/code-runtime-session.d.ts +1 -0
  43. package/dist/cli/commands/code-runtime-session.js +15 -0
  44. package/dist/cli/commands/compact-command.d.ts +6 -2
  45. package/dist/cli/commands/compact-command.js +12 -559
  46. package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
  47. package/dist/cli/commands/compact-dry-run-command.js +87 -0
  48. package/dist/cli/commands/compact-force-command.d.ts +3 -0
  49. package/dist/cli/commands/compact-force-command.js +98 -0
  50. package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
  51. package/dist/cli/commands/compact-harness-window-command.js +174 -0
  52. package/dist/cli/commands/compact-history-command.d.ts +3 -0
  53. package/dist/cli/commands/compact-history-command.js +89 -0
  54. package/dist/cli/commands/compact-read-commands.d.ts +3 -0
  55. package/dist/cli/commands/compact-read-commands.js +123 -0
  56. package/dist/cli/commands/compact-settle-command.d.ts +3 -0
  57. package/dist/cli/commands/compact-settle-command.js +130 -0
  58. package/dist/cli/commands/compact-shared.d.ts +9 -0
  59. package/dist/cli/commands/compact-shared.js +26 -0
  60. package/dist/cli/commands/core/skill-command.d.ts +1 -44
  61. package/dist/cli/commands/core/skill-command.js +13 -541
  62. package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
  63. package/dist/cli/commands/core/skill-context-verdict.js +63 -0
  64. package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
  65. package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
  66. package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
  67. package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
  68. package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
  69. package/dist/cli/commands/core/skill-presence-commands.js +123 -0
  70. package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
  71. package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
  72. package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
  73. package/dist/cli/commands/core/skill-registry-commands.js +117 -0
  74. package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
  75. package/dist/cli/commands/core/skill-runbook-command.js +29 -0
  76. package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
  77. package/dist/cli/commands/dispatch-command-spec.js +62 -0
  78. package/dist/cli/commands/dispatch-commands.js +159 -775
  79. package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
  80. package/dist/cli/commands/dispatch-detached-branch.js +58 -0
  81. package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
  82. package/dist/cli/commands/dispatch-envelope.js +104 -0
  83. package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
  84. package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
  85. package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
  86. package/dist/cli/commands/dispatch-preflight.js +105 -0
  87. package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
  88. package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
  89. package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
  90. package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
  91. package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
  92. package/dist/cli/commands/dispatch-record-write.js +96 -0
  93. package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
  94. package/dist/cli/commands/dispatch-tool-call.js +128 -0
  95. package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
  96. package/dist/cli/commands/evolution-command-shared.js +107 -0
  97. package/dist/cli/commands/evolution-commands.d.ts +11 -1
  98. package/dist/cli/commands/evolution-commands.js +19 -350
  99. package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
  100. package/dist/cli/commands/evolution-evaluate-command.js +138 -0
  101. package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
  102. package/dist/cli/commands/evolution-propose-command.js +145 -0
  103. package/dist/cli/commands/evolution-status-command.d.ts +17 -0
  104. package/dist/cli/commands/evolution-status-command.js +41 -0
  105. package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
  106. package/dist/cli/commands/evolution-verdict-commands.js +100 -0
  107. package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
  108. package/dist/cli/commands/finalize-accumulator.js +55 -0
  109. package/dist/cli/commands/finalize-branches.d.ts +34 -0
  110. package/dist/cli/commands/finalize-branches.js +119 -0
  111. package/dist/cli/commands/finalize-runners.d.ts +3 -0
  112. package/dist/cli/commands/finalize-runners.js +92 -0
  113. package/dist/cli/commands/finalize-selection.d.ts +65 -0
  114. package/dist/cli/commands/finalize-selection.js +35 -0
  115. package/dist/cli/commands/gate-commands.js +26 -8
  116. package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
  117. package/dist/cli/commands/job-checkpoint-commands.js +157 -0
  118. package/dist/cli/commands/job-command-shared.d.ts +109 -0
  119. package/dist/cli/commands/job-command-shared.js +72 -0
  120. package/dist/cli/commands/job-commands.d.ts +4 -64
  121. package/dist/cli/commands/job-commands.js +42 -552
  122. package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
  123. package/dist/cli/commands/job-cost-check-command.js +41 -0
  124. package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
  125. package/dist/cli/commands/job-init-status-commands.js +149 -0
  126. package/dist/cli/commands/job-run-commands.d.ts +3 -0
  127. package/dist/cli/commands/job-run-commands.js +97 -0
  128. package/dist/cli/commands/job-state-root.d.ts +35 -0
  129. package/dist/cli/commands/job-state-root.js +76 -0
  130. package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
  131. package/dist/cli/commands/loop-eval-capture-index.js +44 -0
  132. package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
  133. package/dist/cli/commands/loop-eval-commands.js +12 -626
  134. package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
  135. package/dist/cli/commands/loop-eval-eval-command.js +168 -0
  136. package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
  137. package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
  138. package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
  139. package/dist/cli/commands/loop-eval-parents.js +20 -0
  140. package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
  141. package/dist/cli/commands/loop-eval-run-code.js +40 -0
  142. package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
  143. package/dist/cli/commands/loop-eval-run-command.js +113 -0
  144. package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
  145. package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
  146. package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
  147. package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
  148. package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
  149. package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
  150. package/dist/cli/commands/mcp-commands.d.ts +10 -0
  151. package/dist/cli/commands/mcp-commands.js +157 -0
  152. package/dist/cli/commands/request-command-options.d.ts +49 -0
  153. package/dist/cli/commands/request-command-options.js +1 -0
  154. package/dist/cli/commands/request-commands.d.ts +3 -9
  155. package/dist/cli/commands/request-commands.js +17 -522
  156. package/dist/cli/commands/request-init-command.d.ts +3 -0
  157. package/dist/cli/commands/request-init-command.js +100 -0
  158. package/dist/cli/commands/request-lint-command.d.ts +3 -0
  159. package/dist/cli/commands/request-lint-command.js +50 -0
  160. package/dist/cli/commands/request-list-command.d.ts +10 -0
  161. package/dist/cli/commands/request-list-command.js +53 -0
  162. package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
  163. package/dist/cli/commands/request-repair-status-command.js +61 -0
  164. package/dist/cli/commands/request-show-command.d.ts +3 -0
  165. package/dist/cli/commands/request-show-command.js +56 -0
  166. package/dist/cli/commands/request-transition-command.d.ts +3 -0
  167. package/dist/cli/commands/request-transition-command.js +122 -0
  168. package/dist/cli/commands/request-transition-failures.d.ts +8 -0
  169. package/dist/cli/commands/request-transition-failures.js +90 -0
  170. package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
  171. package/dist/cli/commands/request-transition-hooks.js +115 -0
  172. package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
  173. package/dist/cli/commands/request-transition-preflight.js +107 -0
  174. package/dist/cli/commands/scan-command-shared.d.ts +96 -0
  175. package/dist/cli/commands/scan-command-shared.js +83 -0
  176. package/dist/cli/commands/scan-commands.d.ts +3 -2
  177. package/dist/cli/commands/scan-commands.js +23 -463
  178. package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
  179. package/dist/cli/commands/scan-inventory-commands.js +96 -0
  180. package/dist/cli/commands/scan-project-commands.d.ts +3 -0
  181. package/dist/cli/commands/scan-project-commands.js +65 -0
  182. package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
  183. package/dist/cli/commands/scan-scope-commands.js +149 -0
  184. package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
  185. package/dist/cli/commands/scan-scope-helpers.js +54 -0
  186. package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
  187. package/dist/cli/commands/scan-structural-commands.js +113 -0
  188. package/dist/cli/commands/share-commands.d.ts +12 -68
  189. package/dist/cli/commands/share-commands.js +47 -556
  190. package/dist/cli/commands/share-error-hints.d.ts +14 -0
  191. package/dist/cli/commands/share-error-hints.js +38 -0
  192. package/dist/cli/commands/share-identity.d.ts +13 -0
  193. package/dist/cli/commands/share-identity.js +22 -0
  194. package/dist/cli/commands/share-record-paths.d.ts +37 -0
  195. package/dist/cli/commands/share-record-paths.js +68 -0
  196. package/dist/cli/commands/share-runners.d.ts +3 -0
  197. package/dist/cli/commands/share-runners.js +158 -0
  198. package/dist/cli/commands/shared-read-runners.d.ts +3 -0
  199. package/dist/cli/commands/shared-read-runners.js +51 -0
  200. package/dist/cli/commands/slice-check-command.d.ts +3 -0
  201. package/dist/cli/commands/slice-check-command.js +54 -0
  202. package/dist/cli/commands/slice-commands.d.ts +2 -22
  203. package/dist/cli/commands/slice-commands.js +18 -517
  204. package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
  205. package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
  206. package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
  207. package/dist/cli/commands/slice-decompose-command.js +123 -0
  208. package/dist/cli/commands/slice-list-commands.d.ts +3 -0
  209. package/dist/cli/commands/slice-list-commands.js +182 -0
  210. package/dist/cli/commands/slice-pick-command.d.ts +3 -0
  211. package/dist/cli/commands/slice-pick-command.js +60 -0
  212. package/dist/cli/commands/slice-plan-command.d.ts +24 -0
  213. package/dist/cli/commands/slice-plan-command.js +111 -0
  214. package/dist/cli/commands/workflow-commands.d.ts +10 -1
  215. package/dist/cli/commands/workflow-commands.js +6 -594
  216. package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
  217. package/dist/cli/commands/workflow-gate-commands.js +116 -0
  218. package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
  219. package/dist/cli/commands/workflow-plan-helpers.js +103 -0
  220. package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
  221. package/dist/cli/commands/workflow-planning-commands.js +56 -0
  222. package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
  223. package/dist/cli/commands/workflow-swarm-actions.js +109 -0
  224. package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
  225. package/dist/cli/commands/workflow-swarm-commands.js +138 -0
  226. package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
  227. package/dist/cli/commands/workflow-tech-actions.js +150 -0
  228. package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
  229. package/dist/cli/commands/workspace/init-actions.js +199 -0
  230. package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
  231. package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
  232. package/dist/cli/commands/workspace/init-command.d.ts +8 -124
  233. package/dist/cli/commands/workspace/init-command.js +59 -623
  234. package/dist/cli/commands/workspace/init-context.d.ts +58 -0
  235. package/dist/cli/commands/workspace/init-context.js +121 -0
  236. package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
  237. package/dist/cli/commands/workspace/init-envelope.js +34 -0
  238. package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
  239. package/dist/cli/commands/workspace/init-failures.js +84 -0
  240. package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
  241. package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
  242. package/dist/cli/commands/workspace/init-options.d.ts +102 -0
  243. package/dist/cli/commands/workspace/init-options.js +1 -0
  244. package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
  245. package/dist/cli/commands/worktree-lease-commands.js +42 -512
  246. package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
  247. package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
  248. package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
  249. package/dist/cli/commands/worktree-lease-git.js +48 -0
  250. package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
  251. package/dist/cli/commands/worktree-lease-list-command.js +100 -0
  252. package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
  253. package/dist/cli/commands/worktree-lease-load.js +28 -0
  254. package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
  255. package/dist/cli/commands/worktree-lease-release-command.js +105 -0
  256. package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
  257. package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
  258. package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
  259. package/dist/cli/commands/worktree-lease-session.js +29 -0
  260. package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
  261. package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
  262. package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
  263. package/dist/cli/commands/worktree-lease-status-command.js +86 -0
  264. package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
  265. package/dist/cli/commands/worktree-lease-store.js +24 -0
  266. package/dist/cli/program.js +5 -0
  267. package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
  268. package/dist/services/distribution/mcp-install-runner.js +59 -0
  269. package/dist/services/distribution/mcp-install.d.ts +95 -0
  270. package/dist/services/distribution/mcp-install.js +179 -0
  271. package/dist/services/distribution/plugin-manifest.d.ts +97 -0
  272. package/dist/services/distribution/plugin-manifest.js +126 -0
  273. package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
  274. package/dist/services/hooks/mcp-surface-gate.js +87 -0
  275. package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
  276. package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
  277. package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
  278. package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
  279. package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
  280. package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
  281. package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
  282. package/dist/services/ide/ide-mcp-install-types.js +104 -0
  283. package/dist/services/ide/ide-types.d.ts +45 -1
  284. package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
  285. package/dist/services/ide/mcp-tool-matcher.js +84 -0
  286. package/dist/services/lint/npx-resolver.js +7 -0
  287. package/dist/services/mcp/cli-executor.d.ts +36 -0
  288. package/dist/services/mcp/cli-executor.js +97 -0
  289. package/dist/services/mcp/json-rpc.d.ts +81 -0
  290. package/dist/services/mcp/json-rpc.js +116 -0
  291. package/dist/services/mcp/memory-tool.d.ts +3 -0
  292. package/dist/services/mcp/memory-tool.js +62 -0
  293. package/dist/services/mcp/server-main.d.ts +1 -0
  294. package/dist/services/mcp/server-main.js +66 -0
  295. package/dist/services/mcp/server.d.ts +22 -0
  296. package/dist/services/mcp/server.js +152 -0
  297. package/dist/services/mcp/surface.d.ts +62 -0
  298. package/dist/services/mcp/surface.js +198 -0
  299. package/dist/services/mcp/tool-core.d.ts +42 -0
  300. package/dist/services/mcp/tool-core.js +137 -0
  301. package/dist/services/mcp/tools.d.ts +13 -0
  302. package/dist/services/mcp/tools.js +58 -0
  303. package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
  304. package/dist/services/readonly-surface/argv-guard.js +164 -0
  305. package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
  306. package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
  307. package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
  308. package/dist/services/readonly-surface/whitelist-generator.js +212 -0
  309. package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
  310. package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
  311. package/dist/services/skills/hooks-settings-service.js +10 -2
  312. package/package.json +17 -6
  313. package/scripts/install-skills-bundles.mjs +233 -0
  314. package/scripts/install-skills-fs.mjs +148 -0
  315. package/scripts/install-skills-ide.mjs +279 -0
  316. package/scripts/install-skills-output-style-default.mjs +196 -0
  317. package/scripts/install-skills-output-styles.mjs +121 -0
  318. package/scripts/install-skills-report.mjs +42 -0
  319. package/scripts/install-skills-user-config.mjs +198 -0
  320. package/scripts/install-skills.mjs +36 -1066
  321. package/scripts/watch-tree.mjs +140 -0
  322. package/scripts/watch.mjs +4 -126
  323. 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
+ }
@@ -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';