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,36 @@
1
+ /** Ceiling on one CLI execution. Generous for a read, far short of "forever". */
2
+ export declare const DEFAULT_EXECUTION_TIMEOUT_MS = 15000;
3
+ /** How to start this tree's own CLI. */
4
+ export interface CliLaunch {
5
+ readonly command: string;
6
+ readonly argsPrefix: readonly string[];
7
+ }
8
+ /**
9
+ * Resolve the platform-neutral launch shape. Uses the shared daemon-supervisor
10
+ * resolvers rather than a second copy of them: the entry path and the
11
+ * TypeScript-loader flags have to agree with every other self-spawn in this
12
+ * tree, and the file that owns them says so in as many words.
13
+ */
14
+ export declare function resolveCliLaunch(): CliLaunch;
15
+ export interface CliExecution {
16
+ /** The concrete argv that was handed to the CLI, without the launch prefix. */
17
+ readonly argv: readonly string[];
18
+ /** Exit status, or `null` when the process died on a signal or was killed. */
19
+ readonly exitCode: number | null;
20
+ readonly stdout: string;
21
+ readonly stderr: string;
22
+ /** True when the deadline fired and the child was killed. */
23
+ readonly timedOut: boolean;
24
+ /** Set ONLY when the process could not be started at all. Never set on timeout. */
25
+ readonly launchError?: string;
26
+ }
27
+ export interface ExecuteOptions {
28
+ readonly launch?: CliLaunch;
29
+ readonly timeoutMs?: number;
30
+ readonly cwd?: string;
31
+ }
32
+ /**
33
+ * Run one argv. Always resolves: a failure is a described outcome, never a
34
+ * rejection, because the caller has to report it to the harness either way.
35
+ */
36
+ export declare function executeCliArgv(argv: readonly string[], options?: ExecuteOptions): Promise<CliExecution>;
@@ -0,0 +1,97 @@
1
+ // src/services/mcp/cli-executor.ts
2
+ //
3
+ // The one place the MCP server touches the outside world: it runs a concrete
4
+ // argv against the peaks CLI and hands back what happened.
5
+ //
6
+ // THREE RULES, EACH MEASURED INTO THE SHAPE BELOW.
7
+ //
8
+ // 1. NO SHELL, NO PLATFORM BRANCH (spec §8.2). The launch is
9
+ // `node <this tree's CLI entry>` — `process.execPath` plus the argument list
10
+ // that makes this Node interpret that entry. Naming the CLI as a command
11
+ // (`peaks`) is measurably wrong on a platform whose CLI is a batch shim:
12
+ // Node refuses to spawn `.cmd`/`.bat` without a shell, and with a shell the
13
+ // argv array stops being an argv array. `process.execPath` needs neither, so
14
+ // one shape serves every platform and no `if (platform)` exists.
15
+ //
16
+ // 2. THE TIMEOUT IS MANDATORY, NOT OPTIONAL. `peaks job status --watch` never
17
+ // returns. It is outside the read-only surface, but it proves the class: a
18
+ // server that waits forever on a hung child gives its caller no recovery
19
+ // path. A timeout therefore returns a NAMED failure, never an empty success
20
+ // (spec §7.2 + §9 rule 3).
21
+ //
22
+ // 3. THE CHILD GETS NO STDIN. Our stdin is the JSON-RPC transport; a child that
23
+ // inherited it would read our protocol stream and answer the harness itself.
24
+ import { spawn } from 'node:child_process';
25
+ import { readonlySpawnOptions } from '../readonly-surface/argv-guard.js';
26
+ import { cliEntryPath, interpreterArgs } from '../web/daemon-supervisor.js';
27
+ /** Ceiling on one CLI execution. Generous for a read, far short of "forever". */
28
+ export const DEFAULT_EXECUTION_TIMEOUT_MS = 15_000;
29
+ /**
30
+ * Resolve the platform-neutral launch shape. Uses the shared daemon-supervisor
31
+ * resolvers rather than a second copy of them: the entry path and the
32
+ * TypeScript-loader flags have to agree with every other self-spawn in this
33
+ * tree, and the file that owns them says so in as many words.
34
+ */
35
+ export function resolveCliLaunch() {
36
+ return { command: process.execPath, argsPrefix: interpreterArgs(cliEntryPath()) };
37
+ }
38
+ /** The spawn + drain half: one child, one settlement, whatever happens to it. */
39
+ function runChild(launch, argv, cwd, timeoutMs) {
40
+ return new Promise((resolve) => {
41
+ let stdout = '';
42
+ let stderr = '';
43
+ let timedOut = false;
44
+ let settled = false;
45
+ let launchError;
46
+ const child = spawn(launch.command, [...launch.argsPrefix, ...argv], {
47
+ ...readonlySpawnOptions(),
48
+ cwd,
49
+ // stdin must stay ours; stdout/stderr are read as data.
50
+ stdio: ['ignore', 'pipe', 'pipe'],
51
+ // Windows contract: a spawned child must not allocate a console window.
52
+ windowsHide: true
53
+ });
54
+ const deadline = setTimeout(() => {
55
+ timedOut = true;
56
+ child.kill();
57
+ }, timeoutMs);
58
+ const settle = (exitCode) => {
59
+ if (settled)
60
+ return;
61
+ settled = true;
62
+ clearTimeout(deadline);
63
+ const outcome = { argv, exitCode, stdout, stderr, timedOut };
64
+ resolve(launchError === undefined ? outcome : { ...outcome, launchError });
65
+ };
66
+ child.stdout.setEncoding('utf8');
67
+ child.stdout.on('data', (chunk) => {
68
+ stdout += chunk;
69
+ });
70
+ child.stderr.setEncoding('utf8');
71
+ child.stderr.on('data', (chunk) => {
72
+ stderr += chunk;
73
+ });
74
+ child.on('error', (error) => {
75
+ launchError = error.message;
76
+ });
77
+ // `close` (not `exit`) so both pipes are drained before we read them.
78
+ child.on('close', (code) => {
79
+ settle(code);
80
+ });
81
+ });
82
+ }
83
+ /**
84
+ * Run one argv. Always resolves: a failure is a described outcome, never a
85
+ * rejection, because the caller has to report it to the harness either way.
86
+ */
87
+ export async function executeCliArgv(argv, options = {}) {
88
+ let launch;
89
+ try {
90
+ launch = options.launch ?? resolveCliLaunch();
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ return { argv, exitCode: null, stdout: '', stderr: '', timedOut: false, launchError: message };
95
+ }
96
+ return runChild(launch, argv, options.cwd ?? process.cwd(), options.timeoutMs ?? DEFAULT_EXECUTION_TIMEOUT_MS);
97
+ }
@@ -0,0 +1,81 @@
1
+ /** The only JSON-RPC version this server speaks. */
2
+ export declare const JSON_RPC_VERSION: "2.0";
3
+ /** A request/response correlation id. `null` is legal and means "unknown". */
4
+ export type JsonRpcId = string | number | null;
5
+ export interface JsonRpcErrorObject {
6
+ readonly code: number;
7
+ readonly message: string;
8
+ readonly data?: unknown;
9
+ }
10
+ export interface JsonRpcRequest {
11
+ readonly jsonrpc: typeof JSON_RPC_VERSION;
12
+ readonly id: JsonRpcId;
13
+ readonly method: string;
14
+ readonly params?: unknown;
15
+ }
16
+ /** A request WITHOUT an id. JSON-RPC forbids replying to one. */
17
+ export interface JsonRpcNotification {
18
+ readonly jsonrpc: typeof JSON_RPC_VERSION;
19
+ readonly method: string;
20
+ readonly params?: unknown;
21
+ }
22
+ export interface JsonRpcSuccess {
23
+ readonly jsonrpc: typeof JSON_RPC_VERSION;
24
+ readonly id: JsonRpcId;
25
+ readonly result: unknown;
26
+ }
27
+ export interface JsonRpcFailure {
28
+ readonly jsonrpc: typeof JSON_RPC_VERSION;
29
+ readonly id: JsonRpcId;
30
+ readonly error: JsonRpcErrorObject;
31
+ }
32
+ export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;
33
+ /** Standard JSON-RPC codes. The server adds no private codes of its own. */
34
+ export declare const JSON_RPC_PARSE_ERROR = -32700;
35
+ export declare const JSON_RPC_INVALID_REQUEST = -32600;
36
+ export declare const JSON_RPC_METHOD_NOT_FOUND = -32601;
37
+ export declare const JSON_RPC_INVALID_PARAMS = -32602;
38
+ export declare const JSON_RPC_INTERNAL_ERROR = -32603;
39
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
40
+ /** What one decoded line turned out to be. */
41
+ export type DecodedMessage = {
42
+ readonly kind: 'request';
43
+ readonly request: JsonRpcRequest;
44
+ } | {
45
+ readonly kind: 'notification';
46
+ readonly notification: JsonRpcNotification;
47
+ } | {
48
+ readonly kind: 'invalid';
49
+ readonly id: JsonRpcId;
50
+ readonly error: JsonRpcErrorObject;
51
+ };
52
+ export declare function makeError(code: number, message: string, data?: unknown): JsonRpcErrorObject;
53
+ export declare function successResponse(id: JsonRpcId, result: unknown): JsonRpcSuccess;
54
+ export declare function failureResponse(id: JsonRpcId, error: JsonRpcErrorObject): JsonRpcFailure;
55
+ /**
56
+ * Decode one frame.
57
+ *
58
+ * `undefined` means "nothing to answer" — an empty line, which is legal padding
59
+ * and must not be reported as a parse error (an empty line is not a message the
60
+ * peer sent; it is whitespace between messages).
61
+ */
62
+ export declare function decodeMessage(line: string): DecodedMessage | undefined;
63
+ /**
64
+ * Encode one frame for the wire. `JSON.stringify` escapes every newline inside
65
+ * a string, which is what keeps one message on one line.
66
+ */
67
+ export declare function encodeMessage(message: JsonRpcResponse | JsonRpcNotification): string;
68
+ /**
69
+ * The buffer that turns an arbitrary chunking of bytes into whole lines.
70
+ *
71
+ * Needed because a pipe delivers no framing of its own: one `data` event may
72
+ * carry half a message or three of them, and a server that parsed each event as
73
+ * a message would work on a fast local pipe and fail on a loaded one.
74
+ */
75
+ export declare class LineDecoder {
76
+ private buffer;
77
+ /** Feed a chunk; get back the complete lines it completed (no trailing newline). */
78
+ push(chunk: string): string[];
79
+ /** Whatever is left when the stream ends. A peer that never sent a newline still sent a message. */
80
+ flush(): string[];
81
+ }
@@ -0,0 +1,116 @@
1
+ // src/services/mcp/json-rpc.ts
2
+ //
3
+ // JSON-RPC 2.0 message shapes plus the stdio framing MCP uses, implemented in
4
+ // this tree rather than pulled in as a dependency.
5
+ //
6
+ // WHY NOT THE SDK. peaks-loop ships 12 runtime dependencies to every user
7
+ // globally, and the protocol surface this server needs is four methods and five
8
+ // error codes — all frozen since the first MCP revision. A dependency would be
9
+ // the larger commitment. See the slice report for the exact surface list.
10
+ //
11
+ // THE FRAMING IS NEWLINE-DELIMITED JSON, not LSP's `Content-Length` headers.
12
+ // That is the MCP stdio transport's rule, and it is also why a message may not
13
+ // contain a raw newline: this module therefore serializes with `JSON.stringify`
14
+ // (which escapes newlines inside strings) and never pretty-prints.
15
+ /** The only JSON-RPC version this server speaks. */
16
+ export const JSON_RPC_VERSION = '2.0';
17
+ /** Standard JSON-RPC codes. The server adds no private codes of its own. */
18
+ export const JSON_RPC_PARSE_ERROR = -32700;
19
+ export const JSON_RPC_INVALID_REQUEST = -32600;
20
+ export const JSON_RPC_METHOD_NOT_FOUND = -32601;
21
+ export const JSON_RPC_INVALID_PARAMS = -32602;
22
+ export const JSON_RPC_INTERNAL_ERROR = -32603;
23
+ export function isPlainObject(value) {
24
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
25
+ }
26
+ function isJsonRpcId(value) {
27
+ return typeof value === 'string' || typeof value === 'number' || value === null;
28
+ }
29
+ export function makeError(code, message, data) {
30
+ return data === undefined ? { code, message } : { code, message, data };
31
+ }
32
+ export function successResponse(id, result) {
33
+ return { jsonrpc: JSON_RPC_VERSION, id, result };
34
+ }
35
+ export function failureResponse(id, error) {
36
+ return { jsonrpc: JSON_RPC_VERSION, id, error };
37
+ }
38
+ function invalid(id, code, message) {
39
+ return { kind: 'invalid', id, error: makeError(code, message) };
40
+ }
41
+ /** The message's id when it carries a legal one, else the protocol's `null`. */
42
+ function idOf(parsed) {
43
+ const id = parsed['id'];
44
+ return isJsonRpcId(id) ? id : null;
45
+ }
46
+ /**
47
+ * Build the message from a well-formed envelope. The presence of the `id` KEY is
48
+ * the only thing separating a request from a notification — `id: null` is a legal
49
+ * id, so this cannot be written as a null check.
50
+ */
51
+ function buildMessage(parsed, method) {
52
+ const params = parsed['params'];
53
+ const head = { jsonrpc: JSON_RPC_VERSION, method, ...(params === undefined ? {} : { params }) };
54
+ return 'id' in parsed
55
+ ? { kind: 'request', request: { ...head, id: idOf(parsed) } }
56
+ : { kind: 'notification', notification: head };
57
+ }
58
+ /**
59
+ * Decode one frame.
60
+ *
61
+ * `undefined` means "nothing to answer" — an empty line, which is legal padding
62
+ * and must not be reported as a parse error (an empty line is not a message the
63
+ * peer sent; it is whitespace between messages).
64
+ */
65
+ export function decodeMessage(line) {
66
+ if (line.trim().length === 0)
67
+ return undefined;
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(line);
71
+ }
72
+ catch {
73
+ return invalid(null, JSON_RPC_PARSE_ERROR, 'Invalid JSON received on the transport.');
74
+ }
75
+ if (!isPlainObject(parsed) || parsed['jsonrpc'] !== JSON_RPC_VERSION) {
76
+ const id = isPlainObject(parsed) ? idOf(parsed) : null;
77
+ return invalid(id, JSON_RPC_INVALID_REQUEST, `Messages must declare jsonrpc "${JSON_RPC_VERSION}".`);
78
+ }
79
+ const method = parsed['method'];
80
+ if (typeof method !== 'string' || method.length === 0) {
81
+ return invalid(idOf(parsed), JSON_RPC_INVALID_REQUEST, 'Messages must carry a non-empty "method".');
82
+ }
83
+ return buildMessage(parsed, method);
84
+ }
85
+ /**
86
+ * Encode one frame for the wire. `JSON.stringify` escapes every newline inside
87
+ * a string, which is what keeps one message on one line.
88
+ */
89
+ export function encodeMessage(message) {
90
+ return `${JSON.stringify(message)}\n`;
91
+ }
92
+ /**
93
+ * The buffer that turns an arbitrary chunking of bytes into whole lines.
94
+ *
95
+ * Needed because a pipe delivers no framing of its own: one `data` event may
96
+ * carry half a message or three of them, and a server that parsed each event as
97
+ * a message would work on a fast local pipe and fail on a loaded one.
98
+ */
99
+ export class LineDecoder {
100
+ buffer = '';
101
+ /** Feed a chunk; get back the complete lines it completed (no trailing newline). */
102
+ push(chunk) {
103
+ this.buffer += chunk;
104
+ const lines = this.buffer.split('\n');
105
+ // The last element is either an incomplete line or '' when the chunk ended
106
+ // on a newline. Either way it stays buffered for the next push.
107
+ this.buffer = lines.pop() ?? '';
108
+ return lines;
109
+ }
110
+ /** Whatever is left when the stream ends. A peer that never sent a newline still sent a message. */
111
+ flush() {
112
+ const remainder = this.buffer.trim().length > 0 ? [this.buffer] : [];
113
+ this.buffer = '';
114
+ return remainder;
115
+ }
116
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpToolDefinition } from './surface.js';
2
+ import { type ToolCallContext, type ToolCallResult } from './tool-core.js';
3
+ export declare function callMemorySearchTool(tool: McpToolDefinition, args: Readonly<Record<string, unknown>>, context: ToolCallContext): Promise<ToolCallResult>;
@@ -0,0 +1,62 @@
1
+ // src/services/mcp/memory-tool.ts
2
+ //
3
+ // `peaks_memory_search`: one argv, a bounded reply (§7.1).
4
+ //
5
+ // It answers "is there anything, and what is it called" — never "what does it
6
+ // say". The raw CLI output is measured in megabytes, so the reply is bounded
7
+ // twice over: a hard ceiling on how many matches come back, and a truncated
8
+ // excerpt per match. Both are on §3.2's allowed list for a composite tool
9
+ // (field selection and truncation), and neither concludes anything: the count,
10
+ // the names and the first characters are all the CLI's.
11
+ import { buildArgv } from '../readonly-surface/argv-guard.js';
12
+ import { MEMORY_EXCERPT_CHARS, MEMORY_MAX_RETURNED, rejectedResult, resultOf, runArgv, valuesForEntry } from './tool-core.js';
13
+ /** Project one raw CLI match down to the fields §7.1 allows, with a bounded excerpt. */
14
+ function boundMatch(raw) {
15
+ const match = typeof raw === 'object' && raw !== null ? raw : {};
16
+ const description = typeof match['description'] === 'string' ? match['description'] : '';
17
+ return {
18
+ name: match['name'],
19
+ kind: match['kind'],
20
+ excerpt: description.slice(0, MEMORY_EXCERPT_CHARS)
21
+ };
22
+ }
23
+ export async function callMemorySearchTool(tool, args, context) {
24
+ const entry = tool.entries[0];
25
+ if (entry === undefined) {
26
+ return { content: [{ type: 'text', text: '{"reason":"no entry"}' }], isError: true };
27
+ }
28
+ const values = valuesForEntry(entry, args, context);
29
+ if (values === undefined) {
30
+ return rejectedResult(entry, 'A search query is required.');
31
+ }
32
+ const built = buildArgv(entry, values);
33
+ if (!built.ok) {
34
+ return rejectedResult(entry, `Parameter ${built.param} was refused: ${built.message}`);
35
+ }
36
+ const outcome = await runArgv(entry.id, built.argv, context);
37
+ if (outcome.failure !== undefined)
38
+ return resultOf([outcome]);
39
+ const envelope = outcome.envelope;
40
+ const record = typeof envelope === 'object' && envelope !== null ? envelope : {};
41
+ const matches = record['matches'];
42
+ if (!Array.isArray(matches)) {
43
+ // No `matches` array is not "nothing matched" — it is a shape this tool does
44
+ // not understand, and reporting it as empty is the failure §9 rule 3 names.
45
+ const argv = outcome.execution.argv.join(' ');
46
+ return resultOf([
47
+ { ...outcome, failure: `peaks memory search returned no matches array: peaks ${argv}` }
48
+ ]);
49
+ }
50
+ return {
51
+ content: [
52
+ {
53
+ type: 'text',
54
+ text: JSON.stringify({
55
+ query: record['query'],
56
+ total: record['total'],
57
+ matches: matches.slice(0, MEMORY_MAX_RETURNED).map(boundMatch)
58
+ })
59
+ }
60
+ ]
61
+ };
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,66 @@
1
+ // src/services/mcp/server-main.ts
2
+ //
3
+ // The process entry: a stdin pipe, a stdout pipe, and spec §6's L4 check before
4
+ // either is used.
5
+ //
6
+ // STDOUT CARRIES PROTOCOL AND NOTHING ELSE. Any diagnostic goes to stderr,
7
+ // because a harness reads stdout as the message stream and one stray line is a
8
+ // parse error that is indistinguishable from a broken server. That is also why
9
+ // the startup refusal below writes to stderr and exits non-zero rather than
10
+ // printing a JSON error: at that point there is no session to answer in.
11
+ //
12
+ // L4 REFUSES TO START. If the surface cannot be served as proven — a tool with
13
+ // no description, an argv the whitelist does not carry, a whitelisted argv
14
+ // reachable from no tool, or a tool with no handler — this process exits 1.
15
+ // A server that started anyway would be serving something nobody proved, and the
16
+ // failure would surface as data rather than as a refusal (spec §6, L4).
17
+ import { loadReadOnlyWhitelist } from '../readonly-surface/readonly-whitelist.js';
18
+ import { LineDecoder } from './json-rpc.js';
19
+ import { buildToolDefinitions, validateSurface } from './surface.js';
20
+ import { createMcpServer } from './server.js';
21
+ import { missingHandlers } from './tools.js';
22
+ function refuse(reason) {
23
+ process.stderr.write(`peaks mcp: ${reason}\n`);
24
+ process.exit(1);
25
+ }
26
+ function start() {
27
+ let tools;
28
+ try {
29
+ const whitelist = loadReadOnlyWhitelist();
30
+ tools = buildToolDefinitions(whitelist);
31
+ validateSurface(whitelist, tools);
32
+ }
33
+ catch (error) {
34
+ refuse(`refusing to start: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+ const unserved = missingHandlers(tools);
37
+ if (unserved.length > 0) {
38
+ refuse(`refusing to start: no handler for tool(s) ${unserved.join(', ')}`);
39
+ }
40
+ const server = createMcpServer({ tools });
41
+ const decoder = new LineDecoder();
42
+ const write = (line) => {
43
+ if (line !== undefined)
44
+ process.stdout.write(line);
45
+ };
46
+ const consume = (line) => {
47
+ void server.handleLine(line).then(write);
48
+ };
49
+ process.stdin.setEncoding('utf8');
50
+ process.stdin.on('data', (chunk) => {
51
+ for (const line of decoder.push(chunk))
52
+ consume(line);
53
+ });
54
+ process.stdin.on('end', () => {
55
+ for (const line of decoder.flush())
56
+ consume(line);
57
+ });
58
+ // A read error on our own transport is not recoverable and not ours to
59
+ // interpret: report it and stop, so the harness sees a dead server instead of
60
+ // a live one that answers nothing.
61
+ process.stdin.on('error', (error) => {
62
+ process.stderr.write(`peaks mcp: stdin error: ${error.message}\n`);
63
+ process.exitCode = 1;
64
+ });
65
+ }
66
+ start();
@@ -0,0 +1,22 @@
1
+ import type { McpToolDefinition } from './surface.js';
2
+ import { type ToolCallContext } from './tool-core.js';
3
+ /** Used when the client names no protocol version. */
4
+ export declare const DEFAULT_PROTOCOL_VERSION = "2025-06-18";
5
+ /** The server's own name, as it appears in `initialize`. */
6
+ export declare const SERVER_NAME = "peaks";
7
+ export declare const METHOD_INITIALIZE = "initialize";
8
+ export declare const METHOD_INITIALIZED = "notifications/initialized";
9
+ export declare const METHOD_TOOLS_LIST = "tools/list";
10
+ export declare const METHOD_TOOLS_CALL = "tools/call";
11
+ export interface McpServerOptions {
12
+ readonly tools: readonly McpToolDefinition[];
13
+ readonly context?: ToolCallContext;
14
+ readonly serverVersion?: string;
15
+ }
16
+ export interface McpServer {
17
+ /** Answer one line. A notification (or blank line) yields `undefined`. */
18
+ handleLine(line: string): Promise<string | undefined>;
19
+ /** The tools, for a caller that wants to assert what it built. */
20
+ readonly tools: readonly McpToolDefinition[];
21
+ }
22
+ export declare function createMcpServer(options: McpServerOptions): McpServer;
@@ -0,0 +1,152 @@
1
+ // src/services/mcp/server.ts
2
+ //
3
+ // The protocol core: one decoded line in, at most one encoded line out.
4
+ //
5
+ // WHAT THIS SERVER IS. A table lookup, an execution, and the CLI's own answer —
6
+ // nothing else (spec §3, component 3: "there is no fourth responsibility").
7
+ // It does not know what a gate is, does not cache, keeps no state between
8
+ // messages, and never rewrites a CLI result. `handleLine` is a pure function of
9
+ // (line, surface, context), which is also what makes it testable without a pipe.
10
+ //
11
+ // THE PROTOCOL SURFACE IS DELIBERATELY SMALL and is listed in full here so it can
12
+ // be checked line by line rather than inferred:
13
+ //
14
+ // initialize -> protocolVersion, capabilities.tools, serverInfo
15
+ // notifications/initialized -> accepted, no reply (it is a notification)
16
+ // tools/list -> tools[] {name, description, inputSchema}
17
+ // tools/call -> params {name, arguments} -> content[] | isError
18
+ // (anything else, request) -> -32601 METHOD_NOT_FOUND
19
+ // (anything else, notification) -> ignored; JSON-RPC forbids replying
20
+ // malformed JSON -> -32700 wrong envelope -> -32600
21
+ // bad tools/call params -> -32602 handler crash -> -32603
22
+ //
23
+ // NOT IMPLEMENTED, on purpose: `ping`, `resources/*`, `prompts/*`, `logging/*`,
24
+ // `completion/*`, `notifications/*` beyond `initialized`, progress tokens,
25
+ // cancellation, and resource subscriptions. Any of them arrives as a
26
+ // METHOD_NOT_FOUND rather than as silence.
27
+ //
28
+ // NO HANDSHAKE STATE. The server does not track whether `initialize` has been
29
+ // seen; every method is answerable on any line. MCP asks clients to initialize
30
+ // first, and this server simply does not depend on it — a state machine here
31
+ // would be state kept for no decision.
32
+ //
33
+ // PROTOCOL VERSION. The client's requested version is echoed when it names one,
34
+ // and a constant is used otherwise. Echoing is honest for this surface and not a
35
+ // claim of unlimited support: the four methods above have had identical request
36
+ // and response shapes across every published revision, so there is no version
37
+ // whose meaning this server would get wrong.
38
+ import { CLI_VERSION } from 'peaks-loop-shared/version';
39
+ import { decodeMessage, encodeMessage, failureResponse, isPlainObject, makeError, successResponse, JSON_RPC_INTERNAL_ERROR, JSON_RPC_INVALID_PARAMS, JSON_RPC_METHOD_NOT_FOUND } from './json-rpc.js';
40
+ import { handlerFor } from './tools.js';
41
+ /** Used when the client names no protocol version. */
42
+ export const DEFAULT_PROTOCOL_VERSION = '2025-06-18';
43
+ /** The server's own name, as it appears in `initialize`. */
44
+ export const SERVER_NAME = 'peaks';
45
+ export const METHOD_INITIALIZE = 'initialize';
46
+ export const METHOD_INITIALIZED = 'notifications/initialized';
47
+ export const METHOD_TOOLS_LIST = 'tools/list';
48
+ export const METHOD_TOOLS_CALL = 'tools/call';
49
+ /** The tool shape that goes on the wire — behaviour without the whitelist entries. */
50
+ function wireTool(tool) {
51
+ return {
52
+ name: tool.name,
53
+ description: tool.description,
54
+ inputSchema: tool.inputSchema
55
+ };
56
+ }
57
+ function requestedVersion(params) {
58
+ if (!isPlainObject(params))
59
+ return undefined;
60
+ const version = params['protocolVersion'];
61
+ return typeof version === 'string' && version.length > 0 ? version : undefined;
62
+ }
63
+ function callArguments(params) {
64
+ const args = params['arguments'];
65
+ if (args === undefined)
66
+ return {};
67
+ return isPlainObject(args) ? args : undefined;
68
+ }
69
+ /**
70
+ * Reject an argument the tool does not declare, BEFORE anything runs. The tool
71
+ * schemas set `additionalProperties: false`; enforcing it here is what makes
72
+ * that a promise rather than a hint, and it is the boundary at which an
73
+ * injection-shaped key ("--apply") stops being an argument.
74
+ */
75
+ function unknownArgument(tool, args) {
76
+ const declared = new Set(Object.keys(tool.inputSchema.properties));
77
+ return Object.keys(args).find((name) => !declared.has(name));
78
+ }
79
+ function initializeResult(params, version) {
80
+ return {
81
+ protocolVersion: requestedVersion(params) ?? DEFAULT_PROTOCOL_VERSION,
82
+ capabilities: { tools: { listChanged: false } },
83
+ serverInfo: { name: SERVER_NAME, version }
84
+ };
85
+ }
86
+ function fail(id, message) {
87
+ return encodeMessage(failureResponse(id, makeError(JSON_RPC_INVALID_PARAMS, message)));
88
+ }
89
+ async function toolsCallResult(tools, context, id, params) {
90
+ if (!isPlainObject(params)) {
91
+ return fail(id, 'tools/call requires an object of parameters.');
92
+ }
93
+ const name = params['name'];
94
+ if (typeof name !== 'string' || name.length === 0) {
95
+ return fail(id, 'tools/call requires a tool "name".');
96
+ }
97
+ const tool = tools.find((candidate) => candidate.name === name);
98
+ const handler = tool === undefined ? undefined : handlerFor(tool.name);
99
+ if (tool === undefined || handler === undefined) {
100
+ return fail(id, `Unknown tool: ${name}`);
101
+ }
102
+ const args = callArguments(params);
103
+ if (args === undefined) {
104
+ return fail(id, 'tools/call "arguments" must be an object.');
105
+ }
106
+ const unexpected = unknownArgument(tool, args);
107
+ if (unexpected !== undefined) {
108
+ return fail(id, `Tool ${name} declares no argument "${unexpected}".`);
109
+ }
110
+ return encodeMessage(successResponse(id, await handler(tool, args, context)));
111
+ }
112
+ export function createMcpServer(options) {
113
+ const tools = options.tools;
114
+ const context = options.context ?? {};
115
+ const version = options.serverVersion ?? CLI_VERSION;
116
+ const respond = async (id, method, params) => {
117
+ switch (method) {
118
+ case METHOD_INITIALIZE:
119
+ return encodeMessage(successResponse(id, initializeResult(params, version)));
120
+ case METHOD_TOOLS_LIST:
121
+ return encodeMessage(successResponse(id, { tools: tools.map(wireTool) }));
122
+ case METHOD_TOOLS_CALL:
123
+ return toolsCallResult(tools, context, id, params);
124
+ default:
125
+ return encodeMessage(failureResponse(id, makeError(JSON_RPC_METHOD_NOT_FOUND, `Unknown method: ${method}`)));
126
+ }
127
+ };
128
+ const handleLine = async (line) => {
129
+ const decoded = decodeMessage(line);
130
+ if (decoded === undefined)
131
+ return undefined;
132
+ if (decoded.kind === 'invalid') {
133
+ return encodeMessage(failureResponse(decoded.id, decoded.error));
134
+ }
135
+ if (decoded.kind === 'notification') {
136
+ // Notifications are answered by not answering. `initialized` needs no
137
+ // branch of its own for that reason — but an unknown one is silently
138
+ // dropped, which is the protocol's rule, not this server's leniency.
139
+ return undefined;
140
+ }
141
+ try {
142
+ return await respond(decoded.request.id, decoded.request.method, decoded.request.params);
143
+ }
144
+ catch (error) {
145
+ // A handler crash is reported, never swallowed: the caller has to know the
146
+ // answer is missing rather than empty.
147
+ const message = error instanceof Error ? error.message : 'Internal error';
148
+ return encodeMessage(failureResponse(decoded.request.id, makeError(JSON_RPC_INTERNAL_ERROR, message)));
149
+ }
150
+ };
151
+ return { tools, handleLine };
152
+ }