aiwcli 0.15.7 → 0.17.0

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 (272) hide show
  1. package/README.md +106 -1125
  2. package/bin/run.js +0 -4
  3. package/dist/capabilities/installation/control-plane/clear-command.d.ts +2 -0
  4. package/dist/capabilities/installation/control-plane/clear-command.js +32 -3
  5. package/dist/capabilities/installation/control-plane/init-command.js +2 -2
  6. package/dist/capabilities/launch/contracts.d.ts +39 -4
  7. package/dist/capabilities/launch/control-plane/execute-launch.js +158 -119
  8. package/dist/capabilities/launch/runtime-core/launch-decisions.d.ts +82 -0
  9. package/dist/capabilities/launch/runtime-core/launch-decisions.js +202 -0
  10. package/dist/commands/branch.d.ts +1 -1
  11. package/dist/commands/branch.js +1 -1
  12. package/dist/commands/launch.d.ts +0 -5
  13. package/dist/commands/launch.js +2 -37
  14. package/dist/lib/config.js +1 -2
  15. package/dist/lib/context/context-store.js +28 -2
  16. package/dist/lib/core-installer.d.ts +1 -1
  17. package/dist/lib/core-installer.js +6 -27
  18. package/dist/lib/debug.d.ts +0 -10
  19. package/dist/lib/debug.js +0 -10
  20. package/dist/lib/env-sanitizer.d.ts +25 -0
  21. package/dist/lib/env-sanitizer.js +46 -0
  22. package/dist/lib/errors.d.ts +0 -13
  23. package/dist/lib/errors.js +0 -15
  24. package/dist/lib/git-exclude-manager.js +1 -1
  25. package/dist/lib/hooks/context-monitor-logic.d.ts +6 -0
  26. package/dist/lib/hooks/context-monitor-logic.js +25 -0
  27. package/dist/lib/hooks/hook-utils.js +11 -0
  28. package/dist/lib/hooks/prompt-binding-logic.d.ts +7 -0
  29. package/dist/lib/hooks/prompt-binding-logic.js +50 -0
  30. package/dist/lib/hooks/session-end-logic.js +2 -14
  31. package/dist/lib/install-state.js +6 -13
  32. package/dist/lib/json-io.d.ts +12 -0
  33. package/dist/lib/json-io.js +30 -0
  34. package/dist/lib/multiplexer.d.ts +43 -35
  35. package/dist/lib/multiplexer.js +21 -2
  36. package/dist/lib/multiplexers/psmux.d.ts +14 -34
  37. package/dist/lib/multiplexers/psmux.js +70 -130
  38. package/dist/lib/multiplexers/tmux.d.ts +11 -19
  39. package/dist/lib/multiplexers/tmux.js +79 -120
  40. package/dist/lib/multiplexers/wezterm.d.ts +38 -0
  41. package/dist/lib/multiplexers/wezterm.js +225 -0
  42. package/dist/lib/mux-utils.d.ts +4 -3
  43. package/dist/lib/mux-utils.js +7 -13
  44. package/dist/lib/prompt-file-manager.d.ts +23 -0
  45. package/dist/lib/prompt-file-manager.js +41 -0
  46. package/dist/lib/runtime/agent-launcher.d.ts +67 -0
  47. package/dist/lib/runtime/agent-launcher.js +262 -0
  48. package/dist/lib/runtime/aiw-cli.d.ts +2 -0
  49. package/dist/lib/runtime/aiw-cli.js +3 -1
  50. package/dist/lib/runtime/cli-args.d.ts +5 -2
  51. package/dist/lib/runtime/cli-args.js +18 -3
  52. package/dist/lib/runtime/inference.js +3 -14
  53. package/dist/lib/runtime/models.d.ts +6 -0
  54. package/dist/lib/runtime/models.js +6 -0
  55. package/dist/lib/runtime/state-io.d.ts +2 -1
  56. package/dist/lib/runtime/state-io.js +9 -4
  57. package/dist/lib/runtime/utils.d.ts +8 -0
  58. package/dist/lib/runtime/utils.js +31 -1
  59. package/dist/lib/schemas.d.ts +250 -0
  60. package/dist/lib/schemas.js +216 -0
  61. package/dist/lib/sentinel-manager.d.ts +32 -0
  62. package/dist/lib/sentinel-manager.js +62 -0
  63. package/dist/lib/sentinel-wrapper.d.ts +1 -0
  64. package/dist/lib/sentinel-wrapper.js +12 -3
  65. package/dist/lib/settings-hierarchy.js +3 -20
  66. package/dist/lib/shell-adapters/bash-adapter.d.ts +18 -0
  67. package/dist/lib/shell-adapters/bash-adapter.js +69 -0
  68. package/dist/lib/shell-adapters/index.d.ts +5 -0
  69. package/dist/lib/shell-adapters/index.js +7 -0
  70. package/dist/lib/shell-adapters/powershell-adapter.d.ts +18 -0
  71. package/dist/lib/shell-adapters/powershell-adapter.js +62 -0
  72. package/dist/lib/shell-adapters/shell-adapter.d.ts +45 -0
  73. package/dist/lib/shell-adapters/shell-adapter.js +5 -0
  74. package/dist/lib/spawn-errors.d.ts +3 -0
  75. package/dist/lib/spawn-errors.js +15 -1
  76. package/dist/lib/spinner.d.ts +0 -5
  77. package/dist/lib/spinner.js +0 -16
  78. package/dist/lib/template-installer.d.ts +10 -0
  79. package/dist/lib/template-installer.js +4 -4
  80. package/dist/lib/terminal-strategy.d.ts +1 -0
  81. package/dist/lib/terminal-strategy.js +12 -6
  82. package/dist/lib/terminal.d.ts +7 -5
  83. package/dist/lib/terminal.js +42 -19
  84. package/dist/lib/tmux-primitives.d.ts +0 -2
  85. package/dist/lib/tmux-primitives.js +0 -4
  86. package/dist/lib/tmux-session.js +2 -1
  87. package/dist/lib/windsurf-hooks-hierarchy.js +6 -23
  88. package/dist/platform/launch.d.ts +2 -1
  89. package/dist/platform/launch.js +1 -0
  90. package/dist/templates/CLAUDE.md +0 -1
  91. package/dist/templates/cc-native/.claude/settings.json +0 -10
  92. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +11 -4
  93. package/dist/templates/cc-native/_cc-native/cc-native.config.json +3 -7
  94. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +26 -47
  95. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +7 -9
  96. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +2 -3
  97. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +2 -2
  98. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +0 -25
  99. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +4 -4
  100. package/dist/templates/cc-native/_cc-native/lib-ts/.mocharc.json +9 -0
  101. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/aggregate-agents.test.ts +118 -0
  102. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/artifacts.test.ts +234 -0
  103. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cc-native-state.test.ts +170 -0
  104. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cli-output-parser.test.ts +73 -0
  105. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/config.test.ts +64 -0
  106. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/constants.test.ts +40 -0
  107. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/debug.test.ts +42 -0
  108. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/exports.test.ts +58 -0
  109. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/helpers.ts +107 -0
  110. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/add-plan-context.hook.test.ts +97 -0
  111. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-questions.hook.test.ts +81 -0
  112. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-review.hook.test.ts +71 -0
  113. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/json-parser.test.ts +99 -0
  114. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator-agent.test.ts +288 -0
  115. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator.test.ts +48 -0
  116. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/reviewers.test.ts +32 -0
  117. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/state.test.ts +124 -0
  118. package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/verdict.test.ts +93 -0
  119. package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
  120. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -14
  121. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/format.ts +597 -599
  122. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/index.ts +26 -26
  123. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/tracker.ts +106 -107
  124. package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/write.ts +118 -119
  125. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -0
  126. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +16 -15
  127. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +132 -10
  128. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +6 -6
  129. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/corroboration.ts +119 -119
  130. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +1 -2
  131. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/graduation.ts +132 -132
  132. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +88 -86
  133. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +5 -6
  134. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/orchestrator.ts +70 -70
  135. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/output-builder.ts +130 -121
  136. package/dist/templates/cc-native/_cc-native/lib-ts/package-lock.json +1679 -0
  137. package/dist/templates/cc-native/_cc-native/lib-ts/package.json +24 -0
  138. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +4 -4
  139. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +1 -6
  140. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/plan-questions.ts +101 -101
  141. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/review-pipeline.ts +511 -543
  142. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/__tests__/agent-providers.test.ts +262 -0
  143. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/agent.ts +71 -85
  144. package/dist/templates/{core/lib-ts/agent-exec → cc-native/_cc-native/lib-ts/reviewers/base}/base-agent.ts +138 -152
  145. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/index.ts +12 -12
  146. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/claude-agent.ts +66 -57
  147. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/codex-agent.ts +185 -200
  148. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/gemini-agent.ts +39 -40
  149. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/orchestrator-claude-agent.ts +196 -224
  150. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/schemas.ts +201 -201
  151. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/types.ts +21 -23
  152. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/hyde.test.ts +365 -0
  153. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/ollama-client.test.ts +223 -0
  154. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +12 -16
  155. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +3 -2
  156. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
  157. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +6 -7
  158. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +7 -9
  159. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +14 -17
  160. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +37 -41
  161. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +33 -43
  162. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
  163. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +8 -9
  164. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +3 -4
  165. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +50 -126
  166. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +19 -21
  167. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -88
  168. package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/verdict.ts +72 -72
  169. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +35 -0
  170. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +1 -1
  171. package/dist/templates/cc-native/_cc-native/scripts/council_debate.ts +242 -0
  172. package/dist/templates/cc-native/_cc-native/scripts/council_debate_simple.ts +294 -0
  173. package/dist/templates/cc-native/_cc-native/{plan-review/workflows → workflows}/specdev.md +9 -9
  174. package/dist/templates/core/.claude/skills/codex/SKILL.md +25 -0
  175. package/dist/templates/core/.claude/skills/devin/SKILL.md +25 -0
  176. package/dist/templates/core/.claude/skills/handoff/SKILL.md +11 -0
  177. package/dist/templates/core/.claude/skills/handoff-resume/SKILL.md +11 -0
  178. package/dist/templates/core/.claude/skills/meta-plan/SKILL.md +13 -0
  179. package/dist/templates/core/.codex/skills/codex/SKILL.md +13 -0
  180. package/dist/templates/core/.codex/skills/devin/SKILL.md +19 -0
  181. package/dist/templates/core/.codex/skills/handoff/SKILL.md +11 -0
  182. package/dist/templates/core/.codex/skills/handoff-resume/SKILL.md +11 -0
  183. package/dist/templates/core/.codex/{workflows/meta-plan.md → skills/meta-plan/SKILL.md} +6 -0
  184. package/dist/templates/core/{.cognition → .devin}/AGENTS.md +2 -2
  185. package/dist/templates/core/.devin/skills/codex/SKILL.md +19 -0
  186. package/dist/templates/core/.devin/skills/devin/SKILL.md +13 -0
  187. package/dist/templates/core/.devin/skills/handoff/SKILL.md +11 -0
  188. package/dist/templates/core/.devin/skills/handoff-resume/SKILL.md +11 -0
  189. package/dist/templates/core/.devin/skills/meta-plan/SKILL.md +13 -0
  190. package/dist/templates/core/.windsurf/workflows/handoff-resume.md +9 -0
  191. package/dist/templates/core/hooks-ts/archive_plan.ts +1 -21
  192. package/dist/templates/core/hooks-ts/file-suggestion.ts +1 -19
  193. package/dist/templates/core/hooks-ts/pre_compact.ts +5 -18
  194. package/dist/templates/core/lib-ts/context/context-store.ts +29 -2
  195. package/dist/templates/core/lib-ts/hooks/hook-utils.ts +11 -0
  196. package/dist/templates/core/lib-ts/hooks/session-end-logic.ts +2 -13
  197. package/dist/templates/core/lib-ts/runtime/agent-launcher.ts +74 -0
  198. package/dist/templates/core/lib-ts/runtime/aiw-cli.ts +4 -2
  199. package/dist/templates/core/lib-ts/runtime/cli-args.ts +18 -4
  200. package/dist/templates/core/lib-ts/runtime/inference.ts +3 -15
  201. package/dist/templates/core/lib-ts/runtime/models.ts +7 -0
  202. package/dist/templates/core/lib-ts/runtime/state-io.ts +9 -4
  203. package/dist/templates/core/lib-ts/runtime/utils.ts +30 -1
  204. package/dist/templates/core/lib-ts/schemas.ts +233 -0
  205. package/dist/templates/core/scripts/resolve-run.ts +34 -2
  206. package/dist/templates/core/scripts/status_line.ts +1 -1
  207. package/dist/templates/core/skills/codex/CLAUDE.md +9 -4
  208. package/dist/templates/core/skills/codex/SKILL.md +6 -0
  209. package/dist/templates/core/skills/codex/lib/codex-watcher.ts +3 -10
  210. package/dist/templates/core/skills/codex/scripts/launch-codex.ts +26 -26
  211. package/dist/templates/core/skills/devin/CLAUDE.md +63 -6
  212. package/dist/templates/core/skills/devin/lib/devin-watcher.ts +116 -96
  213. package/dist/templates/core/skills/devin/scripts/launch-devin.ts +22 -21
  214. package/dist/templates/core/skills/handoff-system/CLAUDE.md +1 -1
  215. package/oclif.manifest.json +4 -4
  216. package/package.json +4 -4
  217. package/dist/lib/base-command.d.ts +0 -1
  218. package/dist/lib/base-command.js +0 -1
  219. package/dist/lib/env-compat.d.ts +0 -18
  220. package/dist/lib/env-compat.js +0 -23
  221. package/dist/lib/launch-options.d.ts +0 -1
  222. package/dist/lib/launch-options.js +0 -1
  223. package/dist/lib/stdin.d.ts +0 -48
  224. package/dist/lib/stdin.js +0 -60
  225. package/dist/templates/cc-native/_cc-native/CLAUDE.md +0 -73
  226. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +0 -64
  227. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +0 -70
  228. package/dist/templates/cc-native/_cc-native/plan-review/CODING-STANDARDS-CHECKLIST.md +0 -75
  229. package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +0 -143
  230. package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +0 -213
  231. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +0 -70
  232. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +0 -62
  233. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +0 -61
  234. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +0 -62
  235. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +0 -56
  236. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +0 -53
  237. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +0 -66
  238. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +0 -70
  239. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +0 -62
  240. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +0 -72
  241. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +0 -61
  242. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +0 -64
  243. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +0 -56
  244. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +0 -86
  245. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +0 -59
  246. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +0 -58
  247. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +0 -66
  248. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +0 -62
  249. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +0 -66
  250. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +0 -71
  251. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +0 -74
  252. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +0 -77
  253. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +0 -62
  254. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +0 -68
  255. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +0 -61
  256. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +0 -71
  257. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +0 -61
  258. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +0 -61
  259. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +0 -67
  260. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +0 -65
  261. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +0 -74
  262. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +0 -69
  263. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +0 -7
  264. package/dist/templates/core/.codex/workflows/codex.md +0 -17
  265. package/dist/templates/core/.codex/workflows/handoff.md +0 -5
  266. package/dist/templates/core/lib-ts/agent-exec/backends/headless.ts +0 -34
  267. package/dist/templates/core/lib-ts/agent-exec/backends/index.ts +0 -6
  268. package/dist/templates/core/lib-ts/agent-exec/backends/tmux.ts +0 -148
  269. package/dist/templates/core/lib-ts/agent-exec/execution-backend.ts +0 -50
  270. package/dist/templates/core/lib-ts/agent-exec/index.ts +0 -6
  271. package/dist/templates/core/lib-ts/agent-exec/structured-output.ts +0 -165
  272. /package/dist/templates/core/{.cognition → .devin}/config.json +0 -0
