aiwcli 0.9.7 → 0.10.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 (119) hide show
  1. package/bin/run.js +5 -2
  2. package/dist/lib/claude-settings-types.d.ts +2 -0
  3. package/dist/templates/CLAUDE.md +49 -18
  4. package/dist/templates/_shared/.claude/settings.json +4 -0
  5. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  7. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  8. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  9. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  10. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  11. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  12. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  13. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  14. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  15. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  16. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  17. package/dist/templates/_shared/hooks/archive_plan.py +87 -178
  18. package/dist/templates/_shared/hooks/context_monitor.py +128 -194
  19. package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
  20. package/dist/templates/_shared/hooks/pre_compact.py +104 -0
  21. package/dist/templates/_shared/hooks/session_end.py +154 -0
  22. package/dist/templates/_shared/hooks/session_start.py +145 -59
  23. package/dist/templates/_shared/hooks/task_create_capture.py +26 -49
  24. package/dist/templates/_shared/hooks/task_update_capture.py +42 -100
  25. package/dist/templates/_shared/hooks/user_prompt_submit.py +63 -77
  26. package/dist/templates/_shared/lib/base/__init__.py +16 -0
  27. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  28. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  29. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  30. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  31. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  32. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  33. package/dist/templates/_shared/lib/base/constants.py +18 -4
  34. package/dist/templates/_shared/lib/base/hook_utils.py +199 -11
  35. package/dist/templates/_shared/lib/base/inference.py +121 -0
  36. package/dist/templates/_shared/lib/base/logger.py +291 -0
  37. package/dist/templates/_shared/lib/base/utils.py +49 -11
  38. package/dist/templates/_shared/lib/context/__init__.py +72 -80
  39. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  41. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  42. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  43. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  44. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  45. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  46. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  47. package/dist/templates/_shared/lib/context/context_formatter.py +316 -0
  48. package/dist/templates/_shared/lib/context/context_selector.py +491 -0
  49. package/dist/templates/_shared/lib/context/context_store.py +636 -0
  50. package/dist/templates/_shared/lib/context/plan_manager.py +204 -0
  51. package/dist/templates/_shared/lib/context/task_tracker.py +188 -0
  52. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  54. package/dist/templates/_shared/lib/handoff/document_generator.py +14 -40
  55. package/dist/templates/_shared/lib/templates/README.md +5 -13
  56. package/dist/templates/_shared/lib/templates/__init__.py +2 -6
  57. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  58. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  59. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  60. package/dist/templates/_shared/lib/templates/plan_context.py +25 -79
  61. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  62. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  63. package/dist/templates/_shared/scripts/save_handoff.py +39 -19
  64. package/dist/templates/_shared/scripts/status_line.py +701 -0
  65. package/dist/templates/_shared/workflows/handoff.md +9 -3
  66. package/dist/templates/cc-native/.claude/settings.json +64 -9
  67. package/dist/templates/cc-native/CC-NATIVE-README.md +25 -28
  68. package/dist/templates/cc-native/MIGRATION.md +1 -1
  69. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +14 -39
  70. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -1
  71. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +57 -22
  72. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  73. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  74. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  75. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  76. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  77. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  78. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +57 -57
  79. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +208 -158
  80. package/dist/templates/cc-native/_cc-native/hooks/plan_accepted.py +127 -0
  81. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +81 -0
  82. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +26 -25
  83. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +35 -10
  84. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  85. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  86. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  87. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  88. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  89. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  90. package/dist/templates/cc-native/_cc-native/lib/debug.py +37 -22
  91. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +103 -42
  92. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  93. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  94. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  95. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  96. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  97. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +26 -21
  98. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +12 -7
  99. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +12 -7
  100. package/dist/templates/cc-native/_cc-native/lib/state.py +31 -16
  101. package/dist/templates/cc-native/_cc-native/lib/utils.py +210 -43
  102. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -2
  103. package/oclif.manifest.json +1 -1
  104. package/package.json +1 -1
  105. package/dist/templates/_shared/hooks/context_enforcer.py +0 -625
  106. package/dist/templates/_shared/hooks/task_create_atomicity.py +0 -205
  107. package/dist/templates/_shared/lib/context/cache.py +0 -444
  108. package/dist/templates/_shared/lib/context/context_extractor.py +0 -115
  109. package/dist/templates/_shared/lib/context/context_manager.py +0 -1054
  110. package/dist/templates/_shared/lib/context/discovery.py +0 -444
  111. package/dist/templates/_shared/lib/context/event_log.py +0 -308
  112. package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
  113. package/dist/templates/_shared/lib/context/task_sync.py +0 -290
  114. package/dist/templates/_shared/lib/templates/persona_questions.py +0 -113
  115. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  116. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  117. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  118. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +0 -68
  119. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +0 -98
