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
@@ -1,407 +1,61 @@
1
- import { existsSync, readdirSync, realpathSync as realpathSyncNative } from 'node:fs';
2
- import { join, resolve } from 'node:path';
3
- import { fail, getErrorMessage, ok } from 'peaks-loop-shared/result';
4
- import { addJsonOption, printResult } from '../cli-helpers.js';
5
- import { readSharedChannel, writeSharedEntry, SHARED_CHANNEL_SOFT_VALUE_WARN } from 'peaks-loop-shared-channel';
6
- import { writeLogEntry } from '../../services/log/logger.js';
7
- import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
8
- import { summarizeBatchResults } from './sub-agent-shared.js';
9
- /** The one selection rule `--request-id` and `--batch` now share. */
10
- export const FINALIZE_SELECTION_RULE = 'prefer status=queued; among those, newest by record createdAt (then filename); ' +
11
- 'no queued match means nothing is finalized';
12
- /**
13
- * N3 — pick the record `finalize --request-id` should act on.
14
- *
15
- * The branch this replaces `break`ed on the FIRST file whose record carried
16
- * the requestId and never looked at `status`. With a re-dispatched request
17
- * always resolved to the OLDEST one — the already-`done` RD record — so
18
- * finalizing reported success while the newer `queued` QA record stayed
19
- * queued forever. `--batch` never had that bug: it filters on `queued`.
20
- *
21
- * Both branches are now the same rule. Return `null` when nothing is queued:
22
- * a record that already left `queued` is precisely the one that must NOT be
23
- * re-finalized, so the caller reports the survivors instead of touching one.
24
- */
25
- export function selectFinalizeTarget(candidates) {
26
- const queued = candidates.filter((candidate) => candidate.status === 'queued');
27
- if (queued.length === 0)
28
- return null;
29
- return [...queued].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.recordPath.localeCompare(a.recordPath))[0];
30
- }
31
- /** Why a candidate was not the chosen one — reported, never guessed at. */
32
- export function describeFinalizeRejection(candidate, chosen) {
33
- if (candidate.status !== 'queued' || chosen === null) {
34
- return 'status is ' + candidate.status;
35
- }
36
- return 'superseded by the newer queued record ' + chosen.recordPath;
37
- }
1
+ import { addJsonOption } from '../cli-helpers.js';
2
+ import { runAwait } from './await-runners.js';
3
+ import { runFinalize } from './finalize-runners.js';
4
+ import { runShare } from './share-runners.js';
5
+ import { runSharedRead } from './shared-read-runners.js';
6
+ // The finalize selection contract is imported by the finalize tests from this
7
+ // entry file, so it stays re-exported here even though its implementation
8
+ // moved to a sibling module.
9
+ export { FINALIZE_SELECTION_RULE, describeFinalizeRejection, selectFinalizeTarget } from './finalize-selection.js';
10
+ const SHARE_DESCRIPTION = 'G8.4: write a shared entry to the cross sub-agent shared channel. ' +
11
+ 'Dispatcher-mediated indirect signal: sub-agent A writes, dispatcher ' +
12
+ 'stores, sub-agent B (still in flight) reads via `peaks sub-agent ' +
13
+ 'shared-read`. Not peer-to-peer; pseudo-swarm property 3 preserved.';
14
+ const SHARED_READ_DESCRIPTION = 'G8.4: read entries from the cross sub-agent shared channel. ' +
15
+ 'Returns sibling sub-agent status. Supports --since (ISO8601) ' +
16
+ 'and --key (glob pattern with * wildcard).';
17
+ const AWAIT_DESCRIPTION = '2.7.0 slice-dag-dispatcher MVP: wait for a batch of dispatched sub-agents ' +
18
+ 'to finish (or hit --timeout). Returns one BatchResult per dispatch. ' +
19
+ 'For non-claude-code IDEs, the wait is delegated to the LLM (slice 1.3 will ' +
20
+ 'land real per-IDE joins).';
21
+ const FINALIZE_DESCRIPTION = 'D21: signal that a dispatched sub-agent has finished (success/failure/cancellation). Pass --all-stale for crash-recovery sweep.';
38
22
  export function registerShareCommand(parent, io) {
39
23
  addJsonOption(parent
40
24
  .command('share')
41
- .description('G8.4: write a shared entry to the cross sub-agent shared channel. ' +
42
- 'Dispatcher-mediated indirect signal: sub-agent A writes, dispatcher ' +
43
- 'stores, sub-agent B (still in flight) reads via `peaks sub-agent ' +
44
- 'shared-read`. Not peer-to-peer; pseudo-swarm property 3 preserved.')
25
+ .description(SHARE_DESCRIPTION)
45
26
  .requiredOption('--batch <batchId>', 'batchId (from `peaks sub-agent dispatch` envelope)')
46
27
  .requiredOption('--key <k>', 'entry key (convention: "<role>.<event>")')
47
28
  .requiredOption('--value <json>', 'JSON object value (≤ 1KB soft warn, ≥ 64KB rejected)')
48
29
  .option('--from <role>', 'sub-agent role string; defaults to dispatch record role if available')
49
30
  .option('--request-id <rid>', 'request id (default: "unknown-rid")')
50
31
  .option('--session-id <sid>', 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')
51
- .option('--project <path>', 'target project root (defaults to cwd)')).action((options) => {
52
- const asJson = options.json === true;
53
- if (!options.batch || !options.key || !options.value) {
54
- printResult(io, fail('sub-agent.share', 'MISSING_ARG', '--batch, --key, and --value are required', { ok: false }, ['Re-run with --batch <batchId> --key <key> --value <jsonObject>.']), asJson);
55
- process.exitCode = 1;
56
- return;
57
- }
58
- let parsedValue;
59
- try {
60
- const parsed = JSON.parse(options.value);
61
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
62
- throw new Error('value must be a JSON object');
63
- }
64
- parsedValue = parsed;
65
- }
66
- catch (err) {
67
- printResult(io, fail('sub-agent.share', 'INVALID_VALUE', `value must be a JSON object: ${getErrorMessage(err)}`, { ok: false }, ['Pass --value as a JSON object literal, e.g. --value \'{"reason":"x"}\'.']), asJson);
68
- process.exitCode = 1;
69
- return;
70
- }
71
- try {
72
- const projectRoot = options.project ?? process.cwd();
73
- const sid = options.sessionId ??
74
- process.env.PEAKS_SESSION_ID ??
75
- getCurrentSessionId(projectRoot) ??
76
- 'unknown-sid';
77
- const rid = options.requestId ?? 'unknown-rid';
78
- const from = options.from ?? 'unknown-role';
79
- const result = writeSharedEntry({
80
- projectRoot,
81
- sid,
82
- rid,
83
- batchId: options.batch,
84
- key: options.key,
85
- from,
86
- value: parsedValue
87
- });
88
- if (!result.ok) {
89
- const code = result.code;
90
- printResult(io, fail('sub-agent.share', code, result.message, { ok: false, batchId: options.batch }, [
91
- code === 'VALUE_TOO_LARGE'
92
- ? 'Reduce value size; 1KB is a soft warning, 64KB is a hard reject.'
93
- : 'See error message; check --batch, --key, --value arguments.'
94
- ]), asJson);
95
- process.exitCode = 1;
96
- return;
97
- }
98
- const warnings = [];
99
- if (result.lastWriteWins) {
100
- warnings.push('LAST_WRITE_WINS');
101
- }
102
- if (result.softWarning) {
103
- warnings.push(`VALUE_SIZE_SOFT_WARN: ${result.entry.valueSize} > ${SHARED_CHANNEL_SOFT_VALUE_WARN} bytes`);
104
- }
105
- printResult(io, ok('sub-agent.share', {
106
- envelopeVersion: '2.1.0',
107
- ok: true,
108
- batchId: options.batch,
109
- entryKey: options.key,
110
- writtenAt: result.entry.at,
111
- channelSize: result.channelSize,
112
- lastWriteWins: result.lastWriteWins,
113
- valueSize: result.entry.valueSize
114
- }, warnings, [
115
- 'Sub-agents in the same batch can read this entry via `peaks sub-agent shared-read --batch ' +
116
- options.batch +
117
- '`.'
118
- ]), asJson);
119
- try {
120
- writeLogEntry({
121
- ts: new Date().toISOString(),
122
- level: 'info',
123
- command: 'sub-agent.share',
124
- msg: 'shared',
125
- sessionId: sid,
126
- batchId: options.batch,
127
- data: {
128
- batchId: options.batch,
129
- key: options.key,
130
- valueSize: result.entry.valueSize,
131
- lastWriteWins: result.lastWriteWins
132
- }
133
- });
134
- }
135
- catch {
136
- // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
137
- /* best-effort */
138
- }
139
- }
140
- catch (error) {
141
- const code = error.code ?? 'SHARE_ERROR';
142
- printResult(io, fail('sub-agent.share', code, getErrorMessage(error), { ok: false, batchId: options.batch }, [shareErrorNextActions(code)]), asJson);
143
- process.exitCode = 1;
144
- }
145
- });
146
- }
147
- /**
148
- * runner gets an actionable hint instead of a generic "see error
149
- * message" fallback. Each branch names the most likely next step.
150
- */
151
- function shareErrorNextActions(code) {
152
- if (code === 'LOCK_TIMEOUT') {
153
- return 'A concurrent `peaks sub-agent share` is holding the channel lock for >5s; retry, or check for a crashed holder (the .lock file is reaped after 30s).';
154
- }
155
- if (code === 'RECORD_NOT_FOUND' || code === 'INVALID_RECORD_PATH') {
156
- return 'The dispatch record path is missing or outside .peaks/_sub_agents/. Re-run `peaks sub-agent dispatch <role>` to get a fresh record path, then use that here.';
157
- }
158
- return 'See error message; check that --batch matches the dispatch envelope and the value is a JSON object ≤ 64KB.';
32
+ .option('--project <path>', 'target project root (defaults to cwd)')).action((options) => runShare(options, io));
159
33
  }
