crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 (156) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +227 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +170 -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 +657 -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 +409 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  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 +585 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +826 -0
  40. crackerjack/dynamic_config.py +94 -103
  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 +433 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +443 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +114 -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 +621 -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 +372 -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 +217 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +565 -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/coverage_improvement.py +223 -0
  107. crackerjack/orchestration/execution_strategies.py +341 -0
  108. crackerjack/orchestration/test_progress_streamer.py +636 -0
  109. crackerjack/plugins/__init__.py +15 -0
  110. crackerjack/plugins/base.py +200 -0
  111. crackerjack/plugins/hooks.py +246 -0
  112. crackerjack/plugins/loader.py +335 -0
  113. crackerjack/plugins/managers.py +259 -0
  114. crackerjack/py313.py +8 -3
  115. crackerjack/services/__init__.py +22 -0
  116. crackerjack/services/cache.py +314 -0
  117. crackerjack/services/config.py +358 -0
  118. crackerjack/services/config_integrity.py +99 -0
  119. crackerjack/services/contextual_ai_assistant.py +516 -0
  120. crackerjack/services/coverage_ratchet.py +356 -0
  121. crackerjack/services/debug.py +736 -0
  122. crackerjack/services/dependency_monitor.py +617 -0
  123. crackerjack/services/enhanced_filesystem.py +439 -0
  124. crackerjack/services/file_hasher.py +151 -0
  125. crackerjack/services/filesystem.py +421 -0
  126. crackerjack/services/git.py +176 -0
  127. crackerjack/services/health_metrics.py +611 -0
  128. crackerjack/services/initialization.py +873 -0
  129. crackerjack/services/log_manager.py +286 -0
  130. crackerjack/services/logging.py +174 -0
  131. crackerjack/services/metrics.py +578 -0
  132. crackerjack/services/pattern_cache.py +362 -0
  133. crackerjack/services/pattern_detector.py +515 -0
  134. crackerjack/services/performance_benchmarks.py +653 -0
  135. crackerjack/services/security.py +163 -0
  136. crackerjack/services/server_manager.py +234 -0
  137. crackerjack/services/smart_scheduling.py +144 -0
  138. crackerjack/services/tool_version_service.py +61 -0
  139. crackerjack/services/unified_config.py +437 -0
  140. crackerjack/services/version_checker.py +248 -0
  141. crackerjack/slash_commands/__init__.py +14 -0
  142. crackerjack/slash_commands/init.md +122 -0
  143. crackerjack/slash_commands/run.md +163 -0
  144. crackerjack/slash_commands/status.md +127 -0
  145. crackerjack-0.31.7.dist-info/METADATA +742 -0
  146. crackerjack-0.31.7.dist-info/RECORD +149 -0
  147. crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
  148. crackerjack/.gitignore +0 -34
  149. crackerjack/.libcst.codemod.yaml +0 -18
  150. crackerjack/.pdm.toml +0 -1
  151. crackerjack/crackerjack.py +0 -3805
  152. crackerjack/pyproject.toml +0 -286
  153. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  154. crackerjack-0.30.3.dist-info/RECORD +0 -16
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
  156. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,200 @@