@@ -15,9 +15,14 @@ from typing import Any, Dict, List, Optional
15
15
  _lib_dir = Path(__file__).resolve().parent
16
16
  sys.path.insert(0, str(_lib_dir))
17
17
 
18
- from utils import OrchestratorResult, eprint, parse_json_maybe
18
+ from utils import OrchestratorResult, parse_json_maybe
19
19
  from reviewers.base import AgentConfig, OrchestratorConfig
20
20
 
21
+ # Import logger
22
+ _shared_logger = Path(__file__).resolve().parent.parent.parent / "_shared" / "lib"
23
+ sys.path.insert(0, str(_shared_logger))
24
+ from base.logger import log_debug, log_info, log_warn, log_error
25
+
21
26
  # Import shared subprocess utilities
22
27
  _shared_lib = Path(__file__).resolve().parent.parent.parent / "_shared" / "lib" / "base"
23
28
  sys.path.insert(0, str(_shared_lib))
@@ -59,6 +64,44 @@ ORCHESTRATOR_SCHEMA: Dict[str, Any] = {
59
64
  }
60
65
 
61
66
 
67
+ def build_orchestrator_schema(
68
+ valid_agent_names: List[str],
69
+ categories: List[str],
70
+ ) -> Dict[str, Any]:
71
+ """Build orchestrator JSON schema with enum-constrained agent names.
72
+
73
+ When valid_agent_names is non-empty, selectedAgents items are constrained
74
+ to only those names via JSON schema enum. This prevents the LLM from
75
+ hallucinating or misspelling agent names.
76
+
77
+ Args:
78
+ valid_agent_names: List of valid agent names for enum constraint.
79
+ categories: List of valid complexity categories.
80
+
81
+ Returns:
82
+ JSON schema dict for orchestrator structured output.
83
+ """
84
+ items_schema: Dict[str, Any] = {"type": "string"}
85
+ if valid_agent_names:
86
+ items_schema["enum"] = valid_agent_names
87
+
88
+ return {
89
+ "type": "object",
90
+ "properties": {
91
+ "complexity": {"type": "string", "enum": ["simple", "medium", "high"]},
92
+ "category": {"type": "string", "enum": categories},
93
+ "selectedAgents": {
94
+ "type": "array",
95
+ "items": items_schema,
96
+ },
97
+ "reasoning": {"type": "string"},
98
+ "skipReason": {"type": "string"},
99
+ },
100
+ "required": ["complexity", "category", "selectedAgents", "reasoning"],
101
+ "additionalProperties": False,
102
+ }
103
+
104
+
62
105
  # ---------------------------
63
106
  # Output Parsing
64
107
  # ---------------------------
@@ -81,18 +124,18 @@ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
81
124
  result = json.loads(raw)
82
125
  if isinstance(result, dict):
83
126
  if "structured_output" in result:
84
- eprint("[orchestrator:parse] Found structured_output in root dict")
127
+ log_debug("orchestrator", "Found structured_output in root dict", component="parse")
85
128
  return result["structured_output"]
86
129
  if result.get("type") == "assistant":
87
130
  message = result.get("message", {})
88
131
  content = message.get("content", [])
89
132
  for item in content:
90
133
  if isinstance(item, dict) and item.get("name") == "StructuredOutput":
91
- eprint("[orchestrator:parse] Found StructuredOutput in assistant message content")
134
+ log_debug("orchestrator", "Found StructuredOutput in assistant message content", component="parse")
92
135
  return item.get("input", {})
93
- eprint("[orchestrator:parse] Assistant message found but no StructuredOutput tool use in content")
136
+ log_debug("orchestrator", "Assistant message found but no StructuredOutput tool use in content", component="parse")
94
137
  elif isinstance(result, list):
