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,628 +1,14 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { isUnsafePathInput } from '../../shared/path-safety.js';
4
- import { resolveWorkflow, planWorkflow, planWorkflowRun } from '../../services/workflow/workflow-loader.js';
5
- import { dispatchEvaluator } from '../../services/loop/evaluator-dispatcher.js';
6
- import { DEFAULT_MONOTONIC_THRESHOLD } from '../../services/loop/monotonic-guard.js';
7
- import { runMonotonicCheck, resolveMonotonicContext } from '../../services/loop/monotonic-runner.js';
8
- import { resolveLoopSpec, persistSpec, buildSpec, lintLoopSpec, lintSpecFile, specPath, MONOTONIC_TERMINATION, DEFAULT_MAX_CYCLES } from '../../services/loop/spec-service.js';
9
- import { runLoop, resolveRunContext } from '../../services/loop/run-driver.js';
10
- import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
11
- import { fail, ok } from 'peaks-loop-shared/result';
12
- import { findProjectRoot } from '../../services/config/config-safety.js';
13
- const VALID_EVALUATORS = new Set([
14
- 'karpathy',
15
- 'code-review',
16
- 'security-review',
17
- 'perf-baseline',
18
- 'verdict-aggregate',
19
- 'monotonic-improvement',
20
- 'impact-scan',
21
- 'smoke-run',
22
- 'canary-watch'
23
- ]);
1
+ import { registerWorkflowEvalWorkflowCommands } from './loop-eval-workflow-commands.js';
2
+ import { registerLoopEvalCommand } from './loop-eval-eval-command.js';
3
+ import { registerLoopCheckMonotonicCommand } from './loop-eval-monotonic-command.js';
4
+ import { registerLoopSpecCommands } from './loop-eval-spec-commands.js';
5
+ import { registerLoopSpecLintCommand } from './loop-eval-spec-lint-command.js';
6
+ import { registerLoopRunCommand } from './loop-eval-run-command.js';
24
7
  export function registerWorkflowEvalCommands(program, io) {
25
- // Reuse the existing `workflow` and `loop` parents created by
26
- // `registerWorkflowCommands` / `registerLoopCommands` (program-level
27
- // command conflicts would otherwise throw per Commander's
28
- // add-a-new-subcommand-check-for-existing-top-level-first rule).
29
- const existingWorkflow = program.commands.find((c) => c.name() === 'workflow');
30
- const existingLoop = program.commands.find((c) => c.name() === 'loop');
31
- const workflow = existingWorkflow ??
32
- program.command('workflow').description('Workflow primitive (run / plan / lint)');
33
- const loop = existingLoop ?? program.command('loop').description('Loop primitive (eval)');
34
- // peaks workflow run <id>
35
- addJsonOption(workflow
36
- .command('run')
37
- .description('Slice A.3: replay a captured workflow (.peaks/workflows/<id>.yaml) deterministically. Returns the run-plan order + per-phase status without re-deriving the phase plan.')
38
- .argument('<id>', 'workflow id (matches .peaks/workflows/<id>.yaml)')
39
- .requiredOption('--session <sid>', 'session id (from peaks workspace init)')
40
- .option('--project <path>', 'project root (default: cwd)')).action((id, options) => {
41
- try {
42
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
43
- const resolved = resolveWorkflow(projectRoot, id);
44
- if (resolved.source.kind === 'missing') {
45
- printResult(io, fail('workflow.run', 'WORKFLOW_NOT_FOUND', `workflow "${id}" not found`, { sessionId: options.session }, [`peaks workflow lint ${id} --session ${options.session} --json`]), options.json);
46
- process.exitCode = 1;
47
- return;
48
- }
49
- if (!resolved.lint.ok) {
50
- printResult(io, fail('workflow.run', 'WORKFLOW_LINT_FAILED', `workflow "${id}" has ${resolved.lint.errors.length} lint error(s): ${resolved.lint.errors.join('; ')}`, resolved.lint, [`peaks workflow lint ${id} --session ${options.session} --json`]), options.json);
51
- process.exitCode = 1;
52
- return;
53
- }
54
- const runPlan = planWorkflowRun(resolved.spec);
55
- printResult(io, ok('workflow.run', {
56
- sessionId: options.session,
57
- workflow: { id: resolved.spec.id, label: resolved.spec.label, source: resolved.source },
58
- runPlan,
59
- lintWarnings: resolved.lint.warnings
60
- }, [], [
61
- `Run \`peaks workflow plan ${id} --session ${options.session} --json\` to preview the graph.`
62
- ]), options.json);
63
- }
64
- catch (error) {
65
- printResult(io, fail('workflow.run', 'WORKFLOW_RUN_FAILED', getErrorMessage(error), { sessionId: options.session }, ['Verify the workflow id and session binding.']), options.json);
66
- process.exitCode = 1;
67
- }
68
- });
69
- // peaks workflow graph <id> (dry-run graph render)
70
- // Renamed from `plan` to `graph` to avoid collision with the existing
71
- // `peaks workflow plan <read|refresh|detect-trigger>` family registered
72
- // Reuse the existing `graph` parent if `registerWorkflowLifecycleCommand`
73
- // already created one (it owns `peaks workflow graph show|list`).
74
- const existingGraph = workflow.commands.find((c) => c.name() === 'graph');
75
- const graphParent = existingGraph ??
76
- workflow
77
- .command('graph')
78
- .description('workflow graph read commands (slice A.3 dry-run + slice 4.0.8 lifecycle)');
79
- addJsonOption(graphParent
80
- .description('Slice A.3 dry-run: render the workflow graph (phases + parallel groups + evaluators + budget). No phase is executed.')
81
- .argument('<id>', 'workflow id')
82
- .requiredOption('--session <sid>', 'session id')
83
- .option('--project <path>', 'project root (default: cwd)')).action((id, options) => {
84
- try {
85
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
86
- const resolved = resolveWorkflow(projectRoot, id);
87
- if (resolved.source.kind === 'missing') {
88
- printResult(io, fail('workflow.graph', 'WORKFLOW_NOT_FOUND', `workflow "${id}" not found`, { sessionId: options.session }, [`Create .peaks/workflows/${id}.yaml or use the bundled default-fullauto-md.`]), options.json);
89
- process.exitCode = 1;
90
- return;
91
- }
92
- if (!resolved.lint.ok) {
93
- printResult(io, fail('workflow.graph', 'WORKFLOW_LINT_FAILED', `workflow "${id}" has ${resolved.lint.errors.length} lint error(s): ${resolved.lint.errors.join('; ')}`, resolved.lint, [`peaks workflow lint ${id} --session ${options.session} --json`]), options.json);
94
- process.exitCode = 1;
95
- return;
96
- }
97
- const graph = planWorkflow(resolved.spec, resolved.source);
98
- printResult(io, ok('workflow.graph', {
99
- sessionId: options.session,
100
- graph,
101
- lintWarnings: resolved.lint.warnings
102
- }, [], [
103
- `Run \`peaks workflow run ${id} --session ${options.session} --json\` to materialize the run-plan order.`
104
- ]), options.json);
105
- }
106
- catch (error) {
107
- printResult(io, fail('workflow.graph', 'WORKFLOW_GRAPH_FAILED', getErrorMessage(error), { sessionId: options.session }, ['Verify the workflow id and session binding.']), options.json);
108
- process.exitCode = 1;
109
- }
110
- });
111
- // peaks workflow lint <id>
112
- addJsonOption(workflow
113
- .command('lint')
114
- .description('Slice A.3: validate a workflow spec (phases / gates / evaluators / parallel groups / budget).')
115
- .argument('<id>', 'workflow id')
116
- .requiredOption('--session <sid>', 'session id')
117
- .option('--project <path>', 'project root (default: cwd)')).action((id, options) => {
118
- try {
119
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
120
- const resolved = resolveWorkflow(projectRoot, id);
121
- if (resolved.source.kind === 'missing') {
122
- printResult(io, fail('workflow.lint', 'WORKFLOW_NOT_FOUND', `workflow "${id}" not found`, { sessionId: options.session }, [`Create .peaks/workflows/${id}.yaml`]), options.json);
123
- process.exitCode = 1;
124
- return;
125
- }
126
- printResult(io, ok('workflow.lint', {
127
- sessionId: options.session,
128
- source: resolved.source,
129
- lint: resolved.lint
130
- }, [], []), options.json);
131
- if (!resolved.lint.ok)
132
- process.exitCode = 1;
133
- }
134
- catch (error) {
135
- printResult(io, fail('workflow.lint', 'WORKFLOW_LINT_FAILED', getErrorMessage(error), { sessionId: options.session }, ['Verify the workflow file syntax.']), options.json);
136
- process.exitCode = 1;
137
- }
138
- });
139
- // peaks loop eval <rid> --evaluator <name>
140
- addJsonOption(loop
141
- .command('eval')
142
- .description('Slice B.2: invoke a native evaluator directly. The runtime calls the evaluator (no LLM scheduling) and returns a verdict envelope compatible with peaks verdict aggregate. Pass --capture-score to also persist the score row to .peaks/_runtime/<sid>/loop/<rid>/cycles/cycle-N.json (P0 closure: this is the score the run-driver reads back).')
143
- .argument('<rid>', 'request id (e.g. 2026-06-30-...)')
144
- .requiredOption('--evaluator <name>', `evaluator: ${[...VALID_EVALUATORS].join(', ')}`)
145
- .option('--session <sid>', 'session id (required by --evaluator monotonic-improvement; also required by --capture-score)')
146
- .option('--project <path>', 'project root (default: cwd)')
147
- .option('--scope <scope>', 'optional scope expression (forwarded to the evaluator)')
148
- .option('--threshold <threshold>', 'optional SLA threshold (evaluator-specific)')
149
- .option('--capture-score', 'persist the verdict score to .peaks/_runtime/<sid>/loop/<rid>/cycles/cycle-N.json (requires --session); adds data.score', false)).action((rid, options) => {
150
- try {
151
- if (!VALID_EVALUATORS.has(options.evaluator)) {
152
- printResult(io, fail('loop.eval', 'UNKNOWN_EVALUATOR', `evaluator "${options.evaluator}" is not a native evaluator (allowed: ${[...VALID_EVALUATORS].join(', ')})`, { rid }, [`Use one of: ${[...VALID_EVALUATORS].join(', ')}`]), options.json);
153
- process.exitCode = 1;
154
- return;
155
- }
156
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
157
- const envelope = dispatchEvaluator(options.evaluator, {
158
- projectRoot,
159
- rid,
160
- ...(options.session !== undefined ? { sessionId: options.session } : {}),
161
- ...(options.scope !== undefined ? { scope: options.scope } : {}),
162
- ...(options.threshold !== undefined ? { threshold: options.threshold } : {})
163
- });
164
- const verdict = envelope.gateAction;
165
- const exitCode = envelope.gateAction === 'block' ? 1 : 0;
166
- // --capture-score path: persist a single-row cycle-N.json to
167
- // the run-driver's cycles dir. Mirrors the scoring convention
168
- // of monotonic-guard (pass=1.0, warn=0.5, block=0.0; degraded=0.25).
169
- let capture = null;
170
- if (options.captureScore === true) {
171
- if (options.session === undefined || options.session.length === 0) {
172
- printResult(io, fail('loop.eval', 'CAPTURE_SCORE_NEEDS_SESSION', '--capture-score requires --session <sid>', { rid }, ['Pass --session <sid> alongside --capture-score.']), options.json);
173
- process.exitCode = 1;
174
- return;
175
- }
176
- // Sid axis AND rid axis — the join below takes TWO ids, not one.
177
- // covers "the whole `--capture-score` write path". The security audit of
178
- // the `--session` guard passed and the `rid` slot escaped:
179
- // `peaks loop eval '../../../../…/EVILCYC' --capture-score --session
180
- // <legal>` created `<projectRoot>/../EVILCYC/cycles/cycle-1.json` under
181
- // an `ok: true` envelope. The rid is the CLI positional and has no pinned
182
- // format, so the segment check is the control for that axis.
183
- if (isUnsafePathInput(options.session)) {
184
- printResult(io, fail('loop.eval', 'INVALID_SESSION_ID', `Invalid session id: ${options.session} (must be a single path segment)`, { provided: options.session }, ['Pass a session id that is a single path segment']), options.json);
185
- process.exitCode = 1;
186
- return;
187
- }
188
- if (isUnsafePathInput(rid)) {
189
- printResult(io, fail('loop.eval', 'INVALID_REQUEST_ID', `Invalid request id: ${rid} (must be a single path segment)`, { provided: rid }, ['Pass a request id that is a single path segment']), options.json);
190
- process.exitCode = 1;
191
- return;
192
- }
193
- const score = envelope.degraded
194
- ? 0.25
195
- : envelope.gateAction === 'pass'
196
- ? 1.0
197
- : envelope.gateAction === 'warn'
198
- ? 0.5
199
- : 0.0;
200
- const dir = join(projectRoot, '.peaks', '_runtime', options.session, 'loop', rid, 'cycles');
201
- const n = nextEvalCaptureIndex(projectRoot, options.session, rid);
202
- const path = join(dir, `cycle-${n}.json`);
203
- try {
204
- mkdirSync(dir, { recursive: true });
205
- writeFileSync(path, JSON.stringify({
206
- cycle: n,
207
- rid,
208
- sid: options.session,
209
- persistedAt: new Date().toISOString(),
210
- scores: [
211
- {
212
- evaluator: envelope.kind,
213
- score,
214
- gateAction: envelope.gateAction,
215
- degraded: envelope.degraded,
216
- observedAt: new Date().toISOString()
217
- }
218
- ]
219
- }), 'utf8');
220
- capture = { score, persistedAt: path };
221
- }
222
- catch {
223
- capture = { score, persistedAt: null };
224
- }
225
- }
226
- printResult(io, ok('loop.eval', {
227
- rid,
228
- evaluator: envelope.kind,
229
- verdict,
230
- passed: envelope.passed,
231
- violations: envelope.violations,
232
- summary: envelope.summary,
233
- wallSeconds: envelope.wallSeconds,
234
- degraded: envelope.degraded,
235
- ...(capture !== null ? { score: capture.score, capture } : {})
236
- }, [], envelope.degraded
237
- ? [
238
- 'Evaluator ran in degraded mode (peaks CLI unavailable). Verify the verdict by running `peaks verdict aggregate --from-rid ' +
239
- rid +
240
- '`.',
241
- 'Re-run on a fully-installed peaks-loop environment for a real verdict.'
242
- ]
243
- : [
244
- `Verifier verdict-aggregate can consume this envelope via \`peaks verdict aggregate --from-rid ${rid}\`.`
245
- ]), options.json);
246
- process.exitCode = exitCode;
247
- }
248
- catch (error) {
249
- printResult(io, fail('loop.eval', 'LOOP_EVAL_FAILED', getErrorMessage(error), { rid, evaluator: options.evaluator }, ['Verify the rid and evaluator name.']), options.json);
250
- process.exitCode = 1;
251
- }
252
- });
253
- // peaks loop check-monotonic <rid>
254
- // Slice C: compare adjacent cycles' per-evaluator scores; reject
255
- // score regression > threshold with `MONOTONICITY_VIOLATION`.
256
- addJsonOption(loop
257
- .command('check-monotonic')
258
- .description('Slice C.2: compare adjacent cycles of evaluator scores for a rid. Reject (exit 1) when an evaluator score regresses beyond the configured threshold.')
259
- .argument('<rid>', 'request id (e.g. 2026-06-30-...)')
260
- .requiredOption('--session <sid>', 'session id')
261
- .option('--project <path>', 'project root (default: cwd)')
262
- .option('--threshold <threshold>', `maximum allowed score regression on the 0..1 scale (default: ${DEFAULT_MONOTONIC_THRESHOLD} = 5%)`)
263
- .option('--no-persist', 'skip persisting the current cycle score rows to disk')).action((rid, options) => {
264
- try {
265
- const { projectRoot, sid } = resolveMonotonicContext({
266
- ...(options.project !== undefined ? { project: options.project } : {}),
267
- session: options.session,
268
- rid
269
- });
270
- const persist = options.persist !== false;
271
- const thresholdNum = options.threshold !== undefined ? Number(options.threshold) : DEFAULT_MONOTONIC_THRESHOLD;
272
- if (options.threshold !== undefined &&
273
- (!Number.isFinite(thresholdNum) || thresholdNum < 0 || thresholdNum > 1)) {
274
- printResult(io, fail('loop.check-monotonic', 'INVALID_THRESHOLD', `threshold must be a finite number in [0,1] (got "${options.threshold}")`, { rid }, [`Pass --threshold ${DEFAULT_MONOTONIC_THRESHOLD} (default) or any number in [0,1].`]), options.json);
275
- process.exitCode = 1;
276
- return;
277
- }
278
- const result = runMonotonicCheck({
279
- projectRoot,
280
- sid,
281
- rid,
282
- threshold: thresholdNum,
283
- persist
284
- });
285
- const exitCode = result.report.monotonicityViolation ? 1 : 0;
286
- printResult(io, ok('loop.check-monotonic', {
287
- rid,
288
- sessionId: sid,
289
- projectRoot,
290
- currentCycle: result.currentCycle,
291
- previousCycle: result.previousCycle,
292
- persistedAt: result.persistedAt,
293
- rows: result.rows,
294
- threshold: result.report.threshold,
295
- status: result.report.status,
296
- code: result.report.code,
297
- monotonicityViolation: result.report.monotonicityViolation,
298
- regressions: result.report.regressions,
299
- reason: result.report.reason
300
- }, [], result.report.monotonicityViolation
301
- ? [
302
- 'Investigate which evaluator regressed most and the previous cycle threshold.',
303
- `Inspect ${result.persistedAt ?? 'the persisted cycle row at .peaks/_runtime/<sid>/loop/<rid>/cycle-N.json'}.`
304
- ]
305
- : result.report.status === 'skip'
306
- ? [
307
- 'Cycle is the first run or incomparable; monotonicity guard is a no-op.',
308
- 'A future cycle will surface a violation if any evaluator regresses.'
309
- ]
310
- : [
311
- 'All evaluators held or improved.',
312
- `Verifier verdict-aggregate can consume this envelope via \`peaks verdict aggregate --from-rid ${rid}\`.`
313
- ]), options.json);
314
- process.exitCode = exitCode;
315
- }
316
- catch (error) {
317
- printResult(io, fail('loop.check-monotonic', 'LOOP_CHECK_MONOTONIC_FAILED', getErrorMessage(error), { rid }, ['Verify the rid and session binding.']), options.json);
318
- process.exitCode = 1;
319
- }
320
- });
321
- // peaks loop spec <rid> — Slice E.2: read or bootstrap the
322
- // project-level `.peaks/_runtime/<sid>/loop/<rid>/spec.yaml`. When
323
- // `--bootstrap` is set, a default spec is written; otherwise the
324
- // existing spec is read (or `{kind:'missing'}` is returned).
325
- const spec = loop
326
- .command('spec')
327
- .description('Slice E.2: read or bootstrap the spec for a rid. Defaults to read; pass --bootstrap to write a default spec.');
328
- addJsonOption(spec
329
- .command('show')
330
- .description('Slice E.2: print the resolved spec for a rid.')
331
- .argument('<rid>', 'request id')
332
- .requiredOption('--session <sid>', 'session id')
333
- .option('--project <path>', 'project root (default: cwd)')).action((rid, options) => {
334
- try {
335
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
336
- const resolved = resolveLoopSpec(projectRoot, options.session, rid);
337
- if (resolved.spec === null) {
338
- printResult(io, fail('loop.spec.show', 'SPEC_NOT_FOUND', `no spec.yaml at ${resolved.origin && resolved.origin.kind === 'missing' ? `.peaks/_runtime/${options.session}/loop/${rid}/spec.yaml` : 'unknown'}`, { rid, sessionId: options.session }, [
339
- `Create one via \`peaks loop spec bootstrap ${rid} --session ${options.session} --project ${projectRoot}\`.`
340
- ]), options.json);
341
- process.exitCode = 1;
342
- return;
343
- }
344
- const report = lintLoopSpec(resolved.spec);
345
- printResult(io, ok('loop.spec.show', {
346
- rid,
347
- sessionId: options.session,
348
- projectRoot,
349
- origin: resolved.origin,
350
- spec: resolved.spec,
351
- lint: { ok: report.ok, errors: report.errors, warnings: report.warnings }
352
- }, [], [
353
- `Edit \`${typeof resolved.origin === 'object' && resolved.origin.kind === 'project' ? resolved.origin.path : '<spec>'}\` and re-run \`peaks loop spec lint <file>\`.`
354
- ]), options.json);
355
- if (!report.ok)
356
- process.exitCode = 1;
357
- }
358
- catch (error) {
359
- printResult(io, fail('loop.spec.show', 'LOOP_SPEC_SHOW_FAILED', getErrorMessage(error), { rid }, [
360
- 'Verify the rid and session binding.'
361
- ]), options.json);
362
- process.exitCode = 1;
363
- }
364
- });
365
- addJsonOption(spec
366
- .command('bootstrap')
367
- .description('Slice E.2: write a default spec.yaml for a rid at `.peaks/_runtime/<sid>/loop/<rid>/spec.yaml`. Refuses to overwrite an existing spec without --force (P1 from dogfood audit: bootstrap is destructive on re-run).')
368
- .argument('<rid>', 'request id')
369
- .requiredOption('--session <sid>', 'session id')
370
- .option('--project <path>', 'project root (default: cwd)')
371
- .option('--strategy <strategy>', `termination strategy (manual|max-cycles|${MONOTONIC_TERMINATION})`, MONOTONIC_TERMINATION)
372
- .option('--max-cycles <n>', `max-cycles (only when strategy=max-cycles; default ${DEFAULT_MAX_CYCLES})`, String(DEFAULT_MAX_CYCLES))
373
- .option('--force', 'overwrite an existing spec.yaml (without --force, bootstrap refuses with SPEC_EXISTS_NEEDS_FORCE)', false)).action((rid, options) => {
374
- try {
375
- const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
376
- const path = specPath(projectRoot, options.session, rid);
377
- // P1二次保护:spec.yaml 已存在且未带 --force → SPEC_EXISTS_NEEDS_FORCE
378
- if (existsSync(path) && options.force !== true) {
379
- printResult(io, fail('loop.spec.bootstrap', 'SPEC_EXISTS_NEEDS_FORCE', `spec.yaml already exists at ${path}; re-run with --force to overwrite`, { rid, sessionId: options.session, path }, [
380
- `Re-run with \`peaks loop spec bootstrap ${rid} --session ${options.session} --force\` to overwrite.`,
381
- `Or edit \`${path}\` directly.`
382
- ]), options.json);
383
- process.exitCode = 1;
384
- return;
385
- }
386
- const strategyRaw = options.strategy;
387
- const strategy = strategyRaw === 'max-cycles' ||
388
- strategyRaw === MONOTONIC_TERMINATION ||
389
- strategyRaw === 'manual'
390
- ? strategyRaw
391
- : MONOTONIC_TERMINATION;
392
- const termination = strategy === 'max-cycles'
393
- ? {
394
- strategy,
395
- maxCycles: Math.max(1, Math.floor(Number(options.maxCycles) || DEFAULT_MAX_CYCLES))
396
- }
397
- : { strategy };
398
- const evaluators = [
399
- { kind: 'karpathy', gate: 'Gate B3', scope: 'src/' },
400
- { kind: 'code-review', gate: 'Gate B3', scope: 'src/' },
401
- { kind: 'security-review', gate: 'Gate B4', scope: 'src/' },
402
- { kind: 'perf-baseline', gate: 'Gate B4', scope: 'src/' },
403
- { kind: 'monotonic-improvement', gate: 'Gate D1' }
404
- ];
405
- const sla = [
406
- { evaluator: 'karpathy', maxScore: 0.7 },
407
- { evaluator: 'code-review', maxScore: 0.7 },
408
- { evaluator: 'security-review', maxScore: 0.7 },
409
- { evaluator: 'perf-baseline', maxScore: 0.7 },
410
- { evaluator: 'monotonic-improvement', maxScore: 0.5 }
411
- ];
412
- const specObj = buildSpec({ rid, evaluators, sla, termination }, rid);
413
- const report = lintLoopSpec(specObj);
414
- if (!report.ok) {
415
- printResult(io, fail('loop.spec.bootstrap', 'SPEC_LINT_FAILED', report.errors.join('; '), { rid, report }, ['Verify the strategy flag.']), options.json);
416
- process.exitCode = 1;
417
- return;
418
- }
419
- const writtenPath = persistSpec(projectRoot, options.session, specObj);
420
- printResult(io, ok('loop.spec.bootstrap', {
421
- rid,
422
- sessionId: options.session,
423
- projectRoot,
424
- path: writtenPath,
425
- spec: specObj,
426
- lint: { ok: report.ok, errors: report.errors, warnings: report.warnings },
427
- overwritten: options.force === true && existsSync(writtenPath)
428
- }, [], [
429
- `Run \`peaks loop spec lint ${writtenPath}\` to re-validate.`,
430
- `Then run \`peaks loop run ${rid} --session ${options.session}\` to execute the loop.`
431
- ]), options.json);
432
- }
433
- catch (error) {
434
- printResult(io, fail('loop.spec.bootstrap', 'LOOP_SPEC_BOOTSTRAP_FAILED', getErrorMessage(error), { rid }, ['Verify the rid, session, and strategy flag.']), options.json);
435
- process.exitCode = 1;
436
- }
437
- });
438
- addJsonOption(spec
439
- .command('lint')
440
- .description('Slice E.2: schema-validate a spec.yaml file (path required).')
441
- .argument('<file>', 'path to spec.yaml')
442
- .option('--rid <rid>', 'override the rid embedded in the spec (default: inferred from path)')).action((file, options) => {
443
- try {
444
- const result = lintSpecFile(file, options.rid);
445
- if (result.spec === null) {
446
- printResult(io, fail('loop.spec.lint', 'SPEC_LINT_FAILED', result.report.errors.join('; '), { file, raw: result.raw.slice(0, 200) }, ['Verify the file path and YAML syntax.']), options.json);
447
- process.exitCode = 1;
448
- return;
449
- }
450
- printResult(io, ok('loop.spec.lint', {
451
- file,
452
- spec: result.spec,
453
- lint: {
454
- ok: result.report.ok,
455
- errors: result.report.errors,
456
- warnings: result.report.warnings
457
- }
458
- }, [], []), options.json);
459
- if (!result.report.ok)
460
- process.exitCode = 1;
461
- }
462
- catch (error) {
463
- printResult(io, fail('loop.spec.lint', 'LOOP_SPEC_LINT_FAILED', getErrorMessage(error), { file }, [
464
- 'Verify the file path and rid flag.'
465
- ]), options.json);
466
- process.exitCode = 1;
467
- }
468
- });
469
- // peaks loop run <rid> — Slice F.1 (P0 closure): the closed-loop
470
- // driver that actually consumes `termination.strategy` (previously
471
- // declared-and-validated but never consumed — dogfood audit #2).
472
- addJsonOption(loop
473
- .command('run')
474
- .description(`Slice F.1: drive the closed loop for <rid> per .peaks/_runtime/<sid>/loop/<rid>/spec.yaml. Consumes termination.strategy (${MONOTONIC_TERMINATION} | max-cycles | manual); aborts on MONOTONICITY_VIOLATION.`)
475
- .argument('<rid>', 'request id (e.g. 2026-06-30-...)')
476
- .requiredOption('--session <sid>', 'session id')
477
- .option('--project <path>', 'project root (default: cwd)')
478
- .option('--strategy <strategy>', `override termination.strategy (allowed: ${MONOTONIC_TERMINATION}, max-cycles, manual)`)
479
- .option('--max-cycles <n>', `override termination.maxCycles (default ${DEFAULT_MAX_CYCLES})`)
480
- .option('--threshold <t>', 'monotonic threshold (0..1); default 0.05')
481
- .option('--no-persist', 'skip writing cycle-N.json + summary to disk')).action((rid, options) => {
482
- try {
483
- const { projectRoot, sid, rid: r2 } = resolveRunContext({
484
- ...(options.project !== undefined ? { project: options.project } : {}),
485
- session: options.session,
486
- rid
487
- });
488
- const threshold = options.threshold !== undefined ? Number(options.threshold) : undefined;
489
- if (threshold !== undefined &&
490
- (!Number.isFinite(threshold) || threshold < 0 || threshold > 1)) {
491
- printResult(io, fail('loop.run', 'INVALID_THRESHOLD', `threshold must be finite in [0,1] (got "${options.threshold}")`, { rid: r2 }, [`Pass --threshold 0.05 (default) or any number in [0,1].`]), options.json);
492
- process.exitCode = 1;
493
- return;
494
- }
495
- const maxCycles = options.maxCycles !== undefined
496
- ? Math.max(1, Math.floor(Number(options.maxCycles) || DEFAULT_MAX_CYCLES))
497
- : undefined;
498
- const strategyOverride = options.strategy;
499
- const result = runLoop({
500
- projectRoot,
501
- sid,
502
- rid: r2,
503
- ...(threshold !== undefined ? { threshold } : {}),
504
- ...(maxCycles !== undefined ? { maxCyclesOverride: maxCycles } : {}),
505
- ...(strategyOverride !== undefined ? { strategyOverride } : {}),
506
- persist: options.persist !== false
507
- });
508
- const exitCode = mapRunDriverCodeToExit(result.code);
509
- const finalReportCode = result.finalReport?.code ?? null;
510
- printResult(io, result.ok
511
- ? ok('loop.run', {
512
- rid: r2,
513
- sessionId: sid,
514
- projectRoot,
515
- code: result.code,
516
- strategy: result.strategy,
517
- maxCycles: result.maxCycles,
518
- cycles: result.cycles.map((c) => ({
519
- cycle: c.cycle,
520
- rows: c.rows,
521
- persistedAt: c.persistedAt,
522
- monotonicReport: c.monotonicReport
523
- })),
524
- finalReport: result.finalReport,
525
- finalReportCode,
526
- summary: result.summary
527
- }, [], result.code === 'RUN_OK'
528
- ? [
529
- `Loop completed ${result.summary.totalCycles} cycle(s) with ${result.summary.regressionCount} regression(s).`
530
- ]
531
- : [`Run driver exited with ${result.code}: ${result.message}`])
532
- : fail('loop.run', result.code, result.message, {
533
- rid: r2,
534
- sessionId: sid,
535
- projectRoot,
536
- code: result.code,
537
- strategy: result.strategy,
538
- maxCycles: result.maxCycles,
539
- cycles: result.cycles,
540
- finalReport: result.finalReport,
541
- finalReportCode,
542
- summary: result.summary
543
- }, nextActionsForCode(result.code, r2, sid, result.summary)), options.json);
544
- process.exitCode = exitCode;
545
- }
546
- catch (error) {
547
- printResult(io, fail('loop.run', 'RUN_FAILED', getErrorMessage(error), { rid }, [
548
- 'Verify the rid and session binding.'
549
- ]), options.json);
550
- process.exitCode = 1;
551
- }
552
- });
553
- }
554
- /** Test seam: pick the next cycle-N.json number for capture-score
555
- * writes. The run-driver also writes to the same dir; the next
556
- * index = max(prior)+1, or 1. */
557
- function nextEvalCaptureIndex(projectRoot, sid, rid) {
558
- // BOTH id axes: this function is the join for the capture-score cycle dir.
559
- // The action guards `options.session` and `rid` before calling in, but the
560
- // seam is callable on its own, so the join states its own contract. The rid
561
- // half was missing until 2026-09-14 (repair R1) — the same second-slot hole
562
- // the action had, in the same file, one function below it.
563
- if (isUnsafePathInput(sid)) {
564
- throw new Error(`Invalid session id: ${sid} (must be a single path segment)`);
565
- }
566
- if (isUnsafePathInput(rid)) {
567
- throw new Error(`Invalid request id: ${rid} (must be a single path segment)`);
568
- }
569
- const dir = join(projectRoot, '.peaks', '_runtime', sid, 'loop', rid, 'cycles');
570
- if (!existsSync(dir))
571
- return 1;
572
- // eslint-disable-next-line @typescript-eslint/no-var-requires
573
- const fs = require('node:fs');
574
- let entries;
575
- try {
576
- entries = fs.readdirSync(dir);
577
- }
578
- catch {
579
- return 1;
580
- }
581
- let best = null;
582
- for (const entry of entries) {
583
- const m = entry.match(/^cycle-(\d+)\.json$/);
584
- if (m === null)
585
- continue;
586
- const n = parseInt(m[1] ?? '0', 10);
587
- if (best === null || n > best)
588
- best = n;
589
- }
590
- return (best ?? 0) + 1;
591
- }
592
- /** Map a run-driver code to a process exit code. */
593
- function mapRunDriverCodeToExit(code) {
594
- if (code === 'RUN_OK' || code === 'RUN_OK_REGRESSION')
595
- return 0;
596
- return 1;
597
- }
598
- function nextActionsForCode(code, rid, sid, summary) {
599
- switch (code) {
600
- case 'SPEC_NOT_FOUND':
601
- return [`Create a spec with \`peaks loop spec bootstrap ${rid} --session ${sid}\`.`];
602
- case 'SPEC_INVALID':
603
- return [`Re-run \`peaks loop spec lint <path>\` to see the schema errors.`];
604
- case 'UNKNOWN_TERMINATION_STRATEGY':
605
- return [
606
- `Edit the spec.yaml and set termination.strategy to one of: ${MONOTONIC_TERMINATION}, max-cycles, manual.`
607
- ];
608
- case 'MONOTONICITY_VIOLATION':
609
- return [
610
- 'Inspect the regression rows in the cycles output and the previous cycle row at .peaks/_runtime/' +
611
- sid +
612
- '/loop/' +
613
- rid +
614
- '/cycles/.',
615
- 'Fix the regression in the next iteration and re-run.'
616
- ];
617
- case 'LOCKED':
618
- return ['Wait for the in-flight loop run to complete, or pick a different rid.'];
619
- case 'RUN_OK_REGRESSION':
620
- return ['A regression was observed; the run captured it for operator review.'];
621
- case 'RUN_OK':
622
- return [
623
- `Loop completed ${summary.totalCycles} cycle(s); ${summary.regressionCount} regression(s).`
624
- ];
625
- default:
626
- return ['Inspect the run-driver output above.'];
627
- }
8
+ registerWorkflowEvalWorkflowCommands(program, io);
9
+ registerLoopEvalCommand(program, io);
10
+ registerLoopCheckMonotonicCommand(program, io);
11
+ registerLoopSpecCommands(program, io);
12
+ registerLoopSpecLintCommand(program, io);
13
+ registerLoopRunCommand(program, io);
628
14
  }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerLoopEvalCommand(program: Command, io: ProgramIO): void;