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,295 +1,38 @@
1
1
  // src/cli/commands/job-commands.ts
2
- import { existsSync, readdirSync } from 'node:fs';
3
- import { join } from 'node:path';
2
+ //
3
+ // The `peaks job` registrar. Split into sibling modules by responsibility
4
+ // option shapes + shared helpers, session-root
5
+ // resolution, init/status, checkpoint/block, run (continue/resume/progress/
6
+ // handoff) and karpathy-cost-check. This file wires them up in the registration
7
+ // order the CLI surface dump pins.
8
+ //
9
+ // `job rotate-now` and `job subagent-cleanup` stay registered HERE: their
10
+ // actions carry the three contract-less `async` arrows pinned by
11
+ // `tests/unit/standards/gratuitous-async-guard.test.ts`, which keys its 14-site
12
+ // set on `file | declaration text`.
4
13
  import { Command } from 'commander';
5
- import { fail, ok } from 'peaks-loop-shared/result';
14
+ import { ok } from 'peaks-loop-shared/result';
6
15
  import { addJsonOption, printResult } from '../cli-helpers.js';
7
- import { isUnsafePathInput } from '../../shared/path-safety.js';
8
16
  import { JobStateStore } from '../../services/job/job-state-store.js';
9
- import { JobOrchestrator } from '../../services/job/job-orchestrator.js';
10
- import { writeJobProgress, readJobProgress, tryReadJobProgress, describeNextSlice } from '../../services/job/job-progress-store.js';
11
17
  import { JobRotation } from '../../services/job/job-rotation.js';
12
18
  import { SubAgentJobWrapper } from '../../services/job/subagent-job-wrapper.js';