@@ -1,543 +1,511 @@
1
- /**
2
- * Review pipeline: orchestrates the full plan review lifecycle.
3
- * Wires together plan-discovery, settings, agent-selection, graduation,
4
- * output-builder, and existing modules (orchestrator, corroboration, etc.).
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
-
10
- import { resolveMandatoryAgents, assignModelsToAgents, selectAgents } from "./agent-selection.js";
11
- import { computeCorroboratedDecision } from "./corroboration.js";
12
- import { computePassEligible, extractTopIssuesForTracker, advanceIterationState } from "./graduation.js";
13
- import { runOrchestrator } from "./orchestrator.js";
14
- import { truncateAgentIssues, overrideVerdictsByThreshold, buildReviewOutput } from "./output-builder.js";
15
- import { runPlanQuestions } from "./plan-questions.js";
16
- import { runPreflight } from "./preflight.js";
17
- import { runAgentReview } from "./reviewers/index.js";
18
- import { getContextBySessionId, getAllContexts } from "../../../_core/lib-ts/context/context-store.js";
19
- import { logDiagnostic } from "../../../_core/lib-ts/hooks/hook-utils.js";
20
- import { getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../../_core/lib-ts/runtime/constants.js";
21
- import {
22
- logDebug,
23
- logInfo,
24
- logWarn,
25
- logError,
26
- } from "../../../_core/lib-ts/runtime/logger.js";
27
- import { eprint } from "../../../_core/lib-ts/runtime/utils.js";
28
- import type { ContextState } from "../../../_core/lib-ts/types.js";
29
- import { writeCombinedArtifacts, buildCorroborationReport, buildHighIssuesDocument, writeReviewTracker } from "../../artifacts/lib/index.js";
30
- import type { ReviewTrackerEntry } from "../../artifacts/lib/index.js";
31
- import {
32
- isPlanAlreadyReviewed,
33
- wasPlanPreviouslyDenied,
34
- getLastPlanReview,
35
- markPlanReviewed,
36
- wasPlanQuestionsAgentAsked,
37
- markQuestionsAsked,
38
- resetPlanQuestionsAsked,
39
- } from "../../lib-ts/cc-native-state.js";
40
- import { debugLog } from "../../lib-ts/debug.js";
41
- import { discoverPlan } from "../../lib-ts/plan-discovery.js";
42
- import { loadSettings, loadModelsConfig, loadAgentLibrary, DEFAULT_ORCHESTRATOR } from "../../lib-ts/settings.js";
43
- import { DEFAULT_REVIEW_ITERATIONS, loadIterationState, saveIterationState } from "../../lib-ts/state.js";
44
- import type {
45
- AgentConfig,
46
- AgentReviewSettings,
47
- LoadedSettings,
48
- OrchestratorConfig,
49
- PlanReviewSettings,
50
- ReviewerResult,
51
- CombinedReviewResult,
52
- OrchestratorResult,
53
- IterationState,
54
- PipelineInput,
55
- PipelineResult,
56
- } from "../../lib-ts/types.js";
57
- import { REVIEW_SCHEMA } from "../../lib-ts/types.js";
58
-
59
- const HOOK = "review-pipeline";
60
-
61
- // ---------------------------------------------------------------------------
62
- // Context Lookup (private — only used here)
63
- // ---------------------------------------------------------------------------
64
-
65
- function getActiveContextForReview(sessionId: string, projectRoot: string): ContextState | null {
66
- const ctx = getContextBySessionId(sessionId, projectRoot);
67
- if (ctx) {
68
- logInfo(HOOK, `Found context by session_id: ${ctx.id}`);
69
- return ctx;
70
- }
71
- const allActive = getAllContexts("active", projectRoot);
72
- const planning = allActive.filter(c => c.mode === "active" || c.mode === "has_staged_work");
73
- if (planning.length === 1) {
74
- logInfo(HOOK, `Found single planning context: ${planning[0]!.id}`);
75
- return planning[0]!;
76
- }
77
- if (planning.length > 1) {
78
- logWarn(HOOK, `Multiple planning contexts (${planning.length}), cannot determine which to use`);
79
- } else if (allActive.length > 0) {
80
- logInfo(HOOK, `Found ${allActive.length} active context(s) but none in planning mode`);
81
- } else {
82
- logInfo(HOOK, "No active contexts found");
83
- }
84
- return null;
85
- }
86
-
87
- // ---------------------------------------------------------------------------
88
- // Pipeline
89
- // ---------------------------------------------------------------------------
90
-
91
- export async function runReviewPipeline(input: PipelineInput): Promise<PipelineResult> {
92
- const { sessionId, base, aiwcliDir, transcriptPath, payload } = input;
93
-
94
- // 1. Load settings
95
- const settings: LoadedSettings = loadSettings(aiwcliDir);
96
- const planSettings: PlanReviewSettings = settings.planReview;
97
- const agentSettings: AgentReviewSettings = settings.agentReview;
98
-
99
- const planReviewEnabled = planSettings.enabled ?? true;
100
- const agentReviewEnabled = agentSettings.enabled ?? true;
101
-
102
- if (!planReviewEnabled && !agentReviewEnabled) {
103
- return { action: "skip", reason: "Both plan and agent review disabled" };
104
- }
105
-
106
- // 2. Discover plan
107
- const discovered = discoverPlan(transcriptPath);
108
- if (!discovered) {
109
- return { action: "skip", reason: "No plan file found in ~/.claude/plans/. The plan may not have been written yet." };
110
- }
111
-
112
- const { content: plan, hash: planHash, path: planPath } = discovered;
113
-
114
- // 3. Find active context (moved before questions gate for plan path change detection)
115
- const activeContext = getActiveContextForReview(sessionId, base);
116
- if (!activeContext) {
117
- return { action: "skip", reason: "No active planning context found for this session." };
118
- }
119
-
120
- const contextId = activeContext.id;
121
- const reviewsDir = path.join(getContextReviewsDir(contextId, base), "cc-native");
122
- const contextPath = getContextDir(contextId, base);
123
-
124
- // 4a. Load iteration state
125
- let iterationState: IterationState | null = loadIterationState(reviewsDir);
126
-
127
- // 4a-migration. Backfill sessionId for old iteration files
128
- if (iterationState && !iterationState.sessionId) {
129
- logInfo(HOOK, `Migrating iteration state: adding sessionId=${sessionId}`);
130
- iterationState.sessionId = sessionId;
131
- saveIterationState(reviewsDir, iterationState); // Persist migration
132
- }
133
-
134
- // 4a-session. Detect session change — reset iteration state for new planning session
135
- if (iterationState && iterationState.sessionId && iterationState.sessionId !== sessionId) {
136
- logInfo(HOOK, `Session changed (${iterationState.sessionId} → ${sessionId}), resetting iteration state`);
137
- iterationState = null; // Force fresh state creation below
138
- }
139
-
140
- // 4a-init. Initialize if null
141
- if (!iterationState) {
142
- iterationState = {
143
- current: 1, max: 1, complexity: "medium",
144
- history: [], graduated: [], passStreaks: {},
145
- lastPlanHash: "", lastPlanPath: "",
146
- sessionId,
147
- };
148
- saveIterationState(reviewsDir, iterationState);
149
- }
150
-
151
- // 4b. Detect plan file change — reset iteration state for new plan topic
152
- const lastPath = iterationState.lastPlanPath ?? "";
153
- if (lastPath && lastPath !== planPath) {
154
- logInfo(HOOK, `Plan file changed (${path.basename(lastPath)}→${path.basename(planPath)}), resetting iteration state for new plan`);
155
- iterationState = {
156
- current: 1, max: 1, complexity: "medium",
157
- history: [], graduated: [], passStreaks: {},
158
- lastPlanHash: "", lastPlanPath: "",
159
- sessionId,
160
- };
161
- saveIterationState(reviewsDir, iterationState);
162
- resetPlanQuestionsAsked(sessionId, base);
163
- }
164
-
165
- // 5. Questions gate
166
- if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
167
- logInfo(HOOK, "Questions gate: plan-questions agent has not run yet, running now");
168
- const questionsTimeout = agentSettings.timeout ?? 120;
169
- const questionsResult = await runPlanQuestions(plan, aiwcliDir, questionsTimeout, undefined, sessionId);
170
-
171
- markQuestionsAsked(sessionId, base, "agent");
172
-
173
- const hasQuestions = questionsResult && (
174
- questionsResult.questions.length > 0 ||
175
- questionsResult.assumptions.length > 0 ||
176
- questionsResult.ambiguities.length > 0
177
- );
178
-
179
- if (hasQuestions) {
180
- const questionsList = questionsResult.questions.map((q: string, i: number) => `${i + 1}. ${q}`).join("\n");
181
- const assumptionsList = questionsResult.assumptions.length > 0
182
- ? `\n\nAssumptions detected:\n${questionsResult.assumptions.map((a: string) => `- ${a}`).join("\n")}`
183
- : "";
184
- const ambiguitiesList = questionsResult.ambiguities.length > 0
185
- ? `\n\nAmbiguities detected:\n${questionsResult.ambiguities.map((a: string) => `- ${a}`).join("\n")}`
186
- : "";
187
- const contextMsg = `## Plan Questions (from independent review)\n\nAn agent reviewed your plan in a fresh context — without access to your session history or codebase exploration. It identified these questions:\n\n${questionsList}${assumptionsList}${ambiguitiesList}\n\nAsk the user these questions using AskUserQuestion before submitting the plan.`;
188
- return {
189
- action: "block",
190
- contextText: contextMsg,
191
- blockReason: "Ask the user clarifying questions before submitting the plan. Use AskUserQuestion with the questions above.",
192
- };
193
- }
194
-
195
- logInfo(HOOK, "Questions gate: no questions generated, proceeding to review");
196
- } else {
197
- logInfo(HOOK, "Questions gate: agent already ran, skipping");
198
- }
199
-
200
- // 6. Hash + dedup
201
- logDiagnostic(HOOK, "receive", `plan_size=${plan.length}, session=${sessionId.slice(0, 8)}`, {
202
- inputs: { plan_hash: planHash, plan_size: plan.length, session_id: sessionId.slice(0, 12) },
203
- });
204
-
205
- // Plan-hash deduplication
206
- logDebug(HOOK, `Plan hash: ${planHash}`);
207
- if (isPlanAlreadyReviewed(sessionId, planHash, base)) {
208
- const lastReview = getLastPlanReview(sessionId, planHash, base);
209
-
210
- if (wasPlanPreviouslyDenied(sessionId, planHash, base)) {
211
- return {
212
- action: "block",
213
- contextText: "[Plan Review] Plan content unchanged since last review which found issues.",
214
- blockReason: "Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
215
- };
216
- }
217
- const verdict = lastReview?.iteration?.latest_verdict || "pass";
218
- return { action: "skip", reason: `Plan already reviewed (verdict: ${verdict}). Skipping re-review.` };
219
-
220
- }
221
-
222
- // 7. Iteration bounds check
223
- if (iterationState.current > iterationState.max) {
224
- return { action: "skip", reason: `Max review iterations reached (${iterationState.current - 1}/${iterationState.max}), allowing plan through.` };
225
- }
226
-
227
- // Initialize result containers
228
- let orchResult: OrchestratorResult | null = null;
229
- const agentResults: Record<string, ReviewerResult> = {};
230
- let detectedComplexity = "medium";
231
-
232
- // Preflight: validate provider+model combos before committing agents or orchestrator
233
- const preflightEnabled = agentSettings.preflight?.enabled ?? true;
234
- let preflightAvailable: Map<string, Set<string>> | undefined;
235
-
236
- if (preflightEnabled && agentReviewEnabled) {
237
- logInfo(HOOK, "=== PREFLIGHT: Checking provider availability ===");
238
- const preflightTimeoutMs = agentSettings.preflight?.timeoutMs;
239
- const modelsConfig = loadModelsConfig(settings);
240
- const preflightReport = await runPreflight(modelsConfig, preflightTimeoutMs);
241
-
242
- if (preflightReport.allFailed) {
243
- logWarn(HOOK, "All providers failed preflight checks");
244
- // Preflight failures skip review rather than block because an unavailable
245
- // reviewer is worse than no reviewer. A permanently broken config will
246
- // silently pass all plans — mitigated by the log warnings above.
247
- eprint("[plan-review] All AI providers unavailable. Skipping review.");
248
- return { action: "skip", reason: "No AI providers passed preflight. Check CLI, API keys, model access, and quota." };
249
- }
250
-
251
- preflightAvailable = preflightReport.available;
252
- }
253
-
254
- // 7. PHASE 1: Orchestrator
255
- const graduatedSet = new Set(iterationState.graduated);
256
- if (graduatedSet.size > 0) {
257
- logInfo(HOOK, `Graduated agents from previous iterations: ${[...graduatedSet].sort().join(", ")}`);
258
- }
259
-
260
- const agentLibrary = agentReviewEnabled ? loadAgentLibrary(aiwcliDir, agentSettings) : [];
261
- const originalAgentCount = agentLibrary.length;
262
- const enabledAgents = agentLibrary.filter(a => !graduatedSet.has(a.name));
263
- const timeout = agentSettings.timeout ?? 120;
264
- const legacyMode = agentSettings.legacyMode === true;
265
-
266
- const orchSettings = agentSettings.orchestrator ?? DEFAULT_ORCHESTRATOR;
267
- const orchestratorConfig: OrchestratorConfig = {
268
- enabled: (orchSettings.enabled ?? true) && agentReviewEnabled,
269
- model: orchSettings.model ?? "haiku",
270
- provider: orchSettings.provider,
271
- timeout: orchSettings.timeout ?? 30,
272
- };
273
-
274
- const mandatoryConfig = agentSettings.mandatoryAgents ?? ["handoff-readiness", "clarity-auditor", "skeptic"];
275
- const alwaysMandatory = resolveMandatoryAgents(mandatoryConfig, "simple");
276
-
277
- logDebug(HOOK, `Agent library: ${agentLibrary.map(a => a.name)}`);
278
- logDebug(HOOK, `Mandatory agents: ${[...alwaysMandatory].sort()}`);
279
- logDebug(HOOK, `Orchestrator enabled: ${orchestratorConfig.enabled}`);
280
-
281
- const phase1Promises: Array<{ name: string; promise: Promise<ReviewerResult | OrchestratorResult> }> = [];
282
-
283
- if (orchestratorConfig.enabled && enabledAgents.length > 0 && !legacyMode) {
284
- // Guard orchestrator against preflight failures (always uses claude provider)
285
- const orchProvider = orchestratorConfig.provider ?? "claude";
286
- const orchModel = orchestratorConfig.model;
287
- const orchPassed = !preflightAvailable ||
288
- (preflightAvailable.has(orchProvider) && preflightAvailable.get(orchProvider)!.has(orchModel));
289
-
290
- if (!orchPassed) {
291
- logWarn(HOOK, `Orchestrator model ${orchProvider}:${orchModel} failed preflight, skipping`);
292
- } else {
293
- phase1Promises.push({
294
- name: "orchestrator",
295
- promise: runOrchestrator(plan, enabledAgents, orchestratorConfig, agentSettings, alwaysMandatory),
296
- });
297
- }
298
- }
299
-
300
- logInfo(HOOK, `=== PHASE 1: Running ${phase1Promises.length} tasks in parallel ===`);
301
-
302
- if (phase1Promises.length > 0) {
303
- const results = await Promise.allSettled(
304
- phase1Promises.map(async ({ name, promise }) => {
305
- const result = await promise;
306
- return { name, result };
307
- }),
308
- );
309
- for (const [i, r] of results.entries()) {
310
- if (r.status === "fulfilled") {
311
- if (r.value.name === "orchestrator") orchResult = r.value.result as OrchestratorResult;
312
- logInfo(HOOK, `${r.value.name} completed`);
313
- } else {
314
- const failedName = phase1Promises[i]?.name ?? "unknown";
315
- logError(HOOK, `${failedName} failed: ${r.reason}`);
316
- }
317
- }
318
- }
319
-
320
- // 8. PHASE 2: Agent Selection
321
- let selectedAgents: AgentConfig[] = [];
322
- let mandatoryNames = alwaysMandatory;
323
-
324
- if (agentReviewEnabled) {
325
- logInfo(HOOK, "=== PHASE 2: Agent Selection ===");
326
-
327
- const selectionResult = selectAgents({
328
- enabledAgents,
329
- orchResult,
330
- mandatoryConfig,
331
- agentSettings,
332
- legacyMode,
333
- });
334
-
335
- selectedAgents = selectionResult.selectedAgents;
336
- mandatoryNames = selectionResult.mandatoryNames;
337
- detectedComplexity = selectionResult.detectedComplexity;
338
-
339
- logDiagnostic(HOOK, "decide", `Selected ${selectedAgents.length} agents, complexity=${detectedComplexity}`, {
340
- decision: "agents_selected",
341
- reasoning: `orchestrator=${orchResult !== null}, legacy=${legacyMode}`,
342
- inputs: {
343
- agents: selectedAgents.map(a => a.name),
344
- complexity: detectedComplexity,
345
- mandatory_count: selectedAgents.filter(a => mandatoryNames.has(a.name)).length,
346
- },
347
- });
348
-
349
- // Update iteration state with complexity/max
350
- const reviewIterations: Record<string, number> = {
351
- ...DEFAULT_REVIEW_ITERATIONS,
352
- ...agentSettings.reviewIterations,
353
- };
354
- iterationState.complexity = detectedComplexity;
355
- iterationState.max = reviewIterations[detectedComplexity] ?? iterationState.max;
356
- logDebug(HOOK, `Iteration state: ${iterationState.current}/${iterationState.max} (${detectedComplexity})`);
357
-
358
- // Assign providers + models (filtered by preflight results if available)
359
- const modelsConfig = loadModelsConfig(settings);
360
- selectedAgents = assignModelsToAgents(selectedAgents, modelsConfig, preflightAvailable);
361
- logInfo(HOOK, `Model assignments: ${selectedAgents.map(a => `${a.name}→${a.provider}:${a.model}`).join(", ")}`);
362
-
363
- // 9. PHASE 3: Run agents
364
- if (selectedAgents.length > 0) {
365
- logInfo(HOOK, "=== PHASE 3: Agent Reviews ===");
366
- logInfo(HOOK, `Launching ${selectedAgents.length} agents in parallel`);
367
-
368
- debugLog(contextPath, sessionId, "hook", "agent_review_start", {
369
- agents: selectedAgents.map(a => a.name),
370
- timeout,
371
- complexity: detectedComplexity,
372
- });
373
-
374
- const agentPromises = selectedAgents.map(async agent => {
375
- const result = await runAgentReview(plan, agent, REVIEW_SCHEMA, timeout, contextPath, sessionId);
376
- return { agent, result };
377
- });
378
-
379
- const agentSettled = await Promise.allSettled(agentPromises);
380
- for (const [i, r] of agentSettled.entries()) {
381
- if (r.status === "fulfilled") {
382
- const { agent, result } = r.value;
383
- agentResults[agent.name] = result;
384
- logInfo(HOOK, `${agent.name} completed with verdict: ${result.verdict}`);
385
- } else {
386
- const failedAgent = selectedAgents[i]!;
387
- logError(HOOK, `${failedAgent.name} failed with exception: ${r.reason}`);
388
- agentResults[failedAgent.name] = {
389
- name: failedAgent.name,
390
- ok: false,
391
- verdict: "error",
392
- data: {},
393
- raw: "",
394
- err: String(r.reason),
395
- };
396
- }
397
- }
398
- }
399
- }
400
-
401
- // 10. Issue truncation + verdict override
402
- const maxIssuesPerAgent = agentSettings.maxIssuesPerAgent ?? 3;
403
- truncateAgentIssues(agentResults, maxIssuesPerAgent);
404
-
405
- const passEligible = computePassEligible(agentResults);
406
- if (passEligible.length > 0) {
407
- logInfo(HOOK, `Pass-eligible agents this iteration: ${passEligible.join(", ")}`);
408
- }
409
-
410
- const highIssueThreshold = agentSettings.highIssueThreshold ?? 3;
411
- overrideVerdictsByThreshold(agentResults, highIssueThreshold);
412
-
413
- // PHASE 4: Generate Output
414
- logInfo(HOOK, "=== PHASE 4: Generate Output ===");
415
-
416
- if (Object.keys(agentResults).length === 0) {
417
- if (graduatedSet.size > 0 && originalAgentCount > 0) {
418
- return { action: "skip", reason: "All agent reviewers graduated from previous iterations — no review needed." };
419
- }
420
- return { action: "skip", reason: "All reviewers failed to produce results. Check stderr logs for details." };
421
- }
422
-
423
- // 11. Corroboration
424
- const corroborationResult = computeCorroboratedDecision(agentResults);
425
- const overall = corroborationResult.verdict;
426
-
427
- const combinedResult: CombinedReviewResult = {
428
- plan_hash: planHash,
429
- overall_verdict: overall,
430
- orchestration: orchResult,
431
- agents: agentResults,
432
- timestamp: new Date().toISOString(),
433
- };
434
-
435
- const displaySettings = {
436
- ...planSettings.display,
437
- ...agentSettings.display,
438
- };
439
- const combinedSettings = { display: displaySettings };
440
-
441
- const currentIteration = iterationState.current;
442
-
443
- // 12. Write artifacts
444
- const reviewFolder = getReviewFolderPath(contextId, currentIteration, base);
445
- fs.mkdirSync(reviewFolder, { recursive: true });
446
- logInfo(HOOK, `Created review folder: ${reviewFolder}`);
447
-
448
- const reviewFile = writeCombinedArtifacts(
449
- base, plan, combinedResult, payload, combinedSettings,
450
- undefined, reviewFolder, currentIteration, corroborationResult,
451
- );
452
- logInfo(HOOK, `Saved review: ${reviewFile}`);
453
-
454
- const corroborationReport = buildCorroborationReport(corroborationResult);
455
- const corroborationPath = path.join(reviewFolder, "corroboration.md");
456
- fs.writeFileSync(corroborationPath, corroborationReport, "utf-8");
457
- logInfo(HOOK, `Saved corroboration report: ${corroborationPath}`);
458
-
459
- try {
460
- fs.writeFileSync(path.join(reviewFolder, "plan.md"), plan, "utf-8");
461
- logDebug(HOOK, `Saved plan snapshot: ${path.join(reviewFolder, "plan.md")}`);
462
- } catch (error) {
463
- logWarn(HOOK, `Failed to save plan snapshot: ${error}`);
464
- }
465
-
466
- // Build high-issues document
467
- const highIssuesDoc = buildHighIssuesDocument(combinedResult, corroborationResult);
468
- const highIssuesPath = path.join(reviewFolder, "high-issues.md");
469
- fs.writeFileSync(highIssuesPath, highIssuesDoc, "utf-8");
470
-
471
- // 15. Build output
472
- const shouldDeny = corroborationResult.blocking.length > 0;
473
- const reviewScore = shouldDeny ? 1 : 0;
474
- const denyReason = shouldDeny ? "corroborated_issues" : "no_corroboration";
475
-
476
- logInfo(HOOK, `REVIEW_DECISION: verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}, score=${reviewScore.toFixed(2)}`);
477
- logDiagnostic(HOOK, "result", `verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}`, {
478
- decision: shouldDeny ? "deny" : "allow",
479
- reasoning: `reason=${denyReason}, score=${reviewScore.toFixed(2)}`,
480
- inputs: {
481
- overall_verdict: combinedResult.overall_verdict,
482
- review_score: Math.round(reviewScore * 100) / 100,
483
- agent_count: Object.keys(agentResults).length,
484
- },
485
- });
486
-
487
- // Terminal progress
488
- const verdictEmoji = shouldDeny ? "❌" : "✅";
489
- eprint(`[plan-review] ${verdictEmoji} ${combinedResult.overall_verdict.toUpperCase()} (score=${reviewScore.toFixed(2)})`);
490
- if (shouldDeny) {
491
- eprint(`[plan-review] Blocking ExitPlanMode — ${denyReason}`);
492
- }
493
-
494
- // 13. Advance iteration
495
- const advancement = advanceIterationState(
496
- iterationState, planHash, planPath, overall, shouldDeny, passEligible, agentResults,
497
- );
498
- iterationState = advancement.updatedState;
499
- if (advancement.newGraduates.length > 0) {
500
- logInfo(HOOK, `Newly graduated (2 consecutive passes): ${advancement.newGraduates.join(", ")}`);
501
- }
502
-
503
- // 14. Save iteration state
504
- saveIterationState(reviewsDir, iterationState);
505
-
506
- // Write review tracker
507
- const ccNativeReviewsDir = path.dirname(reviewFolder);
508
- const trackerDecision = shouldDeny ? "blocked" : "allow";
509
- const topIssuesList = extractTopIssuesForTracker(combinedResult, 5);
510
- const trackerEntry: ReviewTrackerEntry = {
511
- iteration: currentIteration,
512
- timestamp: new Date().toISOString().replace("T", " ").slice(0, 16),
513
- planHash,
514
- verdict: combinedResult.overall_verdict,
515
- decision: trackerDecision,
516
- score: reviewScore,
517
- topIssues: topIssuesList,
518
- reviewFolder,
519
- };
520
- writeReviewTracker(ccNativeReviewsDir, trackerEntry);
521
- logInfo(HOOK, `Updated review tracker: ${path.join(ccNativeReviewsDir, "review-tracker.md")}`);
522
-
523
- // Build final output
524
- const output = buildReviewOutput({
525
- combinedResult,
526
- corroborationResult,
527
- iterationState: { ...iterationState, current: currentIteration },
528
- reviewFile,
529
- highIssuesPath,
530
- });
531
-
532
- // Mark plan reviewed
533
- const disposition = shouldDeny
534
- ? `hook_deny_iter_${currentIteration}`
535
- : `hook_allow_iter_${currentIteration}`;
536
- markPlanReviewed(sessionId, planHash, base, HOOK, { ...iterationState, current: currentIteration }, disposition);
537
-
538
- return {
539
- action: "block",
540
- contextText: output.contextText,
541
- blockReason: output.blockReason,
542
- };
543
- }
1
+ /**
2
+ * Review pipeline: orchestrates the full plan review lifecycle.
3
+ * Wires together plan-discovery, settings, agent-selection, graduation,
4
+ * output-builder, and existing modules (orchestrator, corroboration, etc.).
5
+ */
6
+
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+
10
+ import {
11
+ logDebug,
12
+ logInfo,
13
+ logWarn,
14
+ logError,
15
+ } from "../../_core/lib-ts/runtime/logger.js";
16
+ import { logDiagnostic } from "../../_core/lib-ts/hooks/hook-utils.js";
17
+ import { eprint } from "../../_core/lib-ts/runtime/utils.js";
18
+ import { getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_core/lib-ts/runtime/constants.js";
19
+ import { getContextBySessionId, getAllContexts } from "../../_core/lib-ts/context/context-store.js";
20
+
21
+ import type {
22
+ AgentConfig,
23
+ OrchestratorConfig,
24
+ ReviewerResult,
25
+ CombinedReviewResult,
26
+ OrchestratorResult,
27
+ IterationState,
28
+ PipelineInput,
29
+ PipelineResult,
30
+ } from "./types.js";
31
+ import { REVIEW_SCHEMA } from "./types.js";
32
+ import type { ContextState } from "../../_core/lib-ts/types.js";
33
+
34
+ import { discoverPlan } from "./plan-discovery.js";
35
+ import { loadSettings, loadModelsConfig, loadAgentLibrary, DEFAULT_ORCHESTRATOR } from "./settings.js";
36
+ import { resolveMandatoryAgents, assignModelsToAgents, selectAgents } from "./agent-selection.js";
37
+ import { computePassEligible, extractTopIssuesForTracker, advanceIterationState } from "./graduation.js";
38
+ import { truncateAgentIssues, overrideVerdictsByThreshold, buildReviewOutput } from "./output-builder.js";
39
+ import { DEFAULT_REVIEW_ITERATIONS, loadIterationState, saveIterationState } from "./state.js";
40
+
41
+ import {
42
+ isPlanAlreadyReviewed,
43
+ wasPlanPreviouslyDenied,
44
+ getLastPlanReview,
45
+ markPlanReviewed,
46
+ wasPlanQuestionsAgentAsked,
47
+ markQuestionsAsked,
48
+ resetPlanQuestionsAsked,
49
+ } from "./cc-native-state.js";
50
+
51
+ import { computeCorroboratedDecision } from "./corroboration.js";
52
+ import { runOrchestrator } from "./orchestrator.js";
53
+ import { debugLog } from "./debug.js";
54
+ import { writeCombinedArtifacts, buildCorroborationReport, buildHighIssuesDocument, writeReviewTracker } from "./artifacts.js";
55
+ import type { ReviewTrackerEntry } from "./artifacts.js";
56
+ import { runAgentReview } from "./reviewers/index.js";
57
+ import { runPlanQuestions } from "./plan-questions.js";
58
+
59
+ const HOOK = "review-pipeline";
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Context Lookup (private — only used here)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ function getActiveContextForReview(sessionId: string, projectRoot: string): ContextState | null {
66
+ const ctx = getContextBySessionId(sessionId, projectRoot);
67
+ if (ctx) {
68
+ logInfo(HOOK, `Found context by session_id: ${ctx.id}`);
69
+ return ctx;
70
+ }
71
+ const allActive = getAllContexts("active", projectRoot);
72
+ const planning = allActive.filter(c => c.mode === "active" || c.mode === "has_plan");
73
+ if (planning.length === 1) {
74
+ logInfo(HOOK, `Found single planning context: ${planning[0]!.id}`);
75
+ return planning[0]!;
76
+ }
77
+ if (planning.length > 1) {
78
+ logWarn(HOOK, `Multiple planning contexts (${planning.length}), cannot determine which to use`);
79
+ } else if (allActive.length > 0) {
80
+ logInfo(HOOK, `Found ${allActive.length} active context(s) but none in planning mode`);
81
+ } else {
82
+ logInfo(HOOK, "No active contexts found");
83
+ }
84
+ return null;
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Pipeline
89
+ // ---------------------------------------------------------------------------
90
+
91
+ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineResult> {
92
+ const { sessionId, base, aiwcliDir, transcriptPath, payload } = input;
93
+
94
+ // 1. Load settings
95
+ const settings = loadSettings(aiwcliDir);
96
+ const planSettings = settings.planReview ?? {};
97
+ const agentSettings = settings.agentReview ?? {};
98
+
99
+ const planReviewEnabled = planSettings.enabled ?? true;
100
+ const agentReviewEnabled = agentSettings.enabled ?? true;
101
+
102
+ if (!planReviewEnabled && !agentReviewEnabled) {
103
+ return { action: "skip", reason: "Both plan and agent review disabled" };
104
+ }
105
+
106
+ // 2. Discover plan
107
+ const discovered = discoverPlan(transcriptPath);
108
+ if (!discovered) {
109
+ return { action: "skip", reason: "No plan file found in ~/.claude/plans/. The plan may not have been written yet." };
110
+ }
111
+
112
+ const { content: plan, hash: planHash, path: planPath } = discovered;
113
+
114
+ // 3. Find active context (moved before questions gate for plan path change detection)
115
+ const activeContext = getActiveContextForReview(sessionId, base);
116
+ if (!activeContext) {
117
+ return { action: "skip", reason: "No active planning context found for this session." };
118
+ }
119
+
120
+ const contextId = activeContext.id;
121
+ const reviewsDir = path.join(getContextReviewsDir(contextId, base), "cc-native");
122
+ const contextPath = getContextDir(contextId, base);
123
+
124
+ // 4a. Load iteration state
125
+ let iterationState: IterationState | null = loadIterationState(reviewsDir);
126
+
127
+ // 4a-migration. Backfill sessionId for old iteration files
128
+ if (iterationState && !iterationState.sessionId) {
129
+ logInfo(HOOK, `Migrating iteration state: adding sessionId=${sessionId}`);
130
+ iterationState.sessionId = sessionId;
131
+ saveIterationState(reviewsDir, iterationState); // Persist migration
132
+ }
133
+
134
+ // 4a-session. Detect session change — reset iteration state for new planning session
135
+ if (iterationState && iterationState.sessionId && iterationState.sessionId !== sessionId) {
136
+ logInfo(HOOK, `Session changed (${iterationState.sessionId} → ${sessionId}), resetting iteration state`);
137
+ iterationState = null; // Force fresh state creation below
138
+ }
139
+
140
+ // 4a-init. Initialize if null
141
+ if (!iterationState) {
142
+ iterationState = {
143
+ current: 1, max: 1, complexity: "medium",
144
+ history: [], graduated: [], passStreaks: {},
145
+ lastPlanHash: "", lastPlanPath: "",
146
+ sessionId: sessionId,
147
+ };
148
+ saveIterationState(reviewsDir, iterationState);
149
+ }
150
+
151
+ // 4b. Detect plan file change — reset iteration state for new plan topic
152
+ const lastPath = iterationState.lastPlanPath ?? "";
153
+ if (lastPath && lastPath !== planPath) {
154
+ logInfo(HOOK, `Plan file changed (${path.basename(lastPath)}→${path.basename(planPath)}), resetting iteration state for new plan`);
155
+ iterationState = {
156
+ current: 1, max: 1, complexity: "medium",
157
+ history: [], graduated: [], passStreaks: {},
158
+ lastPlanHash: "", lastPlanPath: "",
159
+ sessionId: sessionId,
160
+ };
161
+ saveIterationState(reviewsDir, iterationState);
162
+ resetPlanQuestionsAsked(sessionId, base);
163
+ }
164
+
165
+ // 5. Questions gate
166
+ if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
167
+ logInfo(HOOK, "Questions gate: plan-questions agent has not run yet, running now");
168
+ const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
169
+ const questionsResult = await runPlanQuestions(plan, aiwcliDir, timeout, undefined, sessionId);
170
+
171
+ markQuestionsAsked(sessionId, base, "agent");
172
+
173
+ const hasQuestions = questionsResult && (
174
+ questionsResult.questions.length > 0 ||
175
+ questionsResult.assumptions.length > 0 ||
176
+ questionsResult.ambiguities.length > 0
177
+ );
178
+
179
+ if (hasQuestions) {
180
+ const questionsList = questionsResult.questions.map((q: string, i: number) => `${i + 1}. ${q}`).join("\n");
181
+ const assumptionsList = questionsResult.assumptions.length > 0
182
+ ? `\n\nAssumptions detected:\n${questionsResult.assumptions.map((a: string) => `- ${a}`).join("\n")}`
183
+ : "";
184
+ const ambiguitiesList = questionsResult.ambiguities.length > 0
185
+ ? `\n\nAmbiguities detected:\n${questionsResult.ambiguities.map((a: string) => `- ${a}`).join("\n")}`
186
+ : "";
187
+ const contextMsg = `## Plan Questions (from independent review)\n\nAn agent reviewed your plan in a fresh context — without access to your session history or codebase exploration. It identified these questions:\n\n${questionsList}${assumptionsList}${ambiguitiesList}\n\nAsk the user these questions using AskUserQuestion before submitting the plan.`;
188
+ return {
189
+ action: "block",
190
+ contextText: contextMsg,
191
+ blockReason: "Ask the user clarifying questions before submitting the plan. Use AskUserQuestion with the questions above.",
192
+ };
193
+ }
194
+
195
+ logInfo(HOOK, "Questions gate: no questions generated, proceeding to review");
196
+ } else {
197
+ logInfo(HOOK, "Questions gate: agent already ran, skipping");
198
+ }
199
+
200
+ // 6. Hash + dedup
201
+ logDiagnostic(HOOK, "receive", `plan_size=${plan.length}, session=${sessionId.slice(0, 8)}`, {
202
+ inputs: { plan_hash: planHash, plan_size: plan.length, session_id: sessionId.slice(0, 12) },
203
+ });
204
+
205
+ // Plan-hash deduplication
206
+ logDebug(HOOK, `Plan hash: ${planHash}`);
207
+ if (isPlanAlreadyReviewed(sessionId, planHash, base)) {
208
+ const lastReview = getLastPlanReview(sessionId, planHash, base);
209
+
210
+ if (wasPlanPreviouslyDenied(sessionId, planHash, base)) {
211
+ return {
212
+ action: "block",
213
+ contextText: "[Plan Review] Plan content unchanged since last review which found issues.",
214
+ blockReason: "Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
215
+ };
216
+ } else {
217
+ const verdict = lastReview?.iteration?.latest_verdict || "pass";
218
+ return { action: "skip", reason: `Plan already reviewed (verdict: ${verdict}). Skipping re-review.` };
219
+ }
220
+ }
221
+
222
+ // 7. Iteration bounds check
223
+ if (iterationState.current > iterationState.max) {
224
+ return { action: "skip", reason: `Max review iterations reached (${iterationState.current - 1}/${iterationState.max}), allowing plan through.` };
225
+ }
226
+
227
+ // Initialize result containers
228
+ let orchResult: OrchestratorResult | null = null;
229
+ const agentResults: Record<string, ReviewerResult> = {};
230
+ let detectedComplexity = "medium";
231
+
232
+ // 7. PHASE 1: Orchestrator
233
+ const graduatedSet = new Set(iterationState.graduated);
234
+ if (graduatedSet.size > 0) {
235
+ logInfo(HOOK, `Graduated agents from previous iterations: ${[...graduatedSet].sort().join(", ")}`);
236
+ }
237
+
238
+ const agentLibrary = agentReviewEnabled ? loadAgentLibrary(aiwcliDir, agentSettings) : [];
239
+ const originalAgentCount = agentLibrary.length;
240
+ const enabledAgents = agentLibrary.filter(a => !graduatedSet.has(a.name));
241
+ const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
242
+ const legacyMode = agentSettings.legacyMode === true;
243
+
244
+ const orchSettings = agentSettings.orchestrator ?? DEFAULT_ORCHESTRATOR;
245
+ const orchestratorConfig: OrchestratorConfig = {
246
+ enabled: (orchSettings.enabled ?? true) && agentReviewEnabled,
247
+ model: orchSettings.model ?? "haiku",
248
+ timeout: orchSettings.timeout ?? 30,
249
+ };
250
+
251
+ const mandatoryConfig = agentSettings.mandatoryAgents ?? ["handoff-readiness", "clarity-auditor", "skeptic"];
252
+ const alwaysMandatory = resolveMandatoryAgents(mandatoryConfig, "simple");
253
+
254
+ logDebug(HOOK, `Agent library: ${agentLibrary.map(a => a.name)}`);
255
+ logDebug(HOOK, `Mandatory agents: ${[...alwaysMandatory].sort()}`);
256
+ logDebug(HOOK, `Orchestrator enabled: ${orchestratorConfig.enabled}`);
257
+
258
+ const phase1Promises: Array<{ name: string; promise: Promise<ReviewerResult | OrchestratorResult> }> = [];
259
+
260
+ if (orchestratorConfig.enabled && enabledAgents.length > 0 && !legacyMode) {
261
+ phase1Promises.push({
262
+ name: "orchestrator",
263
+ promise: runOrchestrator(plan, enabledAgents, orchestratorConfig, agentSettings, alwaysMandatory),
264
+ });
265
+ }
266
+
267
+ logInfo(HOOK, `=== PHASE 1: Running ${phase1Promises.length} tasks in parallel ===`);
268
+
269
+ if (phase1Promises.length > 0) {
270
+ const results = await Promise.allSettled(
271
+ phase1Promises.map(async ({ name, promise }) => {
272
+ const result = await promise;
273
+ return { name, result };
274
+ }),
275
+ );
276
+ for (const [i, r] of results.entries()) {
277
+ if (r.status === "fulfilled") {
278
+ if (r.value.name === "orchestrator") orchResult = r.value.result as OrchestratorResult;
279
+ logInfo(HOOK, `${r.value.name} completed`);
280
+ } else {
281
+ const failedName = phase1Promises[i]?.name ?? "unknown";
282
+ logError(HOOK, `${failedName} failed: ${r.reason}`);
283
+ }
284
+ }
285
+ }
286
+
287
+ // 8. PHASE 2: Agent Selection
288
+ let selectedAgents: AgentConfig[] = [];
289
+ let mandatoryNames = alwaysMandatory;
290
+
291
+ if (agentReviewEnabled) {
292
+ logInfo(HOOK, "=== PHASE 2: Agent Selection ===");
293
+
294
+ const selectionResult = selectAgents({
295
+ enabledAgents,
296
+ orchResult,
297
+ mandatoryConfig,
298
+ agentSettings,
299
+ legacyMode,
300
+ });
301
+
302
+ selectedAgents = selectionResult.selectedAgents;
303
+ mandatoryNames = selectionResult.mandatoryNames;
304
+ detectedComplexity = selectionResult.detectedComplexity;
305
+
306
+ logDiagnostic(HOOK, "decide", `Selected ${selectedAgents.length} agents, complexity=${detectedComplexity}`, {
307
+ decision: "agents_selected",
308
+ reasoning: `orchestrator=${orchResult !== null}, legacy=${legacyMode}`,
309
+ inputs: {
310
+ agents: selectedAgents.map(a => a.name),
311
+ complexity: detectedComplexity,
312
+ mandatory_count: selectedAgents.filter(a => mandatoryNames.has(a.name)).length,
313
+ },
314
+ });
315
+
316
+ // Update iteration state with complexity/max
317
+ const reviewIterations: Record<string, number> = {
318
+ ...DEFAULT_REVIEW_ITERATIONS,
319
+ ...(agentSettings.reviewIterations ?? {}),
320
+ };
321
+ iterationState.complexity = detectedComplexity;
322
+ iterationState.max = reviewIterations[detectedComplexity] ?? iterationState.max;
323
+ logDebug(HOOK, `Iteration state: ${iterationState.current}/${iterationState.max} (${detectedComplexity})`);
324
+
325
+ // Assign random providers + models
326
+ const modelsConfig = loadModelsConfig(settings);
327
+ selectedAgents = assignModelsToAgents(selectedAgents, modelsConfig);
328
+ logInfo(HOOK, `Model assignments: ${selectedAgents.map(a => `${a.name}→${a.provider}:${a.model}`).join(", ")}`);
329
+
330
+ // 9. PHASE 3: Run agents
331
+ if (selectedAgents.length > 0) {
332
+ logInfo(HOOK, "=== PHASE 3: Agent Reviews ===");
333
+ logInfo(HOOK, `Launching ${selectedAgents.length} agents in parallel`);
334
+
335
+ debugLog(contextPath, sessionId, "hook", "agent_review_start", {
336
+ agents: selectedAgents.map(a => a.name),
337
+ timeout,
338
+ complexity: detectedComplexity,
339
+ });
340
+
341
+ const agentPromises = selectedAgents.map(async agent => {
342
+ const result = await runAgentReview(plan, agent, REVIEW_SCHEMA, timeout, contextPath, sessionId);
343
+ return { agent, result };
344
+ });
345
+
346
+ const agentSettled = await Promise.allSettled(agentPromises);
347
+ for (const [i, r] of agentSettled.entries()) {
348
+ if (r.status === "fulfilled") {
349
+ const { agent, result } = r.value;
350
+ agentResults[agent.name] = result;
351
+ logInfo(HOOK, `${agent.name} completed with verdict: ${result.verdict}`);
352
+ } else {
353
+ const failedAgent = selectedAgents[i]!;
354
+ logError(HOOK, `${failedAgent.name} failed with exception: ${r.reason}`);
355
+ agentResults[failedAgent.name] = {
356
+ name: failedAgent.name,
357
+ ok: false,
358
+ verdict: "error",
359
+ data: {},
360
+ raw: "",
361
+ err: String(r.reason),
362
+ };
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ // 10. Issue truncation + verdict override
369
+ const maxIssuesPerAgent = typeof agentSettings.maxIssuesPerAgent === "number"
370
+ ? agentSettings.maxIssuesPerAgent : 3;
371
+ truncateAgentIssues(agentResults, maxIssuesPerAgent);
372
+
373
+ const passEligible = computePassEligible(agentResults);
374
+ if (passEligible.length > 0) {
375
+ logInfo(HOOK, `Pass-eligible agents this iteration: ${passEligible.join(", ")}`);
376
+ }
377
+
378
+ const highIssueThreshold = typeof agentSettings.highIssueThreshold === "number" ? agentSettings.highIssueThreshold : 3;
379
+ overrideVerdictsByThreshold(agentResults, highIssueThreshold);
380
+
381
+ // PHASE 4: Generate Output
382
+ logInfo(HOOK, "=== PHASE 4: Generate Output ===");
383
+
384
+ if (Object.keys(agentResults).length === 0) {
385
+ if (graduatedSet.size > 0 && originalAgentCount > 0) {
386
+ return { action: "skip", reason: "All agent reviewers graduated from previous iterations — no review needed." };
387
+ }
388
+ return { action: "skip", reason: "All reviewers failed to produce results. Check stderr logs for details." };
389
+ }
390
+
391
+ // 11. Corroboration
392
+ const corroborationResult = computeCorroboratedDecision(agentResults);
393
+ const overall = corroborationResult.verdict;
394
+
395
+ const combinedResult: CombinedReviewResult = {
396
+ plan_hash: planHash,
397
+ overall_verdict: overall,
398
+ orchestration: orchResult,
399
+ agents: agentResults,
400
+ timestamp: new Date().toISOString(),
401
+ };
402
+
403
+ const displaySettings = {
404
+ ...(planSettings.display ?? {}),
405
+ ...(agentSettings.display ?? {}),
406
+ };
407
+ const combinedSettings = { display: displaySettings };
408
+
409
+ const currentIteration = iterationState.current;
410
+
411
+ // 12. Write artifacts
412
+ const reviewFolder = getReviewFolderPath(contextId, currentIteration, base);
413
+ fs.mkdirSync(reviewFolder, { recursive: true });
414
+ logInfo(HOOK, `Created review folder: ${reviewFolder}`);
415
+
416
+ const reviewFile = writeCombinedArtifacts(
417
+ base, plan, combinedResult, payload, combinedSettings,
418
+ undefined, reviewFolder, currentIteration, corroborationResult,
419
+ );
420
+ logInfo(HOOK, `Saved review: ${reviewFile}`);
421
+
422
+ const corroborationReport = buildCorroborationReport(corroborationResult);
423
+ const corroborationPath = path.join(reviewFolder, "corroboration.md");
424
+ fs.writeFileSync(corroborationPath, corroborationReport, "utf-8");
425
+ logInfo(HOOK, `Saved corroboration report: ${corroborationPath}`);
426
+
427
+ try {
428
+ fs.writeFileSync(path.join(reviewFolder, "plan.md"), plan, "utf-8");
429
+ logDebug(HOOK, `Saved plan snapshot: ${path.join(reviewFolder, "plan.md")}`);
430
+ } catch (e) {
431
+ logWarn(HOOK, `Failed to save plan snapshot: ${e}`);
432
+ }
433
+
434
+ // Build high-issues document
435
+ const highIssuesDoc = buildHighIssuesDocument(combinedResult, corroborationResult);
436
+ const highIssuesPath = path.join(reviewFolder, "high-issues.md");
437
+ fs.writeFileSync(highIssuesPath, highIssuesDoc, "utf-8");
438
+
439
+ // 15. Build output
440
+ const shouldDeny = corroborationResult.blocking.length > 0;
441
+ const reviewScore = shouldDeny ? 1.0 : 0.0;
442
+ const denyReason = shouldDeny ? "corroborated_issues" : "no_corroboration";
443
+
444
+ logInfo(HOOK, `REVIEW_DECISION: verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}, score=${reviewScore.toFixed(2)}`);
445
+ logDiagnostic(HOOK, "result", `verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}`, {
446
+ decision: shouldDeny ? "deny" : "allow",
447
+ reasoning: `reason=${denyReason}, score=${reviewScore.toFixed(2)}`,
448
+ inputs: {
449
+ overall_verdict: combinedResult.overall_verdict,
450
+ review_score: Math.round(reviewScore * 100) / 100,
451
+ agent_count: Object.keys(agentResults).length,
452
+ },
453
+ });
454
+
455
+ // Terminal progress
456
+ const verdictEmoji = shouldDeny ? "" : "✅";
457
+ eprint(`[plan-review] ${verdictEmoji} ${combinedResult.overall_verdict.toUpperCase()} (score=${reviewScore.toFixed(2)})`);
458
+ if (shouldDeny) {
459
+ eprint(`[plan-review] Blocking ExitPlanMode — ${denyReason}`);
460
+ }
461
+
462
+ // 13. Advance iteration
463
+ const advancement = advanceIterationState(
464
+ iterationState, planHash, planPath, overall, shouldDeny, passEligible, agentResults,
465
+ );
466
+ iterationState = advancement.updatedState;
467
+ if (advancement.newGraduates.length > 0) {
468
+ logInfo(HOOK, `Newly graduated (2 consecutive passes): ${advancement.newGraduates.join(", ")}`);
469
+ }
470
+
471
+ // 14. Save iteration state
472
+ saveIterationState(reviewsDir, iterationState);
473
+
474
+ // Write review tracker
475
+ const ccNativeReviewsDir = path.dirname(reviewFolder);
476
+ const trackerDecision = shouldDeny ? "blocked" : "allow";
477
+ const topIssuesList = extractTopIssuesForTracker(combinedResult, 5);
478
+ const trackerEntry: ReviewTrackerEntry = {
479
+ iteration: currentIteration,
480
+ timestamp: new Date().toISOString().replace("T", " ").slice(0, 16),
481
+ planHash,
482
+ verdict: combinedResult.overall_verdict,
483
+ decision: trackerDecision,
484
+ score: reviewScore,
485
+ topIssues: topIssuesList,
486
+ reviewFolder,
487
+ };
488
+ writeReviewTracker(ccNativeReviewsDir, trackerEntry);
489
+ logInfo(HOOK, `Updated review tracker: ${path.join(ccNativeReviewsDir, "review-tracker.md")}`);
490
+
491
+ // Build final output
492
+ const output = buildReviewOutput({
493
+ combinedResult,
494
+ corroborationResult,
495
+ iterationState: { ...iterationState, current: currentIteration },
496
+ reviewFile,
497
+ highIssuesPath,
498
+ });
499
+
500
+ // Mark plan reviewed
501
+ const disposition = shouldDeny
502
+ ? `hook_deny_iter_${currentIteration}`
503
+ : `hook_allow_iter_${currentIteration}`;
504
+ markPlanReviewed(sessionId, planHash, base, HOOK, { ...iterationState, current: currentIteration }, disposition);
505
+
506
+ return {
507
+ action: "block",
508
+ contextText: output.contextText,
509
+ blockReason: output.blockReason,
510
+ };
511
+ }