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,356 @@
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
+ # Clean trailing whitespace and ensure single trailing newline
193
+ from crackerjack.services.filesystem import FileSystemService
194
+
195
+ updated_content = (
196
+ FileSystemService.clean_trailing_whitespace_and_newlines(
197
+ updated_content
198
+ )
199
+ )
200
+
201
+ self.pyproject_file.write_text(updated_content)
202
+ self.console.print(
203
+ f"[cyan]📝[/cyan] Updated pyproject.toml coverage requirement to {new_coverage:.0f}%"
204
+ )
205
+
206
+ except Exception as e:
207
+ self.console.print(
208
+ f"[yellow]⚠️[/yellow] Failed to update pyproject.toml: {e}"
209
+ )
210
+
211
+ def get_progress_visualization(self) -> str:
212
+ """Get a visual progress bar toward 100% coverage."""
213
+ data = self.get_ratchet_data()
214
+ if not data:
215
+ return "Coverage ratchet not initialized"
216
+
217
+ current = data["baseline"]
218
+ target = 100.0
219
+ next_milestone = data.get("next_milestone")
220
+
221
+ # Create progress bar
222
+ progress_chars = int(current / target * 20)
223
+ bar = "█" * progress_chars + "░" * (20 - progress_chars)
224
+
225
+ result = f"Coverage Progress: {current:.2f}% [{bar}] → 100%\n"
226
+ result += f" Current ─┘{'':>18} └─ Goal\n"
227
+
228
+ if next_milestone:
229
+ points_needed = next_milestone - current
230
+ result += f"Next milestone: {next_milestone:.0f}% (+{points_needed:.2f}% needed)\n"
231
+
232
+ return result
233
+
234
+ def get_status_report(self) -> dict[str, t.Any]:
235
+ """Get comprehensive status report for monitoring."""
236
+ data = self.get_ratchet_data()
237
+ if not data:
238
+ return {"status": "not_initialized"}
239
+
240
+ return {
241
+ "status": "active",
242
+ "current_coverage": data["baseline"],
243
+ "target_coverage": data["target"],
244
+ "next_milestone": data.get("next_milestone"),
245
+ "milestones_achieved": data.get("milestones_achieved", []),
246
+ "total_milestones": len(self.MILESTONES),
247
+ "progress_percent": (data["baseline"] / data["target"]) * 100,
248
+ "last_updated": data["last_updated"],
249
+ "history_count": len(data.get("history", [])),
250
+ "improvement_trend": self._calculate_trend(data),
251
+ }
252
+
253
+ def _calculate_trend(self, data: dict[str, t.Any]) -> str:
254
+ """Calculate coverage improvement trend."""
255
+ history = data.get("history", [])
256
+ if len(history) < 2:
257
+ return "insufficient_data"
258
+
259
+ recent_entries = history[-5:] # Last 5 entries
260
+ if len(recent_entries) < 2:
261
+ return "insufficient_data"
262
+
263
+ start_coverage = recent_entries[0]["coverage"]
264
+ end_coverage = recent_entries[-1]["coverage"]
265
+
266
+ if end_coverage > start_coverage + 0.5:
267
+ return "improving"
268
+ elif end_coverage < start_coverage - 0.5:
269
+ return "declining"
270
+ return "stable"
271
+
272
+ def display_milestone_celebration(self, milestones: list[float]) -> None:
273
+ """Display celebration for achieved milestones."""
274
+ for milestone in milestones:
275
+ if milestone == 100.0:
276
+ self.console.print(
277
+ "[gold]🎉🏆 PERFECT! 100% COVERAGE ACHIEVED! 🏆🎉[/gold]"
278
+ )
279
+ elif milestone >= 90:
280
+ self.console.print(
281
+ f"[gold]🏆 Milestone achieved: {milestone:.0f}% coverage! Approaching perfection![/gold]"
282
+ )
283
+ elif milestone >= 50:
284
+ self.console.print(
285
+ f"[green]🎯 Milestone achieved: {milestone:.0f}% coverage! Great progress![/green]"
286
+ )
287
+ else:
288
+ self.console.print(
289
+ f"[cyan]📈 Milestone achieved: {milestone:.0f}% coverage! Keep it up![/cyan]"
290
+ )
291
+
292
+ def show_progress_with_spinner(self) -> None:
293
+ """Show animated progress toward 100% coverage."""
294
+ data = self.get_ratchet_data()
295
+ if not data:
296
+ return
297
+
298
+ current = data["baseline"]
299
+ target = 100.0
300
+
301
+ with Progress(
302
+ SpinnerColumn(),
303
+ TextColumn("[progress.description]{task.description}"),
304
+ BarColumn(),
305
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
306
+ ) as progress:
307
+ task = progress.add_task(
308
+ "Coverage Progress", total=target, completed=current
309
+ )
310
+ progress.update(task, description=f"Coverage: {current:.1f}%/100%")
311
+
312
+ def get_coverage_report(self) -> str | None:
313
+ """Get coverage report from the ratchet data."""
314
+ data = self.get_ratchet_data()
315
+ if not data:
316
+ return None
317
+
318
+ current_coverage = data.get("baseline", 0.0)
319
+ next_milestone = data.get("next_milestone")
320
+
321
+ report = f"Coverage: {current_coverage:.2f}%"
322
+ if next_milestone:
323
+ progress = (current_coverage / next_milestone) * 100
324
+ report += f" (next milestone: {next_milestone:.0f}%, {progress:.1f}% there)"
325
+
326
+ return report
327
+
328
+ def check_and_update_coverage(self) -> dict[str, t.Any]:
329
+ """Check coverage from current test run and update ratchet."""
330
+ # Try to read coverage from standard pytest-cov output
331
+ try:
332
+ # Look for .coverage file or coverage.json
333
+ coverage_file = self.pkg_path / "coverage.json"
334
+ if not coverage_file.exists():
335
+ # Look for coverage data in htmlcov/index.html or other standard locations
336
+ return {
337
+ "success": False,
338
+ "error": "No coverage data found",
339
+ "message": "Run tests with coverage enabled first",
340
+ }
341
+
342
+ # Parse coverage data (simplified for now)
343
+ coverage_data = json.loads(coverage_file.read_text())
344
+ current_coverage = coverage_data.get("totals", {}).get(
345
+ "percent_covered", 0.0
346
+ )
347
+
348
+ # Update the ratchet
349
+ return self.update_coverage(current_coverage)
350
+
351
+ except Exception as e:
352
+ return {
353
+ "success": False,
354
+ "error": str(e),
355
+ "message": "Failed to read coverage data",
356
+ }