95
- eprint(f"[orchestrator:parse] Received list of {len(result)} events, searching for assistant message")
138
+ log_debug("orchestrator", f"Received list of {len(result)} events, searching for assistant message", component="parse")
96
139
  for i, event in enumerate(result):
97
140
  if not isinstance(event, dict):
98
141
  continue
@@ -101,16 +144,16 @@ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
101
144
  content = message.get("content", [])
102
145
  for item in content:
103
146
  if isinstance(item, dict) and item.get("name") == "StructuredOutput":
104
- eprint(f"[orchestrator:parse] Found StructuredOutput in event[{i}] assistant message")
147
+ log_debug("orchestrator", f"Found StructuredOutput in event[{i}] assistant message", component="parse")
105
148
  return item.get("input", {})
106
- eprint("[orchestrator:parse] No StructuredOutput found in any assistant message in event list")
149
+ log_debug("orchestrator", "No StructuredOutput found in any assistant message in event list", component="parse")
107
150
  except json.JSONDecodeError as e:
108
- eprint(f"[orchestrator:parse] JSON decode error: {e}")
151
+ log_warn("orchestrator", f"JSON decode error: {e}", component="parse")
109
152
  except Exception as e:
110
- eprint(f"[orchestrator:parse] Unexpected error during structured parsing: {e}")
153
+ log_error("orchestrator", f"Unexpected error during structured parsing: {e}", component="parse")
111
154
 
112
155
  # Fallback to heuristic extraction
113
- eprint("[orchestrator:parse] No structured output found, falling back to heuristic JSON extraction")
156
+ log_debug("orchestrator", "No structured output found, falling back to heuristic JSON extraction", component="parse")
114
157
  return parse_json_maybe(raw)
115
158
 
116
159
 
@@ -123,6 +166,7 @@ def run_orchestrator(
123
166
  agent_library: List[AgentConfig],
124
167
  config: OrchestratorConfig,
125
168
  settings: Dict[str, Any],
169
+ mandatory_names: Optional[set] = None,
126
170
  ) -> OrchestratorResult:
127
171
  """Run the orchestrator agent to analyze plan complexity and select reviewers.
128
172
 
@@ -131,40 +175,54 @@ def run_orchestrator(
131
175
  agent_library: List of available agents
132
176
  config: Orchestrator configuration (model, timeout)
133
177
  settings: Agent review settings (agentSelection, complexityCategories)
178
+ mandatory_names: Set of agent names that always run (excluded from selection)
134
179
 
135
180
  Returns:
136
181
  OrchestratorResult with complexity, category, and selected agents
137
182
  """
138
- eprint("[orchestrator] Starting plan analysis...")
183
+ log_info("orchestrator", "Starting plan analysis...")
184
+
185
+ if mandatory_names is None:
186
+ mandatory_names = set()
139
187
 
140
188
  selection = settings.get("agentSelection", DEFAULT_AGENT_SELECTION)
141
189
  categories = settings.get("complexityCategories", DEFAULT_COMPLEXITY_CATEGORIES)
142
190
  fallback_count = selection.get("fallbackCount", 2)
143
191
 
192
+ # Filter out mandatory agents — they always run, no need for orchestrator to select them
193
+ non_mandatory = [a for a in agent_library if a.enabled and a.name not in mandatory_names]
194
+ valid_names = [a.name for a in non_mandatory]
195
+
196
+ log_debug("orchestrator", f"Mandatory agents (always run): {sorted(mandatory_names)}")
197
+ log_debug("orchestrator", f"Non-mandatory agents for selection: {valid_names}")
198
+
144
199
  claude_path = shutil.which("claude")
145
200
  if claude_path is None:
146
- eprint("[orchestrator] Claude CLI not found on PATH, falling back to medium complexity")
201
+ log_warn("orchestrator", "Claude CLI not found on PATH, falling back to medium complexity")
147
202
  return OrchestratorResult(
148
203
  complexity="medium",
149
204
  category="code",
150
- selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
205
+ selected_agents=[a.name for a in non_mandatory][:fallback_count],
151
206
  reasoning="Orchestrator skipped - Claude CLI not found",
152
207
  error="claude CLI not found on PATH",
153
208
  )
154
209
 
155
- eprint(f"[orchestrator] Found Claude CLI at: {claude_path}")
210
+ log_debug("orchestrator", f"Found Claude CLI at: {claude_path}")
156
211
 
