crackerjack 0.30.3__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.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +652 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +370 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.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
|
+
}
|