crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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 (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,171 @@
1
+ from contextlib import suppress
2
+ from pathlib import Path
3
+
4
+ from rich.console import Console
5
+
6
+ from .regex_patterns import SAFE_PATTERNS
7
+
8
+
9
+ class CoverageBadgeService:
10
+ """Service for managing coverage badges in README.md files."""
11
+
12
+ def __init__(self, console: Console, project_root: Path) -> None:
13
+ self.console = console
14
+ self.project_root = project_root
15
+ self.readme_path = project_root / "README.md"
16
+
17
+ def update_readme_coverage_badge(self, coverage_percent: float) -> bool:
18
+ """Update or insert coverage badge in README.md with current coverage percentage."""
19
+ if not self.readme_path.exists():
20
+ self.console.print(
21
+ "[yellow]⚠️[/yellow] README.md not found, skipping badge update"
22
+ )
23
+ return False
24
+
25
+ try:
26
+ readme_content = self.readme_path.read_text(encoding="utf-8")
27
+ badge_url = self._generate_badge_url(coverage_percent)
28
+
29
+ if self._has_coverage_badge(readme_content):
30
+ updated_content = self._update_existing_badge(readme_content, badge_url)
31
+ action = "updated"
32
+ else:
33
+ updated_content = self._insert_new_badge(readme_content, badge_url)
34
+ action = "added"
35
+
36
+ if updated_content != readme_content:
37
+ self.readme_path.write_text(updated_content, encoding="utf-8")
38
+ self.console.print(
39
+ f"[green]📊[/green] Coverage badge {action}: {coverage_percent:.1f}%"
40
+ )
41
+ return True
42
+ else:
43
+ return False
44
+
45
+ except Exception as e:
46
+ self.console.print(f"[red]❌[/red] Failed to update coverage badge: {e}")
47
+ return False
48
+
49
+ def _generate_badge_url(self, coverage_percent: float) -> str:
50
+ """Generate shields.io badge URL with appropriate color coding."""
51
+ color = self._get_badge_color(coverage_percent)
52
+ # URL encode the % symbol as %25
53
+ encoded_percent = f"{coverage_percent:.1f}%25"
54
+ return f"https://img.shields.io/badge/coverage-{encoded_percent}-{color}"
55
+
56
+ def _get_badge_color(self, coverage_percent: float) -> str:
57
+ """Determine badge color based on coverage percentage."""
58
+ if coverage_percent < 50:
59
+ return "red"
60
+ elif coverage_percent < 80:
61
+ return "yellow"
62
+ return "brightgreen"
63
+
64
+ def _has_coverage_badge(self, content: str) -> bool:
65
+ """Check if README already contains a coverage badge."""
66
+ # Use safe pattern for badge detection
67
+ return SAFE_PATTERNS["detect_coverage_badge"].search(content) is not None
68
+
69
+ def _update_existing_badge(self, content: str, new_badge_url: str) -> str:
70
+ """Replace existing coverage badge with new one."""
71
+ # Try different safe patterns for badge replacement
72
+ patterns_to_try = [
73
+ "update_coverage_badge_url",
74
+ "update_coverage_badge_any",
75
+ "update_shields_coverage_url",
76
+ ]
77
+
78
+ for pattern_name in patterns_to_try:
79
+ pattern_obj = SAFE_PATTERNS[pattern_name]
80
+ # Use the pattern and manually replace NEW_BADGE_URL with actual URL
81
+ temp_content = pattern_obj.apply(content)
82
+ if temp_content != content:
83
+ # Replace placeholder with actual URL
84
+ new_content = temp_content.replace("NEW_BADGE_URL", new_badge_url)
85
+ return new_content
86
+
87
+ return content
88
+
89
+ def _insert_new_badge(self, content: str, badge_url: str) -> str:
90
+ """Insert new coverage badge in the appropriate location."""
91
+ lines = content.split("\n")
92
+
93
+ # Find the badge section (after title, before first heading)
94
+ insert_index = self._find_badge_insertion_point(lines)
95
+
96
+ if insert_index is not None:
97
+ coverage_badge = f"![Coverage]({badge_url})"
98
+ lines.insert(insert_index, coverage_badge)
99
+ return "\n".join(lines)
100
+ # Fallback: add after title
101
+ return self._insert_after_title(content, badge_url)
102
+
103
+ def _find_badge_insertion_point(self, lines: list[str]) -> int | None:
104
+ """Find the best location to insert the coverage badge."""
105
+ # Look for existing badge lines
106
+ badge_lines = [
107
+ i for i, line in enumerate(lines) if line.strip().startswith(("[![", "!["))
108
+ ]
109
+
110
+ if badge_lines:
111
+ # Insert after the last existing badge
112
+ return badge_lines[-1] + 1
113
+
114
+ # Look for first non-empty line after title
115
+ title_found = False
116
+ for i, line in enumerate(lines):
117
+ if line.startswith("#") and not title_found:
118
+ title_found = True
119
+ continue
120
+ elif title_found and line.strip() == "":
121
+ continue
122
+ elif title_found and line.strip():
123
+ return i
124
+
125
+ return None
126
+
127
+ def _insert_after_title(self, content: str, badge_url: str) -> str:
128
+ """Fallback method to insert badge after the title."""
129
+ lines = content.split("\n")
130
+
131
+ # Find title line
132
+ for i, line in enumerate(lines):
133
+ if line.startswith("#"):
134
+ # Insert after title with blank line
135
+ coverage_badge = f"![Coverage]({badge_url})"
136
+ if i + 1 < len(lines) and lines[i + 1].strip() == "":
137
+ lines.insert(i + 2, coverage_badge)
138
+ else:
139
+ lines.insert(i + 1, "")
140
+ lines.insert(i + 2, coverage_badge)
141
+ break
142
+
143
+ return "\n".join(lines)
144
+
145
+ def should_update_badge(self, coverage_percent: float) -> bool:
146
+ """Check if badge should be updated based on coverage change."""
147
+ if not self.readme_path.exists():
148
+ return False
149
+
150
+ try:
151
+ content = self.readme_path.read_text(encoding="utf-8")
152
+ current_coverage = self._extract_current_coverage(content)
153
+
154
+ if current_coverage is None:
155
+ return True # No badge exists, should add one
156
+
157
+ # Only update if coverage changed by at least 0.1%
158
+ return abs(coverage_percent - current_coverage) >= 0.1
159
+
160
+ except Exception:
161
+ return True # On error, attempt update
162
+
163
+ def _extract_current_coverage(self, content: str) -> float | None:
164
+ """Extract current coverage percentage from existing badge."""
165
+ match = SAFE_PATTERNS["extract_coverage_percentage"].search(content)
166
+
167
+ if match:
168
+ with suppress(ValueError):
169
+ return float(match.group(1))
170
+
171
+ return None
@@ -9,7 +9,7 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
9
9
 
10
10
  class CoverageRatchetService:
11
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
12
+
13
13
  TOLERANCE_MARGIN = 2.0
14
14
 
15
15
  def __init__(self, pkg_path: Path, console: Console) -> None:
@@ -22,7 +22,7 @@ class CoverageRatchetService:
22
22
  if self.ratchet_file.exists():
23
23
  return
24
24
 
25
- ratchet_data = {
25
+ ratchet_data: dict[str, t.Any] = {
26
26
  "baseline": initial_coverage,
27
27
  "current_minimum": initial_coverage,
28
28
  "target": 100.0,
@@ -47,22 +47,39 @@ class CoverageRatchetService:
47
47
  def get_ratchet_data(self) -> dict[str, t.Any]:
48
48
  if not self.ratchet_file.exists():
49
49
  return {}
50
- return json.loads(self.ratchet_file.read_text())
50
+ return t.cast(dict[str, t.Any], json.loads(self.ratchet_file.read_text()))
51
+
52
+ def get_status_report(self) -> dict[str, t.Any]:
53
+ """Get status report for coverage ratchet service."""
54
+ return self.get_ratchet_data()
51
55
 
52
56
  def get_baseline(self) -> float:
53
- return self.get_ratchet_data().get("baseline", 0.0)
57
+ data = self.get_ratchet_data()
58
+ baseline = data.get("baseline")
59
+ return float(baseline) if baseline is not None else 0.0
54
60
 
55
- def update_coverage(self, new_coverage: float) -> dict[str, t.Any]:
56
- """Update coverage with 2% tolerance margin to prevent test flakiness.
61
+ def get_baseline_coverage(self) -> float:
62
+ return self.get_baseline()
63
+
64
+ def update_baseline_coverage(self, new_coverage: float) -> bool:
65
+ result: bool = self.update_coverage(new_coverage).get("success", False)
66
+ return result
57
67
 
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)
68
+ def is_coverage_regression(self, current_coverage: float) -> bool:
69
+ baseline = self.get_baseline()
70
+ return current_coverage < (baseline - self.TOLERANCE_MARGIN)
62
71
 
63
- This prevents failures due to small coverage fluctuations while
64
- still catching significant regressions.
65
- """
72
+ def calculate_coverage_gap(self) -> float:
73
+ data = self.get_ratchet_data()
74
+ baseline = data.get("baseline")
75
+ baseline = float(baseline) if baseline is not None else 0.0
76
+ next_milestone = data.get("next_milestone")
77
+ next_milestone = float(next_milestone) if next_milestone is not None else None
78
+ if next_milestone:
79
+ return next_milestone - baseline
80
+ return 100.0 - baseline
81
+
82
+ def update_coverage(self, new_coverage: float) -> dict[str, t.Any]:
66
83
  if not self.ratchet_file.exists():
67
84
  self.initialize_baseline(new_coverage)
68
85
  return {
@@ -77,7 +94,6 @@ class CoverageRatchetService:
77
94
  data = self.get_ratchet_data()
78
95
  current_baseline = data["baseline"]
79
96
 
80
- # Check if coverage is below the tolerance margin (baseline - 2%)
81
97
  tolerance_threshold = current_baseline - self.TOLERANCE_MARGIN
82
98
  if new_coverage < tolerance_threshold:
83
99
  return {
@@ -104,11 +120,12 @@ class CoverageRatchetService:
104
120
  "next_milestone": self._get_next_milestone(new_coverage),
105
121
  "points_to_next": (next_milestone - new_coverage)
106
122
  if (next_milestone := self._get_next_milestone(new_coverage))
123
+ is not None
107
124
  else 0,
108
125
  "allowed": True,
109
126
  "baseline_updated": True,
110
127
  }
111
- # Coverage is within tolerance margin - treat as maintained
128
+
112
129
  return {
113
130
  "status": "maintained",
114
131
  "message": f"Coverage maintained at {new_coverage: .2f}% (within {self.TOLERANCE_MARGIN}% tolerance margin)",
@@ -119,7 +136,7 @@ class CoverageRatchetService:
119
136
  def _check_milestones(
120
137
  self, old_coverage: float, new_coverage: float, data: dict[str, t.Any]
121
138
  ) -> list[float]:
122
- achieved_milestones = set(data.get("milestones_achieved", []))
139
+ achieved_milestones = set[t.Any](data.get("milestones_achieved", []))
123
140
  return [
124
141
  milestone
125
142
  for milestone in self.MILESTONES
@@ -211,7 +228,14 @@ class CoverageRatchetService:
211
228
 
212
229
  return result
213
230
 
214
- def get_status_report(self) -> dict[str, t.Any]:
231
+ def get_coverage_improvement_needed(self) -> float:
232
+ """Get percentage improvement needed to reach next milestone."""
233
+ current = self.get_baseline_coverage()
234
+ for milestone in self.MILESTONES:
235
+ if current < milestone:
236
+ needed = milestone - current
237
+ return max(0.0, needed)
238
+ return 0.0
215
239
  data = self.get_ratchet_data()
216
240
  if not data:
217
241
  return {"status": "not_initialized"}
@@ -24,13 +24,13 @@ class AIAgentDebugger:
24
24
  self.logger = get_logger("crackerjack.ai_agent.debug")
25
25
  self.session_id = f"debug_{int(time.time())}"
26
26
 
27
+ self.debug_log_path: Path | None = None
28
+
27
29
  if self.enabled:
28
30
  log_manager = get_log_manager()
29
31
  self.debug_log_path = log_manager.create_debug_log_file(
30
32
  f"ai-agent -{self.session_id}",
31
33
  )
32
- else:
33
- self.debug_log_path = None
34
34
 
35
35
  self.mcp_operations: list[dict[str, Any]] = []
36
36
  self.agent_activities: list[dict[str, Any]] = []
@@ -400,7 +400,7 @@ class AIAgentDebugger:
400
400
  self.console.print(table)
401
401
 
402
402
  def _print_agent_activity_breakdown(self, border_style: str = "red") -> None:
403
- agent_stats = {}
403
+ agent_stats: dict[str, dict[str, t.Any]] = {}
404
404
  for activity in self.agent_activities:
405
405
  agent = activity["agent"]
406
406
  if agent not in agent_stats: