aiwcli 0.9.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 (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. package/package.json +109 -0
@@ -0,0 +1,273 @@
1
+ """
2
+ CC-Native Plan Orchestrator Module.
3
+
4
+ Analyzes plan complexity and selects appropriate reviewers.
5
+ """
6
+
7
+ import json
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ # Import from parent lib
15
+ _lib_dir = Path(__file__).resolve().parent
16
+ sys.path.insert(0, str(_lib_dir))
17
+
18
+ from utils import OrchestratorResult, eprint, parse_json_maybe
19
+ from reviewers.base import AgentConfig, OrchestratorConfig
20
+
21
+
22
+ # ---------------------------
23
+ # Constants
24
+ # ---------------------------
25
+
26
+ DEFAULT_AGENT_SELECTION: Dict[str, Any] = {
27
+ "simple": {"min": 3, "max": 3},
28
+ "medium": {"min": 8, "max": 8},
29
+ "high": {"min": 12, "max": 12},
30
+ "fallbackCount": 3,
31
+ }
32
+
33
+ DEFAULT_COMPLEXITY_CATEGORIES: List[str] = [
34
+ "code",
35
+ "infrastructure",
36
+ "documentation",
37
+ "life",
38
+ "business",
39
+ "design",
40
+ "research",
41
+ ]
42
+
43
+ ORCHESTRATOR_SCHEMA: Dict[str, Any] = {
44
+ "type": "object",
45
+ "properties": {
46
+ "complexity": {"type": "string", "enum": ["simple", "medium", "high"]},
47
+ "category": {"type": "string", "enum": DEFAULT_COMPLEXITY_CATEGORIES},
48
+ "selectedAgents": {"type": "array", "items": {"type": "string"}},
49
+ "reasoning": {"type": "string"},
50
+ "skipReason": {"type": "string"},
51
+ },
52
+ "required": ["complexity", "category", "selectedAgents", "reasoning"],
53
+ "additionalProperties": False,
54
+ }
55
+
56
+
57
+ # ---------------------------
58
+ # Output Parsing
59
+ # ---------------------------
60
+
61
+ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
62
+ """Parse Claude CLI JSON output, handling various formats.
63
+
64
+ Claude CLI can output in several formats:
65
+ - Direct structured_output dict
66
+ - Assistant message with StructuredOutput tool use
67
+ - List of events with assistant messages
68
+
69
+ Args:
70
+ raw: Raw stdout from Claude CLI
71
+
72
+ Returns:
73
+ Parsed JSON dict or None if parsing failed
74
+ """
75
+ try:
76
+ result = json.loads(raw)
77
+ if isinstance(result, dict):
78
+ if "structured_output" in result:
79
+ eprint("[orchestrator:parse] Found structured_output in root dict")
80
+ return result["structured_output"]
81
+ if result.get("type") == "assistant":
82
+ message = result.get("message", {})
83
+ content = message.get("content", [])
84
+ for item in content:
85
+ if isinstance(item, dict) and item.get("name") == "StructuredOutput":
86
+ eprint("[orchestrator:parse] Found StructuredOutput in assistant message content")
87
+ return item.get("input", {})
88
+ eprint("[orchestrator:parse] Assistant message found but no StructuredOutput tool use in content")
89
+ elif isinstance(result, list):
90
+ eprint(f"[orchestrator:parse] Received list of {len(result)} events, searching for assistant message")
91
+ for i, event in enumerate(result):
92
+ if not isinstance(event, dict):
93
+ continue
94
+ if event.get("type") == "assistant":
95
+ message = event.get("message", {})
96
+ content = message.get("content", [])
97
+ for item in content:
98
+ if isinstance(item, dict) and item.get("name") == "StructuredOutput":
99
+ eprint(f"[orchestrator:parse] Found StructuredOutput in event[{i}] assistant message")
100
+ return item.get("input", {})
101
+ eprint("[orchestrator:parse] No StructuredOutput found in any assistant message in event list")
102
+ except json.JSONDecodeError as e:
103
+ eprint(f"[orchestrator:parse] JSON decode error: {e}")
104
+ except Exception as e:
105
+ eprint(f"[orchestrator:parse] Unexpected error during structured parsing: {e}")
106
+
107
+ # Fallback to heuristic extraction
108
+ eprint("[orchestrator:parse] No structured output found, falling back to heuristic JSON extraction")
109
+ return parse_json_maybe(raw)
110
+
111
+
112
+ # ---------------------------
113
+ # Orchestrator
114
+ # ---------------------------
115
+
116
+ def run_orchestrator(
117
+ plan: str,
118
+ agent_library: List[AgentConfig],
119
+ config: OrchestratorConfig,
120
+ settings: Dict[str, Any],
121
+ ) -> OrchestratorResult:
122
+ """Run the orchestrator agent to analyze plan complexity and select reviewers.
123
+
124
+ Args:
125
+ plan: The plan content to analyze
126
+ agent_library: List of available agents
127
+ config: Orchestrator configuration (model, timeout, max_turns)
128
+ settings: Agent review settings (agentSelection, complexityCategories)
129
+
130
+ Returns:
131
+ OrchestratorResult with complexity, category, and selected agents
132
+ """
133
+ eprint("[orchestrator] Starting plan analysis...")
134
+
135
+ selection = settings.get("agentSelection", DEFAULT_AGENT_SELECTION)
136
+ categories = settings.get("complexityCategories", DEFAULT_COMPLEXITY_CATEGORIES)
137
+ fallback_count = selection.get("fallbackCount", 2)
138
+
139
+ claude_path = shutil.which("claude")
140
+ if claude_path is None:
141
+ eprint("[orchestrator] Claude CLI not found on PATH, falling back to medium complexity")
142
+ return OrchestratorResult(
143
+ complexity="medium",
144
+ category="code",
145
+ selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
146
+ reasoning="Orchestrator skipped - Claude CLI not found",
147
+ error="claude CLI not found on PATH",
148
+ )
149
+
150
+ eprint(f"[orchestrator] Found Claude CLI at: {claude_path}")
151
+
152
+ # Build agent list for prompt
153
+ agent_list = "\n".join([
154
+ f"- {a.name}: {a.focus} (categories: {', '.join(a.categories)})"
155
+ for a in agent_library if a.enabled
156
+ ])
157
+ category_list = "/".join(categories)
158
+ simple_range = f"{selection.get('simple', {}).get('min', 0)}-{selection.get('simple', {}).get('max', 0)}"
159
+ medium_range = f"{selection.get('medium', {}).get('min', 1)}-{selection.get('medium', {}).get('max', 2)}"
160
+ high_range = f"{selection.get('high', {}).get('min', 2)}-{selection.get('high', {}).get('max', 4)}"
161
+
162
+ prompt = f"""IMPORTANT: Analyze this plan and output your decision immediately using StructuredOutput. Do NOT ask questions.
163
+
164
+ You are a plan orchestrator. Analyze the plan below and determine:
165
+ 1. Complexity level (simple/medium/high)
166
+ 2. Category ({category_list})
167
+ 3. Which agents (if any) should review this plan
168
+
169
+ Available agents:
170
+ {agent_list}
171
+
172
+ Rules:
173
+ - simple complexity = {simple_range} agents (CLI review sufficient)
174
+ - medium complexity = {medium_range} agents
175
+ - high complexity = {high_range} agents
176
+ - Only select agents whose categories match the plan category
177
+ - Non-technical plans (life, business) typically need 0 code-focused agents
178
+
179
+ Analyze and call StructuredOutput with your decision now.
180
+
181
+ PLAN:
182
+ <<<
183
+ {plan}
184
+ >>>
185
+ """
186
+
187
+ schema_json = json.dumps(ORCHESTRATOR_SCHEMA, ensure_ascii=False)
188
+
189
+ cmd_args = [
190
+ claude_path,
191
+ "--agent", "plan-orchestrator",
192
+ "--model", config.model,
193
+ "--permission-mode", "bypassPermissions",
194
+ "--output-format", "json",
195
+ "--max-turns", str(config.max_turns),
196
+ "--json-schema", schema_json,
197
+ "--settings", "{}",
198
+ ]
199
+
200
+ eprint(f"[orchestrator] Running with model: {config.model}, timeout: {config.timeout}s")
201
+
202
+ try:
203
+ p = subprocess.run(
204
+ cmd_args,
205
+ input=prompt,
206
+ text=True,
207
+ capture_output=True,
208
+ timeout=config.timeout,
209
+ encoding="utf-8",
210
+ errors="replace",
211
+ )
212
+ except subprocess.TimeoutExpired:
213
+ eprint(f"[orchestrator] TIMEOUT after {config.timeout}s, falling back to medium complexity")
214
+ return OrchestratorResult(
215
+ complexity="medium",
216
+ category="code",
217
+ selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
218
+ reasoning="Orchestrator timed out - defaulting to medium complexity",
219
+ error=f"Orchestrator timed out after {config.timeout}s",
220
+ )
221
+ except Exception as ex:
222
+ eprint(f"[orchestrator] EXCEPTION: {ex}, falling back to medium complexity")
223
+ return OrchestratorResult(
224
+ complexity="medium",
225
+ category="code",
226
+ selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
227
+ reasoning=f"Orchestrator failed: {ex}",
228
+ error=str(ex),
229
+ )
230
+
231
+ eprint(f"[orchestrator] Exit code: {p.returncode}")
232
+
233
+ raw = (p.stdout or "").strip()
234
+ if p.stderr:
235
+ eprint(f"[orchestrator] stderr: {p.stderr[:300]}")
236
+
237
+ obj = _parse_claude_output(raw)
238
+ if not obj:
239
+ eprint("[orchestrator] Failed to parse output, falling back to medium complexity")
240
+ return OrchestratorResult(
241
+ complexity="medium",
242
+ category="code",
243
+ selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
244
+ reasoning="Orchestrator output could not be parsed",
245
+ error="Failed to parse orchestrator output",
246
+ )
247
+
248
+ # Extract and validate fields
249
+ complexity = obj.get("complexity", "medium")
250
+ if complexity not in ("simple", "medium", "high"):
251
+ complexity = "medium"
252
+
253
+ category = obj.get("category", "code")
254
+ if category not in categories:
255
+ category = "code"
256
+
257
+ selected_agents = obj.get("selectedAgents", [])
258
+ if not isinstance(selected_agents, list):
259
+ selected_agents = []
260
+
261
+ reasoning = str(obj.get("reasoning", "")).strip() or "No reasoning provided"
262
+ skip_reason = obj.get("skipReason")
263
+
264
+ eprint(f"[orchestrator] Result: complexity={complexity}, category={category}, agents={selected_agents}")
265
+ eprint(f"[orchestrator] Reasoning: {reasoning}")
266
+
267
+ return OrchestratorResult(
268
+ complexity=complexity,
269
+ category=category,
270
+ selected_agents=selected_agents,
271
+ reasoning=reasoning,
272
+ skip_reason=skip_reason if skip_reason else None,
273
+ )
@@ -0,0 +1,28 @@
1
+ """CC-Native Plan Reviewers Module.
2
+
3
+ Provides CLI and agent-based plan review implementations.
4
+ """
5
+
6
+ from .base import (
7
+ ReviewerResult,
8
+ REVIEW_SCHEMA,
9
+ REVIEW_PROMPT_PREFIX,
10
+ AGENT_REVIEW_PROMPT_PREFIX,
11
+ AgentConfig,
12
+ OrchestratorConfig,
13
+ )
14
+ from .codex import run_codex_review
15
+ from .gemini import run_gemini_review
16
+ from .agent import run_agent_review
17
+
18
+ __all__ = [
19
+ "ReviewerResult",
20
+ "REVIEW_SCHEMA",
21
+ "REVIEW_PROMPT_PREFIX",
22
+ "AGENT_REVIEW_PROMPT_PREFIX",
23
+ "AgentConfig",
24
+ "OrchestratorConfig",
25
+ "run_codex_review",
26
+ "run_gemini_review",
27
+ "run_agent_review",
28
+ ]
@@ -0,0 +1,164 @@
1
+ """
2
+ CC-Native Agent Reviewer Module.
3
+
4
+ Runs Claude Code agents to review plans.
5
+ """
6
+
7
+ import json
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ # Import from parent lib
15
+ _lib_dir = Path(__file__).resolve().parent.parent
16
+ sys.path.insert(0, str(_lib_dir))
17
+
18
+ from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
19
+ from .base import AgentConfig, AGENT_REVIEW_PROMPT_PREFIX
20
+
21
+
22
+ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
23
+ """Parse Claude CLI JSON output, handling various formats.
24
+
25
+ Claude CLI can output in several formats:
26
+ - Direct structured_output dict
27
+ - Assistant message with StructuredOutput tool use
28
+ - List of events with assistant messages
29
+
30
+ Args:
31
+ raw: Raw stdout from Claude CLI
32
+
33
+ Returns:
34
+ Parsed JSON dict or None if parsing failed
35
+ """
36
+ try:
37
+ result = json.loads(raw)
38
+ if isinstance(result, dict):
39
+ if "structured_output" in result:
40
+ eprint("[parse] Found structured_output in root dict")
41
+ return result["structured_output"]
42
+ if result.get("type") == "assistant":
43
+ message = result.get("message", {})
44
+ content = message.get("content", [])
45
+ for item in content:
46
+ if isinstance(item, dict) and item.get("name") == "StructuredOutput":
47
+ eprint("[parse] Found StructuredOutput in assistant message content")
48
+ return item.get("input", {})
49
+ eprint("[parse] Assistant message found but no StructuredOutput tool use in content")
50
+ elif isinstance(result, list):
51
+ eprint(f"[parse] Received list of {len(result)} events, searching for assistant message")
52
+ for i, event in enumerate(result):
53
+ if not isinstance(event, dict):
54
+ continue
55
+ if event.get("type") == "assistant":
56
+ message = event.get("message", {})
57
+ content = message.get("content", [])
58
+ for item in content:
59
+ if isinstance(item, dict) and item.get("name") == "StructuredOutput":
60
+ eprint(f"[parse] Found StructuredOutput in event[{i}] assistant message")
61
+ return item.get("input", {})
62
+ eprint("[parse] No StructuredOutput found in any assistant message in event list")
63
+ except json.JSONDecodeError as e:
64
+ eprint(f"[parse] JSON decode error: {e}")
65
+ except Exception as e:
66
+ eprint(f"[parse] Unexpected error during structured parsing: {e}")
67
+
68
+ # Fallback to heuristic extraction with required field validation
69
+ eprint("[parse] No structured output found, falling back to heuristic JSON extraction")
70
+ return parse_json_maybe(raw, require_fields=["verdict", "summary"])
71
+
72
+
73
+ def run_agent_review(
74
+ plan: str,
75
+ agent: AgentConfig,
76
+ schema: Dict[str, Any],
77
+ timeout: int,
78
+ max_turns: int = 3,
79
+ ) -> ReviewerResult:
80
+ """Run a single Claude Code agent to review the plan.
81
+
82
+ Args:
83
+ plan: The plan content to review
84
+ agent: Agent configuration (name, model, etc.)
85
+ schema: JSON schema for the review output
86
+ timeout: Timeout in seconds
87
+ max_turns: Maximum agent turns
88
+
89
+ Returns:
90
+ ReviewerResult with the review output
91
+ """
92
+ claude_path = shutil.which("claude")
93
+ if claude_path is None:
94
+ eprint(f"[{agent.name}] Claude CLI not found on PATH")
95
+ return ReviewerResult(
96
+ name=agent.name,
97
+ ok=False,
98
+ verdict="skip",
99
+ data={},
100
+ raw="",
101
+ err="claude CLI not found on PATH",
102
+ )
103
+
104
+ eprint(f"[{agent.name}] Found Claude CLI at: {claude_path}")
105
+
106
+ prompt = f"""{AGENT_REVIEW_PROMPT_PREFIX}
107
+
108
+ PLAN:
109
+ <<<
110
+ {plan}
111
+ >>>
112
+ """
113
+
114
+ schema_json = json.dumps(schema, ensure_ascii=False)
115
+ cmd_args = [
116
+ claude_path,
117
+ "--agent", agent.name,
118
+ "--model", agent.model,
119
+ "--permission-mode", "bypassPermissions",
120
+ "--output-format", "json",
121
+ "--max-turns", str(max_turns),
122
+ "--json-schema", schema_json,
123
+ "--settings", "{}",
124
+ ]
125
+
126
+ eprint(f"[{agent.name}] Running with model: {agent.model}, timeout: {timeout}s, max-turns: {max_turns}")
127
+
128
+ try:
129
+ p = subprocess.run(
130
+ cmd_args,
131
+ input=prompt,
132
+ text=True,
133
+ capture_output=True,
134
+ timeout=timeout,
135
+ encoding="utf-8",
136
+ errors="replace",
137
+ )
138
+ except subprocess.TimeoutExpired:
139
+ eprint(f"[{agent.name}] TIMEOUT after {timeout}s")
140
+ return ReviewerResult(agent.name, False, "error", {}, "", f"{agent.name} timed out after {timeout}s")
141
+ except Exception as ex:
142
+ eprint(f"[{agent.name}] EXCEPTION: {ex}")
143
+ return ReviewerResult(agent.name, False, "error", {}, "", f"{agent.name} failed to run: {ex}")
144
+
145
+ eprint(f"[{agent.name}] Exit code: {p.returncode}")
146
+ eprint(f"[{agent.name}] stdout length: {len(p.stdout or '')} chars")
147
+ if p.stderr:
148
+ eprint(f"[{agent.name}] stderr: {p.stderr[:500]}")
149
+
150
+ raw = (p.stdout or "").strip()
151
+ err = (p.stderr or "").strip()
152
+
153
+ if raw:
154
+ eprint(f"[{agent.name}] stdout preview: {raw[:500]}")
155
+
156
+ obj = _parse_claude_output(raw)
157
+ if obj:
158
+ eprint(f"[{agent.name}] Parsed JSON successfully, verdict: {obj.get('verdict', 'N/A')}")
159
+ else:
160
+ eprint(f"[{agent.name}] Failed to parse JSON from output")
161
+
162
+ ok, verdict, norm = coerce_to_review(obj, "Retry or check agent configuration.")
163
+
164
+ return ReviewerResult(agent.name, ok, verdict, norm, raw, err)
@@ -0,0 +1,89 @@
1
+ """
2
+ CC-Native Reviewers Base Module.
3
+
4
+ Provides shared constants and types for plan reviewers.
5
+ """
6
+
7
+ import sys
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List
11
+
12
+ # Import from parent lib
13
+ _lib_dir = Path(__file__).resolve().parent.parent
14
+ sys.path.insert(0, str(_lib_dir))
15
+
16
+ from utils import ReviewerResult, REVIEW_SCHEMA
17
+
18
+ # Re-export for convenience
19
+ __all__ = [
20
+ "ReviewerResult",
21
+ "REVIEW_SCHEMA",
22
+ "REVIEW_PROMPT_PREFIX",
23
+ "AGENT_REVIEW_PROMPT_PREFIX",
24
+ "AgentConfig",
25
+ "OrchestratorConfig",
26
+ ]
27
+
28
+
29
+ # ---------------------------
30
+ # Agent Configuration
31
+ # ---------------------------
32
+
33
+ @dataclass
34
+ class AgentConfig:
35
+ """Configuration for a Claude Code review agent."""
36
+ name: str
37
+ model: str = "sonnet"
38
+ focus: str = ""
39
+ enabled: bool = True
40
+ categories: List[str] = field(default_factory=lambda: ["code"])
41
+ description: str = ""
42
+ tools: str = ""
43
+
44
+
45
+ @dataclass
46
+ class OrchestratorConfig:
47
+ """Configuration for the plan orchestrator."""
48
+ enabled: bool = True
49
+ model: str = "haiku"
50
+ timeout: int = 30
51
+ max_turns: int = 3
52
+
53
+
54
+ # ---------------------------
55
+ # Shared Review Prompt Text
56
+ # ---------------------------
57
+
58
+ REVIEW_PROMPT_PREFIX = """You are a senior staff software engineer acting as a strict plan reviewer.
59
+
60
+ Review the PLAN below. Focus on:
61
+ - missing steps, unclear assumptions, edge cases
62
+ - security/privacy concerns
63
+ - testing/rollout/rollback completeness
64
+ - operational concerns (observability, failure modes)
65
+ """
66
+
67
+ AGENT_REVIEW_PROMPT_PREFIX = """# SINGLE-TURN PLAN REVIEW
68
+
69
+ ## CRITICAL: ONE TURN ONLY
70
+ You have exactly ONE response to complete this review. Do NOT attempt multi-step workflows, context queries, or phased analysis. Analyze the plan and output your review immediately.
71
+
72
+ ## YOUR TASK
73
+ Review the plan below from your area of expertise. Then call StructuredOutput with your assessment.
74
+
75
+ ## REQUIRED OUTPUT (all fields must have content)
76
+ Call StructuredOutput with:
77
+ - **verdict**: "pass" (no concerns), "warn" (some concerns), or "fail" (critical issues)
78
+ - **summary**: 2-3 sentences with your overall assessment and key findings (REQUIRED)
79
+ - **issues**: Array of concerns found. Format each as:
80
+ {"severity": "high/medium/low", "category": "...", "issue": "...", "suggested_fix": "..."}
81
+ - **missing_sections**: Topics the plan should address but doesn't
82
+ - **questions**: Things that need clarification before implementation
83
+
84
+ ## IMPORTANT RULES
85
+ 1. A "warn" verdict MUST include at least one issue explaining why
86
+ 2. Summary MUST explain your reasoning, not just "looks good" or empty
87
+ 3. Focus on your expertise area (architecture, security, performance, etc.)
88
+ 4. Output StructuredOutput NOW - no other tools, no questions, no delays
89
+ """
@@ -0,0 +1,119 @@
1
+ """
2
+ CC-Native Codex Reviewer Module.
3
+
4
+ Runs Codex CLI to review plans.
5
+ """
6
+
7
+ import json
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ from pathlib import Path
13
+ from typing import Any, Dict
14
+
15
+ # Import from parent lib
16
+ _lib_dir = Path(__file__).resolve().parent.parent
17
+ sys.path.insert(0, str(_lib_dir))
18
+
19
+ from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
20
+ from .base import REVIEW_PROMPT_PREFIX
21
+
22
+
23
+ def run_codex_review(
24
+ plan: str,
25
+ schema: Dict[str, Any],
26
+ settings: Dict[str, Any],
27
+ ) -> ReviewerResult:
28
+ """Run Codex CLI to review the plan.
29
+
30
+ Args:
31
+ plan: The plan content to review
32
+ schema: JSON schema for the review output
33
+ settings: Codex reviewer settings (timeout, model)
34
+
35
+ Returns:
36
+ ReviewerResult with the review output
37
+ """
38
+ codex_settings = settings.get("reviewers", {}).get("codex", {})
39
+ timeout = codex_settings.get("timeout", 120)
40
+ model = codex_settings.get("model", "")
41
+
42
+ codex_path = shutil.which("codex")
43
+ if codex_path is None:
44
+ eprint("[codex] CLI not found on PATH")
45
+ return ReviewerResult(
46
+ name="codex",
47
+ ok=False,
48
+ verdict="skip",
49
+ data={},
50
+ raw="",
51
+ err="codex CLI not found on PATH",
52
+ )
53
+
54
+ eprint(f"[codex] Found CLI at: {codex_path}")
55
+
56
+ prompt = f"""{REVIEW_PROMPT_PREFIX}
57
+ Return ONLY a JSON object that matches this JSON Schema:
58
+ {json.dumps(schema, ensure_ascii=False)}
59
+
60
+ PLAN:
61
+ <<<
62
+ {plan}
63
+ >>>
64
+ """
65
+
66
+ with tempfile.TemporaryDirectory() as td:
67
+ td_path = Path(td)
68
+ schema_path = td_path / "schema.json"
69
+ out_path = td_path / "codex_review.json"
70
+
71
+ schema_path.write_text(json.dumps(schema, indent=2), encoding="utf-8")
72
+
73
+ cmd = [
74
+ codex_path,
75
+ "exec",
76
+ "--full-auto",
77
+ "--sandbox",
78
+ "read-only",
79
+ "--output-schema",
80
+ str(schema_path),
81
+ "-o",
82
+ str(out_path),
83
+ "-",
84
+ ]
85
+
86
+ if model:
87
+ cmd.insert(2, "--model")
88
+ cmd.insert(3, model)
89
+
90
+ eprint(f"[codex] Running command: {' '.join(cmd)}")
91
+
92
+ try:
93
+ p = subprocess.run(
94
+ cmd,
95
+ input=prompt,
96
+ text=True,
97
+ capture_output=True,
98
+ timeout=timeout,
99
+ encoding='utf-8',
100
+ errors='replace',
101
+ )
102
+ except subprocess.TimeoutExpired:
103
+ eprint(f"[codex] TIMEOUT after {timeout}s")
104
+ return ReviewerResult("codex", False, "error", {}, "", f"codex timed out after {timeout}s")
105
+ except Exception as ex:
106
+ eprint(f"[codex] EXCEPTION: {ex}")
107
+ return ReviewerResult("codex", False, "error", {}, "", f"codex failed to run: {ex}")
108
+
109
+ eprint(f"[codex] Exit code: {p.returncode}")
110
+
111
+ raw = ""
112
+ if out_path.exists():
113
+ raw = out_path.read_text(encoding="utf-8", errors="replace")
114
+
115
+ obj = parse_json_maybe(raw) or parse_json_maybe(p.stdout)
116
+ ok, verdict, norm = coerce_to_review(obj, "Retry or check CLI auth/config.")
117
+
118
+ err = (p.stderr or "").strip()
119
+ return ReviewerResult("codex", ok, verdict, norm, raw or p.stdout, err)