aiwcli 0.10.2 → 0.11.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 (249) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.d.ts +11 -6
  3. package/dist/commands/clear.js +229 -381
  4. package/dist/commands/init/index.d.ts +1 -17
  5. package/dist/commands/init/index.js +22 -107
  6. package/dist/lib/gitignore-manager.d.ts +32 -0
  7. package/dist/lib/gitignore-manager.js +141 -2
  8. package/dist/lib/template-installer.d.ts +7 -12
  9. package/dist/lib/template-installer.js +69 -193
  10. package/dist/lib/template-settings-reconstructor.d.ts +35 -0
  11. package/dist/lib/template-settings-reconstructor.js +130 -0
  12. package/dist/templates/CLAUDE.md +8 -8
  13. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  14. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  17. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  18. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  19. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  20. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  21. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  22. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  23. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  24. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  25. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  26. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  27. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
  29. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
  30. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
  31. package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
  32. package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
  33. package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
  34. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
  35. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
  36. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
  37. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
  38. package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
  39. package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
  40. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
  41. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
  42. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  44. package/dist/templates/_shared/lib-ts/package.json +21 -0
  45. package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
  46. package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
  47. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
  48. package/dist/templates/_shared/lib-ts/types.ts +164 -0
  49. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  50. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  51. package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
  52. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  53. package/dist/templates/cc-native/.claude/settings.json +175 -185
  54. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  55. package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
  56. package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
  57. package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
  58. package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
  59. package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
  60. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
  61. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
  62. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
  63. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
  64. package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
  65. package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
  66. package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
  67. package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
  68. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
  69. package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
  70. package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
  71. package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
  72. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
  73. package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
  74. package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
  75. package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
  76. package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
  77. package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
  78. package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
  79. package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
  80. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
  81. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
  82. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
  83. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
  85. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
  86. package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
  87. package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
  88. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  89. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  90. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  91. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  92. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  93. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  94. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  95. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  96. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  97. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  98. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  99. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  100. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  101. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  102. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  103. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  104. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  105. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  106. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  107. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  108. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  109. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  110. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  111. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
  113. package/oclif.manifest.json +1 -1
  114. package/package.json +1 -1
  115. package/dist/lib/template-merger.d.ts +0 -47
  116. package/dist/lib/template-merger.js +0 -162
  117. package/dist/templates/_shared/hooks/__init__.py +0 -16
  118. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/hooks/archive_plan.py +0 -169
  131. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  132. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  133. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  134. package/dist/templates/_shared/hooks/session_end.py +0 -173
  135. package/dist/templates/_shared/hooks/session_start.py +0 -206
  136. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  137. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  138. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  139. package/dist/templates/_shared/lib/__init__.py +0 -1
  140. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  142. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  151. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  152. package/dist/templates/_shared/lib/base/constants.py +0 -358
  153. package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
  154. package/dist/templates/_shared/lib/base/inference.py +0 -318
  155. package/dist/templates/_shared/lib/base/logger.py +0 -291
  156. package/dist/templates/_shared/lib/base/stop_words.py +0 -213
  157. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  158. package/dist/templates/_shared/lib/base/utils.py +0 -242
  159. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  160. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  161. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  162. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  163. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  164. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  165. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  166. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  167. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  168. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  169. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  170. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  171. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  172. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  173. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  174. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  175. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  176. package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
  177. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  178. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  179. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  180. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  181. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  182. package/dist/templates/_shared/lib/templates/README.md +0 -206
  183. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  184. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  185. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  186. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  187. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  188. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  189. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  190. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  191. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  192. package/dist/templates/_shared/scripts/status_line.py +0 -701
  193. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  194. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  195. package/dist/templates/cc-native/MIGRATION.md +0 -86
  196. package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
  197. package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
  198. package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
  199. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
  200. package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
  201. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
  202. package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
  203. package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
  204. package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
  205. package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
  206. package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
  207. package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
  208. package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
  209. package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
  210. package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
  211. package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
  212. package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
  213. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  214. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  215. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  216. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  217. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  218. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  219. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  220. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
  221. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  222. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  223. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  224. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  225. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  227. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  228. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  229. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  230. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  231. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  232. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  233. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  234. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  235. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  236. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  238. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  239. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  240. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  241. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  242. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  243. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  244. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  245. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  246. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
  247. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  248. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  249. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -1,869 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- CC-Native Plan Review Hook (Unified)