157
- # Build agent list for prompt with rich descriptions
212
+ # Build agent list from non-mandatory agents only
158
213
  agent_list = "\n".join([
159
214
  f"- {a.name} [{', '.join(a.categories)}]\n"
160
215
  f" Focus: {a.focus}\n"
161
216
  f" Expertise: {a.description}"
162
- for a in agent_library if a.enabled
217
+ for a in non_mandatory
163
218
  ])
164
219
  category_list = "/".join(categories)
165
- simple_range = f"{selection.get('simple', {}).get('min', 0)}-{selection.get('simple', {}).get('max', 0)}"
166
- medium_range = f"{selection.get('medium', {}).get('min', 1)}-{selection.get('medium', {}).get('max', 2)}"
167
- high_range = f"{selection.get('high', {}).get('min', 2)}-{selection.get('high', {}).get('max', 4)}"
220
+
221
+ # Compute additional agent counts (total minus mandatory count)
222
+ mandatory_count = len([a for a in agent_library if a.name in mandatory_names])
223
+ simple_additional = max(0, selection.get("simple", {}).get("max", 3) - mandatory_count)
224
+ medium_additional = max(0, selection.get("medium", {}).get("max", 8) - mandatory_count)
225
+ high_additional = max(0, selection.get("high", {}).get("max", 12) - mandatory_count)
168
226
 
169
227
  # System prompt with orchestrator instructions
170
228
  system_prompt = """You are a plan orchestrator for code review. Your job is to analyze plans and select appropriate reviewer agents.
@@ -180,15 +238,16 @@ When selecting agents:
180
238
  # User prompt with plan and agent list
181
239
  prompt = f"""Analyze this plan and select appropriate reviewer agents.
182
240
 
183
- Available agents:
241
+ Available agents (select ONLY from this list):
184
242
  {agent_list}
185
243
 
186
- Selection rules:
187
- - simple complexity = {simple_range} agents
188
- - medium complexity = {medium_range} agents
189
- - high complexity = {high_range} agents
244
+ Selection rules (number of ADDITIONAL agents to select from the list above):
245
+ - simple complexity = {simple_additional} agents
246
+ - medium complexity = {medium_additional} agents
247
+ - high complexity = {high_additional} agents
190
248
  - Only select agents whose categories match the plan category ({category_list})
191
249
  - Non-technical plans (life, business) typically need 0 code-focused agents
250
+ - Note: mandatory agents run separately and are NOT listed above
192
251
 
193
252
  PLAN:
194
253
  <<<
@@ -197,7 +256,9 @@ PLAN:
197
256
 
