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,92 @@
1
+ /**
2
+ * The `finalize` action body for `peaks sub-agent`, split out of
3
+ * `share-commands.ts` so that registrar file stays under the line cap. The
4
+ * registrar fires this runner without awaiting it (the original fired a
5
+ * floating async IIFE in the same way).
6
+ *
7
+ * `runFinalizeSweep` builds the accumulator, applies the outcome mapping and
8
+ * prints the envelope; the three selection branches live in
9
+ * `finalize-branches.ts` and the selection rule itself in
10
+ * `finalize-selection.ts`.
11
+ */
12
+ import { resolve } from 'node:path';
13
+ import { fail, getErrorMessage, ok } from 'peaks-loop-shared/result';
14
+ import { printResult } from '../cli-helpers.js';
15
+ import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
16
+ import { OUTCOME_MAP, buildFinalizeHints, makeAccumulator, tryReadRecord } from './finalize-accumulator.js';
17
+ import { finalizeAllStale, finalizeByBatch, finalizeByRequestId } from './finalize-branches.js';
18
+ export async function runFinalize(options, io) {
19
+ const asJson = options.json === true;
20
+ try {
21
+ const projectRoot = resolve(options.project ?? process.cwd());
22
+ const sessionId = options.sessionId ?? getCurrentSessionId(projectRoot) ?? 'unknown-sid';
23
+ if (!options.allStale && !options.requestId && !options.batch) {
24
+ 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);
25
+ process.exitCode = 1;
26
+ return;
27
+ }
28
+ await runFinalizeSweep({ options, io, asJson, projectRoot, sessionId });
29
+ }
30
+ catch (error) {
31
+ printResult(io, fail('sub-agent.finalize', 'FINALIZE_ERROR', getErrorMessage(error), { ok: false }, [
32
+ 'Inspect the error.'
33
+ ]), asJson);
34
+ process.exitCode = 1;
35
+ }
36
+ }
37
+ async function runFinalizeSweep(input) {
38
+ const { options, io, asJson, projectRoot, sessionId } = input;
39
+ const outcome = (options.outcome ?? 'done');
40
+ const writerMod = await import('../../services/dispatch/dispatch-record-writer.js');
41
+ const { readRecord, markCompleted, readActiveDispatchIndex } = writerMod;
42
+ const mapped = OUTCOME_MAP[outcome] ?? OUTCOME_MAP['done'];
43
+ const acc = makeAccumulator();
44
+ const readOne = (recordPath) => tryReadRecord(recordPath, readRecord, acc);
45
+ const apply = (recordPath, rid) => {
46
+ markCompleted({
47
+ recordPath,
48
+ now: () => new Date(),
49
+ status: mapped.status,
50
+ outcome: mapped.outcome,
51
+ projectRoot
52
+ });
53
+ acc.finalized.push({ recordPath, requestId: rid, status: mapped.status });
54
+ };
55
+ let selection = null;
56
+ if (options.allStale) {
57
+ finalizeAllStale({ readActiveDispatchIndex, projectRoot, sessionId, apply, acc });
58
+ }
59
+ else if (options.requestId) {
60
+ const result = await finalizeByRequestId({
61
+ projectRoot,
62
+ sessionId,
63
+ requestId: options.requestId,
64
+ io,
65
+ asJson,
66
+ readOne,
67
+ apply,
68
+ acc
69
+ });
70
+ if (result.kind === 'not-found')
71
+ return;
72
+ selection = result.selection;
73
+ }
74
+ else {
75
+ await finalizeByBatch({ projectRoot, sessionId, batchId: options.batch, readOne, apply, acc });
76
+ }
77
+ printFinalizeResult({ io, asJson, sessionId, outcome, acc, selection });
78
+ if (acc.errors.length > 0)
79
+ process.exitCode = 1;
80
+ }
81
+ /** Print the `sub-agent.finalize` envelope and its hints. */
82
+ function printFinalizeResult(input) {
83
+ const { acc } = input;
84
+ printResult(input.io, ok('sub-agent.finalize', {
85
+ finalized: acc.finalized,
86
+ skipped: acc.skipped,
87
+ errors: acc.errors,
88
+ selection: input.selection,
89
+ sessionId: input.sessionId,
90
+ outcome: input.outcome
91
+ }, acc.errors.length > 0 ? [acc.errors.length + ' failed'] : [], buildFinalizeHints(acc, input.selection)), input.asJson);
92
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The pure `--request-id` selection contract for `peaks sub-agent finalize`, plus
3
+ * the option shape the command registers. Extracted from `share-commands.ts` so
4
+ * that file stays under the line cap; these are the pieces the finalize tests
5
+ * import directly, and none of them touches the filesystem.
6
+ */
7
+ /**
8
+ * A `dispatch-*.json` candidate for `finalize --request-id`, as read off disk.
9
+ * The record's own `createdAt` is carried as the recency key — NOT the file's
10
+ * mtime, which any heartbeat or finalize rewrites, so a `done` record can look
11
+ * newer than the `queued` one that superseded it. Keeping the key on the
12
+ * candidate makes the selection rule a pure function of the records and
13
+ * testable without a filesystem.
14
+ */
15
+ export interface FinalizeCandidate {
16
+ readonly recordPath: string;
17
+ readonly requestId: string;
18
+ readonly status: string;
19
+ readonly createdAt: string;
20
+ }
21
+ /**
22
+ * What `--request-id` actually did, reported in the envelope. N3: the branch
23
+ * used to resolve silently and could not say WHY a record was or was not the
24
+ * one finalized.
25
+ */
26
+ export interface FinalizeSelection {
27
+ readonly requestId: string;
28
+ readonly rule: string;
29
+ readonly matched: number;
30
+ readonly chosen: string | null;
31
+ readonly rejected: readonly {
32
+ readonly recordPath: string;
33
+ readonly status: string;
34
+ readonly reason: string;
35
+ }[];
36
+ }
37
+ /** The one selection rule `--request-id` and `--batch` now share. */
38
+ export declare const FINALIZE_SELECTION_RULE: string;
39
+ /** The options `peaks sub-agent finalize` accepts. */
40
+ export interface FinalizeOptions {
41
+ batch?: string;
42
+ requestId?: string;
43
+ outcome?: string;
44
+ error?: string;
45
+ allStale?: boolean;
46
+ project?: string;
47
+ sessionId?: string;
48
+ json?: boolean;
49
+ }
50
+ /**
51
+ * N3 — pick the record `finalize --request-id` should act on.
52
+ *
53
+ * The branch this replaces `break`ed on the FIRST file whose record carried
54
+ * the requestId and never looked at `status`. With a re-dispatched request
55
+ * always resolved to the OLDEST one — the already-`done` RD record — so
56
+ * finalizing reported success while the newer `queued` QA record stayed
57
+ * queued forever. `--batch` never had that bug: it filters on `queued`.
58
+ *
59
+ * Both branches are now the same rule. Return `null` when nothing is queued:
60
+ * a record that already left `queued` is precisely the one that must NOT be
61
+ * re-finalized, so the caller reports the survivors instead of touching one.
62
+ */
63
+ export declare function selectFinalizeTarget(candidates: readonly FinalizeCandidate[]): FinalizeCandidate | null;
64
+ /** Why a candidate was not the chosen one — reported, never guessed at. */
65
+ export declare function describeFinalizeRejection(candidate: FinalizeCandidate, chosen: FinalizeCandidate | null): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The pure `--request-id` selection contract for `peaks sub-agent finalize`, plus
3
+ * the option shape the command registers. Extracted from `share-commands.ts` so
4
+ * that file stays under the line cap; these are the pieces the finalize tests
5
+ * import directly, and none of them touches the filesystem.
6
+ */
7
+ /** The one selection rule `--request-id` and `--batch` now share. */
8
+ export const FINALIZE_SELECTION_RULE = 'prefer status=queued; among those, newest by record createdAt (then filename); ' +
9
+ 'no queued match means nothing is finalized';
10
+ /**
11
+ * N3 — pick the record `finalize --request-id` should act on.
12
+ *
13
+ * The branch this replaces `break`ed on the FIRST file whose record carried
14
+ * the requestId and never looked at `status`. With a re-dispatched request
15
+ * always resolved to the OLDEST one — the already-`done` RD record — so
16
+ * finalizing reported success while the newer `queued` QA record stayed
17
+ * queued forever. `--batch` never had that bug: it filters on `queued`.
18
+ *
19
+ * Both branches are now the same rule. Return `null` when nothing is queued:
20
+ * a record that already left `queued` is precisely the one that must NOT be
21
+ * re-finalized, so the caller reports the survivors instead of touching one.
22
+ */
23
+ export function selectFinalizeTarget(candidates) {
24
+ const queued = candidates.filter((candidate) => candidate.status === 'queued');
25
+ if (queued.length === 0)
26
+ return null;
27
+ return [...queued].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.recordPath.localeCompare(a.recordPath))[0];
28
+ }
29
+ /** Why a candidate was not the chosen one — reported, never guessed at. */
30
+ export function describeFinalizeRejection(candidate, chosen) {
31
+ if (candidate.status !== 'queued' || chosen === null) {
32
+ return 'status is ' + candidate.status;
33
+ }
34
+ return 'superseded by the newer queued record ' + chosen.recordPath;
35
+ }
@@ -4,6 +4,7 @@ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
4
4
  import { emitHint } from '../../services/hooks/output.js';
