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.
- package/README.md +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- 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
|
+
]
|
package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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)
|