198
257
  Call StructuredOutput now with: complexity, category, selectedAgents, reasoning"""
199
258
 
200
- schema_json = json.dumps(ORCHESTRATOR_SCHEMA, ensure_ascii=False)
259
+ # Use dynamic schema with enum constraint when we have valid agent names
260
+ schema = build_orchestrator_schema(valid_names, categories) if valid_names else ORCHESTRATOR_SCHEMA
261
+ schema_json = json.dumps(schema, ensure_ascii=False)
201
262
 
202
263
  cmd_args = [
203
264
  claude_path,
@@ -210,7 +271,7 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning"
210
271
  "--system-prompt", system_prompt,
211
272
  ]
212
273
 
213
- eprint(f"[orchestrator] Running with model: {config.model}, timeout: {config.timeout}s")
274
+ log_info("orchestrator", f"Running with model: {config.model}, timeout: {config.timeout}s")
214
275
 
215
276
  # Get environment for internal subprocess (bypasses hooks)
216
277
  env = get_internal_subprocess_env()
@@ -227,48 +288,48 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning"
227
288
  env=env,
228
289
  )
229
290
  except subprocess.TimeoutExpired:
230
- eprint(f"[orchestrator] TIMEOUT after {config.timeout}s, falling back to medium complexity")
291
+ log_warn("orchestrator", f"TIMEOUT after {config.timeout}s, falling back to medium complexity")
231
292
  return OrchestratorResult(
232
293
  complexity="medium",
233
294
  category="code",
234
- selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
295
+ selected_agents=[a.name for a in non_mandatory][:fallback_count],
235
296
  reasoning="Orchestrator timed out - defaulting to medium complexity",
236
297
  error=f"Orchestrator timed out after {config.timeout}s",
237
298
  )
238
299
  except Exception as ex:
239
- eprint(f"[orchestrator] EXCEPTION: {ex}, falling back to medium complexity")
300
+ log_error("orchestrator", f"Exception: {ex}, falling back to medium complexity")
240
301
  return OrchestratorResult(
241
302
  complexity="medium",
242
303
  category="code",
243
- selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
304
+ selected_agents=[a.name for a in non_mandatory][:fallback_count],
244
305
  reasoning=f"Orchestrator failed: {ex}",
245
306
  error=str(ex),
246
307
  )
247
308
 
248
- eprint(f"[orchestrator] Exit code: {p.returncode}")
309
+ log_debug("orchestrator", f"Exit code: {p.returncode}")
249
310
 
250
311
  raw = (p.stdout or "").strip()
251
312
  if p.stderr:
252
- eprint(f"[orchestrator] stderr: {p.stderr[:300]}")
313
+ log_debug("orchestrator", f"stderr: {p.stderr[:300]}")
253
314
 
254
315
  obj = _parse_claude_output(raw)
255
316
 
256
317
  # Debug logging to diagnose empty selectedAgents issue
257
- eprint(f"[orchestrator:debug] Raw output length: {len(raw)} chars")
318
+ log_debug("orchestrator", f"Raw output length: {len(raw)} chars")
258
319
  if raw:
259
- eprint(f"[orchestrator:debug] Raw output (first 500 chars): {raw[:500]}")
260
- eprint(f"[orchestrator:debug] Parsed obj: {obj}")
320
+ log_debug("orchestrator", f"Raw output (first 500 chars): {raw[:500]}")
321
+ log_debug("orchestrator", f"Parsed obj: {obj}")
261
322
  if obj:
262
- eprint(f"[orchestrator:debug] obj keys: {list(obj.keys())}")
263
- eprint(f"[orchestrator:debug] selectedAgents value: {obj.get('selectedAgents', 'MISSING')}")
264
- eprint(f"[orchestrator:debug] reasoning value: {obj.get('reasoning', 'MISSING')}")
323
+ log_debug("orchestrator", f"obj keys: {list(obj.keys())}")
324
+ log_debug("orchestrator", f"selectedAgents value: {obj.get('selectedAgents', 'MISSING')}")
325
+ log_debug("orchestrator", f"reasoning value: {obj.get('reasoning', 'MISSING')}")
265
326
 
266
327
  if not obj:
267
- eprint("[orchestrator] Failed to parse output, falling back to medium complexity")
328
+ log_warn("orchestrator", "Failed to parse output, falling back to medium complexity")
268
329
  return OrchestratorResult(
269
330
  complexity="medium",
270
331
  category="code",
271
- selected_agents=[a.name for a in agent_library if a.enabled][:fallback_count],
332
+ selected_agents=[a.name for a in non_mandatory][:fallback_count],
272
333
  reasoning="Orchestrator output could not be parsed",
273
334
  error="Failed to parse orchestrator output",
274
335
  )
@@ -289,8 +350,8 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning"
289
350
  reasoning = str(obj.get("reasoning", "")).strip() or "No reasoning provided"
290
351
  skip_reason = obj.get("skipReason")
291
352
 
292
- eprint(f"[orchestrator] Result: complexity={complexity}, category={category}, agents={selected_agents}")
293
- eprint(f"[orchestrator] Reasoning: {reasoning}")
353
+ log_info("orchestrator", f"Result: complexity={complexity}, category={category}, agents={selected_agents}")
354
+ log_debug("orchestrator", f"Reasoning: {reasoning}")
294
355
 
295
356
  return OrchestratorResult(
296
357
  complexity=complexity,
@@ -15,10 +15,15 @@ from typing import Any, Dict, Optional
15
15
  _lib_dir = Path(__file__).resolve().parent.parent
16
16
  sys.path.insert(0, str(_lib_dir))
17
17
 
18
- from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
18
+ from utils import ReviewerResult, parse_json_maybe, coerce_to_review
19
19
  from debug import debug_log, debug_raw
20
20
  from .base import AgentConfig, AGENT_REVIEW_PROMPT_PREFIX
21
21
 
22
+ # Import logger
23
+ _shared_logger = Path(__file__).resolve().parent.parent.parent.parent / "_shared" / "lib"
24
+ sys.path.insert(0, str(_shared_logger))
25
+ from base.logger import log_debug, log_info, log_warn, log_error
26
+
22
27
  # Import shared subprocess utilities
23
28
  _shared_lib = Path(__file__).resolve().parent.parent.parent.parent / "_shared" / "lib" / "base"
24
29
  sys.path.insert(0, str(_shared_lib))
@@ -43,18 +48,18 @@ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
43
48
  result = json.loads(raw)
44
49
  if isinstance(result, dict):
45
50
  if "structured_output" in result:
46
- eprint("[parse] Found structured_output in root dict")
51
+ log_debug("agent", "Found structured_output in root dict", component="parse")
47
52
  return result["structured_output"]
48
53
  if result.get("type") == "assistant":
49
54
  message = result.get("message", {})
50
55
  content = message.get("content", [])
51
56
  for item in content:
52
57
  if isinstance(item, dict) and item.get("name") == "StructuredOutput":
53
- eprint("[parse] Found StructuredOutput in assistant message content")
58
+ log_debug("agent", "Found StructuredOutput in assistant message content", component="parse")
54
59
  return item.get("input", {})
55
- eprint("[parse] Assistant message found but no StructuredOutput tool use in content")
60
+ log_debug("agent", "Assistant message found but no StructuredOutput tool use in content", component="parse")
56
61
  elif isinstance(result, list):
57
- eprint(f"[parse] Received list of {len(result)} events, searching for assistant message")
62
+ log_debug("agent", f"Received list of {len(result)} events, searching for assistant message", component="parse")
58
63
  for i, event in enumerate(result):
59
64
  if not isinstance(event, dict):
60
65
  continue
@@ -63,16 +68,16 @@ def _parse_claude_output(raw: str) -> Optional[Dict[str, Any]]:
63
68
  content = message.get("content", [])
64
69
  for item in content:
65
70
  if isinstance(item, dict) and item.get("name") == "StructuredOutput":
66
- eprint(f"[parse] Found StructuredOutput in event[{i}] assistant message")
71
+ log_debug("agent", f"Found StructuredOutput in event[{i}] assistant message", component="parse")
67
72
  return item.get("input", {})
68
- eprint("[parse] No StructuredOutput found in any assistant message in event list")
73
+ log_debug("agent", "No StructuredOutput found in any assistant message in event list", component="parse")
69
74
  except json.JSONDecodeError as e:
70
- eprint(f"[parse] JSON decode error: {e}")
75
+ log_warn("agent", f"JSON decode error: {e}", component="parse")
71
76
  except Exception as e:
72
- eprint(f"[parse] Unexpected error during structured parsing: {e}")
77
+ log_error("agent", f"Unexpected error during structured parsing: {e}", component="parse")
73
78
 
74
79
  # Fallback to heuristic extraction with required field validation
75
- eprint("[parse] No structured output found, falling back to heuristic JSON extraction")
80
+ log_debug("agent", "No structured output found, falling back to heuristic JSON extraction", component="parse")
76
81
  return parse_json_maybe(raw, require_fields=["verdict", "summary"])
77
82
 
78
83
 
@@ -99,7 +104,7 @@ def run_agent_review(
99
104
  """