5
5
  import { parseClaudeShapeStdin } from '../../services/ide/hook-translator.js';
6
6
  import { classifyTool, emitAllowSkipped, emitAllowWorktree, emitSopAllow, emitSopDeny, emitSopWarnings, handleWorktreeGate, parseHookStdin, resolveToolMatcher } from './gate-commands-enforce.js';
7
+ import { handleMcpSurfaceGate } from '../../services/hooks/mcp-surface-gate.js';
7
8
  export function registerGateCommands(program, io) {
8
9
  const gate = program
9
10
  .command('gate')
@@ -53,6 +54,19 @@ function exitWhenRealCliHook() {
53
54
  process.exit(process.exitCode || 0);
54
55
  }
55
56
  }
57
+ /** Read the payload's surface out of the tool name, the adapter's matcher and the command. */
58
+ function classifySurface(parsedStdin) {
59
+ const toolMatcher = resolveToolMatcher(parsedStdin);
60
+ const { toolName, command } = parseClaudeShapeStdin(parsedStdin);
61
+ const toolKind = classifyTool(toolName);
62
+ const isBash = toolName === toolMatcher && typeof command === 'string' && command.trim().length > 0;
63
+ return {
64
+ toolKind,
65
+ command,
66
+ bashCommand: isBash ? command : null,
67
+ isWorktreeToolSurface: toolKind === 'Agent' || toolKind === 'EnterWorktree'
68
+ };
69
+ }
56
70
  /**
57
71
  * Body of the `gate enforce` PreToolUse handler. The worktree-then-SOP ordering and every early
58
72
  * return / exit-code path are documented on the helpers this delegates to in `gate-commands-enforce`.
@@ -63,23 +77,27 @@ async function runGateEnforceAction(io, options) {
63
77
  try {
64
78
  const raw = await readHookPayload();
65
79
  const parsedStdin = parseHookStdin(raw);
66
- const toolMatcher = resolveToolMatcher(parsedStdin);
67
- const { toolName, command } = parseClaudeShapeStdin(parsedStdin);
68
- const toolKind = classifyTool(toolName);
69
- const isBashSurface = toolName === toolMatcher && typeof command === 'string' && command.trim().length > 0;
70
- const isWorktreeToolSurface = toolKind === 'Agent' || toolKind === 'EnterWorktree';
71
- if (!isBashSurface && !isWorktreeToolSurface) {
80
+ // The MCP branch runs FIRST and is fail-CLOSED, so it must not sit behind
81
+ // the "not a guarded surface" early return below — an MCP tool name matches
82
+ // neither the Bash matcher nor a worktree tool, so everything after this
83
+ // point would wave it through. A block here has already been emitted.
84
+ if (handleMcpSurfaceGate(io, parsedStdin)) {
85
+ return;
86
+ }
87
+ const surface = classifySurface(parsedStdin);
88
+ const { toolKind, command } = surface;
89
+ if (surface.bashCommand === null && !surface.isWorktreeToolSurface) {
72
90
  emitAllowSkipped(io, options);
73
91
  return;
74
92
  }
75
93
  if (handleWorktreeGate(io, options, toolKind, { command, parsedStdin })) {
76
94
  return;
77
95
  }
78
- if (!isBashSurface) {
96
+ if (surface.bashCommand === null) {
79
97
  emitAllowWorktree(io, options);
80
98
  return;
81
99
  }
82
- const decision = await enforceBashCommand(options.project, command);
100
+ const decision = await enforceBashCommand(options.project, surface.bashCommand);
83
101
  if (decision.decision === 'deny') {
84
102
  emitSopDeny(io, options, decision);
85
103
  return;
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerJobCheckpointCommands(job: Command, io: ProgramIO): void;
@@ -0,0 +1,157 @@
1
+ import { ok, fail } from 'peaks-loop-shared/result';
2
+ import { addJsonOption, printResult } from '../cli-helpers.js';
3
+ import { JobStateStore } from '../../services/job/job-state-store.js';
4
+ import { JobOrchestrator } from '../../services/job/job-orchestrator.js';
5
+ import { writeJobProgress } from '../../services/job/job-progress-store.js';
6
+ import { JobCheckpointInputSchema, JobBlockInputSchema } from '../../services/job/job-types.js';
7
+ import { codegraphRefreshNotice, refreshCodegraphAfterSlice } from '../../services/codegraph/codegraph-autorefresh.js';
8
+ import { asJson, failResult, projectRoot, resolveSliceId } from './job-command-shared.js';
9
+ import { SESSION_ID_HELP, resolveJobStateRoot } from './job-state-root.js';
10
+ const SLICE_NOT_FOUND_HINTS = ['Re-run with one of the valid slice ids'];
11
+ const NO_REFRESH = { codegraph: null, codegraphWarning: null };
12
+ /**
13
+ * Auto codegraph refresh at the slice-complete boundary. Best-effort:
14
+ * a refresh failure must never fail the checkpoint.
15
+ *
16
+ * A2 (2026-09-17): "must never fail the checkpoint" is not "must never be
17
+ * seen". A refresh that did not happen while a codegraph store IS in use is now
18
+ * a warning line (stderr, `warning: ` prefix, via printResult) naming the reason
19
+ * and the remedy. See `codegraphRefreshNotice` for why `no-codegraph-dir` stays
20
+ * silent.
21
+ *
22
+ * ADVISORY (F1, 2026-09-17) — deliberately exits 0, and the reach of that word
23
+ * is exactly here. The checkpoint itself SUCCEEDED (the slice flipped to done
24
+ * and progress.json was mirrored above); the refresh is a derived index,
25
+ * rebuildable on demand. So the outcome is reported through `ok(...)` + a
26
+ * `warning:` line, never through `failResult`, and `process.exitCode` is left
27
+ * alone even when the refresh throws. Raising it would turn an advisory rebuild
28
+ * into a build breaker for every CI that runs `peaks job checkpoint`. Pinned by
29
+ * the "advisory stays 0" control in tests/unit/cli/job-exit-code.test.ts.
30
+ */
31
+ async function applyCheckpointDone(ctx) {
32
+ const { orch, jobRoot, opts, data, sliceId } = ctx;
33
+ await orch.checkpointDone({
34
+ jobId: data.jobId,
35
+ sliceId,
36
+ ...(data.commitSha ? { commitSha: data.commitSha } : {})
37
+ });
38
+ // v3.1.2: after each --state done, mirror slice progress to
39
+ // .peaks/_runtime/<sessionId>/job/<jid>/progress.json so the
40
+ // next LLM turn (or peaks code gate-step-08 hook) can read it.
41
+ const project = projectRoot(opts);
42
+ const sessId = jobRoot.sessionId;
43
+ const state = orch.status(data.jobId);
44
+ writeJobProgress(project, sessId, {
45
+ jobId: data.jobId,
46
+ done: state.done,
47
+ total: state.total,
48
+ currentSlice: state.currentSlice,
49
+ lastCommitSha: data.commitSha ?? null,
50
+ updatedAt: new Date().toISOString()
51
+ });
52
+ let codegraph;
53
+ try {
54
+ codegraph = await refreshCodegraphAfterSlice(project);
55
+ }
56
+ catch (e) {
57
+ codegraph = {
58
+ refreshed: false,
59
+ reason: 'unavailable',
60
+ note: `auto codegraph refresh failed: ${e instanceof Error ? e.message : String(e)}`
61
+ };
62
+ }
63
+ return { codegraph, codegraphWarning: codegraphRefreshNotice(codegraph) };
64
+ }
65
+ async function applyCheckpointState(ctx) {
66
+ const { orch, data, sliceId } = ctx;
67
+ if (data.state === 'done')
68
+ return applyCheckpointDone(ctx);
69
+ if (data.state === 'skipped') {
70
+ await orch.checkpointSkipped({ jobId: data.jobId, sliceId, reason: data.reason });
71
+ }
72
+ else {
73
+ await orch.checkpointFailed({ jobId: data.jobId, sliceId, reason: data.reason });
74
+ }
75
+ return NO_REFRESH;
76
+ }
77
+ function failCheckpointSliceNotFound(io, opts, data, slice) {
78
+ failResult(io, fail('checkpoint', 'SLICE_NOT_FOUND', slice.message, { jobId: data.jobId, sliceId: data.sliceId, validSliceIds: slice.validSliceIds }, SLICE_NOT_FOUND_HINTS), opts);
79
+ }
80
+ async function runJobCheckpoint(opts, io) {
81
+ const parsed = JobCheckpointInputSchema.safeParse({
82
+ jobId: opts.jobId,
83
+ sliceId: opts.sliceId,
84
+ state: opts.state,
85
+ commitSha: opts.commitSha,
86
+ reason: opts.reason,
87
+ project: projectRoot(opts),
88
+ json: opts.json
89
+ });
90
+ if (!parsed.success)
91
+ return failResult(io, fail('checkpoint', 'INVALID_CHECKPOINT', parsed.error.message, {}), opts);
92
+ const jobRoot = resolveJobStateRoot(opts, opts.jobId);
93
+ const store = new JobStateStore(jobRoot.rootDir);
94
+ // D7: `--slice-id` accepts the canonical `slice-NNN` or the slice's label
95
+ // ("S1"); an id that matches no slice is rejected here, BEFORE any write,
96
+ // so progress.json is never touched by a checkpoint that matched nothing.
97
+ const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
98
+ if ('message' in slice) {
99
+ failCheckpointSliceNotFound(io, opts, parsed.data, slice);
100
+ return;
101
+ }
102
+ const orch = new JobOrchestrator(store);
103
+ const outcome = await applyCheckpointState({
104
+ orch,
105
+ jobRoot,
106
+ opts,
107
+ data: parsed.data,
108
+ sliceId: slice.sliceId
109
+ });
110
+ printResult(io, ok('checkpoint', { sliceId: slice.sliceId, status: parsed.data.state, codegraph: outcome.codegraph }, outcome.codegraphWarning === null ? [] : [outcome.codegraphWarning]), asJson(opts));
111
+ }
112
+ async function runJobBlock(opts, io) {
113
+ const parsed = JobBlockInputSchema.safeParse({
114
+ jobId: opts.jobId,
115
+ sliceId: opts.sliceId,
116
+ reason: opts.reason,
117
+ project: projectRoot(opts),
118
+ json: opts.json
119
+ });
120
+ if (!parsed.success)
121
+ return failResult(io, fail('block', 'INVALID_BLOCK', parsed.error.message, {}), opts);
122
+ const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
123
+ // D7 (same silent no-op as checkpoint): resolve label → sliceId, reject a
124
+ // miss before any write.
125
+ const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
126
+ if ('message' in slice) {
127
+ return failResult(io, fail('block', 'SLICE_NOT_FOUND', slice.message, {
128
+ jobId: parsed.data.jobId,
129
+ sliceId: parsed.data.sliceId,
130
+ validSliceIds: slice.validSliceIds
131
+ }, SLICE_NOT_FOUND_HINTS), opts);
132
+ }
133
+ const orch = new JobOrchestrator(store);
134
+ await orch.blockSlice({ ...parsed.data, sliceId: slice.sliceId });
135
+ printResult(io, ok('block', { blocked: slice.sliceId, reason: parsed.data.reason }), asJson(opts));
136
+ }
137
+ // M3.2: wire the remaining 5 subcommand slots — block, checkpoint, continue,
138
+ // handoff, resume. `block` is registered first here, before `continue` /
139
+ // `resume` / `progress` / `handoff`, to keep the `job` help order stable.
140
+ export function registerJobCheckpointCommands(job, io) {
141
+ addJsonOption(job
142
+ .command('checkpoint')
143
+ .requiredOption('--job-id <jid>')
144
+ .requiredOption('--slice-id <rid>')
145
+ .requiredOption('--state <done|failed|skipped>')
146
+ .option('--commit-sha <sha>')
147
+ .option('--reason <text>')
148
+ .option('--session-id <sid>', SESSION_ID_HELP)
149
+ .option('--project <repo>')).action((opts) => runJobCheckpoint(opts, io));
150
+ addJsonOption(job
151
+ .command('block')
152
+ .requiredOption('--job-id <jid>')
153
+ .requiredOption('--slice-id <rid>')
154
+ .requiredOption('--reason <text>')
155
+ .option('--session-id <sid>', SESSION_ID_HELP)
156
+ .option('--project <repo>')).action((opts) => runJobBlock(opts, io));
157
+ }
@@ -0,0 +1,109 @@
1
+ import type { ResultEnvelope } from 'peaks-loop-shared/result';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ import type { JobStateStore } from '../../services/job/job-state-store.js';
4
+ export declare function asJson(opts: JobJsonOpts): boolean;
5
+ /**
6
+ * process exit non-zero.
7
+ *
8
+ * WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
9
+ * envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
10
+ * `process.exitCode`. Measured with the real CLI before this fix:
11
+ * `peaks job block --job-id j1 --slice-id nope --reason why` printed
12
+ * `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
13
+ * wrapping `peaks job` read the failure as success. Seven of this file's nine
14
+ * failure-reporting sites had that gap; only `job progress` set the code, and
15
+ * that was the file's one accidental precedent rather than a rule.
16
+ *
17
+ * Every `fail(...)` envelope in this file now goes through here, so "reported a
18
+ * failure" and "exited non-zero" cannot drift apart again. The helper takes the
19
+ * Commander `opts` object and calls `asJson(opts)` itself — it must never be
20
+ * handed a bare `opts.json`-less boolean, and it must never pass `opts` to
21
+ * `printResult` (see the `asJson` comment above for that bug's history).
22
+ *
23
+ * DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
24
+ * report a non-fatal outcome through an `ok(...)` envelope and MUST keep
25
+ * exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
26
+ * `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
27
+ * stated design is "a refresh failure must never fail the checkpoint". Those
28
+ * are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
29
+ * directions so a future blanket "every warning exits 1" edit turns red.
30
+ */
31
+ export declare function failResult(io: ProgramIO, result: ResultEnvelope<unknown>, opts: JobJsonOpts): void;
32
+ export declare function projectRoot(opts: JobProjectOpts): string;
33
+ /**
34
+ * D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
35
+ * takes labels, so the natural string to pass back to `--slice-id` is that same
36
+ * label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
37
+ * anything else is a hard error listing the valid ids, so a mistyped
38
+ * `slice-04` can never be accepted as a silent no-op that leaves the slice
39
+ * pending.
40
+ */
41
+ export declare function resolveSliceId(store: JobStateStore, jobId: string, sliceId: string): {
42
+ sliceId: string;
43
+ } | {
44
+ message: string;
45
+ validSliceIds: string[];
46
+ };
47
+ /**
48
+ * S10 (2026-09-20) — the option shapes the `job` subcommands read.
49
+ *
50
+ * WHY. Commander types an action's `opts` as `any`, so every `opts.<field>`
51
+ * access in this file was an `any` access: 109 `no-unsafe-*` findings, the
52
+ * largest single TS root in the S10 census. The root is the *entrance* — the
53
+ * untyped `opts` parameter — not the 109 sites, so the fix is to declare it.
54
+ *
55
+ * Each interface names exactly the options its own command READS, plus `--json`
56
+ * (added by `addJsonOption`). So a field absent here is a field that command
57
+ * does not read, and reading it is now a compile error instead of an `any`
58
+ * access. (A command may declare more than it reads — `job status` declares
59
+ * `--show-cost` and never looks at it; that option is deliberately not listed.)
60
+ * `readonly` is honest: these are read-only inputs; Commander owns the object.
61
+ *
62
+ * The four helpers above (`asJson`, `failResult`, `projectRoot`,
63
+ * `resolveJobStateRoot`) take the narrowest structural type each one actually
64
+ * reads, so all eleven action callbacks remain assignable to them.
65
+ */
66
+ export interface JobJsonOpts {
67
+ readonly json?: boolean;
68
+ }
69
+ export interface JobProjectOpts {
70
+ readonly project?: string;
71
+ }
72
+ export interface JobRootOpts extends JobProjectOpts {
73
+ readonly sessionId?: string;
74
+ }
75
+ export interface JobInitOpts extends JobRootOpts, JobJsonOpts {
76
+ readonly jobId: string;
77
+ readonly sliceList: string;
78
+ readonly parallelismHint?: string;
79
+ readonly exitPolicy?: string;
80
+ readonly mainLoopStrategy?: string;
81
+ readonly rotateEvery?: string;
82
+ }
83
+ export interface JobStatusOpts extends JobRootOpts, JobJsonOpts {
84
+ readonly jobId: string;
85
+ readonly watch?: boolean;
86
+ }
87
+ export interface JobIdOnlyOpts extends JobRootOpts, JobJsonOpts {
88
+ readonly jobId: string;
89
+ }
90
+ export interface JobSubagentCleanupOpts extends JobIdOnlyOpts {
91
+ readonly batchId: string;
92
+ readonly force?: boolean;
93
+ }
94
+ export interface JobCheckpointOpts extends JobIdOnlyOpts {
95
+ readonly sliceId: string;
96
+ readonly state: string;
97
+ readonly commitSha?: string;
98
+ readonly reason?: string;
99
+ }
100
+ export interface JobBlockOpts extends JobIdOnlyOpts {
101
+ readonly sliceId: string;
102
+ readonly reason: string;
103
+ }
104
+ export interface JobProgressOpts extends JobIdOnlyOpts {
105
+ readonly allowMissing?: boolean;
106
+ }
107
+ export interface JobCostCheckOpts extends JobRootOpts, JobJsonOpts {
108
+ readonly reviewFile: string;
109
+ }
@@ -0,0 +1,72 @@
1
+ import { printResult } from '../cli-helpers.js';
2
+ // `printResult`'s third parameter is `asJson: boolean`. Each `job` subcommand
3
+ // registers `--json` (see the `addJsonOption` calls below), so the flag to pass
4
+ // it is `opts.json` — NEVER the whole options object. Commander types an
5
+ // action's `opts` as `any`, so `printResult(io, envelope, opts)` type-checks
6
+ // and is ALWAYS truthy: it forces the envelope branch and makes the
7
+ // `warning: ` / `next: ` human rendering in `cli-helpers.ts` unreachable. That
8
+ // is how the A2 codegraph-refresh warning ended up inside the JSON stdout
9
+ // envelope instead of on stderr as `warning: `. `job progress` always passed
10
+ // `opts.json`; the rest of this file did not, and E1 (2026-09-17) brought them
11
+ // in line. Do not reintroduce the object form.
12
+ //
13
+ // S10 (2026-09-20): the parameter is now `JobJsonOpts` rather than `any`. The
14
+ // gate above is about which VALUE is passed, not about the parameter's type —
15
+ // declaring the one field this function reads is what stops every `opts.<x>`
16
+ // call site in the file from being an `any` access. See the option-interface
17
+ // block above `registerJobCommands`.
18
+ export function asJson(opts) {
19
+ return opts.json === true;
20
+ }
21
+ /**
22
+ * process exit non-zero.
23
+ *
24
+ * WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
25
+ * envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
26
+ * `process.exitCode`. Measured with the real CLI before this fix:
27
+ * `peaks job block --job-id j1 --slice-id nope --reason why` printed
28
+ * `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
29
+ * wrapping `peaks job` read the failure as success. Seven of this file's nine
30
+ * failure-reporting sites had that gap; only `job progress` set the code, and
31
+ * that was the file's one accidental precedent rather than a rule.
32
+ *
33
+ * Every `fail(...)` envelope in this file now goes through here, so "reported a
34
+ * failure" and "exited non-zero" cannot drift apart again. The helper takes the
35
+ * Commander `opts` object and calls `asJson(opts)` itself — it must never be
36
+ * handed a bare `opts.json`-less boolean, and it must never pass `opts` to
37
+ * `printResult` (see the `asJson` comment above for that bug's history).
38
+ *
39
+ * DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
40
+ * report a non-fatal outcome through an `ok(...)` envelope and MUST keep
41
+ * exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
42
+ * `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
43
+ * stated design is "a refresh failure must never fail the checkpoint". Those
44
+ * are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
45
+ * directions so a future blanket "every warning exits 1" edit turns red.
46
+ */
47
+ export function failResult(io, result, opts) {
48
+ printResult(io, result, asJson(opts));
49
+ process.exitCode = 1;
50
+ }
51
+ export function projectRoot(opts) {
52
+ // Reuse the workspace root resolver from peaks CLI; for now, CWD as a safe placeholder.
53
+ return opts.project ?? process.cwd();
54
+ }
55
+ /**
56
+ * D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
57
+ * takes labels, so the natural string to pass back to `--slice-id` is that same
58
+ * label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
59
+ * anything else is a hard error listing the valid ids, so a mistyped
60
+ * `slice-04` can never be accepted as a silent no-op that leaves the slice
61
+ * pending.
62
+ */
63
+ export function resolveSliceId(store, jobId, sliceId) {
64
+ const slices = store.load(jobId).slices;
65
+ const hit = slices.find((sl) => sl.sliceId === sliceId || sl.label === sliceId);
66
+ if (hit)
67
+ return { sliceId: hit.sliceId };
68
+ return {
69
+ message: `no slice "${sliceId}" in job ${jobId}; valid ids: ${slices.map((sl) => `${sl.sliceId} (${sl.label})`).join(', ')}`,
70
+ validSliceIds: slices.map((sl) => sl.sliceId)
71
+ };
72
+ }