crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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 (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,281 @@
1
+ import ast
2
+ import typing as t
3
+ from collections import defaultdict
4
+ from pathlib import Path
5
+
6
+ from .base import (
7
+ AgentContext,
8
+ FixResult,
9
+ Issue,
10
+ IssueType,
11
+ SubAgent,
12
+ agent_registry,
13
+ )
14
+
15
+
16
+ class ImportAnalysis(t.NamedTuple):
17
+ file_path: Path
18
+ mixed_imports: list[str]
19
+ redundant_imports: list[str]
20
+ optimization_opportunities: list[str]
21
+
22
+
23
+ class ImportOptimizationAgent(SubAgent):
24
+ name = "import_optimization"
25
+
26
+ def __init__(self, context: AgentContext) -> None:
27
+ super().__init__(context)
28
+ self.preferred_style = "from_import"
29
+
30
+ def get_supported_types(self) -> set[IssueType]:
31
+ return {IssueType.IMPORT_ERROR, IssueType.DEAD_CODE}
32
+
33
+ async def can_handle(self, issue: Issue) -> float:
34
+ if issue.type in self.get_supported_types():
35
+ return 0.9
36
+
37
+ description_lower = issue.message.lower()
38
+ import_keywords = [
39
+ "import",
40
+ "unused import",
41
+ "redundant import",
42
+ "import style",
43
+ ]
44
+ if any(keyword in description_lower for keyword in import_keywords):
45
+ return 0.7
46
+
47
+ return 0.0
48
+
49
+ async def analyze_and_fix(self, issue: Issue) -> FixResult:
50
+ return await self.fix_issue(issue)
51
+
52
+ async def analyze_file(self, file_path: Path) -> ImportAnalysis:
53
+ if not file_path.exists() or file_path.suffix != ".py":
54
+ return ImportAnalysis(file_path, [], [], [])
55
+
56
+ try:
57
+ with file_path.open(encoding="utf-8") as f:
58
+ tree = ast.parse(f.read())
59
+ except (SyntaxError, OSError) as e:
60
+ self.log(f"Could not parse {file_path}: {e}", level="WARNING")
61
+ return ImportAnalysis(file_path, [], [], [])
62
+
63
+ return self._analyze_imports(file_path, tree)
64
+
65
+ def _analyze_imports(self, file_path: Path, tree: ast.AST) -> ImportAnalysis:
66
+ module_imports: dict[str, list[dict[str, t.Any]]] = defaultdict(list)
67
+ all_imports: list[dict[str, t.Any]] = []
68
+
69
+ for node in ast.walk(tree):
70
+ if isinstance(node, ast.Import):
71
+ for alias in node.names:
72
+ import_info = {
73
+ "type": "standard",
74
+ "module": alias.name,
75
+ "name": alias.asname or alias.name,
76
+ "line": node.lineno,
77
+ }
78
+ all_imports.append(import_info)
79
+ base_module = alias.name.split(".")[0]
80
+ module_imports[base_module].append(import_info)
81
+
82
+ elif isinstance(node, ast.ImportFrom) and node.module:
83
+ for alias in node.names:
84
+ import_info = {
85
+ "type": "from",
86
+ "module": node.module,
87
+ "name": alias.name,
88
+ "asname": alias.asname,
89
+ "line": node.lineno,
90
+ }
91
+ all_imports.append(import_info)
92
+ base_module = node.module.split(".")[0]
93
+ module_imports[base_module].append(import_info)
94
+
95
+ mixed_imports = self._find_mixed_imports(module_imports)
96
+ redundant_imports = self._find_redundant_imports(all_imports)
97
+ optimization_opportunities = self._find_optimization_opportunities(
98
+ module_imports,
99
+ )
100
+
101
+ return ImportAnalysis(
102
+ file_path=file_path,
103
+ mixed_imports=mixed_imports,
104
+ redundant_imports=redundant_imports,
105
+ optimization_opportunities=optimization_opportunities,
106
+ )
107
+
108
+ def _find_mixed_imports(
109
+ self,
110
+ module_imports: dict[str, list[dict[str, t.Any]]],
111
+ ) -> list[str]:
112
+ mixed: list[str] = []
113
+ for module, imports in module_imports.items():
114
+ types = {imp["type"] for imp in imports}
115
+ if len(types) > 1:
116
+ mixed.append(module)
117
+ return mixed
118
+
119
+ def _find_redundant_imports(self, all_imports: list[dict[str, t.Any]]) -> list[str]:
120
+ seen_modules: set[str] = set()
121
+ redundant: list[str] = []
122
+
123
+ for imp in all_imports:
124
+ module_key = f"{imp['module']}: {imp['name']}"
125
+ if module_key in seen_modules:
126
+ redundant.append(f"Line {imp['line']}: {imp['module']}.{imp['name']}")
127
+ seen_modules.add(module_key)
128
+
129
+ return redundant
130
+
131
+ def _find_optimization_opportunities(
132
+ self,
133
+ module_imports: dict[str, list[dict[str, t.Any]]],
134
+ ) -> list[str]:
135
+ opportunities: list[str] = []
136
+
137
+ for module, imports in module_imports.items():
138
+ standard_imports = [imp for imp in imports if imp["type"] == "standard"]
139
+ if len(standard_imports) >= 2:
140
+ opportunities.append(
141
+ f"Consider consolidating {len(standard_imports)} standard imports "
142
+ f"from {module} to from-imports",
143
+ )
144
+
145
+ return opportunities
146
+
147
+ async def fix_issue(self, issue: Issue) -> FixResult:
148
+ if issue.file_path is None:
149
+ return FixResult(
150
+ success=False,
151
+ confidence=0.0,
152
+ remaining_issues=["No file path provided for import optimization"],
153
+ )
154
+ file_path = Path(issue.file_path)
155
+
156
+ analysis = await self.analyze_file(file_path)
157
+
158
+ if not any(
159
+ [
160
+ analysis.mixed_imports,
161
+ analysis.redundant_imports,
162
+ analysis.optimization_opportunities,
163
+ ],
164
+ ):
165
+ return FixResult(
166
+ success=True,
167
+ confidence=1.0,
168
+ fixes_applied=["No import optimizations needed"],
169
+ remaining_issues=[],
170
+ recommendations=["Import patterns are already optimal"],
171
+ files_modified=[],
172
+ )
173
+
174
+ try:
175
+ with file_path.open(encoding="utf-8") as f:
176
+ original_content = f.read()
177
+
178
+ optimized_content = await self._optimize_imports(original_content, analysis)
179
+
180
+ with file_path.open("w", encoding="utf-8") as f:
181
+ f.write(optimized_content)
182
+
183
+ changes: list[str] = []
184
+ if analysis.mixed_imports:
185
+ changes.append(
186
+ f"Standardized mixed imports for modules: {', '.join(analysis.mixed_imports)}",
187
+ )
188
+ if analysis.redundant_imports:
189
+ changes.append(
190
+ f"Removed {len(analysis.redundant_imports)} redundant imports",
191
+ )
192
+ if analysis.optimization_opportunities:
193
+ changes.append(
194
+ f"Applied {len(analysis.optimization_opportunities)} optimizations",
195
+ )
196
+
197
+ return FixResult(
198
+ success=True,
199
+ confidence=0.9,
200
+ fixes_applied=changes,
201
+ remaining_issues=[],
202
+ recommendations=[f"Optimized import statements in {file_path.name}"],
203
+ files_modified=[str(file_path)],
204
+ )
205
+
206
+ except Exception as e:
207
+ return FixResult(
208
+ success=False,
209
+ confidence=0.0,
210
+ fixes_applied=[],
211
+ remaining_issues=[f"Failed to optimize imports: {e}"],
212
+ recommendations=["Manual import review needed"],
213
+ files_modified=[],
214
+ )
215
+
216
+ async def _optimize_imports(self, content: str, analysis: ImportAnalysis) -> str:
217
+ lines = content.splitlines()
218
+
219
+ for module in analysis.mixed_imports:
220
+ if module == "typing":
221
+ lines = self._consolidate_typing_imports(lines)
222
+
223
+ return "\n".join(lines)
224
+
225
+ def _consolidate_typing_imports(self, lines: list[str]) -> list[str]:
226
+ typing_imports: set[str] = set()
227
+ lines_to_remove: list[int] = []
228
+ insert_position = None
229
+
230
+ for i, line in enumerate(lines):
231
+ stripped = line.strip()
232
+ if stripped == "import typing":
233
+ lines_to_remove.append(i)
234
+ if insert_position is None:
235
+ insert_position = i
236
+ elif stripped.startswith("from typing import "):
237
+ import_part = stripped[len("from typing import ") :].strip()
238
+ items = [item.strip() for item in import_part.split(",")]
239
+ typing_imports.update(items)
240
+ lines_to_remove.append(i)
241
+ if insert_position is None:
242
+ insert_position = i
243
+
244
+ for i in reversed(lines_to_remove):
245
+ del lines[i]
246
+
247
+ if typing_imports and insert_position is not None:
248
+ consolidated = f"from typing import {', '.join(sorted(typing_imports))}"
249
+ lines.insert(insert_position, consolidated)
250
+
251
+ return lines
252
+
253
+ async def get_diagnostics(self) -> dict[str, t.Any]:
254
+ diagnostics = {
255
+ "files_analyzed": 0,
256
+ "mixed_import_files": 0,
257
+ "total_mixed_modules": 0,
258
+ "redundant_imports": 0,
259
+ "optimization_opportunities": 0,
260
+ }
261
+
262
+ for py_file in self.context.project_path.rglob("*.py"):
263
+ if py_file.name.startswith("."):
264
+ continue
265
+
266
+ analysis = await self.analyze_file(py_file)
267
+ diagnostics["files_analyzed"] += 1
268
+
269
+ if analysis.mixed_imports:
270
+ diagnostics["mixed_import_files"] += 1
271
+ diagnostics["total_mixed_modules"] += len(analysis.mixed_imports)
272
+
273
+ diagnostics["redundant_imports"] += len(analysis.redundant_imports)
274
+ diagnostics["optimization_opportunities"] += len(
275
+ analysis.optimization_opportunities,
276
+ )
277
+
278
+ return diagnostics
279
+
280
+
281
+ agent_registry.register(ImportOptimizationAgent)