160
34
  export function registerSharedReadCommand(parent, io) {
161
35
  addJsonOption(parent
162
36
  .command('shared-read')
163
- .description('G8.4: read entries from the cross sub-agent shared channel. ' +
164
- 'Returns sibling sub-agent status. Supports --since (ISO8601) ' +
165
- 'and --key (glob pattern with * wildcard).')
37
+ .description(SHARED_READ_DESCRIPTION)
166
38
  .requiredOption('--batch <batchId>', 'batchId (from `peaks sub-agent dispatch` envelope)')
167
39
  .option('--since <iso>', 'only return entries written after this ISO8601 timestamp')
168
40
  .option('--key <pattern>', 'glob pattern, e.g. "rd.*" or "*.completed"')
169
41
  .option('--request-id <rid>', 'request id (default: "unknown-rid")')
170
42
  .option('--session-id <sid>', 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')
171
- .option('--project <path>', 'target project root (defaults to cwd)')).action((options) => {
172
- const asJson = options.json === true;
173
- if (!options.batch) {
174
- printResult(io, fail('sub-agent.shared-read', 'MISSING_BATCH', '--batch is required', { ok: false }, ['Re-run with --batch <batchId>.']), asJson);
175
- process.exitCode = 1;
176
- return;
177
- }
178
- try {
179
- const projectRoot = options.project ?? process.cwd();
180
- const sid = options.sessionId ??
181
- process.env.PEAKS_SESSION_ID ??
182
- getCurrentSessionId(projectRoot) ??
183
- 'unknown-sid';
184
- const rid = options.requestId ?? 'unknown-rid';
185
- const channel = readSharedChannel({
186
- projectRoot,
187
- sid,
188
- rid,
189
- batchId: options.batch,
190
- ...(options.since !== undefined ? { since: options.since } : {}),
191
- ...(options.key !== undefined ? { keyPattern: options.key } : {})
192
- });
193
- printResult(io, ok('sub-agent.shared-read', {
194
- envelopeVersion: '2.1.0',
195
- ok: true,
196
- batchId: options.batch,
197
- entries: channel.entries,
198
- totalEntries: Object.keys(channel.entries).length,
199
- channelSize: JSON.stringify(channel).length,
200
- updatedAt: channel.updatedAt
201
- }, [], [
202
- 'Shared channel is dispatcher-mediated; do not attempt to read sibling dispatch records directly.'
203
- ]), asJson);
204
- }
205
- catch (error) {
206
- const code = error.code ?? 'SHARED_READ_ERROR';
207
- printResult(io, fail('sub-agent.shared-read', code, getErrorMessage(error), { ok: false, batchId: options.batch }, [sharedReadErrorNextActions(code)]), asJson);
208
- process.exitCode = 1;
209
- }
210
- });
211
- }
212
- function sharedReadErrorNextActions(code) {
213
- if (code === 'INVALID_BATCH_ID' || code === 'MISSING_BATCH') {
214
- return 'Pass --batch <batchId> exactly as returned by the `peaks sub-agent dispatch` envelope.';
215
- }
216
- return 'See error message; check that --batch matches the dispatch envelope (typo / wrong batch will return an empty channel, not an error).';
43
+ .option('--project <path>', 'target project root (defaults to cwd)')).action((options) => runSharedRead(options, io));
217
44
  }
