crackerjack 0.31.9__py3-none-any.whl → 0.31.12__py3-none-any.whl

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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +282 -95
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +355 -204
  41. crackerjack/dynamic_config.py +47 -6
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +52 -62
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +51 -76
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +78 -44
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +281 -433
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.12.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.9.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import re
3
2
  import subprocess
4
3
  import time
5
4
  import typing as t
@@ -9,7 +8,9 @@ from pathlib import Path
9
8
  from rich.console import Console
10
9
 
11
10
  from crackerjack.config.hooks import HookDefinition, HookStrategy
11
+ from crackerjack.models.protocols import HookLockManagerProtocol
12
12
  from crackerjack.models.task import HookResult
13
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS
13
14
 
14
15
 
15
16
  @dataclass
@@ -73,33 +74,32 @@ class IndividualExecutionResult:
73
74
 
74
75
 
75
76
  class HookOutputParser:
76
- HOOK_PATTERNS: dict[str, dict[str, re.Pattern[str]]] = {
77
+ # Pattern mapping: tool -> pattern_type -> safe_pattern_name
78
+ HOOK_PATTERNS: dict[str, dict[str, str]] = {
77
79
  "ruff-check": {
78
- "error": re.compile(r"^(.+?):(\d+):(\d+):([A-Z]\d+) (.+)$"),
79
- "summary": re.compile(r"Found (\d+) error"),
80
+ "error": "ruff_check_error",
81
+ "summary": "ruff_check_summary",
80
82
  },
81
83
  "pyright": {
82
- "error": re.compile(r"^(.+?):(\d+):(\d+) - error: (.+)$"),
83
- "warning": re.compile(r"^(.+?):(\d+):(\d+) - warning: (.+)$"),
84
- "summary": re.compile(r"(\d+) error[s]?, (\d+) warning[s]?"),
84
+ "error": "pyright_error",
85
+ "warning": "pyright_warning",
86
+ "summary": "pyright_summary",
85
87
  },
86
88
  "bandit": {
87
- "issue": re.compile(r" >> Issue: \[([A-Z]\d+): \w+\] (.+)"),
88
- "location": re.compile(r" Location: (.+?):(\d+):(\d+)"),
89
- "confidence": re.compile(r" Confidence: (\w+)"),
90
- "severity": re.compile(r" Severity: (\w+)"),
89
+ "issue": "bandit_issue",
90
+ "location": "bandit_location",
91
+ "confidence": "bandit_confidence",
92
+ "severity": "bandit_severity",
91
93
  },
92
94
  "mypy": {
93
- "error": re.compile(r"^(.+?):(\d+): error: (.+)$"),
94
- "note": re.compile(r"^(.+?):(\d+): note: (.+)$"),
95
+ "error": "mypy_error",
96
+ "note": "mypy_note",
95
97
  },
96
98
  "vulture": {
97
- "unused": re.compile(r"^(.+?):(\d+): unused (.+) '(.+)'"),
99
+ "unused": "vulture_unused",
98
100
  },
99
101
  "complexipy": {
100
- "complex": re.compile(
101
- r"^(.+?):(\d+):(\d+) - (.+) is too complex \((\d+)\)",
102
- ),
102
+ "complex": "complexipy_complex",
103
103
  },
104
104
  }
105
105
 
@@ -136,14 +136,16 @@ class HookOutputParser:
136
136
  def _parse_ruff_check(
137
137
  self,
138
138
  output_lines: list[str],
139
- patterns: dict[str, re.Pattern[str]],
139
+ patterns: dict[str, str],
140
140
  result: dict[str, t.Any],
141
141
  ) -> None:
142
+ error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
143
+
142
144
  for line in output_lines:
143
145
  line = line.strip()
144
146
  if not line:
145
147
  continue
146
- if match := patterns["error"].match(line):
148
+ if match := error_pattern.match(line):
147
149
  file_path, line_num, col_num, code, message = match.groups()
148
150
  result["files_processed"].add(file_path)
149
151
  result["errors"].append(
@@ -160,14 +162,17 @@ class HookOutputParser:
160
162
  def _parse_pyright(
161
163
  self,
162
164
  output_lines: list[str],
163
- patterns: dict[str, re.Pattern[str]],
165
+ patterns: dict[str, str],
164
166
  result: dict[str, t.Any],
165
167
  ) -> None:
168
+ error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
169
+ warning_pattern = SAFE_PATTERNS[patterns["warning"]]._get_compiled_pattern()
170
+
166
171
  for line in output_lines:
167
172
  line = line.strip()
168
173
  if not line:
169
174
  continue
170
- if match := patterns["error"].match(line):
175
+ if match := error_pattern.match(line):
171
176
  file_path, line_num, col_num, message = match.groups()
172
177
  result["files_processed"].add(file_path)
173
178
  result["errors"].append(
@@ -179,7 +184,7 @@ class HookOutputParser:
179
184
  "type": "error",
180
185
  },
181
186
  )
182
- elif match := patterns["warning"].match(line):
187
+ elif match := warning_pattern.match(line):
183
188
  file_path, line_num, col_num, message = match.groups()
184
189
  result["files_processed"].add(file_path)
185
190
  result["warnings"].append(
@@ -195,14 +200,16 @@ class HookOutputParser:
195
200
  def _parse_bandit(
196
201
  self,
197
202
  output_lines: list[str],
198
- patterns: dict[str, re.Pattern[str]],
203
+ patterns: dict[str, str],
199
204
  result: dict[str, t.Any],
200
205
  ) -> None:
206
+ issue_pattern = SAFE_PATTERNS[patterns["issue"]]._get_compiled_pattern()
207
+
201
208
  for line in output_lines:
202
209
  line = line.strip()
203
210
  if not line:
204
211
  continue
205
- if match := patterns["issue"].match(line):
212
+ if match := issue_pattern.match(line):
206
213
  code, message = match.groups()
207
214
  result["errors"].append(
208
215
  {"code": code, "message": message, "type": "security"},
@@ -211,14 +218,16 @@ class HookOutputParser:
211
218
  def _parse_vulture(
212
219
  self,
213
220
  output_lines: list[str],
214
- patterns: dict[str, re.Pattern[str]],
221
+ patterns: dict[str, str],
215
222
  result: dict[str, t.Any],
216
223
  ) -> None:
224
+ unused_pattern = SAFE_PATTERNS[patterns["unused"]]._get_compiled_pattern()
225
+
217
226
  for line in output_lines:
218
227
  line = line.strip()
219
228
  if not line:
220
229
  continue
221
- if match := patterns["unused"].match(line):
230
+ if match := unused_pattern.match(line):
222
231
  file_path, line_num, item_type, item_name = match.groups()
223
232
  result["files_processed"].add(file_path)
224
233
  result["warnings"].append(
@@ -233,14 +242,16 @@ class HookOutputParser:
233
242
  def _parse_complexipy(
234
243
  self,
235
244
  output_lines: list[str],
236
- patterns: dict[str, re.Pattern[str]],
245
+ patterns: dict[str, str],
237
246
  result: dict[str, t.Any],
238
247
  ) -> None:
248
+ complex_pattern = SAFE_PATTERNS[patterns["complex"]]._get_compiled_pattern()
249
+
239
250
  for line in output_lines:
240
251
  line = line.strip()
241
252
  if not line:
242
253
  continue
243
- if match := patterns["complex"].match(line):
254
+ if match := complex_pattern.match(line):
244
255
  file_path, line_num, col_num, function_name, complexity = match.groups()
245
256
  result["files_processed"].add(file_path)
246
257
  result["errors"].append(
@@ -256,15 +267,14 @@ class HookOutputParser:
256
267
  def _parse_default_hook(
257
268
  self,
258
269
  output_lines: list[str],
259
- patterns: dict[str, re.Pattern[str]],
270
+ patterns: dict[str, str],
260
271
  result: dict[str, t.Any],
261
272
  ) -> None:
262
- # Default parser for hooks not specifically handled
263
273
  for line in output_lines:
264
274
  line = line.strip()
265
275
  if not line:
266
276
  continue
267
- # Simple heuristic - if it looks like an error, treat it as one
277
+
268
278
  if "error" in line.lower() or "fail" in line.lower():
269
279
  result["errors"].append(
270
280
  {
@@ -303,32 +313,42 @@ class HookOutputParser:
303
313
 
304
314
 
305
315
  class IndividualHookExecutor:
306
- def __init__(self, console: Console, pkg_path: Path) -> None:
316
+ def __init__(
317
+ self,
318
+ console: Console,
319
+ pkg_path: Path,
320
+ hook_lock_manager: HookLockManagerProtocol | None = None,
321
+ ) -> None:
307
322
  self.console = console
308
323
  self.pkg_path = pkg_path
309
324
  self.parser = HookOutputParser()
310
325
  self.progress_callback: t.Callable[[HookProgress], None] | None = None
311
326
  self.suppress_realtime_output = False
312
- self.progress_callback_interval = (
313
- 1 # Only callback every N lines to reduce overhead
314
- )
327
+ self.progress_callback_interval = 1
328
+
329
+ # Use dependency injection for hook lock manager
330
+ if hook_lock_manager is None:
331
+ # Import here to avoid circular imports
332
+ from crackerjack.executors.hook_lock_manager import (
333
+ hook_lock_manager as default_manager,
334
+ )
335
+
336
+ self.hook_lock_manager = default_manager
337
+ else:
338
+ self.hook_lock_manager = hook_lock_manager
315
339
 
316
340
  def set_progress_callback(self, callback: t.Callable[[HookProgress], None]) -> None:
317
341
  self.progress_callback = callback
318
342
 
319
343
  def set_mcp_mode(self, enable: bool = True) -> None:
320
- """Enable MCP mode which suppresses real-time output to prevent terminal lockup."""
321
344
  self.suppress_realtime_output = enable
322
345
  if enable:
323
- self.progress_callback_interval = (
324
- 10 # Reduce callback frequency in MCP mode
325
- )
346
+ self.progress_callback_interval = 10
326
347
 
327
348
  async def execute_strategy_individual(
328
349
  self,
329
350
  strategy: HookStrategy,
330
351
  ) -> IndividualExecutionResult:
331
- """Execute all hooks in a strategy individually (non-parallel)."""
332
352
  start_time = time.time()
333
353
  self._print_strategy_header(strategy)
334
354
 
@@ -340,7 +360,6 @@ class IndividualHookExecutor:
340
360
  return self._finalize_execution_result(strategy, execution_state, start_time)
341
361
 
342
362
  def _initialize_execution_state(self) -> dict[str, t.Any]:
343
- """Initialize state tracking for strategy execution."""
344
363
  return {"hook_results": [], "hook_progress": [], "execution_order": []}
345
364
 
346
365
  async def _execute_single_hook_in_strategy(
@@ -348,7 +367,6 @@ class IndividualHookExecutor:
348
367
  hook: HookDefinition,
349
368
  execution_state: dict[str, t.Any],
350
369
  ) -> None:
351
- """Execute a single hook and update execution state."""
352
370
  execution_state["execution_order"].append(hook.name)
353
371
 
354
372
  progress = HookProgress(
@@ -368,7 +386,6 @@ class IndividualHookExecutor:
368
386
  progress: HookProgress,
369
387
  result: HookResult,
370
388
  ) -> None:
371
- """Update progress status after hook execution."""
372
389
  progress.status = "completed" if result.status == "passed" else "failed"
373
390
  progress.end_time = time.time()
374
391
  progress.duration = progress.end_time - progress.start_time
@@ -382,7 +399,6 @@ class IndividualHookExecutor:
382
399
  execution_state: dict[str, t.Any],
383
400
  start_time: float,
384
401
  ) -> IndividualExecutionResult:
385
- """Finalize and return the execution result."""
386
402
  total_duration = time.time() - start_time
387
403
  success = all(r.status == "passed" for r in execution_state["hook_results"])
388
404
 
@@ -410,38 +426,50 @@ class IndividualHookExecutor:
410
426
  if self.progress_callback:
411
427
  self.progress_callback(progress)
412
428
 
413
- self.console.print(f"\n[bold cyan]🔍 Running {hook.name}[/bold cyan]")
429
+ # Check if this hook requires a lock for sequential execution
430
+ if self.hook_lock_manager.requires_lock(hook.name):
431
+ self.console.print(
432
+ f"\n[bold cyan]🔍 Running {hook.name} (with lock)[/ bold cyan]"
433
+ )
434
+ else:
435
+ self.console.print(f"\n[bold cyan]🔍 Running {hook.name}[/ bold cyan]")
414
436
 
415
437
  cmd = hook.get_command()
416
438
 
417
439
  try:
418
- result = await self._run_command_with_streaming(cmd, hook.timeout, progress)
440
+ # Acquire lock if the hook requires it
441
+ async with self.hook_lock_manager.acquire_hook_lock(hook.name): # type: ignore
442
+ result = await self._run_command_with_streaming(
443
+ cmd, hook.timeout, progress
444
+ )
419
445
 
420
- parsed_output = self.parser.parse_hook_output(
421
- hook.name,
422
- progress.output_lines or [],
423
- )
424
- progress.errors_found = len(parsed_output["errors"])
425
- progress.warnings_found = len(parsed_output["warnings"])
426
- progress.files_processed = parsed_output["files_processed"]
427
- progress.lines_processed = parsed_output["total_lines"]
428
- progress.error_details = parsed_output["errors"] + parsed_output["warnings"]
446
+ parsed_output = self.parser.parse_hook_output(
447
+ hook.name,
448
+ progress.output_lines or [],
449
+ )
450
+ progress.errors_found = len(parsed_output["errors"])
451
+ progress.warnings_found = len(parsed_output["warnings"])
452
+ progress.files_processed = parsed_output["files_processed"]
453
+ progress.lines_processed = parsed_output["total_lines"]
454
+ progress.error_details = (
455
+ parsed_output["errors"] + parsed_output["warnings"]
456
+ )
429
457
 
430
- hook_result = HookResult(
431
- id=hook.name,
432
- name=hook.name,
433
- status="passed" if result.returncode == 0 else "failed",
434
- duration=progress.duration or 0,
435
- )
458
+ hook_result = HookResult(
459
+ id=hook.name,
460
+ name=hook.name,
461
+ status="passed" if result.returncode == 0 else "failed",
462
+ duration=progress.duration or 0,
463
+ )
436
464
 
437
- self._print_hook_summary(hook.name, hook_result, progress)
465
+ self._print_hook_summary(hook.name, hook_result, progress)
438
466
 
439
- return hook_result
467
+ return hook_result
440
468
 
441
469
  except TimeoutError:
442
470
  progress.status = "failed"
443
471
  error_msg = f"Hook {hook.name} timed out after {hook.timeout}s"
444
- self.console.print(f"[red]⏰ {error_msg}[/red]")
472
+ self.console.print(f"[red]⏰ {error_msg}[/ red]")
445
473
 
446
474
  return HookResult(
447
475
  id=hook.name,
@@ -449,6 +477,17 @@ class IndividualHookExecutor:
449
477
  status="failed",
450
478
  duration=hook.timeout,
451
479
  )
480
+ except Exception as e:
481
+ progress.status = "failed"
482
+ error_msg = f"Hook {hook.name} failed with error: {e}"
483
+ self.console.print(f"[red]❌ {error_msg}[/ red]")
484
+
485
+ return HookResult(
486
+ id=hook.name,
487
+ name=hook.name,
488
+ status="failed",
489
+ duration=progress.duration or 0,
490
+ )
452
491
 
453
492
  async def _run_command_with_streaming(
454
493
  self,
@@ -456,7 +495,6 @@ class IndividualHookExecutor:
456
495
  timeout: int,
457
496
  progress: HookProgress,
458
497
  ) -> subprocess.CompletedProcess[str]:
459
- """Run command with streaming output and progress tracking."""
460
498
  process = await self._create_subprocess(cmd)
461
499
 
462
500
  stdout_lines: list[str] = []
@@ -478,10 +516,15 @@ class IndividualHookExecutor:
478
516
  return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
479
517
 
480
518
  async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
481
- """Create subprocess for command execution."""
519
+ # Pre-commit must run from repository root, not package directory
520
+ repo_root = (
521
+ self.pkg_path.parent
522
+ if self.pkg_path.name == "crackerjack"
523
+ else self.pkg_path
524
+ )
482
525
  return await asyncio.create_subprocess_exec(
483
526
  *cmd,
484
- cwd=self.pkg_path,
527
+ cwd=repo_root,
485
528
  stdout=asyncio.subprocess.PIPE,
486
529
  stderr=asyncio.subprocess.PIPE,
487
530
  )
@@ -493,7 +536,6 @@ class IndividualHookExecutor:
493
536
  stderr_lines: list[str],
494
537
  progress: HookProgress,
495
538
  ) -> list[asyncio.Task[None]]:
496
- """Create tasks for reading stdout and stderr streams."""
497
539
  return [
498
540
  asyncio.create_task(
499
541
  self._read_stream(process.stdout, stdout_lines, progress),
@@ -509,7 +551,6 @@ class IndividualHookExecutor:
509
551
  output_list: list[str],
510
552
  progress: HookProgress,
511
553
  ) -> None:
512
- """Read lines from stream and update progress."""
513
554
  if not stream:
514
555
  return
515
556
 
@@ -533,7 +574,6 @@ class IndividualHookExecutor:
533
574
  break
534
575
 
535
576
  def _process_stream_line(self, line: bytes | str) -> str:
536
- """Process a line from stream into clean string."""
537
577
  return (line.decode() if isinstance(line, bytes) else line).rstrip()
538
578
 
539
579
  def _update_progress_with_line(
@@ -543,7 +583,6 @@ class IndividualHookExecutor:
543
583
  progress: HookProgress,
544
584
  line_count: int,
545
585
  ) -> None:
546
- """Update progress tracking with new line."""
547
586
  output_list.append(line_str)
548
587
  progress.output_lines = progress.output_lines or []
549
588
  progress.output_lines.append(line_str)
@@ -552,12 +591,10 @@ class IndividualHookExecutor:
552
591
  self._maybe_callback_progress(progress, line_count)
553
592
 
554
593
  def _maybe_print_line(self, line_str: str) -> None:
555
- """Print line to console if not suppressed."""
556
594
  if not self.suppress_realtime_output and line_str.strip():
557
- self.console.print(f"[dim] {line_str}[/dim]")
595
+ self.console.print(f"[dim] {line_str}[/ dim]")
558
596
 
559
597
  def _maybe_callback_progress(self, progress: HookProgress, line_count: int) -> None:
560
- """Callback progress if conditions are met."""
561
598
  if self.progress_callback and (
562
599
  line_count % self.progress_callback_interval == 0
563
600
  ):
@@ -569,7 +606,6 @@ class IndividualHookExecutor:
569
606
  tasks: list[asyncio.Task[None]],
570
607
  timeout: int,
571
608
  ) -> None:
572
- """Wait for process completion with timeout."""
573
609
  await asyncio.wait_for(process.wait(), timeout=timeout)
574
610
  await asyncio.gather(*tasks, return_exceptions=True)
575
611
 
@@ -578,7 +614,6 @@ class IndividualHookExecutor:
578
614
  process: asyncio.subprocess.Process,
579
615
  tasks: list[asyncio.Task[None]],
580
616
  ) -> None:
581
- """Handle process timeout by killing process and canceling tasks."""
582
617
  process.kill()
583
618
  for task in tasks:
584
619
  task.cancel()
@@ -590,7 +625,6 @@ class IndividualHookExecutor:
590
625
  stdout_lines: list[str],
591
626
  stderr_lines: list[str],
592
627
  ) -> subprocess.CompletedProcess[str]:
593
- """Create CompletedProcess result."""
594
628
  return subprocess.CompletedProcess(
595
629
  args=cmd,
596
630
  returncode=process.returncode or 0,
@@ -601,11 +635,11 @@ class IndividualHookExecutor:
601
635
  def _print_strategy_header(self, strategy: HookStrategy) -> None:
602
636
  self.console.print("\n" + "=" * 80)
603
637
  self.console.print(
604
- f"[bold bright_cyan]🔍 INDIVIDUAL HOOK EXECUTION[/bold bright_cyan] "
605
- f"[bold bright_white]{strategy.name.upper()} HOOKS[/bold bright_white]",
638
+ f"[bold bright_cyan]🔍 INDIVIDUAL HOOK EXECUTION[/ bold bright_cyan] "
639
+ f"[bold bright_white]{strategy.name.upper()} HOOKS[/ bold bright_white]",
606
640
  )
607
641
  self.console.print(
608
- f"[dim]Running {len(strategy.hooks)} hooks individually with real-time streaming[/dim]",
642
+ f"[dim]Running {len(strategy.hooks)} hooks individually with real-time streaming[/ dim]",
609
643
  )
610
644
  self.console.print("=" * 80)
611
645
 
@@ -616,7 +650,7 @@ class IndividualHookExecutor:
616
650
  progress: HookProgress,
617
651
  ) -> None:
618
652
  status_icon = "✅" if result.status == "passed" else "❌"
619
- duration_str = f"{progress.duration:.1f}s" if progress.duration else "0.0s"
653
+ duration_str = f"{progress.duration: .1f}s" if progress.duration else "0.0s"
620
654
 
621
655
  summary_parts: list[str] = []
622
656
  if progress.errors_found > 0:
@@ -629,7 +663,7 @@ class IndividualHookExecutor:
629
663
  summary = ", ".join(summary_parts) if summary_parts else "clean"
630
664
 
631
665
  self.console.print(
632
- f"[bold]{status_icon} {hook_name}[/bold] - {duration_str} - {summary}",
666
+ f"[bold]{status_icon} {hook_name}[/ bold] - {duration_str}-{summary}",
633
667
  )
634
668
 
635
669
  def _print_individual_summary(
@@ -646,17 +680,17 @@ class IndividualHookExecutor:
646
680
 
647
681
  self.console.print("\n" + "-" * 80)
648
682
  self.console.print(
649
- f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/bold] - {strategy.name.upper()}",
683
+ f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
650
684
  )
651
685
  self.console.print(f"✅ Passed: {passed} | ❌ Failed: {failed}")
652
686
  if total_errors > 0:
653
687
  self.console.print(f"🚨 Total Errors: {total_errors}")
654
688
  if total_warnings > 0:
655
689
  self.console.print(f"⚠️ Total Warnings: {total_warnings}")
656
- self.console.print(f"⏱️ Total Duration: {total_duration:.1f}s")
690
+ self.console.print(f"⏱️ Total Duration: {total_duration: .1f}s")
657
691
 
658
692
  if failed > 0:
659
- self.console.print("\n[bold red]Failed Hooks: [/bold red]")
693
+ self.console.print("\n[bold red]Failed Hooks: [/ bold red]")
660
694
  for progress in progress_list:
661
695
  if progress.status == "failed":
662
696
  error_summary = (
@@ -664,6 +698,6 @@ class IndividualHookExecutor:
664
698
  if progress.errors_found > 0
665
699
  else "failed"
666
700
  )
667
- self.console.print(f" ❌ {progress.hook_name} - {error_summary}")
701
+ self.console.print(f" ❌ {progress.hook_name}-{error_summary}")
668
702
 
669
703
  self.console.print("-" * 80)
@@ -1,10 +1,3 @@
1
- """Intelligent Agent Selection System.
2
-
3
- This module provides intelligent selection and orchestration of agents from
4
- multiple sources (crackerjack, user, system) with smart prioritization and
5
- adaptive learning capabilities.
6
- """
7
-
8
1
  from .adaptive_learning import AdaptiveLearningSystem, get_learning_system
9
2
  from .agent_orchestrator import (
10
3
  AgentOrchestrator,