1
+ import abc
2
+ import typing as t
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+
6
+
7
+ class PluginType(Enum):
8
+ HOOK = "hook"
9
+ WORKFLOW = "workflow"
10
+ INTEGRATION = "integration"
11
+ FORMATTER = "formatter"
12
+ ANALYZER = "analyzer"
13
+ PUBLISHER = "publisher"
14
+
15
+
16
+ @dataclass
17
+ class PluginMetadata:
18
+ name: str
19
+ version: str
20
+ plugin_type: PluginType
21
+ description: str
22
+ author: str = ""
23
+ license: str = ""
24
+ requires_python: str = ">=3.11"
25
+ dependencies: list[str] = field(default_factory=list)
26
+ entry_point: str = ""
27
+ config_schema: dict[str, t.Any] = field(default_factory=dict)
28
+
29
+ def to_dict(self) -> dict[str, t.Any]:
30
+ return {
31
+ "name": self.name,
32
+ "version": self.version,
33
+ "plugin_type": self.plugin_type.value,
34
+ "description": self.description,
35
+ "author": self.author,
36
+ "license": self.license,
37
+ "requires_python": self.requires_python,
38
+ "dependencies": self.dependencies,
39
+ "entry_point": self.entry_point,
40
+ "config_schema": self.config_schema,
41
+ }
42
+
43
+
44
+ class PluginBase(abc.ABC):
45
+ def __init__(self, metadata: PluginMetadata) -> None:
46
+ self.metadata = metadata
47
+ self._enabled = True
48
+ self._config: dict[str, t.Any] = {}
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ return self.metadata.name
53
+
54
+ @property
55
+ def version(self) -> str:
56
+ return self.metadata.version
57
+
58
+ @property
59
+ def plugin_type(self) -> PluginType:
60
+ return self.metadata.plugin_type
61
+
62
+ @property
63
+ def enabled(self) -> bool:
64
+ return self._enabled
65
+
66
+ def enable(self) -> None:
67
+ self._enabled = True
68
+
69
+ def disable(self) -> None:
70
+ self._enabled = False
71
+
72
+ def configure(self, config: dict[str, t.Any]) -> None:
73
+ self._config = config.copy()
74
+ self.validate_config(config)
75
+
76
+ @abc.abstractmethod
77
+ def activate(self) -> bool:
78
+ pass
79
+
80
+ @abc.abstractmethod
81
+ def deactivate(self) -> bool:
82
+ pass
83
+
84
+ def validate_config(self, config: dict[str, t.Any]) -> None:
85
+ schema = self.metadata.config_schema
86
+
87
+ if not schema:
88
+ return
89
+
90
+ required_keys = schema.get("required", [])
91
+ for key in required_keys:
92
+ if key not in config:
93
+ msg = f"Required config key '{key}' missing for plugin {self.name}"
94
+ raise ValueError(
95
+ msg,
96
+ )
97
+
98
+ def get_config(self, key: str, default: t.Any = None) -> t.Any:
99
+ return self._config.get(key, default)
100
+
101
+ def get_info(self) -> dict[str, t.Any]:
102
+ return {
103
+ "metadata": self.metadata.to_dict(),
104
+ "enabled": self.enabled,
105
+ "config": self._config,
106
+ }
107
+
108
+
109
+ class PluginRegistry:
110
+ def __init__(self) -> None:
111
+ self._plugins: dict[str, PluginBase] = {}
112
+ self._plugins_by_type: dict[PluginType, list[PluginBase]] = {}
113
+
114
+ def register(self, plugin: PluginBase) -> bool:
115
+ if plugin.name in self._plugins:
116
+ return False
117
+
118
+ self._plugins[plugin.name] = plugin
119
+
120
+ plugin_type = plugin.plugin_type
121
+ if plugin_type not in self._plugins_by_type:
122
+ self._plugins_by_type[plugin_type] = []
123
+ self._plugins_by_type[plugin_type].append(plugin)
124
+
125
+ return True
126
+
127
+ def unregister(self, plugin_name: str) -> bool:
128
+ if plugin_name not in self._plugins:
129
+ return False
130
+
131
+ plugin = self._plugins.pop(plugin_name)
132
+
133
+ plugin_type = plugin.plugin_type
134
+ if plugin_type in self._plugins_by_type:
135
+ self._plugins_by_type[plugin_type] = [
136
+ p for p in self._plugins_by_type[plugin_type] if p.name != plugin_name
137
+ ]
138
+
139
+ return True
140
+
141
+ def get(self, plugin_name: str) -> PluginBase | None:
142
+ return self._plugins.get(plugin_name)
143
+
144
+ def get_by_type(self, plugin_type: PluginType) -> list[PluginBase]:
145
+ return self._plugins_by_type.get(plugin_type, []).copy()
146
+
147
+ def get_enabled(self, plugin_type: PluginType | None = None) -> list[PluginBase]:
148
+ if plugin_type:
149
+ plugins = self.get_by_type(plugin_type)
150
+ else:
151
+ plugins = list(self._plugins.values())
152
+
153
+ return [p for p in plugins if p.enabled]
154
+
155
+ def list_all(self) -> dict[str, PluginBase]:
156
+ return self._plugins.copy()
157
+
158
+ def activate_all(self) -> dict[str, bool]:
159
+ results = {}
160
+ for plugin in self._plugins.values():
161
+ if plugin.enabled:
162
+ try:
163
+ results[plugin.name] = plugin.activate()
164
+ except Exception:
165
+ results[plugin.name] = False
166
+
167
+ return results
168
+
169
+ def deactivate_all(self) -> dict[str, bool]:
170
+ results = {}
171
+ for plugin in self._plugins.values():
172
+ try:
173
+ results[plugin.name] = plugin.deactivate()
174
+ except Exception:
175
+ results[plugin.name] = False
176
+
177
+ return results
178
+
179
+ def get_stats(self) -> dict[str, t.Any]:
180
+ by_type = {}
181
+ for plugin_type in PluginType:
182
+ plugins = self.get_by_type(plugin_type)
183
+ by_type[plugin_type.value] = {
184
+ "total": len(plugins),
185
+ "enabled": len([p for p in plugins if p.enabled]),
186
+ "disabled": len([p for p in plugins if not p.enabled]),
187
+ }
188
+
189
+ return {
190
+ "total_plugins": len(self._plugins),
191
+ "enabled_plugins": len([p for p in self._plugins.values() if p.enabled]),
192
+ "by_type": by_type,
193
+ }
194
+
195
+
196
+ _registry = PluginRegistry()
197
+
198
+
199
+ def get_plugin_registry() -> PluginRegistry:
200
+ return _registry
@@ -0,0 +1,246 @@
1
+ import abc
2
+ import subprocess
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+
8
+ from crackerjack.config.hooks import HookDefinition, HookStage
9
+ from crackerjack.models.protocols import OptionsProtocol
10
+ from crackerjack.models.task import HookResult
11
+
12
+ from .base import PluginBase, PluginMetadata, PluginType
13
+
14
+
15
+ @dataclass
16
+ class CustomHookDefinition:
17
+ name: str
18
+ description: str
19
+ command: list[str] | None = None
20
+ file_patterns: list[str] = field(default_factory=list)
21
+ timeout: int = 60
22
+ stage: HookStage = HookStage.COMPREHENSIVE
23
+ requires_files: bool = True
24
+ parallel_safe: bool = True
25
+
26
+ def to_hook_definition(self) -> HookDefinition:
27
+ cmd = self.command or []
28
+ return HookDefinition(
29
+ name=self.name,
30
+ command=cmd,
31
+ timeout=self.timeout,
32
+ stage=self.stage,
33
+ manual_stage=self.stage == HookStage.COMPREHENSIVE,
34
+ )
35
+
36
+
37
+ class HookPluginBase(PluginBase, abc.ABC):
38
+ def __init__(self, metadata: PluginMetadata) -> None:
39
+ super().__init__(metadata)
40
+ assert metadata.plugin_type == PluginType.HOOK
41
+ self.console: Console | None = None
42
+ self.pkg_path: Path | None = None
43
+
44
+ def initialize(self, console: Console, pkg_path: Path) -> None:
45
+ self.console = console
46
+ self.pkg_path = pkg_path
47
+
48
+ @abc.abstractmethod
49
+ def get_hook_definitions(self) -> list[CustomHookDefinition]:
50
+ pass
51
+
52
+ @abc.abstractmethod
53
+ def execute_hook(
54
+ self,
55
+ hook_name: str,
56
+ files: list[Path],
57
+ options: OptionsProtocol,
58
+ ) -> HookResult:
59
+ pass
60
+
61
+ def should_run_hook(self, hook_name: str, files: list[Path]) -> bool:
62
+ hook_def = self._get_hook_definition(hook_name)
63
+ if not hook_def or not hook_def.requires_files:
64
+ return True
65
+
66
+ if not hook_def.file_patterns:
67
+ return bool(files)
68
+
69
+ for file_path in files:
70
+ for pattern in hook_def.file_patterns:
71
+ if file_path.match(pattern):
72
+ return True
73
+
74
+ return False
75
+
76
+ def _get_hook_definition(self, hook_name: str) -> CustomHookDefinition | None:
77
+ for hook_def in self.get_hook_definitions():
78
+ if hook_def.name == hook_name:
79
+ return hook_def
80
+ return None
81
+
82
+
83
+ class CustomHookPlugin(HookPluginBase):
84
+ def __init__(
85
+ self,
86
+ metadata: PluginMetadata,
87
+ hook_definitions: list[CustomHookDefinition],
88
+ ) -> None:
89
+ super().__init__(metadata)
90
+ self._hook_definitions = hook_definitions
91
+
92
+ def get_hook_definitions(self) -> list[CustomHookDefinition]:
93
+ return self._hook_definitions.copy()
94
+
95
+ def execute_hook(
96
+ self,
97
+ hook_name: str,
98
+ files: list[Path],
99
+ options: OptionsProtocol,
100
+ ) -> HookResult:
101
+ hook_def = self._get_hook_definition(hook_name)
102
+ if not hook_def:
103
+ return HookResult(
104
+ id=hook_name,
105
+ name=hook_name,
106
+ status="error",
107
+ duration=0.0,
108
+ issues_found=[f"Hook definition not found: {hook_name}"],
109
+ )
110
+
111
+ if not hook_def.command:
112
+ return HookResult(
113
+ id=hook_name,
114
+ name=hook_name,
115
+ status="error",
116
+ duration=0.0,
117
+ issues_found=[f"No command defined for hook: {hook_name}"],
118
+ )
119
+
120
+ return self._execute_command_hook(hook_def, files)
121
+
122
+ def _execute_command_hook(
123
+ self,
124
+ hook_def: CustomHookDefinition,
125
+ files: list[Path],
126
+ ) -> HookResult:
127
+ import time
128
+
129
+ start_time = time.time()
130
+
131
+ try:
132
+ cmd = hook_def.command.copy()
133
+ if hook_def.requires_files and files:
134
+ cmd.extend(str(f) for f in files)
135
+
136
+ result = subprocess.run(
137
+ cmd,
138
+ check=False,
139
+ cwd=self.pkg_path,
140
+ capture_output=True,
141
+ text=True,
142
+ timeout=hook_def.timeout,
143
+ )
144
+
145
+ duration = time.time() - start_time
146
+ status = "passed" if result.returncode == 0 else "failed"
147
+
148
+ issues = [result.stderr] if result.returncode != 0 and result.stderr else []
149
+ return HookResult(
150
+ id=hook_def.name,
151
+ name=hook_def.name,
152
+ status=status,
153
+ duration=duration,
154
+ issues_found=issues,
155
+ )
156
+
157
+ except subprocess.TimeoutExpired:
158
+ return HookResult(
159
+ id=hook_def.name,
160
+ name=hook_def.name,
161
+ status="timeout",
162
+ duration=time.time() - start_time,
163
+ issues_found=[f"Hook timed out after {hook_def.timeout}s"],
164
+ )
165
+ except Exception as e:
166
+ return HookResult(
167
+ id=hook_def.name,
168
+ name=hook_def.name,
169
+ status="error",
170
+ duration=time.time() - start_time,
171
+ issues_found=[f"Execution error: {e}"],
172
+ )
173
+
174
+ def activate(self) -> bool:
175
+ return True
176
+
177
+ def deactivate(self) -> bool:
178
+ return True
179
+
180
+
181
+ class HookPluginRegistry:
182
+ def __init__(self) -> None:
183
+ self._hook_plugins: dict[str, HookPluginBase] = {}
184
+
185
+ def register_hook_plugin(self, plugin: HookPluginBase) -> bool:
186
+ if plugin.name in self._hook_plugins:
187
+ return False
188
+
189
+ self._hook_plugins[plugin.name] = plugin
190
+ return True
191
+
192
+ def unregister_hook_plugin(self, plugin_name: str) -> bool:
193
+ return self._hook_plugins.pop(plugin_name, None) is not None
194
+
195
+ def get_all_custom_hooks(self) -> dict[str, CustomHookDefinition]:
196
+ hooks = {}
197
+
198
+ for plugin in self._hook_plugins.values():
199
+ if not plugin.enabled:
200
+ continue
201
+
202
+ for hook_def in plugin.get_hook_definitions():
203
+ if hook_def.name not in hooks:
204
+ hooks[hook_def.name] = hook_def
205
+
206
+ return hooks
207
+
208
+ def execute_custom_hook(
209
+ self,
210
+ hook_name: str,
211
+ files: list[Path],
212
+ options: OptionsProtocol,
213
+ ) -> HookResult | None:
214
+ for plugin in self._hook_plugins.values():
215
+ if not plugin.enabled:
216
+ continue
217
+
218
+ hook_defs = plugin.get_hook_definitions()
219
+ if any(h.name == hook_name for h in hook_defs):
220
+ return plugin.execute_hook(hook_name, files, options)
221
+
222
+ return None
223
+
224
+ def get_hooks_for_files(self, files: list[Path]) -> list[str]:
225
+ applicable_hooks = []
226
+
227
+ for plugin in self._hook_plugins.values():
228
+ if not plugin.enabled:
229
+ continue
230
+
231
+ for hook_def in plugin.get_hook_definitions():
232
+ if plugin.should_run_hook(hook_def.name, files):
233
+ applicable_hooks.append(hook_def.name)
234
+
235
+ return applicable_hooks
236
+
237
+ def initialize_all_plugins(self, console: Console, pkg_path: Path) -> None:
238
+ for plugin in self._hook_plugins.values():
239
+ plugin.initialize(console, pkg_path)
240
+
241
+
242
+ _hook_registry = HookPluginRegistry()
243
+
244
+
245
+ def get_hook_plugin_registry() -> HookPluginRegistry:
246
+ return _hook_registry