218
45
  export function registerAwaitCommand(parent, io) {
219
46
  addJsonOption(parent
220
47
  .command('await')
221
- .description('2.7.0 slice-dag-dispatcher MVP: wait for a batch of dispatched sub-agents ' +
222
- 'to finish (or hit --timeout). Returns one BatchResult per dispatch. ' +
223
- 'For non-claude-code IDEs, the wait is delegated to the LLM (slice 1.3 will ' +
224
- 'land real per-IDE joins).')
48
+ .description(AWAIT_DESCRIPTION)
225
49
  .requiredOption('--batch <batchId>', 'batchId from a dispatch envelope')
226
50
  .option('--timeout <ms>', 'optional cap on how long the join waits (ms; default 60000, max 120000)')
227
51
  .option('--project <path>', 'target project root (defaults to cwd)')
228
- .option('--session-id <sid>', 'override active session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')).action(async (options) => {
229
- const asJson = options.json === true;
230
- if (!options.batch) {
231
- printResult(io, fail('sub-agent.await', 'MISSING_BATCH', '--batch is required', { ok: false }, [
232
- 'Re-run with --batch <batchId> from a dispatch envelope.'
233
- ]), asJson);
234
- process.exitCode = 1;
235
- return;
236
- }
237
- let timeoutMs;
238
- if (typeof options.timeout === 'string' && options.timeout.length > 0) {
239
- const n = Number.parseInt(options.timeout, 10);
240
- if (!Number.isInteger(n) || n <= 0) {
241
- printResult(io, fail('sub-agent.await', 'INVALID_TIMEOUT', `--timeout must be a positive integer ms (got ${options.timeout})`, { ok: false }, ['Pass an integer like --timeout 60000.']), asJson);
242
- process.exitCode = 1;
243
- return;
244
- }
245
- timeoutMs = n;
246
- }
247
- const projectRoot = options.project ?? process.cwd();
248
- const sid = options.sessionId ??
249
- process.env.PEAKS_SESSION_ID ??
250
- getCurrentSessionId(projectRoot) ??
251
- 'unknown-sid';
252
- // Lazy-import IDE modules so `peaks sub-agent share` and
253
- // `peaks sub-agent shared-read` (the high-frequency G8.4 path) do not
254
- // pay for adapter resolution at module-load time. Slice
255
- const { detectInstalledIde } = await import('../../services/ide/ide-detector.js');
256
- const { getAdapter } = await import('../../services/ide/ide-registry.js');
257
- const ide = detectInstalledIde(projectRoot) ?? 'claude-code';
258
- const adapter = getAdapter(ide);
259
- const dispatcher = adapter.subAgentDispatcher;
260
- if (typeof dispatcher.awaitBatch !== 'function') {
261
- printResult(io, fail('sub-agent.await', 'IDE_NOT_SUPPORTED', `IDE ${ide} does not support awaitBatch`, { ok: false }, [
262
- 'Every built-in adapter has an awaitBatch since slice 1.3; a dispatcher without one is a custom adapter registered outside the built-in set.'
263
- ]), asJson);
264
- process.exitCode = 1;
265
- return;
266
- }
267
- try {
268
- // session's dispatch directory. `recordPaths` used to be hardcoded to
269
- // `[]`, and `awaitBatch` short-circuits on an empty list
270
- // (await-batch.ts:134) — so `await` reported zero results and exited 0
271
- // for every batch, forever. A batch we cannot locate is now a failure,
272
- // not a success with nothing in it.
273
- const { readRecord } = await import('../../services/dispatch/dispatch-record-writer.js');
274
- const scan = resolveBatchRecords({
275
- projectRoot,
276
- sessionId: sid,
277
- batchId: options.batch,
278
- readOne: readRecord
279
- });
280
- if (scan.recordPaths.length === 0) {
281
- const unreadableNote = scan.unreadable.length > 0
282
- ? ` ${scan.unreadable.length} dispatch record(s) there could not be read, so they could not be matched to this batch: ${scan.unreadable.join(', ')}`
283
- : '';
284
- printResult(io, fail('sub-agent.await', 'NO_DISPATCH_RECORDS', `No dispatch record with batchId=${options.batch} under ${scan.sessionDir}.${unreadableNote}`, {
285
- ok: false,
286
- batchId: options.batch,
287
- sessionDir: scan.sessionDir,
288
- unreadableRecords: scan.unreadable
289
- }, [awaitErrorNextActions('NO_DISPATCH_RECORDS')]), asJson);
290
- process.exitCode = 1;
291
- return;
292
- }
293
- const input = {
294
- batchId: options.batch,
295
- dispatchCount: scan.recordPaths.length,
296
- recordPaths: scan.recordPaths,
297
- ...(timeoutMs !== undefined ? { timeoutMs } : {})
298
- };
299
- const results = await dispatcher.awaitBatch(input);
300
- const summary = summarizeBatchResults(results);
301
- printResult(io, ok('sub-agent.await', {
302
- envelopeVersion: '2.1.0',
303
- batchId: options.batch,
304
- ide: dispatcher.label,
305
- results,
306
- summary,
307
- unreadableRecords: scan.unreadable
308
- }, scan.unreadable.length > 0
309
- ? [
310
- `${scan.unreadable.length} unreadable dispatch record(s) in this session were skipped and are NOT part of the results: ${scan.unreadable.join(', ')}`
311
- ]
312
- : [], [
313
- // four non-Claude IDEs would report `awaitByLlm: <ide> 1.2 fallback`,
314
- // the slice-1.2 marker that slice 1.3 replaced with a real
315
- // file-polling await. The text survived because nothing tested it —
316
- // no adapter produces that note any more (asserted in
317
- // sub-agent-dispatchers.test.ts), and the emitter that produced it,
318
- // `awaitByLlmFallback`, has since been removed.
319
- `Each non-claude-code IDE labels its own results (see the \`note\` field), so a timed-out slot is attributable to the adapter it came from.`
320
- ]), asJson);
321
- }
322
- catch (error) {
323
- const code = error.code ?? 'AWAIT_ERROR';
324
- printResult(io, fail('sub-agent.await', code, getErrorMessage(error), { ok: false }, [
325
- awaitErrorNextActions(code)
326
- ]), asJson);
327
- process.exitCode = 1;
328
- }
329
- });
330
- }
331
- function awaitErrorNextActions(code) {
332
- if (code === 'INVALID_TIMEOUT') {
333
- return 'Pass --timeout as a positive integer ms (e.g. --timeout 60000). 0 and non-numeric values are rejected.';
334
- }
335
- if (code === 'IDE_NOT_SUPPORTED') {
336
- return 'Switch to claude-code, or rely on LLM-side await for non-claude-code IDEs in slice 1.3.';
337
- }
338
- if (code === 'NO_DISPATCH_RECORDS') {
339
- return 'Check --session-id / --project: records live under .peaks/_sub_agents/<sessionId>/. Use the batchId exactly as printed by the dispatch envelope.';
340
- }
341
- return 'See error message; check that --batch matches the dispatch envelope and --timeout is a positive integer ms.';
342
- }
343
- /**
344
- * Resolve a record's on-disk path through `realpathSync` so the value returned
345
- * here matches the value `writeInitialDispatchRecord` produces — which itself
346
- * passes through `assertSafeDispatchRecordPath` and ends up canonicalized. On
347
- * macOS, `mkdtempSync(join(tmpdir(), prefix))` returns `/var/folders/...`
348
- * while `process.cwd()` after `chdir` returns `/private/var/folders/...`;
349
- * without this helper the test fixture's `queuedPath` (canonical) and the
350
- * envelope's `finalized[].recordPath` (raw) never compare equal. Falls back
351
- * to the lexical path when the file is missing (race between scan and write).
352
- */
353
- function safeRecordPath(p) {
354
- try {
355
- return realpathSyncNative(p);
356
- }
357
- catch {
358
- return p;
359
- }
360
- }
361
- /**
362
- *
363
- * Reuses the conventions already in the repo instead of inventing a new one:
364
- * - records live at `.peaks/_sub_agents/<sid>/dispatch-<rid>-<ts>.json`
365
- * (`dispatchRecordPath`, src/services/security/safe-settings-path.ts);
366
- * - a record's batch is its own `batchId` field, written by
367
- * `writeInitialDispatchRecord`. The `--batch` branch of `finalize` (below)
368
- * and `findBatchRecords` in heartbeat-watch-command.ts resolve a batch the
369
- * same way: scan the session dir, filter `dispatch-*.json`, compare field.
370
- *
371
- * `unreadable` lists the `dispatch-*.json` candidates whose batch could NOT be
372
- * determined. The caller must not fold them into "no such batch": a record it
373
- * cannot read is not evidence that the batch is empty.
374
- */
375
- function resolveBatchRecords(input) {
376
- const sessionDir = resolve(input.projectRoot, '.peaks', '_sub_agents', input.sessionId);
377
- const recordPaths = [];
378
- const unreadable = [];
379
- if (!existsSync(sessionDir))
380
- return { sessionDir, recordPaths, unreadable };
381
- for (const name of readdirSync(sessionDir)) {
382
- // `active-dispatches.json` (the index) and `batch-<uuid>.counter.json` are
383
- // not records; neither carries a `version`, so `readRecord` would reject
384
- // them as "version mismatch". Same filter as the `--batch` branch below.
385
- if (!name.startsWith('dispatch-') || !name.endsWith('.json'))
386
- continue;
387
- const recordPath = safeRecordPath(join(sessionDir, name));
388
- let batchId;
389
- try {
390
- batchId = input.readOne(recordPath).batchId;
391
- }
392
- catch {
393
- unreadable.push(recordPath);
394
- continue;
395
- }
396
- if (batchId === input.batchId)
397
- recordPaths.push(recordPath);
398
- }
399
- return { sessionDir, recordPaths, unreadable };
52
+ .option('--session-id <sid>', 'override active session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')).action((options) => runAwait(options, io, resolveIdeDispatcher));
400
53
  }