4
-
5
- Claude Code PreToolUse hook that intercepts ExitPlanMode and
6
- automatically reviews plans using:
7
- 1. CLI reviewers (Codex + Gemini)
8
- 2. Plan orchestrator for complexity analysis
9
- 3. Claude Code agents in parallel
10
-
11
- Trigger: ExitPlanMode tool use (PreToolUse - runs BEFORE user approval prompt)
12
-
13
- Features:
14
- - Detects plans via ExitPlanMode PreToolUse
15
- - Phase 1: Runs CLI reviewers (Codex/Gemini) if enabled
16
- - Phase 2: Runs orchestrator to analyze complexity and select agents
17
- - Phase 3: Runs selected agents in parallel
18
- - Phase 4: Generates combined output (single JSON + single Markdown)
19
- - Returns feedback to Claude via hook additionalContext
20
- - Optional blocking on FAIL verdict
21
-
22
- Configuration: _cc-native/plan-review.config.json -> planReview, agentReview
23
-
24
- Output: _output/cc-native/plans/{YYYY-MM-DD}/{slug}/reviews/
25
- - review.json (combined review data)
26
- - review.md (combined markdown)
27
- - {reviewer}.json (individual reviewer results)
28
- """
29
-
30
- import json
31
- import os
32
- import random
33
- import sys
34
- from concurrent.futures import ThreadPoolExecutor, as_completed
35
- from datetime import datetime
36
- from pathlib import Path
37
- from typing import Any, Dict, List, Optional
38
-
39
- # Import shared library
40
- try:
41
- _lib = Path(__file__).parent.parent / "lib"
42
- sys.path.insert(0, str(_lib))
43
-
44
- # Add shared library path
45
- _shared = Path(__file__).parent.parent.parent / "_shared"
46
- sys.path.insert(0, str(_shared))
47
-
48
- # Import subprocess and hook utilities
49
- from lib.base.subprocess_utils import is_internal_call
50
- from lib.base.hook_utils import emit_context, emit_context_and_block
51
- from lib.base.logger import log_debug, log_info, log_warn, log_error, log_diagnostic
52
-
53
- from utils import (
54
- DEFAULT_DISPLAY,
55
- DEFAULT_SANITIZATION,
56
- REVIEW_SCHEMA,
57
- ReviewerResult,
58
- CombinedReviewResult,
59
- project_dir,
60
- eprint,
61
- find_plan_file,
62
- compute_plan_hash,
63
- compute_review_decision,
64
- is_plan_already_reviewed,
65
- was_plan_previously_denied,
66
- mark_plan_reviewed,
67
- worst_verdict,
68
- format_combined_markdown,
69
- write_combined_artifacts,
70
- build_inline_review_summary,
71
- extract_top_issues_text,
72
- load_config,
73
- get_display_settings,
74
- )
75
- from reviewers import (
76
- run_codex_review,
77
- run_gemini_review,
78
- run_agent_review,
79
- AgentConfig,
80
- OrchestratorConfig,
81
- )
82
- from orchestrator import (
83
- run_orchestrator,
84
- DEFAULT_AGENT_SELECTION,
85
- DEFAULT_COMPLEXITY_CATEGORIES,
86
- )
87
- # Import shared context system
88
- from lib.context.context_store import (
89
- get_context_by_session_id,
90
- get_all_contexts,
91
- )
92
- from lib.base.constants import get_context_reviews_dir, get_review_folder_path, get_context_dir
93
- from debug import debug_log, debug_raw
94
- except ImportError as e:
95
- try:
96
- from lib.base.logger import log_error as _early_log_error
97
- _early_log_error("cc-native-plan-review", f"Failed to import lib: {e}")
98
- except Exception:
99
- print(f"[cc-native-plan-review] Failed to import lib: {e}", file=sys.stderr)
100
- print(json.dumps({
101
- "hookSpecificOutput": {
102
- "additionalContext": f"[Plan Review Error] Failed to import required module: {e}. The plan review hook could not load its dependencies.",
103
- }
104
- }, ensure_ascii=True))
105
- sys.exit(0) # Non-blocking failure
106
-
107
- # Add scripts directory to path for aggregate_agents import
108
- _scripts_dir = Path(__file__).parent.parent / "scripts"
109
- if str(_scripts_dir) not in sys.path:
110
- sys.path.insert(0, str(_scripts_dir))
111
-
112
- try:
113
- from aggregate_agents import aggregate_agents
114
- except ImportError:
115
- def aggregate_agents(agents_dir: Path) -> List[Dict[str, Any]]:
116
- log_warn("cc-native-plan-review", "aggregate_agents not found")
117
- return []
118
-
119
-
120
- def skip_with_info(reason: str) -> int:
121
- """Exit hook with informational additionalContext instead of silently.
122
-
123
- This ensures Claude always sees WHY the plan review was skipped,
124
- making failures diagnosable instead of invisible.
125
- """
126
- log_info("cc-native-plan-review", f"Skipping: {reason}")
127
- emit_context(f"[Plan Review Skipped] {reason}", ensure_ascii=True)
128
- return 0
129
-
130
-
131
- # ---------------------------
132
- # Default Configuration
133
- # ---------------------------
134
-
135
- DEFAULT_AGENTS: List[Dict[str, Any]] = [
136
- {"name": "architect-reviewer", "model": "sonnet", "focus": "architectural concerns and scalability", "enabled": True, "categories": ["code", "infrastructure", "design"]},
137
- {"name": "penetration-tester", "model": "sonnet", "focus": "security vulnerabilities and attack vectors", "enabled": True, "categories": ["code", "infrastructure"]},
138
- {"name": "performance-engineer", "model": "sonnet", "focus": "performance bottlenecks and optimization", "enabled": True, "categories": ["code", "infrastructure"]},
139
- {"name": "accessibility-tester", "model": "sonnet", "focus": "accessibility compliance and UX concerns", "enabled": True, "categories": ["code", "design"]},
140
- ]
141
-
142
- DEFAULT_ORCHESTRATOR: Dict[str, Any] = {
143
- "enabled": True,
144
- "model": "haiku",
145
- "timeout": 30,
146
- }
147
-
148
- DEFAULT_AGENT_MODEL: str = "sonnet"
149
-
150
- DEFAULT_REVIEW_ITERATIONS: Dict[str, int] = {
151
- "simple": 1,
152
- "medium": 2,
153
- "high": 2,
154
- }
155
-
156
-
157
- # ---------------------------
158
- # Context-based State Management
159
- # ---------------------------
160
-
161
- def get_active_context_for_review(session_id: str, project_root: Path) -> Optional[Any]:
162
- """Find active context for plan review.
163
-
164
- Strategy:
165
- 1. Find context by session_id
166
- 2. Fallback: Single context in 'planning' mode
167
- 3. Return None if multiple planning contexts or no planning contexts found
168
-
169
- Only triggers for contexts in 'planning' mode, not 'handoff_pending' or other modes.
170
-
171
- Args:
172
- session_id: Current session ID
173
- project_root: Project root path
174
-
175
- Returns:
176
- Context object or None
177
- """
178
- # Strategy 1: Find by session_id
179
- context = get_context_by_session_id(session_id, project_root)
180
- if context:
181
- log_info("cc-native-plan-review", f"Found context by session_id: {context.id}")
182
- return context
183
-
184
- # Strategy 2: Single planning context (only planning mode)
185
- all_active = get_all_contexts(status="active", project_root=project_root)
186
- # In the new system, "planning" is runtime-only (not persisted).
187
- # Since this hook fires during ExitPlanMode, any active non-idle context is a candidate.
188
- planning_contexts = [c for c in all_active if c.mode in ("active", "has_plan")]
189
- if len(planning_contexts) == 1:
190
- log_info("cc-native-plan-review", f"Found single planning context: {planning_contexts[0].id}")
191
- return planning_contexts[0]
192
-
193
- # Multiple or no planning contexts found
194
- if len(planning_contexts) > 1:
195
- log_warn("cc-native-plan-review", f"Multiple planning contexts ({len(planning_contexts)}), cannot determine which to use")
196
- elif len(all_active) > 0:
197
- modes = [c.mode for c in all_active]
198
- log_info("cc-native-plan-review", f"Found {len(all_active)} active context(s) with modes {modes}, but none in 'planning' mode")
199
- else:
200
- log_info("cc-native-plan-review", "No active contexts found")
201
- return None
202
-
203
-
204
- def load_iteration_state(reviews_dir: Path) -> Optional[Dict[str, Any]]:
205
- """Load iteration state from context reviews folder.
206
-
207
- Args:
208
- reviews_dir: Path to the reviews directory
209
-
210
- Returns:
211
- Iteration state dict or None if not found
212
- """
213
- iteration_file = reviews_dir / "iteration.json"
214
- if not iteration_file.exists():
215
- return None
216
-
217
- try:
218
- return json.loads(iteration_file.read_text(encoding="utf-8"))
219
- except Exception as e:
220
- log_error("cc-native-plan-review", f"Failed to load iteration state: {e}")
221
- return None
222
-
223
-
224
- def save_iteration_state(reviews_dir: Path, state: Dict[str, Any]) -> bool:
225
- """Save iteration state to context reviews folder.
226
-
227
- Args:
228
- reviews_dir: Path to the reviews directory
229
- state: Iteration state dict
230
-
231
- Returns:
232
- True on success, False on failure
233
- """
234
- iteration_file = reviews_dir / "iteration.json"
235
- try:
236
- reviews_dir.mkdir(parents=True, exist_ok=True)
237
- state["schema_version"] = "1.0.0"
238
- iteration_file.write_text(json.dumps(state, indent=2), encoding="utf-8")
239
- return True
240
- except Exception as e:
241
- log_error("cc-native-plan-review", f"Failed to save iteration state: {e}")
242
- return False
243
-
244
-
245
- def get_iteration_state_from_context(
246
- reviews_dir: Path,
247
- complexity: str,
248
- config: Optional[Dict[str, Any]] = None,
249
- ) -> Dict[str, Any]:
250
- """Get or initialize iteration state based on complexity.
251
-
252
- Args:
253
- reviews_dir: Path to the reviews directory
254
- complexity: Plan complexity level (simple/medium/high)
255
- config: Optional config dict with reviewIterations settings
256
-
257
- Returns:
258
- Iteration dict with: current, max, complexity, history
259
- """
260
- existing = load_iteration_state(reviews_dir)
261
- if existing:
262
- return existing
263
-
264
- # Initialize new iteration state
265
- review_iterations = DEFAULT_REVIEW_ITERATIONS.copy()
266
- if config:
267
- review_iterations.update(config.get("reviewIterations", {}))
268
- max_iterations = review_iterations.get(complexity, 1)
269
-
270
- return {
271
- "current": 1,
272
- "max": max_iterations,
273
- "complexity": complexity,
274
- "history": [],
275
- }
276
-
277
-
278
- def update_iteration_state_in_context(
279
- reviews_dir: Path,
280
- iteration: Dict[str, Any],
281
- plan_hash: str,
282
- verdict: str,
283
- ) -> Dict[str, Any]:
284
- """Record review result in iteration history.
285
-
286
- Args:
287
- reviews_dir: Path to the reviews directory
288
- iteration: The iteration state dict
289
- plan_hash: Hash of the current plan content
290
- verdict: Review verdict (pass/warn/fail)
291
-
292
- Returns:
293
- Updated iteration state dict
294
- """
295
- from datetime import datetime
296
-
297
- iteration["history"].append({
298
- "hash": plan_hash,
299
- "verdict": verdict,
300
- "timestamp": datetime.now().isoformat(),
301
- })
302
- return iteration
303
-
304
-
305
- def should_continue_iterating_context(
306
- iteration: Dict[str, Any],
307
- review_score: float,
308
- config: Optional[Dict[str, Any]] = None,
309
- ) -> bool:
310
- """Determine if more review iterations are needed.
311
-
312
- Args:
313
- iteration: The iteration state dict
314
- review_score: Score from compute_review_decision (0.0 = all pass, >0 = concerns)
315
- config: Optional config dict with earlyExitOnAllPass setting
316
-
317
- Returns:
318
- True if more iterations needed, False otherwise
319
- """
320
- current = iteration.get("current", 1)
321
- max_iter = iteration.get("max", 1)
322
-
323
- # At or past max iterations - no more iterations
324
- if current >= max_iter:
325
- log_info("cc-native-plan-review", f"At max iterations ({current}/{max_iter}), no more iterations")
326
- return False
327
-
328
- # Check early exit on all pass
329
- early_exit = False
330
- if config:
331
- early_exit = config.get("earlyExitOnAllPass", False)
332
- if early_exit and review_score == 0.0:
333
- log_info("cc-native-plan-review", "All reviewers passed (score=0.0) and earlyExitOnAllPass=true, exiting early")
334
- return False
335
-
336
- # More iterations available and score is not zero (or early exit disabled)
337
- log_info("cc-native-plan-review", f"Continuing to next iteration ({current + 1}/{max_iter}), score={review_score:.2f}")
338
- return True
339
-
340
-
341
- # ---------------------------
342
- # Settings Loading
343
- # ---------------------------
344
-
345
- def load_settings(proj_dir: Path) -> Dict[str, Any]:
346
- """Load CC-Native settings from _cc-native/plan-review.config.json"""
347
- defaults = {
348
- "planReview": {
349
- "enabled": True,
350
- "reviewers": {
351
- "codex": {"enabled": True, "model": "", "timeout": 120},
352
- "gemini": {"enabled": False, "model": "", "timeout": 120},
353
- },
354
- "display": DEFAULT_DISPLAY.copy(),
355
- },
356
- "agentReview": {
357
- "enabled": True,
358
- "orchestrator": DEFAULT_ORCHESTRATOR.copy(),
359
- "timeout": 180,
360
- "warnThreshold": 0.5,
361
- "legacyMode": False,
362
- "display": DEFAULT_DISPLAY.copy(),
363
- "agentSelection": DEFAULT_AGENT_SELECTION.copy(),
364
- "agentDefaults": {"model": DEFAULT_AGENT_MODEL},
365
- "complexityCategories": DEFAULT_COMPLEXITY_CATEGORIES.copy(),
366
- "sanitization": DEFAULT_SANITIZATION.copy(),
367
- },
368
- }
369
-
370
- config = load_config(proj_dir)
371
- if not config:
372
- return defaults
373
-
374
- # Merge planReview settings
375
- plan_review = config.get("planReview", {})
376
- merged_plan = defaults["planReview"].copy()
377
- merged_plan.update(plan_review)
378
- if "reviewers" in plan_review:
379
- merged_plan["reviewers"] = defaults["planReview"]["reviewers"].copy()
380
- merged_plan["reviewers"].update(plan_review["reviewers"])
381
- merged_plan["display"] = get_display_settings(config, "planReview")
382
-
383
- # Merge agentReview settings
384
- agent_review = config.get("agentReview", {})
385
- merged_agent = defaults["agentReview"].copy()
386
- merged_agent.update(agent_review)
387
-
388
- # Handle orchestrator nested config
389
- if "orchestrator" not in merged_agent or not isinstance(merged_agent["orchestrator"], dict):
390
- merged_agent["orchestrator"] = DEFAULT_ORCHESTRATOR.copy()
391
- else:
392
- orch = DEFAULT_ORCHESTRATOR.copy()
393
- orch.update(merged_agent["orchestrator"])
394
- merged_agent["orchestrator"] = orch
395
-
396
- merged_agent["display"] = get_display_settings(config, "agentReview")
397
- merged_agent["agentSelection"] = {**DEFAULT_AGENT_SELECTION, **config.get("agentSelection", {})}
398
- merged_agent["agentDefaults"] = {**{"model": DEFAULT_AGENT_MODEL}, **config.get("agentDefaults", {})}
399
- merged_agent["complexityCategories"] = config.get("complexityCategories", DEFAULT_COMPLEXITY_CATEGORIES.copy())
400
- merged_agent["sanitization"] = {**DEFAULT_SANITIZATION, **config.get("sanitization", {})}
401
-
402
- # Merge reviewIterations settings
403
- merged_agent["reviewIterations"] = {**DEFAULT_REVIEW_ITERATIONS, **agent_review.get("reviewIterations", {})}
404
- merged_agent["earlyExitOnAllPass"] = agent_review.get("earlyExitOnAllPass", False)
405
-
406
- return {"planReview": merged_plan, "agentReview": merged_agent}
407
-
408
-
409
- def load_agent_library(proj_dir: Path, settings: Optional[Dict[str, Any]] = None) -> List[AgentConfig]:
410
- """Load agent library by auto-detecting from frontmatter.
411
-
412
- Agents are loaded from _cc-native/agents/ directory. The markdown body
413
- of each agent file becomes the system_prompt for --system-prompt invocation.
414
- """
415
- # aggregate_agents now defaults to _cc-native/agents/ relative to the script
416
- agents_data = aggregate_agents()
417
-
418
- default_model = DEFAULT_AGENT_MODEL
419
- if settings:
420
- default_model = settings.get("agentDefaults", {}).get("model", DEFAULT_AGENT_MODEL)
421
-
422
- if not agents_data:
423
- log_info("cc-native-plan-review", "No agents found in frontmatter, using defaults")
424
- return [
425
- AgentConfig(
426
- name=a["name"],
427
- model=a.get("model", default_model),
428
- focus=a.get("focus", "general review"),
429
- enabled=a.get("enabled", True),
430
- categories=a.get("categories", ["code"]),
431
- )
432
- for a in DEFAULT_AGENTS
433
- ]
434
-
435
- agents = []
436
- for a in agents_data:
437
- if a.get("name") == "plan-orchestrator":
438
- continue
439
- agents.append(AgentConfig(
440
- name=a["name"],
441
- model=a.get("model", default_model),
442
- focus=a.get("focus", "general review"),
443
- enabled=a.get("enabled", True),
444
- categories=a.get("categories", ["code"]),
445
- description=a.get("description", ""),
446
- system_prompt=a.get("system_prompt", ""),
447
- ))
448
-
449
- return agents
450
-
451
-
452
- # ---------------------------
453
- # Main Hook
454
- # ---------------------------
455
-
456
- def main() -> int:
457
- log_info("cc-native-plan-review", "Unified hook started (PreToolUse)")
458
-
459
- # Skip if internal subprocess call (orchestrator, agents)
460
- if is_internal_call():
461
- log_debug("cc-native-plan-review", "Skipping: internal subprocess call")
462
- return 0
463
-
464
- try:
465
- payload = json.load(sys.stdin)
466
- except json.JSONDecodeError as e:
467
- return skip_with_info(f"Invalid JSON input from Claude Code: {e}")
468
-
469
- tool_name = payload.get("tool_name")
470
- log_debug("cc-native-plan-review", f"tool_name: {tool_name}")
471
-
472
- # Only process ExitPlanMode
473
- if tool_name != "ExitPlanMode":
474
- log_debug("cc-native-plan-review", "Skipping: not ExitPlanMode")
475
- return 0
476
-
477
- session_id = str(payload.get("session_id", "unknown"))
478
- base = project_dir(payload)
479
- settings = load_settings(base)
480
-
481
- plan_settings = settings.get("planReview", {})
482
- agent_settings = settings.get("agentReview", {})
483
-
484
- plan_review_enabled = plan_settings.get("enabled", True)
485
- agent_review_enabled = agent_settings.get("enabled", True)
486
-
487
- if not plan_review_enabled and not agent_review_enabled:
488
- log_info("cc-native-plan-review", "Skipping: both plan and agent review disabled")
489
- return 0
490
-
491
- # Find and read plan FIRST (state file is keyed by plan path)
492
- plan_path = find_plan_file()
493
- if not plan_path:
494
- return skip_with_info("No plan file found in ~/.claude/plans/. The plan may not have been written yet.")
495
-
496
- try:
497
- plan = Path(plan_path).read_text(encoding="utf-8").strip()
498
- except Exception as e:
499
- return skip_with_info(f"Failed to read plan file: {e}")
500
-
501
- if not plan:
502
- return skip_with_info("Plan file exists but is empty.")
503
-
504
- log_info("cc-native-plan-review", f"Found plan at: {plan_path}")
505
- log_debug("cc-native-plan-review", f"Plan length: {len(plan)} chars")
506
- log_diagnostic("cc-native-plan-review", "receive", f"plan_size={len(plan)}, session={session_id[:8]}",
507
- inputs={"plan_hash": compute_plan_hash(plan), "plan_size": len(plan),
508
- "session_id": session_id[:12]})
509
-
510
- # Find active context for this review (required)
511
- active_context = get_active_context_for_review(session_id, base)
512
-
513
- if not active_context:
514
- return skip_with_info("No active planning context found for this session. The context system may not have a context in 'planning' mode.")
515
-
516
- # Get base reviews dir from shared lib, then add cc-native namespace
517
- reviews_dir = get_context_reviews_dir(active_context.id, base) / "cc-native"
518
- log_debug("cc-native-plan-review", f"Using context reviews dir: {reviews_dir}")
519
-
520
- # Get context path for debug logging
521
- context_path = get_context_dir(active_context.id, base)
522
- log_debug("cc-native-plan-review", f"Context path for debug: {context_path}")
523
-
524
- # Plan-hash deduplication (decision-aware)
525
- plan_hash = compute_plan_hash(plan)
526
- log_debug("cc-native-plan-review", f"Plan hash: {plan_hash}")
527
- if is_plan_already_reviewed(session_id, plan_hash):
528
- if was_plan_previously_denied(session_id, plan_hash):
529
- # Plan was denied and hasn't changed — block, don't re-review
530
- emit_context_and_block(
531
- "[Plan Review] Plan content unchanged since last review which found issues.",
532
- "Plan unchanged since denial. Modify the plan to address review findings, "
533
- "then attempt ExitPlanMode again.",
534
- )
535
- return 0
536
- else:
537
- # Plan was reviewed and allowed — skip review, allow through
538
- return skip_with_info("Plan already reviewed and approved (same hash).")
539
-
540
- # Initialize combined result
541
- cli_results: Dict[str, ReviewerResult] = {}
542
- orch_result = None
543
- agent_results: Dict[str, ReviewerResult] = {}
544
- all_verdicts: List[str] = []
545
- iteration_state: Optional[Dict[str, Any]] = None
546
- detected_complexity: str = "medium" # Will be updated by orchestrator
547
-
548
- # ============================================
549
- # PHASE 1 & 2: CLI Reviewers + Orchestrator (PARALLEL)
550
- # ============================================
551
- # Run CLI reviewers and orchestrator concurrently for speed
552
- reviewers_config = plan_settings.get("reviewers", {}) if plan_review_enabled else {}
553
- codex_enabled = plan_review_enabled and reviewers_config.get("codex", {}).get("enabled", True)
554
- gemini_enabled = plan_review_enabled and reviewers_config.get("gemini", {}).get("enabled", False)
555
-
556
- agent_library = load_agent_library(base, agent_settings) if agent_review_enabled else []
557
- # Load all agents regardless of enabled status - enabled:false only prevents
558
- # Claude Code auto-suggestion, not plan-review usage
559
- enabled_agents = agent_library
560
- timeout = agent_settings.get("timeout", 120)
561
- legacy_mode = agent_settings.get("legacyMode", False)
562
-
563
- orch_settings = agent_settings.get("orchestrator", DEFAULT_ORCHESTRATOR)
564
- orchestrator_config = OrchestratorConfig(
565
- enabled=orch_settings.get("enabled", True) and agent_review_enabled,
566
- model=orch_settings.get("model", "haiku"),
567
- timeout=orch_settings.get("timeout", 30),
568
- )
569
-
570
- # Compute mandatory agent names early so orchestrator can exclude them
571
- mandatory_names = set(agent_settings.get("mandatoryAgents", [
572
- "handoff-readiness", "clarity-auditor", "skeptic"
573
- ]))
574
-
575
- log_debug("cc-native-plan-review", f"Codex enabled: {codex_enabled}, Gemini enabled: {gemini_enabled}")
576
- log_debug("cc-native-plan-review", f"Agent library: {[a.name for a in agent_library]}")
577
- log_debug("cc-native-plan-review", f"Enabled agents: {[a.name for a in enabled_agents]}")
578
- log_debug("cc-native-plan-review", f"Mandatory agents: {sorted(mandatory_names)}")
579
- log_debug("cc-native-plan-review", f"Orchestrator enabled: {orchestrator_config.enabled}")
580
-
581
- # Run CLI reviewers + orchestrator in parallel
582
- phase1_tasks = []
583
- if codex_enabled:
584
- phase1_tasks.append(("codex", lambda: run_codex_review(plan, REVIEW_SCHEMA, plan_settings)))
585
- if gemini_enabled:
586
- phase1_tasks.append(("gemini", lambda: run_gemini_review(plan, REVIEW_SCHEMA, plan_settings)))
587
- if orchestrator_config.enabled and enabled_agents and not legacy_mode:
588
- phase1_tasks.append(("orchestrator", lambda: run_orchestrator(plan, enabled_agents, orchestrator_config, agent_settings, mandatory_names=mandatory_names)))
589
-
590
- log_info("cc-native-plan-review", f"=== PHASE 1: Running {len(phase1_tasks)} tasks in parallel ===")
591
-
592
- phase1_results: Dict[str, Any] = {}
593
- if phase1_tasks:
594
- with ThreadPoolExecutor(max_workers=len(phase1_tasks)) as executor:
595
- futures = {executor.submit(task_fn): name for name, task_fn in phase1_tasks}
596
- for future in as_completed(futures):
597
- name = futures[future]
598
- try:
599
- phase1_results[name] = future.result()
600
- log_info("cc-native-plan-review", f"{name} completed")
601
- except Exception as ex:
602
- log_error("cc-native-plan-review", f"{name} failed: {ex}")
603
- phase1_results[name] = None
604
-
605
- # Collect CLI results
606
- if "codex" in phase1_results and phase1_results["codex"]:
607
- cli_results["codex"] = phase1_results["codex"]
608
- if phase1_results["codex"].verdict and phase1_results["codex"].verdict not in ("skip", "error"):
609
- all_verdicts.append(phase1_results["codex"].verdict)
610
- if "gemini" in phase1_results and phase1_results["gemini"]:
611
- cli_results["gemini"] = phase1_results["gemini"]
612
- if phase1_results["gemini"].verdict and phase1_results["gemini"].verdict not in ("skip", "error"):
613
- all_verdicts.append(phase1_results["gemini"].verdict)
614
-
615
- # Get orchestrator result
616
- if "orchestrator" in phase1_results and phase1_results["orchestrator"]:
617
- orch_result = phase1_results["orchestrator"]
618
-
619
- # ============================================
620
- # PHASE 2: Agent Selection (from orchestrator result)
621
- # ============================================
622
- if agent_review_enabled:
623
- log_info("cc-native-plan-review", "=== PHASE 2: Agent Selection ===")
624
-
625
- selected_agents: List[AgentConfig] = []
626
-
627
- # Load fallback config (mandatory_names already computed above)
628
- fallback_by_complexity = agent_settings.get("fallbackByComplexity", {
629
- "simple": 0, "medium": 5, "high": 9
630
- })
631
-
632
- if enabled_agents:
633
- # Split into mandatory and non-mandatory pools
634
- mandatory_agents = [a for a in enabled_agents if a.name in mandatory_names]
635
- non_mandatory = [a for a in enabled_agents if a.name not in mandatory_names]
636
-
637
- log_debug("cc-native-plan-review", f"Mandatory agents: {[a.name for a in mandatory_agents]}")
638
- log_debug("cc-native-plan-review", f"Non-mandatory pool: {len(non_mandatory)} agents")
639
-
640
- if orch_result and not legacy_mode:
641
- detected_complexity = orch_result.complexity
642
-
643
- # Get orchestrator's additional selections (excluding mandatory since they always run)
644
- orch_selected_names = set(orch_result.selected_agents) - mandatory_names
645
- orch_selected = [a for a in non_mandatory if a.name in orch_selected_names]
646
-
647
- log_debug("cc-native-plan-review", f"Orchestrator selected (non-mandatory): {[a.name for a in orch_selected]}")
648
-
649
- # Diagnostic: warn if orchestrator returned names not in our agent pool
650
- unmatched = orch_selected_names - {a.name for a in non_mandatory}
651
- if unmatched:
652
- log_warn("cc-native-plan-review", f"Orchestrator selected unknown agents: {unmatched}")
653
-
654
- # Enforce minimum agent count — top up with random agents if orchestrator selected too few
655
- min_additional = fallback_by_complexity.get(detected_complexity, 5)
656
- if len(orch_selected) < min_additional and non_mandatory:
657
- remaining = [a for a in non_mandatory if a not in orch_selected]
658
- top_up_count = min(min_additional - len(orch_selected), len(remaining))
659
- if top_up_count > 0:
660
- top_up = random.sample(remaining, top_up_count)
661
- orch_selected.extend(top_up)
662
- log_debug("cc-native-plan-review", f"Topped up {top_up_count} agents to meet {detected_complexity} minimum: {[a.name for a in top_up]}")
663
-
664
- # Combine: mandatory + orchestrator/fallback selection
665
- selected_agents = mandatory_agents + orch_selected
666
- log_info("cc-native-plan-review", f"Final selection: {len(selected_agents)} agents ({len(mandatory_agents)} mandatory + {len(orch_selected)} additional)")
667
- else:
668
- log_info("cc-native-plan-review", "Running in legacy mode (all enabled agents)")
669
- selected_agents = enabled_agents
670
- detected_complexity = "medium" # Default for legacy mode
671
-
672
- log_diagnostic("cc-native-plan-review", "decide",
673
- f"Selected {len(selected_agents)} agents, complexity={detected_complexity}",
674
- decision="agents_selected",
675
- reasoning=f"orchestrator={orch_result is not None}, legacy={legacy_mode}",
676
- inputs={"agents": [a.name for a in selected_agents],
677
- "complexity": detected_complexity,
678
- "mandatory_count": len([a for a in selected_agents if a.name in mandatory_names])})
679
-
680
- # Initialize iteration state based on complexity (after orchestrator runs)
681
- if reviews_dir:
682
- iteration_state = get_iteration_state_from_context(reviews_dir, detected_complexity, agent_settings)
683
- log_debug("cc-native-plan-review", f"Iteration state: {iteration_state['current']}/{iteration_state['max']} ({detected_complexity})")
684
-
685
- # PHASE 3: Run selected agents in parallel
686
- if selected_agents:
687
- log_info("cc-native-plan-review", "=== PHASE 3: Agent Reviews ===")
688
- max_parallel = agent_settings.get("maxParallelAgents", 0) # 0 = unlimited
689
- num_workers = len(selected_agents) if max_parallel <= 0 else min(max_parallel, len(selected_agents))
690
- log_info("cc-native-plan-review", f"Launching {len(selected_agents)} agents in parallel (workers={num_workers})")
691
-
692
- # Debug log the agent review start
693
- debug_log(context_path, session_id, "hook", "agent_review_start", {
694
- "agents": [a.name for a in selected_agents],
695
- "timeout": timeout,
696
- "complexity": detected_complexity,
697
- })
698
-
699
- with ThreadPoolExecutor(max_workers=num_workers) as executor:
700
- futures = {
701
- executor.submit(run_agent_review, plan, agent, REVIEW_SCHEMA, timeout, context_path, session_id): agent
702
- for agent in selected_agents
703
- }
704
- for future in as_completed(futures):
705
- agent = futures[future]
706
- try:
707
- result = future.result()
708
- agent_results[agent.name] = result
709
- if result.verdict and result.verdict not in ("skip", "error"):
710
- all_verdicts.append(result.verdict)
711
- log_info("cc-native-plan-review", f"{agent.name} completed with verdict: {result.verdict}")
712
- except Exception as ex:
713
- log_error("cc-native-plan-review", f"{agent.name} failed with exception: {ex}")
714
- agent_results[agent.name] = ReviewerResult(
715
- name=agent.name,
716
- ok=False,
717
- verdict="error",
718
- data={},
719
- raw="",
720
- err=str(ex),
721
- )
722
-
723
- # ============================================
724
- # PHASE 4: Generate Combined Output
725
- # ============================================
726
- log_info("cc-native-plan-review", "=== PHASE 4: Generate Output ===")
727
-
728
- if not cli_results and not agent_results:
729
- return skip_with_info("All reviewers failed to produce results. Check stderr logs for details.")
730
-
731
- overall = worst_verdict(all_verdicts) if all_verdicts else "pass"
732
-
733
- combined_result = CombinedReviewResult(
734
- plan_hash=plan_hash,
735
- overall_verdict=overall,
736
- cli_reviewers=cli_results,
737
- orchestration=orch_result,
738
- agents=agent_results,
739
- timestamp=datetime.now().isoformat(),
740
- )
741
-
742
- # Merge display settings from both configs
743
- display_settings = {**plan_settings.get("display", {}), **agent_settings.get("display", {})}
744
- combined_settings = {"display": display_settings}
745
-
746
- # Get current iteration number for folder naming
747
- current_iteration = 1
748
- if iteration_state:
749
- current_iteration = iteration_state.get("current", 1)
750
-
751
- # Create review folder with datetime and iteration in name
752
- review_folder = get_review_folder_path(active_context.id, current_iteration, base)
753
- review_folder.mkdir(parents=True, exist_ok=True)
754
- log_info("cc-native-plan-review", f"Created review folder: {review_folder}")
755
-
756
- review_file = write_combined_artifacts(
757
- base, plan, combined_result, payload, combined_settings,
758
- review_folder=review_folder,
759
- iteration=current_iteration,
760
- )
761
- log_info("cc-native-plan-review", f"Saved review: {review_file}")
762
-
763
- # Build inline review summary for additionalContext
764
- inline_summary = build_inline_review_summary(combined_result)
765
-
766
- context_parts = [inline_summary, f"\nFull review: `{review_file}`\n"]
767
-
768
- # Review decision — only fail triggers a block
769
- warn_threshold = agent_settings.get("warnThreshold", 0.5)
770
- should_deny, deny_reason, review_score = compute_review_decision(all_verdicts, warn_threshold)
771
-
772
- # Count high-severity issues for logging
773
- high_count = sum(
774
- 1 for r in list(combined_result.cli_reviewers.values()) + list(combined_result.agents.values())
775
- if r.data
776
- for issue in r.data.get("issues", [])
777
- if issue.get("severity") == "high"
778
- )
779
-
780
- # Structured log entries for review influence tracking
781
- log_info("cc-native-plan-review", f"REVIEW_DECISION: verdict={combined_result.overall_verdict}, deny={should_deny}, score={review_score:.2f}, high_issues={high_count}")
782
- log_diagnostic("cc-native-plan-review", "result",
783
- f"verdict={combined_result.overall_verdict}, deny={should_deny}, high={high_count}",
784
- decision="deny" if should_deny else "allow",
785
- reasoning=f"score={review_score:.2f}, threshold={warn_threshold}",
786
- inputs={"overall_verdict": combined_result.overall_verdict,
787
- "high_issue_count": high_count, "review_score": round(review_score, 2),
788
- "cli_count": len(cli_results), "agent_count": len(agent_results)})
789
-
790
- # Terminal progress indicator
791
- verdict_emoji = "✅" if not should_deny else "❌"
792
- eprint(f"[plan-review] {verdict_emoji} {combined_result.overall_verdict.upper()} (score={review_score:.2f})")
793
- if should_deny:
794
- eprint(f"[plan-review] Blocking ExitPlanMode — {high_count} high-severity issue(s) found")
795
-
796
- # Handle iteration logic
797
- needs_more_iterations = False
798
- if iteration_state and reviews_dir:
799
- # Update iteration state with this review result
800
- iteration_state = update_iteration_state_in_context(reviews_dir, iteration_state, plan_hash, overall)
801
-
802
- # Check if more iterations needed
803
- if should_continue_iterating_context(iteration_state, review_score, agent_settings):
804
- needs_more_iterations = True
805
- # Increment iteration counter for next round
806
- iteration_state["current"] = iteration_state.get("current", 1) + 1
807
- # Save updated state for next iteration
808
- save_iteration_state(reviews_dir, iteration_state)
809
- else:
810
- # Final iteration - increment current and save state
811
- iteration_state["current"] = iteration_state.get("current", 1) + 1
812
- # Also increment max by 1 to allow another review cycle if the user rejects
813
- # the plan and requests changes. Without this, once iterations are exhausted,
814
- # the hook would skip review entirely even if the user sent the
815
- # planner back to revise. This ensures rejected plans can always be re-reviewed.
816
- iteration_state["max"] = iteration_state.get("max", 1) + 1
817
- save_iteration_state(reviews_dir, iteration_state)
818
-
819
- # Emit output with correct Claude Code hook format
820
- context_text = "".join(context_parts)
821
-
822
- log_debug("cc-native-plan-review", f"REVIEW_CONTEXT_INJECTED: chars={len(context_text)}, inline_chars={len(inline_summary)}")
823
-
824
- _REVIEWER_CAVEAT = (
825
- "Reviewers have limited context compared to your full session — "
826
- "adopt valid points, use your judgment where they lack context."
827
- )
828
-
829
- _RESUBMIT_INSTRUCTION = (
830
- "IMPORTANT: After revising the plan file, you MUST call ExitPlanMode again "
831
- "to trigger re-review. Do not end your turn or ask the user without calling ExitPlanMode."
832
- )
833
-
834
- if needs_more_iterations:
835
- mark_plan_reviewed(session_id, plan_hash, "cc-native-plan-review", iteration_state, decision="deny")
836
- current = iteration_state["current"] - 1 # Display the just-completed iteration
837
- max_iter = iteration_state["max"]
838
- remaining = max_iter - current
839
- top_issues_text = extract_top_issues_text(combined_result, max_count=3, severity="high")
840
- emit_context_and_block(
841
- context_text,
842
- f"Plan review iteration {current}/{max_iter} FAILED ({deny_reason}, score={review_score:.2f}). "
843
- f"Critical issues: {top_issues_text}. "
844
- f"{_REVIEWER_CAVEAT} "
845
- f"Revise the plan, then call ExitPlanMode again. "
846
- f"({remaining} revision{'s' if remaining != 1 else ''} remaining) "
847
- f"{_RESUBMIT_INSTRUCTION}",
848
- )
849
- elif should_deny:
850
- mark_plan_reviewed(session_id, plan_hash, "cc-native-plan-review", iteration_state, decision="deny")
851
- top_issues_text = extract_top_issues_text(combined_result, max_count=3, severity="high")
852
- emit_context_and_block(
853
- context_text,
854
- f"Plan review FAILED ({deny_reason}, score={review_score:.2f}). "
855
- f"Critical issues: {top_issues_text}. "
856
- f"{_REVIEWER_CAVEAT} "
857
- f"Revise the plan, then call ExitPlanMode again. "
858
- f"{_RESUBMIT_INSTRUCTION}",
859
- )
860
- else:
861
- mark_plan_reviewed(session_id, plan_hash, "cc-native-plan-review", iteration_state, decision="allow")
862
- emit_context(context_text, ensure_ascii=True)
863
-
864
- return 0
865
-
866
-
867
- if __name__ == "__main__":
868
- from base.hook_utils import run_hook
869
- run_hook(main, "cc_native_plan_review")