crackerjack 0.38.9__py3-none-any.whl → 0.38.11__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/__main__.py +44 -33
- crackerjack/interactive.py +20 -0
- crackerjack/managers/test_command_builder.py +36 -1
- crackerjack/managers/test_manager.py +122 -106
- crackerjack/monitoring/ai_agent_watchdog.py +1 -1
- crackerjack/orchestration/advanced_orchestrator.py +17 -65
- crackerjack/services/git.py +2 -2
- {crackerjack-0.38.9.dist-info → crackerjack-0.38.11.dist-info}/METADATA +2 -3
- {crackerjack-0.38.9.dist-info → crackerjack-0.38.11.dist-info}/RECORD +12 -12
- {crackerjack-0.38.9.dist-info → crackerjack-0.38.11.dist-info}/WHEEL +0 -0
- {crackerjack-0.38.9.dist-info → crackerjack-0.38.11.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.38.9.dist-info → crackerjack-0.38.11.dist-info}/licenses/LICENSE +0 -0
crackerjack/__main__.py
CHANGED
|
@@ -1543,6 +1543,47 @@ def _handle_specialized_analytics(local_vars: t.Any, console: t.Any) -> bool:
|
|
|
1543
1543
|
return _handle_enterprise_features(local_vars, console)
|
|
1544
1544
|
|
|
1545
1545
|
|
|
1546
|
+
def _display_coverage_info(console: t.Any, coverage_info: dict[str, t.Any]) -> None:
|
|
1547
|
+
"""Display basic coverage information."""
|
|
1548
|
+
coverage_percent = coverage_info.get("coverage_percent", 0.0)
|
|
1549
|
+
coverage_source = coverage_info.get("source", "unknown")
|
|
1550
|
+
|
|
1551
|
+
if coverage_percent > 0:
|
|
1552
|
+
console.print(
|
|
1553
|
+
f"[green]Current Coverage:[/green] {coverage_percent:.2f}% (from {coverage_source})"
|
|
1554
|
+
)
|
|
1555
|
+
else:
|
|
1556
|
+
console.print("[yellow]Current Coverage:[/yellow] No coverage data available")
|
|
1557
|
+
|
|
1558
|
+
# Show status message if available
|
|
1559
|
+
status_message = coverage_info.get("message")
|
|
1560
|
+
if status_message:
|
|
1561
|
+
console.print(f"[dim]{status_message}[/dim]")
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
def _display_coverage_report(console: t.Any, test_manager: t.Any) -> None:
|
|
1565
|
+
"""Display detailed coverage report if available."""
|
|
1566
|
+
coverage_report = test_manager.get_coverage_report()
|
|
1567
|
+
if coverage_report:
|
|
1568
|
+
console.print(f"[cyan]Details:[/cyan] {coverage_report}")
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
def _display_ratchet_status(console: t.Any, test_manager: t.Any) -> None:
|
|
1572
|
+
"""Display coverage ratchet status if available."""
|
|
1573
|
+
from contextlib import suppress
|
|
1574
|
+
|
|
1575
|
+
with suppress(Exception):
|
|
1576
|
+
ratchet_status = test_manager.get_coverage_ratchet_status()
|
|
1577
|
+
if ratchet_status:
|
|
1578
|
+
next_milestone = ratchet_status.get("next_milestone")
|
|
1579
|
+
if next_milestone:
|
|
1580
|
+
console.print(f"[cyan]Next Milestone:[/cyan] {next_milestone:.0f}%")
|
|
1581
|
+
|
|
1582
|
+
milestones = ratchet_status.get("milestones_achieved", [])
|
|
1583
|
+
if milestones:
|
|
1584
|
+
console.print(f"[green]Milestones Achieved:[/green] {len(milestones)}")
|
|
1585
|
+
|
|
1586
|
+
|
|
1546
1587
|
def _handle_coverage_status(
|
|
1547
1588
|
coverage_status: bool, console: t.Any, options: t.Any
|
|
1548
1589
|
) -> bool:
|
|
@@ -1566,43 +1607,13 @@ def _handle_coverage_status(
|
|
|
1566
1607
|
|
|
1567
1608
|
# Get coverage information
|
|
1568
1609
|
coverage_info = test_manager.get_coverage()
|
|
1569
|
-
|
|
1570
|
-
coverage_source = coverage_info.get("source", "unknown")
|
|
1571
|
-
|
|
1572
|
-
if coverage_percent > 0:
|
|
1573
|
-
console.print(
|
|
1574
|
-
f"[green]Current Coverage:[/green] {coverage_percent:.2f}% (from {coverage_source})"
|
|
1575
|
-
)
|
|
1576
|
-
else:
|
|
1577
|
-
console.print(
|
|
1578
|
-
"[yellow]Current Coverage:[/yellow] No coverage data available"
|
|
1579
|
-
)
|
|
1580
|
-
|
|
1581
|
-
# Show status message if available
|
|
1582
|
-
status_message = coverage_info.get("message")
|
|
1583
|
-
if status_message:
|
|
1584
|
-
console.print(f"[dim]{status_message}[/dim]")
|
|
1610
|
+
_display_coverage_info(console, coverage_info)
|
|
1585
1611
|
|
|
1586
1612
|
# Try to get more detailed coverage report
|
|
1587
|
-
|
|
1588
|
-
if coverage_report:
|
|
1589
|
-
console.print(f"[cyan]Details:[/cyan] {coverage_report}")
|
|
1613
|
+
_display_coverage_report(console, test_manager)
|
|
1590
1614
|
|
|
1591
1615
|
# Show coverage ratchet status if available
|
|
1592
|
-
|
|
1593
|
-
ratchet_status = test_manager.get_coverage_ratchet_status()
|
|
1594
|
-
if ratchet_status:
|
|
1595
|
-
next_milestone = ratchet_status.get("next_milestone")
|
|
1596
|
-
if next_milestone:
|
|
1597
|
-
console.print(f"[cyan]Next Milestone:[/cyan] {next_milestone:.0f}%")
|
|
1598
|
-
|
|
1599
|
-
milestones = ratchet_status.get("milestones_achieved", [])
|
|
1600
|
-
if milestones:
|
|
1601
|
-
console.print(
|
|
1602
|
-
f"[green]Milestones Achieved:[/green] {len(milestones)}"
|
|
1603
|
-
)
|
|
1604
|
-
except Exception:
|
|
1605
|
-
pass # Ignore ratchet status errors
|
|
1616
|
+
_display_ratchet_status(console, test_manager)
|
|
1606
1617
|
|
|
1607
1618
|
console.print()
|
|
1608
1619
|
return False # Exit after showing status
|
crackerjack/interactive.py
CHANGED
|
@@ -34,6 +34,26 @@ class InteractiveWorkflowOptions:
|
|
|
34
34
|
interactive: bool = True
|
|
35
35
|
dry_run: bool = False
|
|
36
36
|
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
clean: bool = False,
|
|
40
|
+
test: bool = False,
|
|
41
|
+
publish: str | None = None,
|
|
42
|
+
bump: str | None = None,
|
|
43
|
+
commit: bool = False,
|
|
44
|
+
create_pr: bool = False,
|
|
45
|
+
interactive: bool = True,
|
|
46
|
+
dry_run: bool = False,
|
|
47
|
+
) -> None:
|
|
48
|
+
self.clean = clean
|
|
49
|
+
self.test = test
|
|
50
|
+
self.publish = publish
|
|
51
|
+
self.bump = bump
|
|
52
|
+
self.commit = commit
|
|
53
|
+
self.create_pr = create_pr
|
|
54
|
+
self.interactive = interactive
|
|
55
|
+
self.dry_run = dry_run
|
|
56
|
+
|
|
37
57
|
@classmethod
|
|
38
58
|
def from_args(cls, args: t.Any) -> "InteractiveWorkflowOptions":
|
|
39
59
|
return cls(
|
|
@@ -33,12 +33,47 @@ class TestCommandBuilder:
|
|
|
33
33
|
return 900
|
|
34
34
|
return 300
|
|
35
35
|
|
|
36
|
+
def _detect_package_name(self) -> str:
|
|
37
|
+
"""Detect the main package name for coverage reporting."""
|
|
38
|
+
# Method 1: Try to read from pyproject.toml
|
|
39
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
40
|
+
if pyproject_path.exists():
|
|
41
|
+
from contextlib import suppress
|
|
42
|
+
|
|
43
|
+
with suppress(Exception):
|
|
44
|
+
import tomllib
|
|
45
|
+
|
|
46
|
+
with pyproject_path.open("rb") as f:
|
|
47
|
+
data = tomllib.load(f)
|
|
48
|
+
project_name = data.get("project", {}).get("name")
|
|
49
|
+
if project_name:
|
|
50
|
+
# Convert project name to package name (hyphens to underscores)
|
|
51
|
+
return project_name.replace("-", "_")
|
|
52
|
+
# Fall back to directory detection
|
|
53
|
+
|
|
54
|
+
# Method 2: Look for Python packages in the project root
|
|
55
|
+
for item in self.pkg_path.iterdir():
|
|
56
|
+
if (
|
|
57
|
+
item.is_dir()
|
|
58
|
+
and not item.name.startswith(".")
|
|
59
|
+
and item.name not in ("tests", "docs", "build", "dist", "__pycache__")
|
|
60
|
+
and (item / "__init__.py").exists()
|
|
61
|
+
):
|
|
62
|
+
return item.name
|
|
63
|
+
|
|
64
|
+
# Method 3: Fallback to crackerjack if nothing found (for crackerjack itself)
|
|
65
|
+
return "crackerjack"
|
|
66
|
+
|
|
36
67
|
def _add_coverage_options(self, cmd: list[str], options: OptionsProtocol) -> None:
|
|
68
|
+
# Determine package name from project structure
|
|
69
|
+
package_name = self._detect_package_name()
|
|
70
|
+
|
|
37
71
|
cmd.extend(
|
|
38
72
|
[
|
|
39
|
-
"--cov=
|
|
73
|
+
f"--cov={package_name}",
|
|
40
74
|
"--cov-report=term-missing",
|
|
41
75
|
"--cov-report=html",
|
|
76
|
+
"--cov-report=json", # Required for badge updates
|
|
42
77
|
"--cov-fail-under=0",
|
|
43
78
|
]
|
|
44
79
|
)
|
|
@@ -128,66 +128,91 @@ class TestManager:
|
|
|
128
128
|
except Exception:
|
|
129
129
|
return None
|
|
130
130
|
|
|
131
|
+
def _get_coverage_from_file(self) -> float | None:
|
|
132
|
+
"""Extract coverage from coverage.json file."""
|
|
133
|
+
import json
|
|
134
|
+
|
|
135
|
+
coverage_json_path = self.pkg_path / "coverage.json"
|
|
136
|
+
if not coverage_json_path.exists():
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
with coverage_json_path.open() as f:
|
|
141
|
+
coverage_data = json.load(f)
|
|
142
|
+
|
|
143
|
+
# Extract coverage percentage from totals
|
|
144
|
+
totals = coverage_data.get("totals", {})
|
|
145
|
+
percent_covered = totals.get("percent_covered", None)
|
|
146
|
+
|
|
147
|
+
if percent_covered is not None:
|
|
148
|
+
return float(percent_covered)
|
|
149
|
+
|
|
150
|
+
# Alternative extraction methods for different coverage formats
|
|
151
|
+
if "percent_covered" in coverage_data:
|
|
152
|
+
return float(coverage_data["percent_covered"])
|
|
153
|
+
|
|
154
|
+
# Check for coverage in files section
|
|
155
|
+
files = coverage_data.get("files", {})
|
|
156
|
+
if files:
|
|
157
|
+
total_lines = 0
|
|
158
|
+
covered_lines = 0
|
|
159
|
+
for file_data in files.values():
|
|
160
|
+
summary = file_data.get("summary", {})
|
|
161
|
+
total_lines += summary.get("num_statements", 0)
|
|
162
|
+
covered_lines += summary.get("covered_lines", 0)
|
|
163
|
+
|
|
164
|
+
if total_lines > 0:
|
|
165
|
+
return (covered_lines / total_lines) * 100
|
|
166
|
+
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
except (json.JSONDecodeError, ValueError, KeyError, TypeError):
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def _handle_no_ratchet_status(
|
|
173
|
+
self, direct_coverage: float | None
|
|
174
|
+
) -> dict[str, t.Any]:
|
|
175
|
+
"""Handle case when ratchet is not initialized."""
|
|
176
|
+
if direct_coverage is not None:
|
|
177
|
+
return {
|
|
178
|
+
"status": "coverage_available",
|
|
179
|
+
"coverage_percent": direct_coverage,
|
|
180
|
+
"message": "Coverage data available from coverage.json",
|
|
181
|
+
"source": "coverage.json",
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"status": "not_initialized",
|
|
186
|
+
"coverage_percent": 0.0,
|
|
187
|
+
"message": "Coverage ratchet not initialized",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def _get_final_coverage(
|
|
191
|
+
self, ratchet_coverage: float, direct_coverage: float | None
|
|
192
|
+
) -> float:
|
|
193
|
+
"""Determine final coverage value."""
|
|
194
|
+
return direct_coverage if direct_coverage is not None else ratchet_coverage
|
|
195
|
+
|
|
131
196
|
def get_coverage(self) -> dict[str, t.Any]:
|
|
132
197
|
try:
|
|
133
198
|
status = self.coverage_ratchet.get_status_report()
|
|
134
199
|
|
|
135
200
|
# Check if we have actual coverage data from coverage.json even if ratchet is not initialized
|
|
136
|
-
|
|
137
|
-
direct_coverage = None
|
|
138
|
-
|
|
139
|
-
if coverage_json_path.exists():
|
|
140
|
-
try:
|
|
141
|
-
import json
|
|
142
|
-
|
|
143
|
-
with coverage_json_path.open() as f:
|
|
144
|
-
data = json.load(f)
|
|
145
|
-
# Check for totals field first (newer format)
|
|
146
|
-
direct_coverage = data.get("totals", {}).get("percent_covered")
|
|
147
|
-
|
|
148
|
-
# If no totals, calculate from files data (standard pytest-cov format)
|
|
149
|
-
if direct_coverage is None and "files" in data:
|
|
150
|
-
total_statements = 0
|
|
151
|
-
total_covered = 0
|
|
152
|
-
|
|
153
|
-
for file_data in data["files"].values():
|
|
154
|
-
summary = file_data.get("summary", {})
|
|
155
|
-
statements = summary.get("num_statements", 0)
|
|
156
|
-
covered = summary.get("covered_lines", 0)
|
|
157
|
-
total_statements += statements
|
|
158
|
-
total_covered += covered
|
|
159
|
-
|
|
160
|
-
if total_statements > 0:
|
|
161
|
-
direct_coverage = (
|
|
162
|
-
total_covered / total_statements
|
|
163
|
-
) * 100
|
|
164
|
-
except (json.JSONDecodeError, KeyError):
|
|
165
|
-
pass # Fall back to ratchet data
|
|
201
|
+
direct_coverage = self._get_coverage_from_file()
|
|
166
202
|
|
|
167
203
|
# If ratchet is not initialized but we have direct coverage data, use it
|
|
168
204
|
if (
|
|
169
205
|
not status or status.get("status") == "not_initialized"
|
|
170
206
|
) and direct_coverage is not None:
|
|
171
|
-
return
|
|
172
|
-
"status": "coverage_available",
|
|
173
|
-
"coverage_percent": direct_coverage,
|
|
174
|
-
"message": "Coverage data available from coverage.json",
|
|
175
|
-
"source": "coverage.json",
|
|
176
|
-
}
|
|
207
|
+
return self._handle_no_ratchet_status(direct_coverage)
|
|
177
208
|
|
|
178
209
|
# If ratchet is not initialized and no direct coverage, return not initialized
|
|
179
210
|
if not status or status.get("status") == "not_initialized":
|
|
180
|
-
return
|
|
181
|
-
"status": "not_initialized",
|
|
182
|
-
"coverage_percent": 0.0,
|
|
183
|
-
"message": "Coverage ratchet not initialized",
|
|
184
|
-
}
|
|
211
|
+
return self._handle_no_ratchet_status(None)
|
|
185
212
|
|
|
186
213
|
# Use ratchet data, but prefer direct coverage if available and different
|
|
187
214
|
ratchet_coverage = status.get("current_coverage", 0.0)
|
|
188
|
-
final_coverage = (
|
|
189
|
-
direct_coverage if direct_coverage is not None else ratchet_coverage
|
|
190
|
-
)
|
|
215
|
+
final_coverage = self._get_final_coverage(ratchet_coverage, direct_coverage)
|
|
191
216
|
|
|
192
217
|
return {
|
|
193
218
|
"status": "active",
|
|
@@ -277,74 +302,65 @@ class TestManager:
|
|
|
277
302
|
|
|
278
303
|
return self._handle_ratchet_result(ratchet_result)
|
|
279
304
|
|
|
280
|
-
def
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
current_coverage = None
|
|
287
|
-
coverage_json_path = self.pkg_path / "coverage.json"
|
|
288
|
-
|
|
289
|
-
# Primary: Try to extract from coverage.json
|
|
290
|
-
if coverage_json_path.exists():
|
|
291
|
-
try:
|
|
292
|
-
with coverage_json_path.open() as f:
|
|
293
|
-
data = json.load(f)
|
|
294
|
-
# Check for totals field first (newer format)
|
|
295
|
-
current_coverage = data.get("totals", {}).get("percent_covered")
|
|
296
|
-
|
|
297
|
-
# If no totals, calculate from files data (standard pytest-cov format)
|
|
298
|
-
if current_coverage is None and "files" in data:
|
|
299
|
-
total_statements = 0
|
|
300
|
-
total_covered = 0
|
|
301
|
-
|
|
302
|
-
for file_data in data["files"].values():
|
|
303
|
-
summary = file_data.get("summary", {})
|
|
304
|
-
statements = summary.get("num_statements", 0)
|
|
305
|
-
covered = summary.get("covered_lines", 0)
|
|
306
|
-
total_statements += statements
|
|
307
|
-
total_covered += covered
|
|
308
|
-
|
|
309
|
-
if total_statements > 0:
|
|
310
|
-
current_coverage = (
|
|
311
|
-
total_covered / total_statements
|
|
312
|
-
) * 100
|
|
313
|
-
|
|
314
|
-
if current_coverage is not None:
|
|
315
|
-
self.console.print(
|
|
316
|
-
f"[dim]📊 Coverage extracted from coverage.json: {current_coverage:.2f}%[/dim]"
|
|
317
|
-
)
|
|
318
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
319
|
-
self.console.print(
|
|
320
|
-
f"[yellow]⚠️[/yellow] Failed to parse coverage.json: {e}"
|
|
321
|
-
)
|
|
305
|
+
def _attempt_coverage_extraction(self) -> float | None:
|
|
306
|
+
"""Attempt to extract coverage from various sources."""
|
|
307
|
+
# Primary: Try to extract from coverage.json
|
|
308
|
+
current_coverage = self._get_coverage_from_file()
|
|
309
|
+
if current_coverage is not None:
|
|
310
|
+
return current_coverage
|
|
322
311
|
|
|
323
|
-
|
|
324
|
-
if current_coverage is None:
|
|
325
|
-
current_coverage = ratchet_result.get("current_coverage")
|
|
326
|
-
if current_coverage is not None:
|
|
327
|
-
self.console.print(
|
|
328
|
-
f"[dim]📊 Coverage from ratchet result: {current_coverage:.2f}%[/dim]"
|
|
329
|
-
)
|
|
312
|
+
return None
|
|
330
313
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
314
|
+
def _handle_coverage_extraction_result(
|
|
315
|
+
self, current_coverage: float | None
|
|
316
|
+
) -> float | None:
|
|
317
|
+
"""Handle the result of coverage extraction attempts."""
|
|
318
|
+
if current_coverage is not None:
|
|
319
|
+
self.console.print(
|
|
320
|
+
f"[dim]📊 Coverage extracted from coverage.json: {current_coverage:.2f}%[/dim]"
|
|
321
|
+
)
|
|
322
|
+
return current_coverage
|
|
323
|
+
|
|
324
|
+
def _get_fallback_coverage(
|
|
325
|
+
self, ratchet_result: dict[str, t.Any], current_coverage: float | None
|
|
326
|
+
) -> float | None:
|
|
327
|
+
"""Get coverage from fallback sources."""
|
|
328
|
+
# Secondary: Try ratchet result if coverage.json failed
|
|
329
|
+
if current_coverage is None:
|
|
330
|
+
current_coverage = self._get_coverage_from_ratchet(ratchet_result)
|
|
331
|
+
if current_coverage is not None:
|
|
332
|
+
self.console.print(
|
|
333
|
+
f"[dim]📊 Coverage from ratchet result: {current_coverage:.2f}%[/dim]"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Tertiary: Try coverage service, but only accept non-zero values
|
|
337
|
+
if current_coverage is None:
|
|
338
|
+
current_coverage = self._get_coverage_from_service()
|
|
339
|
+
if current_coverage is not None:
|
|
340
|
+
self.console.print(
|
|
341
|
+
f"[dim]📊 Coverage from service fallback: {current_coverage:.2f}%[/dim]"
|
|
342
|
+
)
|
|
343
|
+
else:
|
|
344
|
+
coverage_json_path = self.pkg_path / "coverage.json"
|
|
345
|
+
if coverage_json_path.exists():
|
|
344
346
|
self.console.print(
|
|
345
347
|
"[yellow]⚠️[/yellow] Skipping 0.0% fallback when coverage.json exists"
|
|
346
348
|
)
|
|
347
349
|
|
|
350
|
+
return current_coverage
|
|
351
|
+
|
|
352
|
+
def _update_coverage_badge(self, ratchet_result: dict[str, t.Any]) -> None:
|
|
353
|
+
"""Update coverage badge in README.md if coverage changed."""
|
|
354
|
+
try:
|
|
355
|
+
# Get current coverage directly from coverage.json to ensure freshest data
|
|
356
|
+
current_coverage = self._attempt_coverage_extraction()
|
|
357
|
+
current_coverage = self._handle_coverage_extraction_result(current_coverage)
|
|
358
|
+
|
|
359
|
+
# Get fallback coverage if needed
|
|
360
|
+
current_coverage = self._get_fallback_coverage(
|
|
361
|
+
ratchet_result, current_coverage
|
|
362
|
+
)
|
|
363
|
+
|
|
348
364
|
# Only update badge if we have valid coverage data
|
|
349
365
|
if current_coverage is not None and current_coverage >= 0:
|
|
350
366
|
if self._coverage_badge_service.should_update_badge(current_coverage):
|
|
@@ -21,7 +21,7 @@ class AgentPerformanceMetrics:
|
|
|
21
21
|
failed_fixes: int = 0
|
|
22
22
|
average_confidence: float = 0.0
|
|
23
23
|
average_execution_time: float = 0.0
|
|
24
|
-
issue_types_handled: dict[IssueType, int] = field(default_factory=dict
|
|
24
|
+
issue_types_handled: dict[IssueType, int] = field(default_factory=dict)
|
|
25
25
|
recent_failures: list[str] = field(default_factory=list)
|
|
26
26
|
last_successful_fix: datetime | None = None
|
|
27
27
|
regression_patterns: list[str] = field(default_factory=list)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import typing as t
|
|
3
|
-
from contextlib import suppress
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from rich.console import Console, Group
|
|
@@ -101,94 +100,47 @@ class CorrelationTracker:
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
|
|
104
|
-
class
|
|
103
|
+
class ProgressStreamer:
|
|
104
|
+
"""Base class for streaming progress updates during orchestration."""
|
|
105
|
+
|
|
105
106
|
def __init__(
|
|
106
107
|
self,
|
|
107
108
|
config: OrchestrationConfig | None = None,
|
|
108
109
|
session: SessionCoordinator | None = None,
|
|
109
110
|
) -> None:
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
self.config = config
|
|
112
|
+
self.session = session
|
|
112
113
|
|
|
113
114
|
def update_stage(self, stage: str, substage: str = "") -> None:
|
|
115
|
+
"""Update current stage."""
|
|
114
116
|
pass
|
|
115
117
|
|
|
116
118
|
def update_hook_progress(self, progress: HookProgress) -> None:
|
|
119
|
+
"""Update hook progress."""
|
|
117
120
|
pass
|
|
118
121
|
|
|
119
122
|
def _stream_update(self, data: dict[str, t.Any]) -> None:
|
|
123
|
+
"""Stream update data."""
|
|
120
124
|
pass
|
|
121
125
|
|
|
122
126
|
|
|
123
|
-
class ProgressStreamer:
|
|
127
|
+
class MinimalProgressStreamer(ProgressStreamer):
|
|
124
128
|
def __init__(
|
|
125
129
|
self,
|
|
126
|
-
config: OrchestrationConfig,
|
|
127
|
-
session: SessionCoordinator,
|
|
130
|
+
config: OrchestrationConfig | None = None,
|
|
131
|
+
session: SessionCoordinator | None = None,
|
|
128
132
|
) -> None:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
self.current_stage = "initialization"
|
|
132
|
-
self.current_substage = ""
|
|
133
|
-
self.hook_progress: dict[str, HookProgress] = {}
|
|
133
|
+
# Minimal implementation doesn't use config or session
|
|
134
|
+
pass
|
|
134
135
|
|
|
135
136
|
def update_stage(self, stage: str, substage: str = "") -> None:
|
|
136
|
-
|
|
137
|
-
self.current_substage = substage
|
|
138
|
-
self._stream_update(
|
|
139
|
-
{
|
|
140
|
-
"type": "stage_update",
|
|
141
|
-
"stage": stage,
|
|
142
|
-
"substage": substage,
|
|
143
|
-
"timestamp": time.time(),
|
|
144
|
-
},
|
|
145
|
-
)
|
|
137
|
+
pass
|
|
146
138
|
|
|
147
139
|
def update_hook_progress(self, progress: HookProgress) -> None:
|
|
148
|
-
|
|
149
|
-
self._stream_update(
|
|
150
|
-
{
|
|
151
|
-
"type": "hook_progress",
|
|
152
|
-
"hook_name": progress.hook_name,
|
|
153
|
-
"progress": progress.to_dict(),
|
|
154
|
-
"timestamp": time.time(),
|
|
155
|
-
},
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
def _stream_update(self, update_data: dict[str, t.Any]) -> None:
|
|
159
|
-
self.session.update_stage(
|
|
160
|
-
self.current_stage,
|
|
161
|
-
f"{self.current_substage}: {update_data.get('hook_name', 'processing')}",
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if hasattr(self.session, "web_job_id") and self.session.web_job_id:
|
|
165
|
-
self._update_websocket_progress(update_data)
|
|
166
|
-
|
|
167
|
-
def _update_websocket_progress(self, update_data: dict[str, t.Any]) -> None:
|
|
168
|
-
with suppress(Exception):
|
|
169
|
-
if hasattr(self.session, "progress_file") and self.session.progress_file:
|
|
170
|
-
import json
|
|
171
|
-
|
|
172
|
-
progress_data: dict[str, t.Any] = {}
|
|
173
|
-
if self.session.progress_file.exists():
|
|
174
|
-
with self.session.progress_file.open() as f:
|
|
175
|
-
progress_data = json.load(f)
|
|
176
|
-
|
|
177
|
-
progress_data.update(
|
|
178
|
-
{
|
|
179
|
-
"current_stage": self.current_stage,
|
|
180
|
-
"current_substage": self.current_substage,
|
|
181
|
-
"hook_progress": {
|
|
182
|
-
name: prog.to_dict()
|
|
183
|
-
for name, prog in self.hook_progress.items()
|
|
184
|
-
},
|
|
185
|
-
"last_update": update_data,
|
|
186
|
-
"updated_at": time.time(),
|
|
187
|
-
},
|
|
188
|
-
)
|
|
140
|
+
pass
|
|
189
141
|
|
|
190
|
-
|
|
191
|
-
|
|
142
|
+
def _stream_update(self, data: dict[str, t.Any]) -> None:
|
|
143
|
+
pass
|
|
192
144
|
|
|
193
145
|
|
|
194
146
|
class AdvancedWorkflowOrchestrator:
|
crackerjack/services/git.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import subprocess
|
|
1
|
+
import subprocess # nosec B404
|
|
2
2
|
import typing as t
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
@@ -156,7 +156,7 @@ class GitService:
|
|
|
156
156
|
|
|
157
157
|
def _retry_commit_after_restage(self, message: str) -> bool:
|
|
158
158
|
self.console.print(
|
|
159
|
-
"[yellow]🔄[/
|
|
159
|
+
"[yellow]🔄[/yellow] Pre-commit hooks modified files - attempting to re-stage and retry commit"
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
add_result = self._run_git_command(GIT_COMMANDS["add_updated"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crackerjack
|
|
3
|
-
Version: 0.38.
|
|
3
|
+
Version: 0.38.11
|
|
4
4
|
Summary: Crackerjack Python project management tool
|
|
5
5
|
Project-URL: documentation, https://github.com/lesleslie/crackerjack
|
|
6
6
|
Project-URL: homepage, https://github.com/lesleslie/crackerjack
|
|
@@ -48,7 +48,6 @@ Requires-Dist: refurb>=2.1
|
|
|
48
48
|
Requires-Dist: rich>=14.1
|
|
49
49
|
Requires-Dist: ruff>=0.12.9
|
|
50
50
|
Requires-Dist: scipy>=1.14.0
|
|
51
|
-
Requires-Dist: session-mgmt-mcp>=0.3.11
|
|
52
51
|
Requires-Dist: skylos>=2.1.2
|
|
53
52
|
Requires-Dist: structlog>=24.4
|
|
54
53
|
Requires-Dist: textual>=0.89
|
|
@@ -75,7 +74,7 @@ Description-Content-Type: text/markdown
|
|
|
75
74
|
[](https://github.com/astral-sh/uv)
|
|
76
75
|
[](https://github.com/pre-commit/pre-commit)
|
|
77
76
|
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
78
|
-

|
|
79
78
|
|
|
80
79
|
## 🎯 Purpose
|
|
81
80
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
crackerjack/CLAUDE.md,sha256=FnF1XgcaCB59HEZxglEl4qEBTMyHQcqKoL0YjnAq24s,41230
|
|
2
2
|
crackerjack/__init__.py,sha256=DajG9zHB8qBdgdiKMumrrssUbKeMXmtIQ3oOaSTb46Y,1426
|
|
3
|
-
crackerjack/__main__.py,sha256=
|
|
3
|
+
crackerjack/__main__.py,sha256=hOekfMkVBYQuw294OHa8fhpuQa-E8knJh3x4KY1WxG8,55552
|
|
4
4
|
crackerjack/api.py,sha256=PyCRaZHvKWdu62_2O4t_HcEfKNBdqyrfPdonS_PNn4c,21495
|
|
5
5
|
crackerjack/code_cleaner.py,sha256=M1zVaq31uW0nOkPneKR8kfR3892gyyVx0VhFgRaxsj4,44338
|
|
6
6
|
crackerjack/dynamic_config.py,sha256=4c8Fts9vyH8Tdon_47OFVT1iTBINSzSgB0WoeSvpzII,22418
|
|
7
7
|
crackerjack/errors.py,sha256=yYbZ92kn_y6acEWgQvEPvozAYs2HT65uLwAXrtXxGsE,10049
|
|
8
|
-
crackerjack/interactive.py,sha256=
|
|
8
|
+
crackerjack/interactive.py,sha256=t5FbxWeOErSl5kod4V8Gu5yF5yuWoZlwqlOdquOQ-vo,21943
|
|
9
9
|
crackerjack/adapters/__init__.py,sha256=k-8ajMDL9DS9hV2FYOu694nmNQg3HkudJRuNcXmx8N4,451
|
|
10
10
|
crackerjack/adapters/lsp_client.py,sha256=4kQ3T5JiWC7uc6kOjZuPdtUboseKSDjZpuKQpV74onc,10963
|
|
11
11
|
crackerjack/adapters/rust_tool_adapter.py,sha256=ui_qMt_WIwInRvRCeT7MnIdp8eln7Fvp4hakXQiVnjg,5999
|
|
@@ -86,9 +86,9 @@ crackerjack/managers/__init__.py,sha256=PFWccXx4hDQA76T02idAViOLVD-aPeVpgjdfSkh_
|
|
|
86
86
|
crackerjack/managers/async_hook_manager.py,sha256=c0HFR98sFwfk0uZ3NmAe_6OVZpBrq9I570V8A2DoIxw,5129
|
|
87
87
|
crackerjack/managers/hook_manager.py,sha256=_FT0ngwPwujqg0KZGpLz-pP07mwDmptJ5pVkiy5yS8k,7820
|
|
88
88
|
crackerjack/managers/publish_manager.py,sha256=E0jqHuscfn89pI2Ely0R6xyi3EGTvYFQeFTWyRmdjBM,22067
|
|
89
|
-
crackerjack/managers/test_command_builder.py,sha256=
|
|
89
|
+
crackerjack/managers/test_command_builder.py,sha256=9LWnXFOQ1nJfzbOTJNO6btIgDVc6uDHK7qiD2ol0FGI,4922
|
|
90
90
|
crackerjack/managers/test_executor.py,sha256=2837Ti4OaNsmLxnmELjbQ18hmfL0-Z2EW-W2UeFSDcE,13871
|
|
91
|
-
crackerjack/managers/test_manager.py,sha256=
|
|
91
|
+
crackerjack/managers/test_manager.py,sha256=fiJXSIHFi_knKsOI6f8-TOTJa1jgG1TzZL6T2KYlTdM,18382
|
|
92
92
|
crackerjack/managers/test_manager_backup.py,sha256=CR8D7WZ68ZrTADFqYJtVDUWnlznJXJNriPIdsp6ZB1E,37932
|
|
93
93
|
crackerjack/managers/test_progress.py,sha256=B1013ygUk2nAo37whDXNA7n-FYdsEO4qj17fuDm_fdg,3058
|
|
94
94
|
crackerjack/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -135,12 +135,12 @@ crackerjack/models/protocols.py,sha256=ZtyatubJxnmnnTKzXWCbl8CvREPt2pywSco5KYBJI
|
|
|
135
135
|
crackerjack/models/resource_protocols.py,sha256=oZiq0NEG2kWV0XQ871GIYRx_0v_oQulvEjTbidTbueM,7297
|
|
136
136
|
crackerjack/models/task.py,sha256=Du5vwXj-P_aSIZsFt1Ek4jCTkXz_6DXfnflUxgXkecY,4542
|
|
137
137
|
crackerjack/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
138
|
-
crackerjack/monitoring/ai_agent_watchdog.py,sha256=
|
|
138
|
+
crackerjack/monitoring/ai_agent_watchdog.py,sha256=_Usni6d8A-1CKAx2d5LUMXYldMiQB5cdZPd-NrCe2p8,15138
|
|
139
139
|
crackerjack/monitoring/metrics_collector.py,sha256=PTeOTXk07HWvPwTDK5j-Ef7LPrrNiwouz4_jw0LcP7M,15920
|
|
140
140
|
crackerjack/monitoring/regression_prevention.py,sha256=hV7jwKBXjS9gBVbqHTGEr9BxtKLHkXEg6zPDgTmOeWc,20986
|
|
141
141
|
crackerjack/monitoring/websocket_server.py,sha256=tmCYh5Qp58iicS_txj5OTdmt3I85M5bAGONeyrv0-EQ,24965
|
|
142
142
|
crackerjack/orchestration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
|
-
crackerjack/orchestration/advanced_orchestrator.py,sha256=
|
|
143
|
+
crackerjack/orchestration/advanced_orchestrator.py,sha256=05AZhCkPIiKSVVpIx7gb2_wVvGAKHv7gStu146ivcmY,32066
|
|
144
144
|
crackerjack/orchestration/coverage_improvement.py,sha256=XUTUcgub0nP3vvQ8Ud5SmCYKgulEe8SAY65jbcFQzdw,6869
|
|
145
145
|
crackerjack/orchestration/execution_strategies.py,sha256=M3hplgaCULMwE9TiqP9rWvivnrqdSR6DJTzY_rvueww,12482
|
|
146
146
|
crackerjack/orchestration/test_progress_streamer.py,sha256=Yu6uHuhoCvX6SZP0QNG3Yt8Q4s2tufEHr40o16QU98c,22541
|
|
@@ -175,7 +175,7 @@ crackerjack/services/enterprise_optimizer.py,sha256=q6srIGxe18N9zH-MNhZ9R34sWnr_
|
|
|
175
175
|
crackerjack/services/error_pattern_analyzer.py,sha256=YKFQi_nXx5dq-Wvi6AngKEaA-Bv5ggTQ2B2ML0AtF0c,24415
|
|
176
176
|
crackerjack/services/file_hasher.py,sha256=eReytwwK-_-B8JBnpwytDC52cKKgg4qpaxaZKcQjD-0,5211
|
|
177
177
|
crackerjack/services/filesystem.py,sha256=nmL3mYqylS_BSQpwFbC7EMHoA44K5qUxa9CPg1QFZvc,17480
|
|
178
|
-
crackerjack/services/git.py,sha256=
|
|
178
|
+
crackerjack/services/git.py,sha256=E4z-hdA0UR-y5FY2A8d3fugt5Q41lonLAIt_op2Zde0,12727
|
|
179
179
|
crackerjack/services/health_metrics.py,sha256=nDuKEC2a5csOhMpy6zXJkls1Y4Vfrr62-4cFcWCr8ow,21536
|
|
180
180
|
crackerjack/services/heatmap_generator.py,sha256=zz5V-zXPfoCGNXoj7iuyOeFuDRRUhFpxuENUnd0X75g,26200
|
|
181
181
|
crackerjack/services/initialization.py,sha256=_ZjGpIG5eGHzrVXCxlhlouhY-E-4OITEln1GDTswZ6s,26049
|
|
@@ -225,8 +225,8 @@ crackerjack/tools/validate_input_validator_patterns.py,sha256=NN7smYlXWrHLQXTb-8
|
|
|
225
225
|
crackerjack/tools/validate_regex_patterns.py,sha256=J7GG9EP1fASpRIsG8qRPeiCSkdCwmk0sdo29GgoJ6w8,5863
|
|
226
226
|
crackerjack/ui/__init__.py,sha256=eMb1OeTU-dSLICAACn0YdYB4Amdr8wHckjKfn0wOIZE,37
|
|
227
227
|
crackerjack/ui/server_panels.py,sha256=F5IH6SNN06BaZQMsFx_D-OA286aojmaFPJ5kvvSRv_c,4232
|
|
228
|
-
crackerjack-0.38.
|
|
229
|
-
crackerjack-0.38.
|
|
230
|
-
crackerjack-0.38.
|
|
231
|
-
crackerjack-0.38.
|
|
232
|
-
crackerjack-0.38.
|
|
228
|
+
crackerjack-0.38.11.dist-info/METADATA,sha256=zlNpnIwM-e0waeO6GFqQpCowVWE4inrrE05ZSCdDk64,38083
|
|
229
|
+
crackerjack-0.38.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
230
|
+
crackerjack-0.38.11.dist-info/entry_points.txt,sha256=AJKNft0WXm9xoGUJ3Trl-iXHOWxRAYbagQiza3AILr4,57
|
|
231
|
+
crackerjack-0.38.11.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
|
|
232
|
+
crackerjack-0.38.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|