54
+ /** Contract: LLM MUST call once per dispatched Task, in post-completion branch. */
401
55
  export function registerFinalizeCommand(parent, io) {
402
56
  addJsonOption(parent
403
57
  .command('finalize')
404
- .description('D21: signal that a dispatched sub-agent has finished (success/failure/cancellation). Pass --all-stale for crash-recovery sweep.')
58
+ .description(FINALIZE_DESCRIPTION)
405
59
  .option('--batch <batchId>', 'batchId from dispatch envelope')
406
60
  .option('--request-id <rid>', 'requestId from dispatch envelope')
407
61
  .option('--outcome <state>', 'done | failed | cancelled (default: done)')
@@ -409,186 +63,23 @@ export function registerFinalizeCommand(parent, io) {
409
63
  .option('--all-stale', 'bulk-mark every queued record for this session as done')
410
64
  .option('--project <path>', 'target project root (defaults to cwd)')
411
65
  .option('--session-id <sid>', 'override active session id')).action((options) => {
412
- void (async () => {
413
- const asJson = options.json === true;
414
- try {
415
- const projectRoot = resolve(options.project ?? process.cwd());
416
- const sessionId = options.sessionId ?? getCurrentSessionId(projectRoot) ?? 'unknown-sid';
417
- if (!options.allStale && !options.requestId && !options.batch) {
418
- printResult(io, fail('sub-agent.finalize', 'MISSING_TARGET', 'Pass --request-id or --batch (or --all-stale)', { ok: false }, ['Call finalize after each Task completes.']), asJson);
419
- process.exitCode = 1;
420
- return;
421
- }
422
- const outcome = (options.outcome ?? 'done');
423
- const writerMod = await import('../../services/dispatch/dispatch-record-writer.js');
424
- const { readRecord, markCompleted, readActiveDispatchIndex } = writerMod;
425
- // map outcome -> (status, outcome)
426
- const outcomeMap = {
427
- done: { status: 'done', outcome: 'success' },
428
- failed: { status: 'failed', outcome: 'failed' },
429
- cancelled: { status: 'cancelled', outcome: 'cancelled' }
430
- };
431
- const mapped = outcomeMap[outcome] ?? outcomeMap['done'];
432
- const finalized = [];
433
- const skipped = [];
434
- const errors = [];
435
- let selection = null;
436
- /**
437
- * N2 — one unreadable record must not abort the sweep.
438
- *
439
- * Skipping `active-dispatches.json` and `batch-*.counter.json` by
440
- * FILENAME removed the two non-record files that happened to be in the
441
- * directory, but a single stale or foreign `dispatch-*.json` (an old
442
- * `version`, hand-edited JSON, a truncated write) still made
443
- * `readRecord` throw from OUTSIDE any try/catch — and the throw
444
- * escaped to the action's outer handler, so `--request-id` AND
445
- * `--batch` both died with `FINALIZE_ERROR` and exit 1 without
446
- * touching a single healthy record.
447
- *
448
- * A record that cannot be read is now reported in `errors[]` and
449
- * skipped; every other record is processed as before.
450
- */
451
- const tryReadRecord = (recordPath) => {
452
- try {
453
- return readRecord(recordPath);
454
- }
455
- catch (e) {
456
- errors.push({ recordPath, error: getErrorMessage(e) });
457
- return null;
458
- }
459
- };
460
- const applyOutcome = (recordPath, rid) => {
461
- markCompleted({
462
- recordPath,
463
- now: () => new Date(),
464
- status: mapped.status,
465
- outcome: mapped.outcome,
466
- projectRoot
467
- });
468
- finalized.push({ recordPath, requestId: rid, status: mapped.status });
469
- };
470
- if (options.allStale) {
471
- const index = readActiveDispatchIndex(projectRoot, sessionId);
472
- for (const [recordPath, entry] of Object.entries(index)) {
473
- if (entry.status !== 'queued') {
474
- skipped.push({ recordPath, reason: 'status is ' + entry.status });
475
- continue;
476
- }
477
- try {
478
- applyOutcome(recordPath, entry.requestId);
479
- }
480
- catch (e) {
481
- errors.push({ recordPath, error: getErrorMessage(e) });
482
- }
483
- }
484
- }
485
- else if (options.requestId) {
486
- const fs2 = await import('node:fs');
487
- const path2 = await import('node:path');
488
- const dir = path2.resolve(projectRoot, '.peaks', '_sub_agents', sessionId);
489
- const candidates = [];
490
- if (fs2.existsSync(dir)) {
491
- for (const f of fs2.readdirSync(dir)) {
492
- // Only dispatch records are readable records. The session
493
- // directory also holds `active-dispatches.json` (an index)
494
- // and `batch-<uuid>.counter.json` (batch counters); neither
495
- // carries a `version` field, so `readRecord` on them throws
496
- // `Dispatch record version mismatch ... got undefined`. The
497
- // `--batch` branch below has always used this same filter.
498
- if (!f.startsWith('dispatch-') || !f.endsWith('.json'))
499
- continue;
500
- const p = safeRecordPath(path2.join(dir, f));
501
- const r = tryReadRecord(p);
502
- if (r === null || r.requestId !== options.requestId)
503
- continue;
504
- candidates.push({
505
- recordPath: p,
506
- requestId: r.requestId,
507
- status: r.status,
508
- createdAt: r.createdAt
509
- });
510
- }
511
- }
512
- if (candidates.length === 0) {
513
- printResult(io, fail('sub-agent.finalize', 'RECORD_NOT_FOUND', 'No dispatch record for requestId=' + options.requestId, { ok: false }, ['Check --request-id matches the dispatch envelope.']), asJson);
514
- process.exitCode = 1;
515
- return;
516
- }
517
- // N3: same rule as `--batch` — see `selectFinalizeTarget`.
518
- const chosen = selectFinalizeTarget(candidates);
519
- selection = {
520
- requestId: options.requestId,
521
- rule: FINALIZE_SELECTION_RULE,
522
- matched: candidates.length,
523
- chosen: chosen?.recordPath ?? null,
524
- rejected: candidates
525
- .filter((candidate) => candidate !== chosen)
526
- .map((candidate) => ({
527
- recordPath: candidate.recordPath,
528
- status: candidate.status,
529
- reason: describeFinalizeRejection(candidate, chosen)
530
- }))
531
- };
532
- for (const rejected of selection.rejected) {
533
- skipped.push({ recordPath: rejected.recordPath, reason: rejected.reason });
534
- }
535
- if (chosen !== null) {
536
- try {
537
- applyOutcome(chosen.recordPath, chosen.requestId);
538
- }
539
- catch (e) {
540
- errors.push({ recordPath: chosen.recordPath, error: getErrorMessage(e) });
541
- }
542
- }
543
- }
544
- else {
545
- const fs2 = await import('node:fs');
546
- const path2 = await import('node:path');
547
- const dir = path2.resolve(projectRoot, '.peaks', '_sub_agents', sessionId);
548
- if (fs2.existsSync(dir)) {
549
- for (const f of fs2.readdirSync(dir)) {
550
- if (!f.startsWith('dispatch-') || !f.endsWith('.json'))
551
- continue;
552
- const p = safeRecordPath(path2.join(dir, f));
553
- const r = tryReadRecord(p);
554
- if (r === null)
555
- continue;
556
- if (r.batchId !== options.batch)
557
- continue;
558
- if (r.status !== 'queued') {
559
- skipped.push({ recordPath: p, reason: 'status is ' + r.status });
560
- continue;
561
- }
562
- try {
563
- applyOutcome(p, r.requestId);
564
- }
565
- catch (e) {
566
- errors.push({ recordPath: p, error: getErrorMessage(e) });
567
- }
568
- }
569
- }
570
- }
571
- const hints = [];
572
- if (errors.length > 0) {
573
- hints.push('Re-run after fixing; unreadable records are listed in errors[] and were skipped, not fatal.');
574
- }
575
- else if (finalized.length === 0 && skipped.length > 0) {
576
- hints.push("Nothing was finalized: every matching record had already left `queued`. See skipped[] for each record's status.");
577
- }
578
- else {
579
- hints.push('All targeted records transitioned out of queued.');
580
- }
581
- if (selection !== null) {
582
- hints.push(`--request-id selection (${selection.rule}): chose ${selection.chosen ?? '(none)'} of ${selection.matched} matching record(s); ${selection.rejected.length} rejected.`);
583
- }
584
- printResult(io, ok('sub-agent.finalize', { finalized, skipped, errors, selection, sessionId, outcome }, errors.length > 0 ? [errors.length + ' failed'] : [], hints), asJson);
585
- if (errors.length > 0)
586
- process.exitCode = 1;
587
- }
588
- catch (error) {
589
- printResult(io, fail('sub-agent.finalize', 'FINALIZE_ERROR', getErrorMessage(error), { ok: false }, ['Inspect the error.']), asJson);
590
- process.exitCode = 1;
591
- }
592
- })();
66
+ void runFinalize(options, io);
593
67
  });
594
68
  }
69
+ /**
70
+ * Resolve the per-IDE sub-agent dispatcher for `await`, importing the IDE
71
+ * modules lazily so `peaks sub-agent share` and `peaks sub-agent shared-read`
72
+ * (the high-frequency G8.4 path) do not pay for adapter resolution at
73
+ * module-load time.
74
+ *
75
+ * This lives here, not in the await runner, because this module is the adapter
76
+ * registry's consumer — `tests/unit/runtime/vendor-neutral-identity-guard.test.ts`
77
+ * pins that this file is in the guard's measured scope.
78
+ */
79
+ async function resolveIdeDispatcher(projectRoot) {
80
+ const { detectInstalledIde } = await import('../../services/ide/ide-detector.js');
81
+ const { getAdapter } = await import('../../services/ide/ide-registry.js');
82
+ const ide = detectInstalledIde(projectRoot) ?? 'claude-code';
83
+ const adapter = getAdapter(ide);
84
+ return { ide, dispatcher: adapter.subAgentDispatcher };
85
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Per-code "what to do next" hints for the failure envelopes of
3
+ * `peaks sub-agent share`, `peaks sub-agent shared-read`, and
4
+ * `peaks sub-agent await`. Extracted from `share-commands.ts` so that file
5
+ * stays under the line cap; each helper is pure — it maps an error code to the
6
+ * single actionable hint the runner places in the envelope, and nothing here
7
+ * prints, reads state, or throws.
8
+ *
9
+ * Each branch names the most likely next step, so a caller gets an actionable
10
+ * hint instead of a generic "see error message" fallback.
11
+ */
12
+ export declare function shareErrorNextActions(code: string): string;
13
+ export declare function sharedReadErrorNextActions(code: string): string;
14
+ export declare function awaitErrorNextActions(code: string): string;