100
105
  claude_path = shutil.which("claude")
101
106
  if claude_path is None:
102
- eprint(f"[{agent.name}] Claude CLI not found on PATH")
107
+ log_warn(agent.name, "Claude CLI not found on PATH")
103
108
  return ReviewerResult(
104
109
  name=agent.name,
105
110
  ok=False,
@@ -109,7 +114,7 @@ def run_agent_review(
109
114
  err="claude CLI not found on PATH",
110
115
  )
111
116
 
112
- eprint(f"[{agent.name}] Found Claude CLI at: {claude_path}")
117
+ log_debug(agent.name, f"Found Claude CLI at: {claude_path}")
113
118
 
114
119
  # User prompt - direct instruction to call StructuredOutput immediately
115
120
  prompt = f"""IMMEDIATELY call StructuredOutput with your review of the plan below.
@@ -139,7 +144,7 @@ PLAN:
139
144
  full_prompt = AGENT_REVIEW_PROMPT_PREFIX + "\n\n---\n\n" + agent.system_prompt
140
145
  cmd_args.extend(["--system-prompt", full_prompt])
141
146
 
142
- eprint(f"[{agent.name}] Running with model: {agent.model}, timeout: {timeout}s")
147
+ log_info(agent.name, f"Running with model: {agent.model}, timeout: {timeout}s")
143
148
 
144
149
  # Get environment for internal subprocess (bypasses hooks)
145
150
  env = get_internal_subprocess_env()
@@ -156,16 +161,16 @@ PLAN:
156
161
  env=env,
157
162
  )
158
163
  except subprocess.TimeoutExpired:
159
- eprint(f"[{agent.name}] TIMEOUT after {timeout}s")
164
+ log_warn(agent.name, f"TIMEOUT after {timeout}s")
160
165
  return ReviewerResult(agent.name, False, "error", {}, "", f"{agent.name} timed out after {timeout}s")
161
166
  except Exception as ex:
162
- eprint(f"[{agent.name}] EXCEPTION: {ex}")
167
+ log_error(agent.name, f"Exception: {ex}")
163
168
  return ReviewerResult(agent.name, False, "error", {}, "", f"{agent.name} failed to run: {ex}")
164
169
 
165
- eprint(f"[{agent.name}] Exit code: {p.returncode}")
166
- eprint(f"[{agent.name}] stdout length: {len(p.stdout or '')} chars")
170
+ log_debug(agent.name, f"Exit code: {p.returncode}")
171
+ log_debug(agent.name, f"stdout length: {len(p.stdout or '')} chars")
167
172
  if p.stderr:
168
- eprint(f"[{agent.name}] stderr: {p.stderr[:500]}")
173
+ log_debug(agent.name, f"stderr: {p.stderr[:500]}")
169
174
 
170
175
  raw = (p.stdout or "").strip()
171
176
  err = (p.stderr or "").strip()
@@ -184,7 +189,7 @@ PLAN:
184
189
  })
185
190
 
186
191
  if raw:
187
- eprint(f"[{agent.name}] stdout preview: {raw[:500]}")
192
+ log_debug(agent.name, f"stdout preview: {raw[:500]}")
188
193
 
189
194
  obj = _parse_claude_output(raw)
190
195
 
@@ -201,9 +206,9 @@ PLAN:
201
206
  })
202
207
 
203
208
  if obj:
204
- eprint(f"[{agent.name}] Parsed JSON successfully, verdict: {obj.get('verdict', 'N/A')}")
209
+ log_info(agent.name, f"Parsed JSON successfully, verdict: {obj.get('verdict', 'N/A')}")
205
210
  else:
206
- eprint(f"[{agent.name}] Failed to parse JSON from output")
211
+ log_warn(agent.name, "Failed to parse JSON from output")
207
212
 
208
213
  ok, verdict, norm = coerce_to_review(obj, "Retry or check agent configuration.")
209
214
 
@@ -16,9 +16,14 @@ from typing import Any, Dict
16
16
  _lib_dir = Path(__file__).resolve().parent.parent
17
17
  sys.path.insert(0, str(_lib_dir))
18
18
 
19
- from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
19
+ from utils import ReviewerResult, parse_json_maybe, coerce_to_review
20
20
  from .base import REVIEW_PROMPT_PREFIX
21
21
 
22
+ # Import logger
23
+ _shared_logger = Path(__file__).resolve().parent.parent.parent.parent / "_shared" / "lib"
24
+ sys.path.insert(0, str(_shared_logger))
25
+ from base.logger import log_debug, log_info, log_warn, log_error
26
+
22
27
 
23
28
  def run_codex_review(
24
29
  plan: str,
@@ -41,7 +46,7 @@ def run_codex_review(
41
46
 
42
47
  codex_path = shutil.which("codex")
43
48
  if codex_path is None:
44
- eprint("[codex] CLI not found on PATH")
49
+ log_warn("codex", "CLI not found on PATH")
45
50
  return ReviewerResult(
46
51
  name="codex",
47
52
  ok=False,
@@ -51,7 +56,7 @@ def run_codex_review(
51
56
  err="codex CLI not found on PATH",
52
57
  )
53
58
 
54
- eprint(f"[codex] Found CLI at: {codex_path}")
59
+ log_debug("codex", f"Found CLI at: {codex_path}")
55
60
 
56
61
  prompt = f"""{REVIEW_PROMPT_PREFIX}
57
62
  Return ONLY a JSON object that matches this JSON Schema:
@@ -87,7 +92,7 @@ PLAN:
87
92
  cmd.insert(2, "--model")
88
93
  cmd.insert(3, model)
89
94
 
90
- eprint(f"[codex] Running command: {' '.join(cmd)}")
95
+ log_debug("codex", f"Running command: {' '.join(cmd)}")
91
96
 
92
97
  try:
93
98
  p = subprocess.run(
@@ -100,13 +105,13 @@ PLAN:
100
105
  errors='replace',
101
106
  )
102
107
  except subprocess.TimeoutExpired:
103
- eprint(f"[codex] TIMEOUT after {timeout}s")
108
+ log_warn("codex", f"TIMEOUT after {timeout}s")
104
109
  return ReviewerResult("codex", False, "error", {}, "", f"codex timed out after {timeout}s")
105
110
  except Exception as ex:
106
- eprint(f"[codex] EXCEPTION: {ex}")
111
+ log_error("codex", f"Exception: {ex}")
107
112
  return ReviewerResult("codex", False, "error", {}, "", f"codex failed to run: {ex}")
108
113
 
109
- eprint(f"[codex] Exit code: {p.returncode}")
114
+ log_debug("codex", f"Exit code: {p.returncode}")
110
115
 
111
116
  raw = ""
112
117
  if out_path.exists():
@@ -15,7 +15,12 @@ from typing import Any, Dict
15
15
  _lib_dir = Path(__file__).resolve().parent.parent
16
16
  sys.path.insert(0, str(_lib_dir))
17
17
 
18
- from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
18
+ from utils import ReviewerResult, parse_json_maybe, coerce_to_review
19
+
20
+ # Import logger
21
+ _shared_logger = Path(__file__).resolve().parent.parent.parent.parent / "_shared" / "lib"
22
+ sys.path.insert(0, str(_shared_logger))
23
+ from base.logger import log_debug, log_info, log_warn, log_error
19
24
 
20
25
 
21
26
  def run_gemini_review(
@@ -39,7 +44,7 @@ def run_gemini_review(
39
44
 
40
45
  gemini_path = shutil.which("gemini")
41
46
  if gemini_path is None:
42
- eprint("[gemini] CLI not found on PATH")
47
+ log_warn("gemini", "CLI not found on PATH")
43
48
  return ReviewerResult(
44
49
  name="gemini",
45
50
  ok=False,
@@ -49,7 +54,7 @@ def run_gemini_review(
49
54
  err="gemini CLI not found on PATH",
50
55
  )
51
56
 
52
- eprint(f"[gemini] Found CLI at: {gemini_path}")
57
+ log_debug("gemini", f"Found CLI at: {gemini_path}")
53
58
 
54
59
  instruction = f"""
55
60
 
@@ -73,7 +78,7 @@ Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fe
73
78
  if model:
74
79
  cmd.extend(["--model", model])
75
80
 
76
- eprint("[gemini] Running command: gemini -y -p <instruction>")
81
+ log_debug("gemini", "Running command: gemini -y -p <instruction>")
77
82
 
78
83
  try:
79
84
  p = subprocess.run(
@@ -86,13 +91,13 @@ Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fe
86
91
  errors='replace',
87
92
  )
88
93
  except subprocess.TimeoutExpired:
89
- eprint(f"[gemini] TIMEOUT after {timeout}s")
94
+ log_warn("gemini", f"TIMEOUT after {timeout}s")
90
95
  return ReviewerResult("gemini", False, "error", {}, "", f"gemini timed out after {timeout}s")
91
96
  except Exception as ex:
92
- eprint(f"[gemini] EXCEPTION: {ex}")
97
+ log_error("gemini", f"Exception: {ex}")
93
98
  return ReviewerResult("gemini", False, "error", {}, "", f"gemini failed to run: {ex}")
94
99
 
95
- eprint(f"[gemini] Exit code: {p.returncode}")
100
+ log_debug("gemini", f"Exit code: {p.returncode}")
96
101
 
97
102
  raw = (p.stdout or "").strip()
98
103
  err = (p.stderr or "").strip()