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,347 @@
1
+ import json
2
+ import typing as t
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
8
+
9
+
10
+ class CoverageRatchetService:
11
+ """
12
+ Coverage ratchet system that prevents regression and targets 100% coverage.
13
+
14
+ Core principles:
15
+ - Coverage can only increase, never decrease
16
+ - Celebrates milestones and progress toward 100%
17
+ - Automatically updates pyproject.toml when coverage improves
18
+ - Tracks history and provides visualization
19
+ """
20
+
21
+ # Milestone thresholds for celebration
22
+ MILESTONES = [15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 95, 100]
23
+
24
+ def __init__(self, pkg_path: Path, console: Console) -> None:
25
+ self.pkg_path = pkg_path
26
+ self.console = console
27
+ self.ratchet_file = pkg_path / ".coverage-ratchet.json"
28
+ self.pyproject_file = pkg_path / "pyproject.toml"
29
+
30
+ def initialize_baseline(self, initial_coverage: float) -> None:
31
+ """Initialize the coverage ratchet with current baseline."""
32
+ if self.ratchet_file.exists():
33
+ return # Already initialized
34
+
35
+ ratchet_data = {
36
+ "baseline": initial_coverage,
37
+ "current_minimum": initial_coverage,
38
+ "target": 100.0,
39
+ "last_updated": datetime.now().isoformat(),
40
+ "history": [
41
+ {
42
+ "date": datetime.now().isoformat(),
43
+ "coverage": initial_coverage,
44
+ "commit": "baseline",
45
+ "milestone": False,
46
+ }
47
+ ],
48
+ "milestones_achieved": [],
49
+ "next_milestone": self._get_next_milestone(initial_coverage),
50
+ }
51
+
52
+ self.ratchet_file.write_text(json.dumps(ratchet_data, indent=2))
53
+ self.console.print(
54
+ f"[cyan]📊[/cyan] Coverage ratchet initialized at {initial_coverage:.2f}% baseline"
55
+ )
56
+
57
+ def get_ratchet_data(self) -> dict[str, t.Any]:
58
+ """Get current ratchet data."""
59
+ if not self.ratchet_file.exists():
60
+ return {}
61
+ return json.loads(self.ratchet_file.read_text())
62
+
63
+ def get_baseline(self) -> float:
64
+ """Get current coverage baseline."""
65
+ return self.get_ratchet_data().get("baseline", 0.0)
66
+
67
+ def update_coverage(self, new_coverage: float) -> dict[str, t.Any]:
68
+ """
69
+ Update coverage and return achievement info.
70
+
71
+ Returns:
72
+ dict with status, message, milestones hit, and whether build should pass
73
+ """
74
+ if not self.ratchet_file.exists():
75
+ self.initialize_baseline(new_coverage)
76
+ return {
77
+ "status": "initialized",
78
+ "message": f"Coverage ratchet initialized at {new_coverage:.2f}%",
79
+ "milestones": [],
80
+ "progress_to_100": f"{new_coverage:.1f}% of the way to 100% coverage",
81
+ "allowed": True,
82
+ "baseline_updated": True,
83
+ }
84
+
85
+ data = self.get_ratchet_data()
86
+ current_baseline = data["baseline"]
87
+
88
+ if (
89
+ new_coverage < current_baseline - 0.01
90
+ ): # Allow tiny float precision differences
91
+ return {
92
+ "status": "regression",
93
+ "message": f"Coverage decreased from {current_baseline:.2f}% to {new_coverage:.2f}%",
94
+ "regression_amount": current_baseline - new_coverage,
95
+ "allowed": False,
96
+ "baseline_updated": False,
97
+ }
98
+ elif new_coverage > current_baseline + 0.01: # Significant improvement
99
+ milestones_hit = self._check_milestones(
100
+ current_baseline, new_coverage, data
101
+ )
102
+ self._update_baseline(new_coverage, data, milestones_hit)
103
+ self._update_pyproject_requirement(new_coverage)
104
+
105
+ return {
106
+ "status": "improved",
107
+ "message": f"Coverage improved from {current_baseline:.2f}% to {new_coverage:.2f}%!",
108
+ "improvement": new_coverage - current_baseline,
109
+ "milestones": milestones_hit,
110
+ "progress_to_100": f"{new_coverage:.1f}% of the way to 100% coverage",
111
+ "next_milestone": self._get_next_milestone(new_coverage),
112
+ "points_to_next": (next_milestone - new_coverage)
113
+ if (next_milestone := self._get_next_milestone(new_coverage))
114
+ else 0,
115
+ "allowed": True,
116
+ "baseline_updated": True,
117
+ }
118
+ return {
119
+ "status": "maintained",
120
+ "message": f"Coverage maintained at {new_coverage:.2f}%",
121
+ "allowed": True,
122
+ "baseline_updated": False,
123
+ }
124
+
125
+ def _check_milestones(
126
+ self, old_coverage: float, new_coverage: float, data: dict[str, t.Any]
127
+ ) -> list[float]:
128
+ """Check which milestones were crossed."""
129
+ achieved_milestones = set(data.get("milestones_achieved", []))
130
+ return [
131
+ milestone
132
+ for milestone in self.MILESTONES
133
+ if (
134
+ old_coverage < milestone <= new_coverage
135
+ and milestone not in achieved_milestones
136
+ )
137
+ ]
138
+
139
+ def _get_next_milestone(self, coverage: float) -> float | None:
140
+ """Get the next milestone to target."""
141
+ for milestone in self.MILESTONES:
142
+ if milestone > coverage:
143
+ return milestone
144
+ return None
145
+
146
+ def _update_baseline(
147
+ self, new_coverage: float, data: dict[str, t.Any], milestones_hit: list[float]
148
+ ) -> None:
149
+ """Update the ratchet baseline and history."""
150
+ data["baseline"] = new_coverage
151
+ data["current_minimum"] = new_coverage
152
+ data["last_updated"] = datetime.now().isoformat()
153
+
154
+ # Add to history
155
+ data["history"].append(
156
+ {
157
+ "date": datetime.now().isoformat(),
158
+ "coverage": new_coverage,
159
+ "commit": "current", # Could integrate with git later
160
+ "milestone": len(milestones_hit) > 0,
161
+ "milestones_hit": milestones_hit,
162
+ }
163
+ )
164
+
165
+ # Update achieved milestones
166
+ for milestone in milestones_hit:
167
+ if milestone not in data["milestones_achieved"]:
168
+ data["milestones_achieved"].append(milestone)
169
+
170
+ data["next_milestone"] = self._get_next_milestone(new_coverage)
171
+
172
+ # Keep history manageable (last 50 entries)
173
+ if len(data["history"]) > 50:
174
+ data["history"] = data["history"][-50:]
175
+
176
+ self.ratchet_file.write_text(json.dumps(data, indent=2))
177
+
178
+ def _update_pyproject_requirement(self, new_coverage: float) -> None:
179
+ """Update pyproject.toml with new coverage requirement."""
180
+ try:
181
+ content = self.pyproject_file.read_text()
182
+
183
+ import re
184
+
185
+ # Update the --cov-fail-under value
186
+ pattern = r"(--cov-fail-under=)\d+\.?\d*"
187
+ replacement = f"\\g<1>{new_coverage:.0f}"
188
+
189
+ updated_content = re.sub(pattern, replacement, content)
190
+
191
+ if updated_content != content:
192
+ self.pyproject_file.write_text(updated_content)
193
+ self.console.print(
194
+ f"[cyan]📝[/cyan] Updated pyproject.toml coverage requirement to {new_coverage:.0f}%"
195
+ )
196
+
197
+ except Exception as e:
198
+ self.console.print(
199
+ f"[yellow]⚠️[/yellow] Failed to update pyproject.toml: {e}"
200
+ )
201
+
202
+ def get_progress_visualization(self) -> str:
203
+ """Get a visual progress bar toward 100% coverage."""
204
+ data = self.get_ratchet_data()
205
+ if not data:
206
+ return "Coverage ratchet not initialized"
207
+
208
+ current = data["baseline"]
209
+ target = 100.0
210
+ next_milestone = data.get("next_milestone")
211
+
212
+ # Create progress bar
213
+ progress_chars = int(current / target * 20)
214
+ bar = "█" * progress_chars + "░" * (20 - progress_chars)
215
+
216
+ result = f"Coverage Progress: {current:.2f}% [{bar}] → 100%\n"
217
+ result += f" Current ─┘{'':>18} └─ Goal\n"
218
+
219
+ if next_milestone:
220
+ points_needed = next_milestone - current
221
+ result += f"Next milestone: {next_milestone:.0f}% (+{points_needed:.2f}% needed)\n"
222
+
223
+ return result
224
+
225
+ def get_status_report(self) -> dict[str, t.Any]:
226
+ """Get comprehensive status report for monitoring."""
227
+ data = self.get_ratchet_data()
228
+ if not data:
229
+ return {"status": "not_initialized"}
230
+
231
+ return {
232
+ "status": "active",
233
+ "current_coverage": data["baseline"],
234
+ "target_coverage": data["target"],
235
+ "next_milestone": data.get("next_milestone"),
236
+ "milestones_achieved": data.get("milestones_achieved", []),
237
+ "total_milestones": len(self.MILESTONES),
238
+ "progress_percent": (data["baseline"] / data["target"]) * 100,
239
+ "last_updated": data["last_updated"],
240
+ "history_count": len(data.get("history", [])),
241
+ "improvement_trend": self._calculate_trend(data),
242
+ }
243
+
244
+ def _calculate_trend(self, data: dict[str, t.Any]) -> str:
245
+ """Calculate coverage improvement trend."""
246
+ history = data.get("history", [])
247
+ if len(history) < 2:
248
+ return "insufficient_data"
249
+
250
+ recent_entries = history[-5:] # Last 5 entries
251
+ if len(recent_entries) < 2:
252
+ return "insufficient_data"
253
+
254
+ start_coverage = recent_entries[0]["coverage"]
255
+ end_coverage = recent_entries[-1]["coverage"]
256
+
257
+ if end_coverage > start_coverage + 0.5:
258
+ return "improving"
259
+ elif end_coverage < start_coverage - 0.5:
260
+ return "declining"
261
+ return "stable"
262
+
263
+ def display_milestone_celebration(self, milestones: list[float]) -> None:
264
+ """Display celebration for achieved milestones."""
265
+ for milestone in milestones:
266
+ if milestone == 100.0:
267
+ self.console.print(
268
+ "[gold]🎉🏆 PERFECT! 100% COVERAGE ACHIEVED! 🏆🎉[/gold]"
269
+ )
270
+ elif milestone >= 90:
271
+ self.console.print(
272
+ f"[gold]🏆 Milestone achieved: {milestone:.0f}% coverage! Approaching perfection![/gold]"
273
+ )
274
+ elif milestone >= 50:
275
+ self.console.print(
276
+ f"[green]🎯 Milestone achieved: {milestone:.0f}% coverage! Great progress![/green]"
277
+ )
278
+ else:
279
+ self.console.print(
280
+ f"[cyan]📈 Milestone achieved: {milestone:.0f}% coverage! Keep it up![/cyan]"
281
+ )
282
+
283
+ def show_progress_with_spinner(self) -> None:
284
+ """Show animated progress toward 100% coverage."""
285
+ data = self.get_ratchet_data()
286
+ if not data:
287
+ return
288
+
289
+ current = data["baseline"]
290
+ target = 100.0
291
+
292
+ with Progress(
293
+ SpinnerColumn(),
294
+ TextColumn("[progress.description]{task.description}"),
295
+ BarColumn(),
296
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
297
+ ) as progress:
298
+ task = progress.add_task(
299
+ "Coverage Progress", total=target, completed=current
300
+ )
301
+ progress.update(task, description=f"Coverage: {current:.1f}%/100%")
302
+
303
+ def get_coverage_report(self) -> str | None:
304
+ """Get coverage report from the ratchet data."""
305
+ data = self.get_ratchet_data()
306
+ if not data:
307
+ return None
308
+
309
+ current_coverage = data.get("baseline", 0.0)
310
+ next_milestone = data.get("next_milestone")
311
+
312
+ report = f"Coverage: {current_coverage:.2f}%"
313
+ if next_milestone:
314
+ progress = (current_coverage / next_milestone) * 100
315
+ report += f" (next milestone: {next_milestone:.0f}%, {progress:.1f}% there)"
316
+
317
+ return report
318
+
319
+ def check_and_update_coverage(self) -> dict[str, t.Any]:
320
+ """Check coverage from current test run and update ratchet."""
321
+ # Try to read coverage from standard pytest-cov output
322
+ try:
323
+ # Look for .coverage file or coverage.json
324
+ coverage_file = self.pkg_path / "coverage.json"
325
+ if not coverage_file.exists():
326
+ # Look for coverage data in htmlcov/index.html or other standard locations
327
+ return {
328
+ "success": False,
329
+ "error": "No coverage data found",
330
+ "message": "Run tests with coverage enabled first",
331
+ }
332
+
333
+ # Parse coverage data (simplified for now)
334
+ coverage_data = json.loads(coverage_file.read_text())
335
+ current_coverage = coverage_data.get("totals", {}).get(
336
+ "percent_covered", 0.0
337
+ )
338
+
339
+ # Update the ratchet
340
+ return self.update_coverage(current_coverage)
341
+
342
+ except Exception as e:
343
+ return {
344
+ "success": False,
345
+ "error": str(e),
346
+ "message": "Failed to read coverage data",
347
+ }