crackerjack 0.31.10__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.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,18 +8,9 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
|
8
8
|
|
|
9
9
|
|
|
10
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
11
|
MILESTONES = [15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 95, 100]
|
|
12
|
+
# 2% tolerance margin for coverage fluctuations to prevent test flakiness
|
|
13
|
+
TOLERANCE_MARGIN = 2.0
|
|
23
14
|
|
|
24
15
|
def __init__(self, pkg_path: Path, console: Console) -> None:
|
|
25
16
|
self.pkg_path = pkg_path
|
|
@@ -28,9 +19,8 @@ class CoverageRatchetService:
|
|
|
28
19
|
self.pyproject_file = pkg_path / "pyproject.toml"
|
|
29
20
|
|
|
30
21
|
def initialize_baseline(self, initial_coverage: float) -> None:
|
|
31
|
-
"""Initialize the coverage ratchet with current baseline."""
|
|
32
22
|
if self.ratchet_file.exists():
|
|
33
|
-
return
|
|
23
|
+
return
|
|
34
24
|
|
|
35
25
|
ratchet_data = {
|
|
36
26
|
"baseline": initial_coverage,
|
|
@@ -51,33 +41,35 @@ class CoverageRatchetService:
|
|
|
51
41
|
|
|
52
42
|
self.ratchet_file.write_text(json.dumps(ratchet_data, indent=2))
|
|
53
43
|
self.console.print(
|
|
54
|
-
f"[cyan]📊[/cyan] Coverage ratchet initialized at {initial_coverage
|
|
44
|
+
f"[cyan]📊[/ cyan] Coverage ratchet initialized at {initial_coverage: .2f}% baseline"
|
|
55
45
|
)
|
|
56
46
|
|
|
57
47
|
def get_ratchet_data(self) -> dict[str, t.Any]:
|
|
58
|
-
"""Get current ratchet data."""
|
|
59
48
|
if not self.ratchet_file.exists():
|
|
60
49
|
return {}
|
|
61
50
|
return json.loads(self.ratchet_file.read_text())
|
|
62
51
|
|
|
63
52
|
def get_baseline(self) -> float:
|
|
64
|
-
"""Get current coverage baseline."""
|
|
65
53
|
return self.get_ratchet_data().get("baseline", 0.0)
|
|
66
54
|
|
|
67
55
|
def update_coverage(self, new_coverage: float) -> dict[str, t.Any]:
|
|
68
|
-
"""
|
|
69
|
-
Update coverage and return achievement info.
|
|
56
|
+
"""Update coverage with 2% tolerance margin to prevent test flakiness.
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
Behavior:
|
|
59
|
+
- Coverage below (baseline - 2%): FAIL (regression)
|
|
60
|
+
- Coverage within ±2% of baseline: PASS (maintained)
|
|
61
|
+
- Coverage above baseline: PASS (improved, updates baseline)
|
|
62
|
+
|
|
63
|
+
This prevents failures due to small coverage fluctuations while
|
|
64
|
+
still catching significant regressions.
|
|
73
65
|
"""
|
|
74
66
|
if not self.ratchet_file.exists():
|
|
75
67
|
self.initialize_baseline(new_coverage)
|
|
76
68
|
return {
|
|
77
69
|
"status": "initialized",
|
|
78
|
-
"message": f"Coverage ratchet initialized at {new_coverage
|
|
70
|
+
"message": f"Coverage ratchet initialized at {new_coverage: .2f}%",
|
|
79
71
|
"milestones": [],
|
|
80
|
-
"progress_to_100": f"{new_coverage
|
|
72
|
+
"progress_to_100": f"{new_coverage: .1f}% of the way to 100 % coverage",
|
|
81
73
|
"allowed": True,
|
|
82
74
|
"baseline_updated": True,
|
|
83
75
|
}
|
|
@@ -85,17 +77,18 @@ class CoverageRatchetService:
|
|
|
85
77
|
data = self.get_ratchet_data()
|
|
86
78
|
current_baseline = data["baseline"]
|
|
87
79
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
# Check if coverage is below the tolerance margin (baseline - 2%)
|
|
81
|
+
tolerance_threshold = current_baseline - self.TOLERANCE_MARGIN
|
|
82
|
+
if new_coverage < tolerance_threshold:
|
|
91
83
|
return {
|
|
92
84
|
"status": "regression",
|
|
93
|
-
"message": f"Coverage decreased from {current_baseline
|
|
85
|
+
"message": f"Coverage decreased from {current_baseline: .2f}% to {new_coverage: .2f}% (below {self.TOLERANCE_MARGIN}% tolerance margin)",
|
|
94
86
|
"regression_amount": current_baseline - new_coverage,
|
|
87
|
+
"tolerance_threshold": tolerance_threshold,
|
|
95
88
|
"allowed": False,
|
|
96
89
|
"baseline_updated": False,
|
|
97
90
|
}
|
|
98
|
-
elif new_coverage > current_baseline + 0.01:
|
|
91
|
+
elif new_coverage > current_baseline + 0.01:
|
|
99
92
|
milestones_hit = self._check_milestones(
|
|
100
93
|
current_baseline, new_coverage, data
|
|
101
94
|
)
|
|
@@ -104,10 +97,10 @@ class CoverageRatchetService:
|
|
|
104
97
|
|
|
105
98
|
return {
|
|
106
99
|
"status": "improved",
|
|
107
|
-
"message": f"Coverage improved from {current_baseline
|
|
100
|
+
"message": f"Coverage improved from {current_baseline: .2f}% to {new_coverage: .2f}% !",
|
|
108
101
|
"improvement": new_coverage - current_baseline,
|
|
109
102
|
"milestones": milestones_hit,
|
|
110
|
-
"progress_to_100": f"{new_coverage
|
|
103
|
+
"progress_to_100": f"{new_coverage: .1f}% of the way to 100 % coverage",
|
|
111
104
|
"next_milestone": self._get_next_milestone(new_coverage),
|
|
112
105
|
"points_to_next": (next_milestone - new_coverage)
|
|
113
106
|
if (next_milestone := self._get_next_milestone(new_coverage))
|
|
@@ -115,9 +108,10 @@ class CoverageRatchetService:
|
|
|
115
108
|
"allowed": True,
|
|
116
109
|
"baseline_updated": True,
|
|
117
110
|
}
|
|
111
|
+
# Coverage is within tolerance margin - treat as maintained
|
|
118
112
|
return {
|
|
119
113
|
"status": "maintained",
|
|
120
|
-
"message": f"Coverage maintained at {new_coverage
|
|
114
|
+
"message": f"Coverage maintained at {new_coverage: .2f}% (within {self.TOLERANCE_MARGIN}% tolerance margin)",
|
|
121
115
|
"allowed": True,
|
|
122
116
|
"baseline_updated": False,
|
|
123
117
|
}
|
|
@@ -125,7 +119,6 @@ class CoverageRatchetService:
|
|
|
125
119
|
def _check_milestones(
|
|
126
120
|
self, old_coverage: float, new_coverage: float, data: dict[str, t.Any]
|
|
127
121
|
) -> list[float]:
|
|
128
|
-
"""Check which milestones were crossed."""
|
|
129
122
|
achieved_milestones = set(data.get("milestones_achieved", []))
|
|
130
123
|
return [
|
|
131
124
|
milestone
|
|
@@ -137,7 +130,6 @@ class CoverageRatchetService:
|
|
|
137
130
|
]
|
|
138
131
|
|
|
139
132
|
def _get_next_milestone(self, coverage: float) -> float | None:
|
|
140
|
-
"""Get the next milestone to target."""
|
|
141
133
|
for milestone in self.MILESTONES:
|
|
142
134
|
if milestone > coverage:
|
|
143
135
|
return milestone
|
|
@@ -146,50 +138,40 @@ class CoverageRatchetService:
|
|
|
146
138
|
def _update_baseline(
|
|
147
139
|
self, new_coverage: float, data: dict[str, t.Any], milestones_hit: list[float]
|
|
148
140
|
) -> None:
|
|
149
|
-
"""Update the ratchet baseline and history."""
|
|
150
141
|
data["baseline"] = new_coverage
|
|
151
142
|
data["current_minimum"] = new_coverage
|
|
152
143
|
data["last_updated"] = datetime.now().isoformat()
|
|
153
144
|
|
|
154
|
-
# Add to history
|
|
155
145
|
data["history"].append(
|
|
156
146
|
{
|
|
157
147
|
"date": datetime.now().isoformat(),
|
|
158
148
|
"coverage": new_coverage,
|
|
159
|
-
"commit": "current",
|
|
149
|
+
"commit": "current",
|
|
160
150
|
"milestone": len(milestones_hit) > 0,
|
|
161
151
|
"milestones_hit": milestones_hit,
|
|
162
152
|
}
|
|
163
153
|
)
|
|
164
154
|
|
|
165
|
-
# Update achieved milestones
|
|
166
155
|
for milestone in milestones_hit:
|
|
167
156
|
if milestone not in data["milestones_achieved"]:
|
|
168
157
|
data["milestones_achieved"].append(milestone)
|
|
169
158
|
|
|
170
159
|
data["next_milestone"] = self._get_next_milestone(new_coverage)
|
|
171
160
|
|
|
172
|
-
# Keep history manageable (last 50 entries)
|
|
173
161
|
if len(data["history"]) > 50:
|
|
174
162
|
data["history"] = data["history"][-50:]
|
|
175
163
|
|
|
176
164
|
self.ratchet_file.write_text(json.dumps(data, indent=2))
|
|
177
165
|
|
|
178
166
|
def _update_pyproject_requirement(self, new_coverage: float) -> None:
|
|
179
|
-
"""Update pyproject.toml with new coverage requirement."""
|
|
180
167
|
try:
|
|
181
168
|
content = self.pyproject_file.read_text()
|
|
182
169
|
|
|
183
|
-
import
|
|
184
|
-
|
|
185
|
-
# Update the --cov-fail-under value
|
|
186
|
-
pattern = r"(--cov-fail-under=)\d+\.?\d*"
|
|
187
|
-
replacement = f"\\g<1>{new_coverage:.0f}"
|
|
170
|
+
from crackerjack.services.regex_patterns import update_coverage_requirement
|
|
188
171
|
|
|
189
|
-
updated_content =
|
|
172
|
+
updated_content = update_coverage_requirement(content, new_coverage)
|
|
190
173
|
|
|
191
174
|
if updated_content != content:
|
|
192
|
-
# Clean trailing whitespace and ensure single trailing newline
|
|
193
175
|
from crackerjack.services.filesystem import FileSystemService
|
|
194
176
|
|
|
195
177
|
updated_content = (
|
|
@@ -200,16 +182,15 @@ class CoverageRatchetService:
|
|
|
200
182
|
|
|
201
183
|
self.pyproject_file.write_text(updated_content)
|
|
202
184
|
self.console.print(
|
|
203
|
-
f"[cyan]📝[/cyan] Updated pyproject.toml coverage requirement to {new_coverage
|
|
185
|
+
f"[cyan]📝[/ cyan] Updated pyproject.toml coverage requirement to {new_coverage: .0f}%"
|
|
204
186
|
)
|
|
205
187
|
|
|
206
188
|
except Exception as e:
|
|
207
189
|
self.console.print(
|
|
208
|
-
f"[yellow]⚠️[/yellow] Failed to update pyproject.toml: {e}"
|
|
190
|
+
f"[yellow]⚠️[/ yellow] Failed to update pyproject.toml: {e}"
|
|
209
191
|
)
|
|
210
192
|
|
|
211
193
|
def get_progress_visualization(self) -> str:
|
|
212
|
-
"""Get a visual progress bar toward 100% coverage."""
|
|
213
194
|
data = self.get_ratchet_data()
|
|
214
195
|
if not data:
|
|
215
196
|
return "Coverage ratchet not initialized"
|
|
@@ -218,21 +199,19 @@ class CoverageRatchetService:
|
|
|
218
199
|
target = 100.0
|
|
219
200
|
next_milestone = data.get("next_milestone")
|
|
220
201
|
|
|
221
|
-
# Create progress bar
|
|
222
202
|
progress_chars = int(current / target * 20)
|
|
223
203
|
bar = "█" * progress_chars + "░" * (20 - progress_chars)
|
|
224
204
|
|
|
225
|
-
result = f"Coverage Progress: {current
|
|
226
|
-
result += f"
|
|
205
|
+
result = f"Coverage Progress: {current: .2f}% [{bar}] → 100 %\n"
|
|
206
|
+
result += f" Current ─┘{'': > 18} └─ Goal\n"
|
|
227
207
|
|
|
228
208
|
if next_milestone:
|
|
229
209
|
points_needed = next_milestone - current
|
|
230
|
-
result += f"Next milestone: {next_milestone
|
|
210
|
+
result += f"Next milestone: {next_milestone: .0f}% (+{points_needed: .2f}% needed)\n"
|
|
231
211
|
|
|
232
212
|
return result
|
|
233
213
|
|
|
234
214
|
def get_status_report(self) -> dict[str, t.Any]:
|
|
235
|
-
"""Get comprehensive status report for monitoring."""
|
|
236
215
|
data = self.get_ratchet_data()
|
|
237
216
|
if not data:
|
|
238
217
|
return {"status": "not_initialized"}
|
|
@@ -251,12 +230,11 @@ class CoverageRatchetService:
|
|
|
251
230
|
}
|
|
252
231
|
|
|
253
232
|
def _calculate_trend(self, data: dict[str, t.Any]) -> str:
|
|
254
|
-
"""Calculate coverage improvement trend."""
|
|
255
233
|
history = data.get("history", [])
|
|
256
234
|
if len(history) < 2:
|
|
257
235
|
return "insufficient_data"
|
|
258
236
|
|
|
259
|
-
recent_entries = history[-5:]
|
|
237
|
+
recent_entries = history[-5:]
|
|
260
238
|
if len(recent_entries) < 2:
|
|
261
239
|
return "insufficient_data"
|
|
262
240
|
|
|
@@ -270,27 +248,25 @@ class CoverageRatchetService:
|
|
|
270
248
|
return "stable"
|
|
271
249
|
|
|
272
250
|
def display_milestone_celebration(self, milestones: list[float]) -> None:
|
|
273
|
-
"""Display celebration for achieved milestones."""
|
|
274
251
|
for milestone in milestones:
|
|
275
252
|
if milestone == 100.0:
|
|
276
253
|
self.console.print(
|
|
277
|
-
"[gold]🎉🏆 PERFECT! 100% COVERAGE ACHIEVED! 🏆🎉[/gold]"
|
|
254
|
+
"[gold]🎉🏆 PERFECT ! 100 % COVERAGE ACHIEVED ! 🏆🎉[/ gold]"
|
|
278
255
|
)
|
|
279
256
|
elif milestone >= 90:
|
|
280
257
|
self.console.print(
|
|
281
|
-
f"[gold]🏆 Milestone achieved: {milestone
|
|
258
|
+
f"[gold]🏆 Milestone achieved: {milestone: .0f}% coverage ! Approaching perfection ![/ gold]"
|
|
282
259
|
)
|
|
283
260
|
elif milestone >= 50:
|
|
284
261
|
self.console.print(
|
|
285
|
-
f"[green]🎯 Milestone achieved: {milestone
|
|
262
|
+
f"[green]🎯 Milestone achieved: {milestone: .0f}% coverage ! Great progress ![/ green]"
|
|
286
263
|
)
|
|
287
264
|
else:
|
|
288
265
|
self.console.print(
|
|
289
|
-
f"[cyan]📈 Milestone achieved: {milestone
|
|
266
|
+
f"[cyan]📈 Milestone achieved: {milestone: .0f}% coverage ! Keep it up ![/ cyan]"
|
|
290
267
|
)
|
|
291
268
|
|
|
292
269
|
def show_progress_with_spinner(self) -> None:
|
|
293
|
-
"""Show animated progress toward 100% coverage."""
|
|
294
270
|
data = self.get_ratchet_data()
|
|
295
271
|
if not data:
|
|
296
272
|
return
|
|
@@ -302,15 +278,14 @@ class CoverageRatchetService:
|
|
|
302
278
|
SpinnerColumn(),
|
|
303
279
|
TextColumn("[progress.description]{task.description}"),
|
|
304
280
|
BarColumn(),
|
|
305
|
-
TextColumn("[progress.percentage]{task.percentage
|
|
281
|
+
TextColumn("[progress.percentage]{task.percentage: > 3.0f}%"),
|
|
306
282
|
) as progress:
|
|
307
283
|
task = progress.add_task(
|
|
308
284
|
"Coverage Progress", total=target, completed=current
|
|
309
285
|
)
|
|
310
|
-
progress.update(task, description=f"Coverage: {current
|
|
286
|
+
progress.update(task, description=f"Coverage: {current: .1f}% / 100 %")
|
|
311
287
|
|
|
312
288
|
def get_coverage_report(self) -> str | None:
|
|
313
|
-
"""Get coverage report from the ratchet data."""
|
|
314
289
|
data = self.get_ratchet_data()
|
|
315
290
|
if not data:
|
|
316
291
|
return None
|
|
@@ -318,36 +293,32 @@ class CoverageRatchetService:
|
|
|
318
293
|
current_coverage = data.get("baseline", 0.0)
|
|
319
294
|
next_milestone = data.get("next_milestone")
|
|
320
295
|
|
|
321
|
-
report = f"Coverage: {current_coverage
|
|
296
|
+
report = f"Coverage: {current_coverage: .2f}%"
|
|
322
297
|
if next_milestone:
|
|
323
298
|
progress = (current_coverage / next_milestone) * 100
|
|
324
|
-
report +=
|
|
299
|
+
report += (
|
|
300
|
+
f" (next milestone: {next_milestone: .0f}%, {progress: .1f}% there)"
|
|
301
|
+
)
|
|
325
302
|
|
|
326
303
|
return report
|
|
327
304
|
|
|
328
305
|
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
306
|
try:
|
|
332
|
-
# Look for .coverage file or coverage.json
|
|
333
307
|
coverage_file = self.pkg_path / "coverage.json"
|
|
334
308
|
if not coverage_file.exists():
|
|
335
|
-
# No coverage data - this is acceptable, return success
|
|
336
309
|
return {
|
|
337
310
|
"success": True,
|
|
338
311
|
"status": "no_coverage_data",
|
|
339
|
-
"message": "No coverage data found
|
|
312
|
+
"message": "No coverage data found-tests passed without coverage",
|
|
340
313
|
"allowed": True,
|
|
341
314
|
"baseline_updated": False,
|
|
342
315
|
}
|
|
343
316
|
|
|
344
|
-
# Parse coverage data (simplified for now)
|
|
345
317
|
coverage_data = json.loads(coverage_file.read_text())
|
|
346
318
|
current_coverage = coverage_data.get("totals", {}).get(
|
|
347
319
|
"percent_covered", 0.0
|
|
348
320
|
)
|
|
349
321
|
|
|
350
|
-
# Update the ratchet
|
|
351
322
|
result = self.update_coverage(current_coverage)
|
|
352
323
|
result["success"] = result.get("allowed", True)
|
|
353
324
|
return result
|
crackerjack/services/debug.py
CHANGED
|
@@ -27,7 +27,7 @@ class AIAgentDebugger:
|
|
|
27
27
|
if self.enabled:
|
|
28
28
|
log_manager = get_log_manager()
|
|
29
29
|
self.debug_log_path = log_manager.create_debug_log_file(
|
|
30
|
-
f"ai-agent-{self.session_id}",
|
|
30
|
+
f"ai-agent -{self.session_id}",
|
|
31
31
|
)
|
|
32
32
|
else:
|
|
33
33
|
self.debug_log_path = None
|
|
@@ -37,7 +37,6 @@ class AIAgentDebugger:
|
|
|
37
37
|
self.workflow_phases: list[dict[str, Any]] = []
|
|
38
38
|
self.error_events: list[dict[str, Any]] = []
|
|
39
39
|
|
|
40
|
-
# Enhanced iteration tracking
|
|
41
40
|
self.iteration_stats: list[dict[str, Any]] = []
|
|
42
41
|
self.current_iteration = 0
|
|
43
42
|
self.total_test_failures = 0
|
|
@@ -86,7 +85,7 @@ class AIAgentDebugger:
|
|
|
86
85
|
else "Debug Log: None (disabled)"
|
|
87
86
|
)
|
|
88
87
|
header = Panel(
|
|
89
|
-
f"[bold cyan]🐛 AI Agent Debug Mode Active[/bold cyan]\n"
|
|
88
|
+
f"[bold cyan]🐛 AI Agent Debug Mode Active[/ bold cyan]\n"
|
|
90
89
|
f"Session ID: {self.session_id}\n"
|
|
91
90
|
f"{debug_log_info}\n"
|
|
92
91
|
f"Verbose Mode: {'✅' if self.verbose else '❌'}",
|
|
@@ -94,7 +93,7 @@ class AIAgentDebugger:
|
|
|
94
93
|
border_style="cyan",
|
|
95
94
|
)
|
|
96
95
|
self.console.print(header)
|
|
97
|
-
self.console.print()
|
|
96
|
+
self.console.print()
|
|
98
97
|
|
|
99
98
|
@contextmanager
|
|
100
99
|
def debug_operation(self, operation: str, **kwargs: Any) -> t.Iterator[str]:
|
|
@@ -109,7 +108,7 @@ class AIAgentDebugger:
|
|
|
109
108
|
self.logger.debug(f"Starting operation: {operation}", extra=kwargs)
|
|
110
109
|
|
|
111
110
|
if self.verbose:
|
|
112
|
-
self.console.print(f"[dim]🔍 {operation} starting...[/dim]")
|
|
111
|
+
self.console.print(f"[dim]🔍 {operation} starting...[/ dim]")
|
|
113
112
|
|
|
114
113
|
try:
|
|
115
114
|
yield op_id
|
|
@@ -121,7 +120,7 @@ class AIAgentDebugger:
|
|
|
121
120
|
|
|
122
121
|
if self.verbose:
|
|
123
122
|
self.console.print(
|
|
124
|
-
f"[dim green]✅ {operation} completed ({duration
|
|
123
|
+
f"[dim green]✅ {operation} completed ({duration: .2f}s)[/ dim green]",
|
|
125
124
|
)
|
|
126
125
|
|
|
127
126
|
except Exception as e:
|
|
@@ -133,7 +132,7 @@ class AIAgentDebugger:
|
|
|
133
132
|
|
|
134
133
|
if self.verbose:
|
|
135
134
|
self.console.print(
|
|
136
|
-
f"[dim red]❌ {operation} failed ({duration
|
|
135
|
+
f"[dim red]❌ {operation} failed ({duration: .2f}s): {e}[/ dim red]",
|
|
137
136
|
)
|
|
138
137
|
raise
|
|
139
138
|
|
|
@@ -180,12 +179,12 @@ class AIAgentDebugger:
|
|
|
180
179
|
|
|
181
180
|
self.console.print(
|
|
182
181
|
f"[{status_color}]{status_icon} MCP {operation_type}[/{status_color}]: "
|
|
183
|
-
f"[bold]{tool_name}[/bold]"
|
|
184
|
-
+ (f" ({duration
|
|
182
|
+
f"[bold]{tool_name}[/ bold]"
|
|
183
|
+
+ (f" ({duration: .2f}s)" if duration else ""),
|
|
185
184
|
)
|
|
186
185
|
|
|
187
186
|
if error and self.verbose:
|
|
188
|
-
self.console.print(f" [red]Error: {error}[/red]")
|
|
187
|
+
self.console.print(f" [red]Error: {error}[/ red]")
|
|
189
188
|
self.console.print()
|
|
190
189
|
|
|
191
190
|
def log_agent_activity(
|
|
@@ -226,11 +225,11 @@ class AIAgentDebugger:
|
|
|
226
225
|
)
|
|
227
226
|
|
|
228
227
|
if self.verbose:
|
|
229
|
-
confidence_text = f" (confidence: {confidence
|
|
228
|
+
confidence_text = f" (confidence: {confidence: .2f})" if confidence else ""
|
|
230
229
|
issue_text = f" [issue: {issue_id}]" if issue_id else ""
|
|
231
230
|
|
|
232
231
|
self.console.print(
|
|
233
|
-
f"[blue]🤖 {agent_name}[/blue]: {activity}{confidence_text}{issue_text}",
|
|
232
|
+
f"[blue]🤖 {agent_name}[/ blue]: {activity}{confidence_text}{issue_text}",
|
|
234
233
|
)
|
|
235
234
|
|
|
236
235
|
def log_workflow_phase(
|
|
@@ -270,7 +269,7 @@ class AIAgentDebugger:
|
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
color = status_colors.get(status, "white")
|
|
273
|
-
duration_text = f" ({duration
|
|
272
|
+
duration_text = f" ({duration: .2f}s)" if duration else ""
|
|
274
273
|
|
|
275
274
|
self.console.print(
|
|
276
275
|
f"[{color}]📋 Workflow {status}: {phase}{duration_text}[/{color}]",
|
|
@@ -305,17 +304,15 @@ class AIAgentDebugger:
|
|
|
305
304
|
)
|
|
306
305
|
|
|
307
306
|
if self.verbose:
|
|
308
|
-
self.console.print(f"[red]💥 {error_type}: {message}[/red]")
|
|
307
|
+
self.console.print(f"[red]💥 {error_type}: {message}[/ red]")
|
|
309
308
|
|
|
310
309
|
def print_debug_summary(self) -> None:
|
|
311
310
|
if not self.enabled:
|
|
312
311
|
return
|
|
313
312
|
|
|
314
|
-
# Determine border style based on workflow success
|
|
315
313
|
border_style = "green" if self.workflow_success else "red"
|
|
316
314
|
title_style = "green" if self.workflow_success else "red"
|
|
317
315
|
|
|
318
|
-
# Main debug summary table
|
|
319
316
|
table = Table(
|
|
320
317
|
title=f"[{title_style}]AI Agent Debug Summary[/{title_style}]",
|
|
321
318
|
border_style=border_style,
|
|
@@ -348,7 +345,6 @@ class AIAgentDebugger:
|
|
|
348
345
|
f"Types: {len({err['error_type'] for err in self.error_events})}",
|
|
349
346
|
)
|
|
350
347
|
|
|
351
|
-
# Add iteration statistics row
|
|
352
348
|
table.add_row(
|
|
353
349
|
"Iterations Completed",
|
|
354
350
|
str(self.current_iteration),
|
|
@@ -357,7 +353,6 @@ class AIAgentDebugger:
|
|
|
357
353
|
|
|
358
354
|
self.console.print(table)
|
|
359
355
|
|
|
360
|
-
# Print iteration breakdown if we have iterations
|
|
361
356
|
if self.iteration_stats:
|
|
362
357
|
self._print_iteration_breakdown(border_style)
|
|
363
358
|
|
|
@@ -367,22 +362,20 @@ class AIAgentDebugger:
|
|
|
367
362
|
if self.verbose and self.mcp_operations:
|
|
368
363
|
self._print_mcp_operation_breakdown(border_style)
|
|
369
364
|
|
|
370
|
-
# Print total statistics
|
|
371
365
|
self._print_total_statistics(border_style)
|
|
372
366
|
|
|
373
367
|
self.console.print(
|
|
374
|
-
f"\n[dim]📝 Full debug log available at: {self.debug_log_path}[/dim]"
|
|
368
|
+
f"\n[dim]📝 Full debug log available at: {self.debug_log_path}[/ dim]"
|
|
375
369
|
if self.debug_log_path
|
|
376
370
|
else "",
|
|
377
371
|
)
|
|
378
372
|
|
|
379
373
|
def _print_iteration_breakdown(self, border_style: str = "red") -> None:
|
|
380
|
-
"""Print detailed breakdown of each iteration."""
|
|
381
374
|
if not self.iteration_stats:
|
|
382
375
|
return
|
|
383
376
|
|
|
384
377
|
table = Table(
|
|
385
|
-
title="[cyan]Iteration Breakdown[/cyan]",
|
|
378
|
+
title="[cyan]Iteration Breakdown[/ cyan]",
|
|
386
379
|
border_style=border_style,
|
|
387
380
|
)
|
|
388
381
|
table.add_column("Iteration", style="yellow")
|
|
@@ -399,7 +392,9 @@ class AIAgentDebugger:
|
|
|
399
392
|
str(iteration["test_fixes"]),
|
|
400
393
|
str(iteration["hook_failures"]),
|
|
401
394
|
str(iteration["hook_fixes"]),
|
|
402
|
-
f"{iteration['duration']
|
|
395
|
+
f"{iteration['duration']: .1f}s"
|
|
396
|
+
if iteration.get("duration")
|
|
397
|
+
else "N / A",
|
|
403
398
|
)
|
|
404
399
|
|
|
405
400
|
self.console.print(table)
|
|
@@ -426,7 +421,7 @@ class AIAgentDebugger:
|
|
|
426
421
|
)
|
|
427
422
|
|
|
428
423
|
table = Table(
|
|
429
|
-
title="[cyan]Agent Activity Breakdown[/cyan]",
|
|
424
|
+
title="[cyan]Agent Activity Breakdown[/ cyan]",
|
|
430
425
|
border_style=border_style,
|
|
431
426
|
)
|
|
432
427
|
table.add_column("Agent", style="blue")
|
|
@@ -435,16 +430,15 @@ class AIAgentDebugger:
|
|
|
435
430
|
|
|
436
431
|
for agent, stats in sorted(agent_stats.items()):
|
|
437
432
|
confidence_text = (
|
|
438
|
-
f"{stats['avg_confidence']
|
|
433
|
+
f"{stats['avg_confidence']: .2f}"
|
|
439
434
|
if stats["avg_confidence"] > 0
|
|
440
|
-
else "N/A"
|
|
435
|
+
else "N / A"
|
|
441
436
|
)
|
|
442
437
|
table.add_row(agent, str(stats["activities"]), confidence_text)
|
|
443
438
|
|
|
444
439
|
self.console.print(table)
|
|
445
440
|
|
|
446
441
|
def _print_total_statistics(self, border_style: str = "red") -> None:
|
|
447
|
-
"""Print total cumulative statistics across all iterations."""
|
|
448
442
|
success_icon = "✅" if self.workflow_success else "❌"
|
|
449
443
|
status_text = "SUCCESS" if self.workflow_success else "IN PROGRESS"
|
|
450
444
|
status_style = "green" if self.workflow_success else "red"
|
|
@@ -477,7 +471,7 @@ class AIAgentDebugger:
|
|
|
477
471
|
|
|
478
472
|
table.add_row(
|
|
479
473
|
"Overall Fix Rate",
|
|
480
|
-
f"{fix_rate
|
|
474
|
+
f"{fix_rate: .1f}%",
|
|
481
475
|
f"{total_fixes}/{total_issues} issues resolved",
|
|
482
476
|
)
|
|
483
477
|
|
|
@@ -497,7 +491,7 @@ class AIAgentDebugger:
|
|
|
497
491
|
tool_stats[tool]["total_duration"] += op["duration"]
|
|
498
492
|
|
|
499
493
|
table = Table(
|
|
500
|
-
title="[cyan]MCP Tool Usage[/cyan]",
|
|
494
|
+
title="[cyan]MCP Tool Usage[/ cyan]",
|
|
501
495
|
border_style=border_style,
|
|
502
496
|
)
|
|
503
497
|
table.add_column("Tool", style="cyan")
|
|
@@ -513,13 +507,12 @@ class AIAgentDebugger:
|
|
|
513
507
|
tool,
|
|
514
508
|
str(stats["calls"]),
|
|
515
509
|
str(stats["errors"]),
|
|
516
|
-
f"{avg_duration
|
|
510
|
+
f"{avg_duration: .2f}s" if avg_duration > 0 else "N / A",
|
|
517
511
|
)
|
|
518
512
|
|
|
519
513
|
self.console.print(table)
|
|
520
514
|
|
|
521
515
|
def log_iteration_start(self, iteration_number: int) -> None:
|
|
522
|
-
"""Log the start of a new iteration."""
|
|
523
516
|
if not self.enabled:
|
|
524
517
|
return
|
|
525
518
|
|
|
@@ -537,15 +530,13 @@ class AIAgentDebugger:
|
|
|
537
530
|
|
|
538
531
|
if self.verbose:
|
|
539
532
|
self.console.print(
|
|
540
|
-
f"[yellow]🔄 Starting Iteration {iteration_number}[/yellow]",
|
|
533
|
+
f"[yellow]🔄 Starting Iteration {iteration_number}[/ yellow]",
|
|
541
534
|
)
|
|
542
535
|
|
|
543
536
|
def log_iteration_end(self, iteration_number: int, success: bool) -> None:
|
|
544
|
-
"""Log the end of an iteration with statistics."""
|
|
545
537
|
if not self.enabled or not self.iteration_stats:
|
|
546
538
|
return
|
|
547
539
|
|
|
548
|
-
# Find the iteration data
|
|
549
540
|
iteration_data = None
|
|
550
541
|
for data in self.iteration_stats:
|
|
551
542
|
if data["iteration"] == iteration_number:
|
|
@@ -562,7 +553,6 @@ class AIAgentDebugger:
|
|
|
562
553
|
)
|
|
563
554
|
|
|
564
555
|
def log_test_failures(self, count: int) -> None:
|
|
565
|
-
"""Log test failure count for current iteration."""
|
|
566
556
|
if not self.enabled or not self.iteration_stats:
|
|
567
557
|
return
|
|
568
558
|
|
|
@@ -571,7 +561,6 @@ class AIAgentDebugger:
|
|
|
571
561
|
self.total_test_failures += count
|
|
572
562
|
|
|
573
563
|
def log_test_fixes(self, count: int) -> None:
|
|
574
|
-
"""Log test fix count for current iteration."""
|
|
575
564
|
if not self.enabled or not self.iteration_stats:
|
|
576
565
|
return
|
|
577
566
|
|
|
@@ -580,7 +569,6 @@ class AIAgentDebugger:
|
|
|
580
569
|
self.total_test_fixes += count
|
|
581
570
|
|
|
582
571
|
def log_hook_failures(self, count: int) -> None:
|
|
583
|
-
"""Log hook failure count for current iteration."""
|
|
584
572
|
if not self.enabled or not self.iteration_stats:
|
|
585
573
|
return
|
|
586
574
|
|
|
@@ -589,7 +577,6 @@ class AIAgentDebugger:
|
|
|
589
577
|
self.total_hook_failures += count
|
|
590
578
|
|
|
591
579
|
def log_hook_fixes(self, count: int) -> None:
|
|
592
|
-
"""Log hook fix count for current iteration."""
|
|
593
580
|
if not self.enabled or not self.iteration_stats:
|
|
594
581
|
return
|
|
595
582
|
|
|
@@ -598,7 +585,6 @@ class AIAgentDebugger:
|
|
|
598
585
|
self.total_hook_fixes += count
|
|
599
586
|
|
|
600
587
|
def set_workflow_success(self, success: bool) -> None:
|
|
601
|
-
"""Set the overall workflow success status."""
|
|
602
588
|
if not self.enabled:
|
|
603
589
|
return
|
|
604
590
|
|