13
- import { emitJobEvent } from '../../services/job/job-event-emitter.js';
14
- import { JobInitInputSchema, JobCheckpointInputSchema, JobBlockInputSchema } from '../../services/job/job-types.js';
15
- import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
16
- import { buildCostCheckEnvelope, runKarpathyCostCheck } from '../../services/karpathy-cost/karpathy-cost-check-service.js';
17
- import { read24hState } from '../../services/24h-mode/store.js';
18
- import { codegraphRefreshNotice, refreshCodegraphAfterSlice } from '../../services/codegraph/codegraph-autorefresh.js';
19
- // `printResult`'s third parameter is `asJson: boolean`. Each `job` subcommand
20
- // registers `--json` (see the `addJsonOption` calls below), so the flag to pass
21
- // it is `opts.json` — NEVER the whole options object. Commander types an
22
- // action's `opts` as `any`, so `printResult(io, envelope, opts)` type-checks
23
- // and is ALWAYS truthy: it forces the envelope branch and makes the
24
- // `warning: ` / `next: ` human rendering in `cli-helpers.ts` unreachable. That
25
- // is how the A2 codegraph-refresh warning ended up inside the JSON stdout
26
- // envelope instead of on stderr as `warning: `. `job progress` always passed
27
- // `opts.json`; the rest of this file did not, and E1 (2026-09-17) brought them
28
- // in line. Do not reintroduce the object form.
29
- //
30
- // S10 (2026-09-20): the parameter is now `JobJsonOpts` rather than `any`. The
31
- // gate above is about which VALUE is passed, not about the parameter's type —
32
- // declaring the one field this function reads is what stops every `opts.<x>`
33
- // call site in the file from being an `any` access. See the option-interface
34
- // block above `registerJobCommands`.
35
- export function asJson(opts) {
36
- return opts.json === true;
37
- }
38
- /**
39
- * process exit non-zero.
40
- *
41
- * WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
42
- * envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
43
- * `process.exitCode`. Measured with the real CLI before this fix:
44
- * `peaks job block --job-id j1 --slice-id nope --reason why` printed
45
- * `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
46
- * wrapping `peaks job` read the failure as success. Seven of this file's nine
47
- * failure-reporting sites had that gap; only `job progress` set the code, and
48
- * that was the file's one accidental precedent rather than a rule.
49
- *
50
- * Every `fail(...)` envelope in this file now goes through here, so "reported a
51
- * failure" and "exited non-zero" cannot drift apart again. The helper takes the
52
- * Commander `opts` object and calls `asJson(opts)` itself — it must never be
53
- * handed a bare `opts.json`-less boolean, and it must never pass `opts` to
54
- * `printResult` (see the `asJson` comment above for that bug's history).
55
- *
56
- * DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
57
- * report a non-fatal outcome through an `ok(...)` envelope and MUST keep
58
- * exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
59
- * `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
60
- * stated design is "a refresh failure must never fail the checkpoint". Those
61
- * are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
62
- * directions so a future blanket "every warning exits 1" edit turns red.
63
- */
64
- function failResult(io, result, opts) {
65
- printResult(io, result, asJson(opts));
66
- process.exitCode = 1;
67
- }
68
- function projectRoot(opts) {
69
- // Reuse the workspace root resolver from peaks CLI; for now, CWD as a safe placeholder.
70
- return opts.project ?? process.cwd();
71
- }
72
- /**
73
- * D6: every job subcommand resolves a job root, so every one of them must be
74
- * able to name the session that holds the job. Same precedence as the rest of
75
- * the CLI (`peaks sub-agent dispatch`, `peaks web *`, `peaks share *`), except
76
- * that job state has no "unknown-sid" location to land in — an unresolvable
77
- * session is an error, not a silent fallback.
78
- */
79
- export const SESSION_ID_HELP = 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback: NO_ACTIVE_SESSION error)';
80
- /**
81
- * The session (a direct child of `<project>/.peaks/_runtime/`) that holds
82
- * `jobId`, or null. Only used to explain a miss: a job that lives in another
83
- * session must be reported by name so the caller can re-run with --session-id.
84
- */
85
- function findSessionHoldingJob(project, jobId) {
86
- const runtimeDir = join(project, '.peaks', '_runtime');
87
- if (!existsSync(runtimeDir))
88
- return null;
89
- for (const entry of readdirSync(runtimeDir, { withFileTypes: true })) {
90
- if (entry.isDirectory() &&
91
- existsSync(join(runtimeDir, entry.name, 'job', jobId, 'state.json'))) {
92
- return entry.name;
93
- }
94
- }
95
- return null;
96
- }
97
- /**
98
- * Resolves the on-disk root for Job state files.
99
- *
100
- * Per spec §3.3 + §4.5 (2.7.1 single-scope-axis layout), Job state lives at:
101
- * `<projectRoot>/.peaks/_runtime/<sessionId>/job/<jobId>/state.json`
102
- *
103
- * The `JobStateStore` itself only knows its `rootDir` + `jobId` and joins them. We
104
- * compute the canonical root here (per-call) so the store can stay layout-agnostic.
105
- *
106
- * Resolution order (D6 — a job must stay addressable while the single per-project
107
- * `.peaks/_runtime/session.json` binding points at another session):
108
- * 1. `--session-id` flag (explicit override)
109
- * 2. `PEAKS_SESSION_ID` env var
110
- * 3. `getCurrentSessionId(project)` — this caller's session binding, falling back
111
- * to `.peaks/_runtime/session.json` when no caller binding is resolvable
112
- * 4. Error (NO_ACTIVE_SESSION) — must never silently fall back to a random uuid
113
- *
114
- * When `jobId` is passed and it is absent from the resolved session, the thrown
115
- * error names the session that does hold it (if any), instead of leaving the
116
- * caller with a bare "no state for <job> at <other-sid>" path.
117
- */
118
- export function resolveJobStateRoot(opts, jobId) {
119
- const project = projectRoot(opts);
120
- const sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
121
- if (!sessionId) {
122
- throw new Error('NO_ACTIVE_SESSION: peaks job requires --session-id or an active peaks-code session via peaks workspace init');
123
- }
124
- // Sid axis — the sibling of the jobId guard in `JobStateStore.jobDir`. A
125
- // caller-supplied `--session-id` reaches this join unmodified, so a
126
- // traversal value lands `job/<id>/state.json` outside every project root
127
- // while the envelope still reads `ok: true`.
128
- if (isUnsafePathInput(sessionId)) {
129
- throw new Error(`Invalid session id: ${sessionId} (must be a single path segment)`);
130
- }
131
- const rootDir = join(project, '.peaks', '_runtime', sessionId, 'job');
132
- if (jobId && !existsSync(join(rootDir, jobId, 'state.json'))) {
133
- const other = findSessionHoldingJob(project, jobId);
134
- throw new Error(other
135
- ? `JOB_NOT_IN_SESSION: no job "${jobId}" in session "${sessionId}"; it lives in session "${other}" — re-run with --session-id ${other}`
136
- : `JOB_NOT_IN_SESSION: no job "${jobId}" in session "${sessionId}" (and no other session under .peaks/_runtime/ has it)`);
137
- }
138
- return { rootDir, sessionId, projectRoot: project };
139
- }
140
- /**
141
- * D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
142
- * takes labels, so the natural string to pass back to `--slice-id` is that same
143
- * label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
144
- * anything else is a hard error listing the valid ids, so a mistyped
145
- * `slice-04` can never be accepted as a silent no-op that leaves the slice
146
- * pending.
147
- */
148
- function resolveSliceId(store, jobId, sliceId) {
149
- const slices = store.load(jobId).slices;
150
- const hit = slices.find((sl) => sl.sliceId === sliceId || sl.label === sliceId);
151
- if (hit)
152
- return { sliceId: hit.sliceId };
153
- return {
154
- message: `no slice "${sliceId}" in job ${jobId}; valid ids: ${slices.map((sl) => `${sl.sliceId} (${sl.label})`).join(', ')}`,
155
- validSliceIds: slices.map((sl) => sl.sliceId)
156
- };
157
- }
158
- export function registerJobCommands(program, io = {
159
- stdout: (t) => process.stdout.write(t),
160
- stderr: (t) => process.stderr.write(t)
161
- }) {
162
- const job = new Command('job').description('Drive long multi-slice work as one Job (peaks-code Step 0.8+)');
163
- job
164
- .command('init')
165
- .requiredOption('--job-id <jid>')
166
- .requiredOption('--slice-list <list>')
167
- .option('--parallelism-hint <serial|llm-decides>', 'llm-decides')
168
- .option('--exit-policy <strict|best-effort>', 'strict')
169
- .option('--main-loop-strategy <single|rotating>', 'rotating')
170
- .option('--rotate-every <n>', 'rotate every N slices (rotating mode)', '3')
171
- .option('--session-id <sid>', SESSION_ID_HELP)
172
- .option('--project <repo>')
173
- .action((opts) => {
174
- const project = projectRoot(opts);
175
- // Resolve sessionId: explicit flag > PEAKS_SESSION_ID > caller-first session binding
176
- // (this caller's binding, else the project-global session.json) > FAIL.
177
- // Per spec §3.3, Job state lives at .peaks/_runtime/<sessionId>/job/<jobId>/state.json —
178
- // a random UUID would scatter state across dirs and break resume/auto-compact.
179
- let sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
180
- if (!sessionId) {
181
- return failResult(io, fail('init', 'NO_ACTIVE_SESSION', 'peaks job init requires --session-id (or an active peaks-code session via peaks workspace init)', { project }, [
182
- 'Re-run with --session-id <sid>',
183
- 'Or run `peaks workspace init` to create a session first'
184
- ]), opts);
185
- }
186
- const parsed = JobInitInputSchema.safeParse({
187
- jobId: opts.jobId,
188
- sessionId,
189
- sliceList: opts.sliceList
190
- .split(',')
191
- .map((s) => s.trim())
192
- .filter(Boolean),
193
- parallelismHint: opts.parallelismHint,
194
- exitPolicy: opts.exitPolicy,
195
- mainLoopStrategy: opts.mainLoopStrategy,
196
- rotateEvery: Number(opts.rotateEvery),
197
- project,
198
- json: opts.json
199
- });
200
- if (!parsed.success)
201
- return failResult(io, fail('init', 'INVALID_INIT', parsed.error.message, {}), opts);
202
- const jobRoot = resolveJobStateRoot(opts);
203
- const store = new JobStateStore(jobRoot.rootDir);
204
- const orch = new JobOrchestrator(store);
205
- const state = orch.init({
206
- jobId: parsed.data.jobId,
207
- sessionId: parsed.data.sessionId,
208
- sliceList: parsed.data.sliceList,
209
- parallelismHint: parsed.data.parallelismHint,
210
- exitPolicy: parsed.data.exitPolicy,
211
- mainLoopStrategy: parsed.data.mainLoopStrategy,
212
- rotateEvery: parsed.data.rotateEvery
213
- });
214
- try {
215
- emitJobEvent({
216
- kind: 'job-started',
217
- jobId: state.jobId,
218
- total: state.slices.length,
219
- strategy: state.mainLoopStrategy
220
- });
221
- }
222
- catch (e) {
223
- // ADVISORY (F1, 2026-09-17) — deliberately exits 0. Event emission is a
224
- // telemetry side effect; a failed emit does not mean `job init` failed
225
- // (the state file was already written above). This catch must NOT be
226
- // routed through `failResult`. Pinned by the "advisory stays 0" control
227
- // in tests/unit/cli/job-exit-code.test.ts.
228
- void e;
229
- }
230
- printResult(io, ok('init', {
231
- jobId: state.jobId,
232
- sliceCount: state.slices.length,
233
- statePath: `${jobRoot.rootDir}/${state.jobId}/state.json`
234
- }), asJson(opts));
235
- });
236
- addJsonOption(job.commands.find((c) => c.name() === 'init'));
237
- job
238
- .command('status')
239
- .requiredOption('--job-id <jid>')
240
- .option('--watch', 'poll every 3s')
241
- .option('--show-cost', 'overlay cost from peaks budget')
242
- .option('--session-id <sid>', SESSION_ID_HELP)
243
- .option('--project <repo>')
244
- .action((opts) => {
245
- const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
246
- const orch = new JobOrchestrator(store);
247
- const s = orch.status(opts.jobId);
248
- if (opts.watch) {
249
- const draw = () => {
250
- const bar = `[${'='.repeat(s.done)}${' '.repeat(s.total - s.done)}]`;
251
- process.stdout.write(`\rjob ${opts.jobId}: ${bar} ${s.done}/${s.total}${s.currentSlice ? ` next=${s.currentSlice}` : ''} `);
252
- };
253
- draw();
254
- const iv = setInterval(() => {
255
- const u = orch.status(opts.jobId);
256
- Object.assign(s, u);
257
- draw();
258
- if (u.done + u.failed + u.skipped + u.blocked >= u.total)
259
- clearInterval(iv);
260
- }, 3000);
261
- process.on('SIGINT', () => {
262
- clearInterval(iv);
263
- process.stdout.write('\n');
264
- process.exit(0);
265
- });
266
- return;
267
- }
268
- try {
269
- emitJobEvent({
270
- kind: 'job-progress',
271
- jobId: opts.jobId,
272
- done: s.done,
273
- total: s.total,
274
- ...(s.currentSlice ? { currentSlice: s.currentSlice } : {})
275
- });
276
- }
277
- catch (e) {
278
- // ADVISORY (F1, 2026-09-17) — deliberately exits 0. The status itself
279
- // was already read successfully; the emit is telemetry. Same rule as
280
- // the `job init` catch above: do NOT route through `failResult`.
281
- void e;
282
- }
283
- printResult(io, ok('status', s), asJson(opts));
284
- });
285
- addJsonOption(job.commands.find((c) => c.name() === 'status'));
286
- // M4.2: wire rotate-now to JobRotation (session-rotate callbacks are stubs pending M6.5 batch-fix).
287
- job
19
+ import { asJson } from './job-command-shared.js';
20
+ import { SESSION_ID_HELP, resolveJobStateRoot } from './job-state-root.js';
21
+ import { registerJobInitStatusCommands } from './job-init-status-commands.js';
22
+ import { registerJobCheckpointCommands } from './job-checkpoint-commands.js';
23
+ import { registerJobRunCommands } from './job-run-commands.js';
24
+ import { registerJobCostCheckCommand } from './job-cost-check-command.js';
25
+ // Published import path preserved: `job-add-slice-command.ts` imports these
26
+ // from `./job-commands.js`.
27
+ export { asJson, SESSION_ID_HELP, resolveJobStateRoot };
28
+ // M4.2: wire rotate-now to JobRotation (session-rotate callbacks are stubs
29
+ // pending M6.5 batch-fix).
30
+ function registerJobRotateNowCommand(job, io) {
31
+ addJsonOption(job
288
32
  .command('rotate-now')
289
33
  .requiredOption('--job-id <jid>')
290
34
  .option('--session-id <sid>', SESSION_ID_HELP)
291
- .option('--project <repo>')
292
- .action(async (opts) => {
35
+ .option('--project <repo>')).action(async (opts) => {
293
36
  const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
294
37
  const rotation = new JobRotation(store, async (_jid) => {
295
38
  /* delegate to peaks session rotate — implementation wired in M6.5 batch-fix */ return {
@@ -299,15 +42,15 @@ export function registerJobCommands(program, io = {
299
42
  const r = await rotation.rotateNow(opts.jobId);
300
43
  printResult(io, ok('rotate-now', r), asJson(opts));
301
44
  });
302
- addJsonOption(job.commands.find((c) => c.name() === 'rotate-now'));
303
- job
45
+ }
46
+ function registerJobSubagentCleanupCommand(job, io) {
47
+ addJsonOption(job
304
48
  .command('subagent-cleanup')
305
49
  .requiredOption('--job-id <jid>')
306
50
  .requiredOption('--batch-id <bid>')
307
51
  .option('--force')
308
52
  .option('--session-id <sid>', SESSION_ID_HELP)
309
- .option('--project <repo>')
310
- .action(async (opts) => {
53
+ .option('--project <repo>')).action(async (opts) => {
311
54
  const wrapper = new SubAgentJobWrapper(new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir), async () => ({ batchId: opts.batchId }));
312
55
  const r = await wrapper.cleanup({
313
56
  jobId: opts.jobId,
@@ -316,270 +59,17 @@ export function registerJobCommands(program, io = {
316
59
  });
317
60
  printResult(io, ok('subagent-cleanup', r), asJson(opts));
318
61
  });
319
- addJsonOption(job.commands.find((c) => c.name() === 'subagent-cleanup'));
320
- // M3.2: wire the remaining 5 subcommand slots — block, checkpoint, continue, handoff, resume.
321
- job
322
- .command('checkpoint')
323
- .requiredOption('--job-id <jid>')
324
- .requiredOption('--slice-id <rid>')
325
- .requiredOption('--state <done|failed|skipped>')
326
- .option('--commit-sha <sha>')
327
- .option('--reason <text>')
328
- .option('--session-id <sid>', SESSION_ID_HELP)
329
- .option('--project <repo>')
330
- .action(async (opts) => {
331
- const parsed = JobCheckpointInputSchema.safeParse({
332
- jobId: opts.jobId,
333
- sliceId: opts.sliceId,
334
- state: opts.state,
335
- commitSha: opts.commitSha,
336
- reason: opts.reason,
337
- project: projectRoot(opts),
338
- json: opts.json
339
- });
340
- if (!parsed.success)
341
- return failResult(io, fail('checkpoint', 'INVALID_CHECKPOINT', parsed.error.message, {}), opts);
342
- const jobRoot = resolveJobStateRoot(opts, opts.jobId);
343
- const store = new JobStateStore(jobRoot.rootDir);
344
- // D7: `--slice-id` accepts the canonical `slice-NNN` or the slice's label
345
- // ("S1"); an id that matches no slice is rejected here, BEFORE any write,
346
- // so progress.json is never touched by a checkpoint that matched nothing.
347
- const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
348
- if ('message' in slice) {
349
- return failResult(io, fail('checkpoint', 'SLICE_NOT_FOUND', slice.message, {
350
- jobId: parsed.data.jobId,
351
- sliceId: parsed.data.sliceId,
352
- validSliceIds: slice.validSliceIds
353
- }, ['Re-run with one of the valid slice ids']), opts);
354
- }
355
- const sliceId = slice.sliceId;
356
- const orch = new JobOrchestrator(store);
357
- // envelope carries a non-blocking `codegraph` result; null for
358
- // failed/skipped (no slice-complete boundary).
359
- let codegraph = null;
360
- // A2 (2026-09-17): the human-visible half of the refresh outcome. null
361
- // when the refresh succeeded or when no codegraph store was in use.
362
- let codegraphWarning = null;
363
- if (parsed.data.state === 'done') {
364
- await orch.checkpointDone({
365
- jobId: parsed.data.jobId,
366
- sliceId,
367
- ...(parsed.data.commitSha ? { commitSha: parsed.data.commitSha } : {})
368
- });
369
- // v3.1.2: after each --state done, mirror slice progress to
370
- // .peaks/_runtime/<sessionId>/job/<jid>/progress.json so the
371
- // next LLM turn (or peaks code gate-step-08 hook) can read it.
372
- const project = projectRoot(opts);
373
- const sessId = jobRoot.sessionId;
374
- const state = orch.status(parsed.data.jobId);
375
- writeJobProgress(project, sessId, {
376
- jobId: parsed.data.jobId,
377
- done: state.done,
378
- total: state.total,
379
- currentSlice: state.currentSlice,
380
- lastCommitSha: parsed.data.commitSha ?? null,
381
- updatedAt: new Date().toISOString()
382
- });
383
- // Auto codegraph refresh at the slice-complete boundary. Best-effort:
384
- // a refresh failure must never fail the checkpoint.
385
- //
386
- // A2 (2026-09-17): "must never fail the checkpoint" is not "must
387
- // never be seen". A refresh that did not happen while a codegraph
388
- // store IS in use is now a warning line (stderr, `warning: ` prefix,
389
- // via printResult) naming the reason and the remedy. See
390
- // `codegraphRefreshNotice` for why `no-codegraph-dir` stays silent.
391
- //
392
- // ADVISORY (F1, 2026-09-17) — deliberately exits 0, and the reach of
393
- // that word is exactly here. The checkpoint itself SUCCEEDED (the slice
394
- // flipped to done and progress.json was mirrored above); the refresh is
395
- // a derived index, rebuildable on demand. So the outcome is reported
396
- // through `ok(...)` + a `warning:` line, never through `failResult`,
397
- // and `process.exitCode` is left alone even when the refresh throws.
398
- // Raising it would turn an advisory rebuild into a build breaker for
399
- // every CI that runs `peaks job checkpoint`. Pinned by the "advisory
400
- // stays 0" control in tests/unit/cli/job-exit-code.test.ts.
401
- try {
402
- codegraph = await refreshCodegraphAfterSlice(project);
403
- }
404
- catch (e) {
405
- codegraph = {
406
- refreshed: false,
407
- reason: 'unavailable',
408
- note: `auto codegraph refresh failed: ${e instanceof Error ? e.message : String(e)}`
409
- };
410
- }
411
- codegraphWarning = codegraphRefreshNotice(codegraph);
412
- }
413
- else if (parsed.data.state === 'skipped') {
414
- await orch.checkpointSkipped({
415
- jobId: parsed.data.jobId,
416
- sliceId,
417
- reason: parsed.data.reason
418
- });
419
- }
420
- else {
421
- await orch.checkpointFailed({
422
- jobId: parsed.data.jobId,
423
- sliceId,
424
- reason: parsed.data.reason
425
- });
426
- }
427
- printResult(io, ok('checkpoint', { sliceId, status: parsed.data.state, codegraph }, codegraphWarning === null ? [] : [codegraphWarning]), asJson(opts));
428
- });
429
- addJsonOption(job.commands.find((c) => c.name() === 'checkpoint'));
430
- job
431
- .command('block')
432
- .requiredOption('--job-id <jid>')
433
- .requiredOption('--slice-id <rid>')
434
- .requiredOption('--reason <text>')
435
- .option('--session-id <sid>', SESSION_ID_HELP)
436
- .option('--project <repo>')
437
- .action(async (opts) => {
438
- const parsed = JobBlockInputSchema.safeParse({
439
- jobId: opts.jobId,
440
- sliceId: opts.sliceId,
441
- reason: opts.reason,
442
- project: projectRoot(opts),
443
- json: opts.json
444
- });
445
- if (!parsed.success)
446
- return failResult(io, fail('block', 'INVALID_BLOCK', parsed.error.message, {}), opts);
447
- const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
448
- // D7 (same silent no-op as checkpoint): resolve label → sliceId, reject a
449
- // miss before any write.
450
- const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
451
- if ('message' in slice) {
452
- return failResult(io, fail('block', 'SLICE_NOT_FOUND', slice.message, {
453
- jobId: parsed.data.jobId,
454
- sliceId: parsed.data.sliceId,
455
- validSliceIds: slice.validSliceIds
456
- }, ['Re-run with one of the valid slice ids']), opts);
457
- }
458
- const orch = new JobOrchestrator(store);
459
- await orch.blockSlice({ ...parsed.data, sliceId: slice.sliceId });
460
- printResult(io, ok('block', { blocked: slice.sliceId, reason: parsed.data.reason }), asJson(opts));
461
- });
462
- addJsonOption(job.commands.find((c) => c.name() === 'block'));
463
- job
464
- .command('continue')
465
- .requiredOption('--job-id <jid>')
466
- .option('--session-id <sid>', SESSION_ID_HELP)
467
- .option('--project <repo>')
468
- .action((opts) => {
469
- const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
470
- const orch = new JobOrchestrator(store);
471
- const r = orch.continueNow(opts.jobId);
472
- printResult(io, ok('continue', r), asJson(opts));
473
- });
474
- addJsonOption(job.commands.find((c) => c.name() === 'continue'));
475
- job
476
- .command('resume')
477
- .requiredOption('--job-id <jid>')
478
- .option('--session-id <sid>', SESSION_ID_HELP)
479
- .option('--project <repo>')
480
- .action((opts) => {
481
- const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
482
- const orch = new JobOrchestrator(store);
483
- const s = orch.status(opts.jobId);
484
- printResult(io, ok('resume', { resumed: opts.jobId, ...s }), asJson(opts));
485
- });
486
- addJsonOption(job.commands.find((c) => c.name() === 'resume'));
487
- // v3.1.2: read the on-disk slice progress mirror written by `peaks job
488
- // checkpoint --state done`; gate-step-08 and Step 0.7 (resume) read it too. The
489
- // `next:` sentence comes from describeNextSlice, never re-derived from state.json.
490
- job
491
- .command('progress')
492
- .description('v3.1.2: read the on-disk slice progress mirror (.peaks/_runtime/<sid>/job/<jid>/progress.json). ' +
493
- 'Returns { jobId, done, total, currentSlice, lastCommitSha, updatedAt }.')
494
- .requiredOption('--job-id <jid>')
495
- .option('--session-id <sid>', SESSION_ID_HELP)
496
- .option('--project <repo>')
497
- // "return done=0/total=0 envelope instead of failing when progress.json is
498
- // absent". No code path has ever returned such an envelope — the option was
499
- // born in `d9a1a098` with `process.exitCode = 1` on the same branch — so it
500
- // described a contract that never existed, and `--allow-missing` appeared to
501
- // be broken. It is not: its job is to pick WHICH structured code reports the
502
- // absence. The help text was the side that was wrong; it now says what the
503
- // flag does. See `rd/requests/013-…-exit-code-root-cause.md` for why the
504
- // alternative reading was rejected (the literal promise is also
505
- // unimplementable without a new absent-vs-corrupt distinction:
506
- // `tryReadJobProgress` returns `null` for a corrupt file too, so "done=0"
507
- // there would report unreadable state as empty state).
508
- .option('--allow-missing', 'report an absent progress.json as NO_PROGRESS (expected absence) rather than PROGRESS_READ_FAILED (read error); the command still exits non-zero')
509
- .action((opts) => {
510
- try {
511
- const jobRoot = resolveJobStateRoot(opts, opts.jobId);
512
- const sessId = jobRoot.sessionId;
513
- const project = projectRoot(opts);
514
- const progress = opts.allowMissing === true
515
- ? tryReadJobProgress(project, sessId, opts.jobId)
516
- : readJobProgress(project, sessId, opts.jobId);
517
- if (progress === null) {
518
- // F1: this site already set `process.exitCode = 1` by hand; it is
519
- // routed through `failResult` so all nine failure sites in this file
520
- // share one rule instead of this one being the lone precedent.
521
- failResult(io, fail('progress', 'NO_PROGRESS', `No progress.json for job ${opts.jobId} at .peaks/_runtime/${sessId}/job/${opts.jobId}/progress.json`, { jobId: opts.jobId, sessionId: sessId }, [
522
- 'Run `peaks job checkpoint --state done ...` at least once to seed progress.json.',
523
- // return a zero-progress envelope." That line could only ever be
524
- // printed when `--allow-missing` had ALREADY been passed — this
525
- // `progress === null` branch is unreachable otherwise, because the
526
- // no-flag path throws into the catch below and reports
527
- // PROGRESS_READ_FAILED. So the CLI was advising the caller to pass
528
- // the flag they had just passed, to obtain an envelope that does
529
- // not exist. Replaced with the fact the caller actually needs.
530
- '--allow-missing selects this NO_PROGRESS envelope over a PROGRESS_READ_FAILED read error; it does not change the exit code.'
531
- ]), opts);
532
- return;
533
- }
534
- printResult(io, ok('progress', progress, [], [`Next: ${describeNextSlice(progress, progress.jobId)}`]), asJson(opts));
535
- }
536
- catch (err) {
537
- failResult(io, fail('progress', 'PROGRESS_READ_FAILED', err instanceof Error ? err.message : String(err), { jobId: opts.jobId }, ['Verify the job id and try again']), opts);
538
- }
539
- });
540
- addJsonOption(job.commands.find((c) => c.name() === 'progress'));
541
- job
542
- .command('handoff')
543
- .requiredOption('--job-id <jid>')
544
- .option('--session-id <sid>', SESSION_ID_HELP)
545
- .option('--project <repo>')
546
- .action((opts) => {
547
- const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
548
- const orch = new JobOrchestrator(store);
549
- const s = orch.status(opts.jobId);
550
- printResult(io, ok('handoff', { handoffFor: opts.jobId, ...s }), asJson(opts));
551
- });
552
- addJsonOption(job.commands.find((c) => c.name() === 'handoff'));
553
- job
554
- .command('karpathy-cost-check')
555
- .description("Read the slice's rd/karpathy-review.md and decide whether to downgrade a block gateAction to warn (slice 2026-07-30-karpathy-cost-self-review).")
556
- .requiredOption('--review-file <path>', 'path to rd/karpathy-review.md (or its .json sibling if the file is JSON)')
557
- .option('--project <repo>')
558
- .option('--session-id <sid>', SESSION_ID_HELP)
559
- .action((opts) => {
560
- const project = projectRoot(opts);
561
- const sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
562
- if (!sessionId) {
563
- return failResult(io, fail('karpathy-cost-check', 'NO_ACTIVE_SESSION', 'karpathy-cost-check requires --session-id (or an active peaks-code session)', { project }, [
564
- 'Re-run with --session-id <sid>',
565
- 'Or run `peaks workspace init` to create a session first'
566
- ]), opts);
567
- }
568
- const is24hModeActive = () => {
569
- try {
570
- const snapshot = read24hState(project, sessionId);
571
- return snapshot.state === '24H_ACTIVE';
572
- }
573
- catch {
574
- return false;
575
- }
576
- };
577
- const out = runKarpathyCostCheck({
578
- reviewFilePath: opts.reviewFile,
579
- is24hModeActive
580
- });
581
- printResult(io, buildCostCheckEnvelope(out), asJson(opts));
582
- });
583
- addJsonOption(job.commands.find((c) => c.name() === 'karpathy-cost-check'));
62
+ }
63
+ export function registerJobCommands(program, io = {
64
+ stdout: (t) => process.stdout.write(t),
65
+ stderr: (t) => process.stderr.write(t)
66
+ }) {
67
+ const job = new Command('job').description('Drive long multi-slice work as one Job (peaks-code Step 0.8+)');
68
+ registerJobInitStatusCommands(job, io);
69
+ registerJobRotateNowCommand(job, io);
70
+ registerJobSubagentCleanupCommand(job, io);
71
+ registerJobCheckpointCommands(job, io);
72
+ registerJobRunCommands(job, io);
73
+ registerJobCostCheckCommand(job, io);
584
74
  program.addCommand(job);
585
75
  }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerJobCostCheckCommand(job: Command, io: ProgramIO): void;