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.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +94 -103
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/crackerjack.py +0 -3805
  151. crackerjack/pyproject.toml +0 -286
  152. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  153. crackerjack-0.30.3.dist-info/RECORD +0 -16
  154. {crackerjack-0.30.3.dist-info โ†’ crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  155. {crackerjack-0.30.3.dist-info โ†’ crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,99 @@
1
+ """Configuration integrity checking service.
2
+
3
+ This module handles detection of configuration file changes and validates
4
+ required configuration sections. Split from tool_version_service.py.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ from rich.console import Console
10
+
11
+
12
+ class ConfigIntegrityService:
13
+ """Service for checking configuration file integrity and required sections."""
14
+
15
+ def __init__(self, console: Console, project_path: Path) -> None:
16
+ self.console = console
17
+ self.project_path = project_path
18
+ self.cache_dir = Path.home() / ".cache" / "crackerjack"
19
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
20
+
21
+ def check_config_integrity(self) -> bool:
22
+ """Check for configuration file drift and missing required sections."""
23
+ config_files = [
24
+ ".pre-commit-config.yaml",
25
+ "pyproject.toml",
26
+ ]
27
+
28
+ drift_detected = False
29
+
30
+ for file_name in config_files:
31
+ file_path = self.project_path / file_name
32
+ if file_path.exists() and self._check_file_drift(file_path):
33
+ drift_detected = True
34
+
35
+ if not self._has_required_config_sections():
36
+ self.console.print(
37
+ "[yellow]โš ๏ธ Configuration missing required sections[/yellow]",
38
+ )
39
+ drift_detected = True
40
+
41
+ return drift_detected
42
+
43
+ def _check_file_drift(self, file_path: Path) -> bool:
44
+ """Check if a configuration file has been modified since last check."""
45
+ cache_file = self.cache_dir / f"{file_path.name}.hash"
46
+
47
+ try:
48
+ current_content = file_path.read_text()
49
+ current_hash = hash(current_content)
50
+
51
+ if cache_file.exists():
52
+ from contextlib import suppress
53
+
54
+ with suppress(OSError, ValueError):
55
+ cached_hash = int(cache_file.read_text().strip())
56
+ if current_hash != cached_hash:
57
+ self.console.print(
58
+ f"[yellow]โš ๏ธ {file_path.name} has been modified manually[/yellow]",
59
+ )
60
+ return True
61
+
62
+ cache_file.write_text(str(current_hash))
63
+ return False
64
+
65
+ except OSError as e:
66
+ self.console.print(f"[red]โŒ Error checking {file_path.name}: {e}[/red]")
67
+ return False
68
+
69
+ def _has_required_config_sections(self) -> bool:
70
+ """Check if pyproject.toml has all required configuration sections."""
71
+ pyproject = self.project_path / "pyproject.toml"
72
+ if not pyproject.exists():
73
+ return False
74
+
75
+ try:
76
+ import tomllib
77
+
78
+ with pyproject.open("rb") as f:
79
+ config = tomllib.load(f)
80
+
81
+ required = ["tool.ruff", "tool.pyright", "tool.pytest.ini_options"]
82
+
83
+ for section in required:
84
+ keys = section.split(".")
85
+ current = config
86
+
87
+ for key in keys:
88
+ if key not in current:
89
+ self.console.print(
90
+ f"[yellow]โš ๏ธ Missing required config section: {section}[/yellow]",
91
+ )
92
+ return False
93
+ current = current[key]
94
+
95
+ return True
96
+
97
+ except Exception as e:
98
+ self.console.print(f"[red]โŒ Error parsing pyproject.toml: {e}[/red]")
99
+ return False
@@ -0,0 +1,516 @@
1
+ import json
2
+ import subprocess
3
+ import time
4
+ import tomllib
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ from crackerjack.models.protocols import FileSystemInterface
11
+
12
+
13
+ @dataclass
14
+ class AIRecommendation:
15
+ category: str
16
+ priority: str
17
+ title: str
18
+ description: str
19
+ action_command: str | None = None
20
+ reasoning: str = ""
21
+ confidence: float = 0.0
22
+
23
+
24
+ @dataclass
25
+ class ProjectContext:
26
+ has_tests: bool = False
27
+ test_coverage: float = 0.0
28
+ lint_errors_count: int = 0
29
+ security_issues: list[str] = field(default_factory=list)
30
+ outdated_dependencies: list[str] = field(default_factory=list)
31
+ last_commit_days: int = 0
32
+ project_size: str = "small"
33
+ main_languages: list[str] = field(default_factory=list)
34
+ has_ci_cd: bool = False
35
+ has_documentation: bool = False
36
+ project_type: str = "library"
37
+
38
+
39
+ class ContextualAIAssistant:
40
+ def __init__(
41
+ self,
42
+ filesystem: FileSystemInterface,
43
+ console: Console | None = None,
44
+ ) -> None:
45
+ self.filesystem = filesystem
46
+ self.console = console or Console()
47
+ self.project_root = Path.cwd()
48
+ self.pyproject_path = self.project_root / "pyproject.toml"
49
+ self.cache_file = self.project_root / ".crackerjack" / "ai_context.json"
50
+
51
+ def get_contextual_recommendations(
52
+ self,
53
+ max_recommendations: int = 5,
54
+ ) -> list[AIRecommendation]:
55
+ context = self._analyze_project_context()
56
+ recommendations = self._generate_recommendations(context)
57
+
58
+ recommendations.sort(
59
+ key=lambda r: (
60
+ {"high": 3, "medium": 2, "low": 1}[r.priority],
61
+ r.confidence,
62
+ ),
63
+ reverse=True,
64
+ )
65
+
66
+ return recommendations[:max_recommendations]
67
+
68
+ def _analyze_project_context(self) -> ProjectContext:
69
+ context = ProjectContext()
70
+
71
+ context.has_tests = self._has_test_directory()
72
+ context.test_coverage = self._get_current_coverage()
73
+ context.lint_errors_count = self._count_current_lint_errors()
74
+ context.project_size = self._determine_project_size()
75
+ context.main_languages = self._detect_main_languages()
76
+ context.has_ci_cd = self._has_ci_cd_config()
77
+ context.has_documentation = self._has_documentation()
78
+ context.project_type = self._determine_project_type()
79
+ context.last_commit_days = self._days_since_last_commit()
80
+
81
+ context.security_issues = self._detect_security_issues()
82
+ context.outdated_dependencies = self._get_outdated_dependencies()
83
+
84
+ return context
85
+
86
+ def _generate_recommendations(
87
+ self,
88
+ context: ProjectContext,
89
+ ) -> list[AIRecommendation]:
90
+ recommendations: list[AIRecommendation] = []
91
+
92
+ recommendations.extend(self._get_testing_recommendations(context))
93
+ recommendations.extend(self._get_code_quality_recommendations(context))
94
+ recommendations.extend(self._get_security_recommendations(context))
95
+ recommendations.extend(self._get_maintenance_recommendations(context))
96
+ recommendations.extend(self._get_workflow_recommendations(context))
97
+ recommendations.extend(self._get_documentation_recommendations(context))
98
+
99
+ return recommendations
100
+
101
+ def _get_testing_recommendations(
102
+ self,
103
+ context: ProjectContext,
104
+ ) -> list[AIRecommendation]:
105
+ recommendations = []
106
+
107
+ if not context.has_tests:
108
+ recommendations.append(
109
+ AIRecommendation(
110
+ category="testing",
111
+ priority="high",
112
+ title="Add Test Suite",
113
+ description="No test directory found. Adding tests improves code reliability and enables CI/CD.",
114
+ action_command="python -m crackerjack -t",
115
+ reasoning="Projects without tests have 40% more bugs in production",
116
+ confidence=0.9,
117
+ ),
118
+ )
119
+ elif context.test_coverage < 75:
120
+ # Calculate next milestone
121
+ milestones = [15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100]
122
+ next_milestone = next(
123
+ (m for m in milestones if m > context.test_coverage), 100
124
+ )
125
+
126
+ recommendations.append(
127
+ AIRecommendation(
128
+ category="testing",
129
+ priority="medium",
130
+ title="Progress Toward 100% Coverage",
131
+ description=f"Current coverage: {context.test_coverage:.1f}%. Next milestone: {next_milestone}% on the journey to 100%.",
132
+ action_command="python -m crackerjack -t",
133
+ reasoning="Coverage ratchet system prevents regression and targets 100% coverage incrementally",
134
+ confidence=0.85,
135
+ ),
136
+ )
137
+
138
+ return recommendations
139
+
140
+ def _get_code_quality_recommendations(
141
+ self,
142
+ context: ProjectContext,
143
+ ) -> list[AIRecommendation]:
144
+ recommendations = []
145
+
146
+ if context.lint_errors_count > 20:
147
+ recommendations.append(
148
+ AIRecommendation(
149
+ category="code_quality",
150
+ priority="high",
151
+ title="Fix Lint Errors",
152
+ description=f"Found {context.lint_errors_count} lint errors that should be addressed.",
153
+ action_command="python -m crackerjack --ai-agent",
154
+ reasoning="High lint error count indicates technical debt and potential bugs",
155
+ confidence=0.95,
156
+ ),
157
+ )
158
+ elif context.lint_errors_count > 5:
159
+ recommendations.append(
160
+ AIRecommendation(
161
+ category="code_quality",
162
+ priority="medium",
163
+ title="Clean Up Code Style",
164
+ description=f"Found {context.lint_errors_count} minor lint issues to resolve.",
165
+ action_command="python -m crackerjack",
166
+ reasoning="Clean code is easier to maintain and has fewer bugs",
167
+ confidence=0.8,
168
+ ),
169
+ )
170
+
171
+ return recommendations
172
+
173
+ def _get_security_recommendations(
174
+ self,
175
+ context: ProjectContext,
176
+ ) -> list[AIRecommendation]:
177
+ recommendations = []
178
+
179
+ if context.security_issues:
180
+ recommendations.append(
181
+ AIRecommendation(
182
+ category="security",
183
+ priority="high",
184
+ title="Address Security Vulnerabilities",
185
+ description=f"Found {len(context.security_issues)} security issues in dependencies.",
186
+ action_command="python -m crackerjack --check-dependencies",
187
+ reasoning="Security vulnerabilities can expose your application to attacks",
188
+ confidence=0.95,
189
+ ),
190
+ )
191
+
192
+ return recommendations
193
+
194
+ def _get_maintenance_recommendations(
195
+ self,
196
+ context: ProjectContext,
197
+ ) -> list[AIRecommendation]:
198
+ recommendations = []
199
+
200
+ if len(context.outdated_dependencies) > 10:
201
+ recommendations.append(
202
+ AIRecommendation(
203
+ category="maintenance",
204
+ priority="medium",
205
+ title="Update Dependencies",
206
+ description=f"Found {len(context.outdated_dependencies)} outdated dependencies.",
207
+ action_command="python -m crackerjack --check-dependencies",
208
+ reasoning="Outdated dependencies may have security vulnerabilities or performance issues",
209
+ confidence=0.75,
210
+ ),
211
+ )
212
+
213
+ return recommendations
214
+
215
+ def _get_workflow_recommendations(
216
+ self,
217
+ context: ProjectContext,
218
+ ) -> list[AIRecommendation]:
219
+ recommendations = []
220
+
221
+ if not context.has_ci_cd and context.project_size != "small":
222
+ recommendations.append(
223
+ AIRecommendation(
224
+ category="workflow",
225
+ priority="medium",
226
+ title="Set Up CI / CD Pipeline",
227
+ description="No CI/CD configuration found. Automated testing and deployment improve reliability.",
228
+ reasoning="CI/CD prevents 60% of deployment issues and improves team productivity",
229
+ confidence=0.8,
230
+ ),
231
+ )
232
+
233
+ return recommendations
234
+
235
+ def _get_documentation_recommendations(
236
+ self,
237
+ context: ProjectContext,
238
+ ) -> list[AIRecommendation]:
239
+ recommendations = []
240
+
241
+ if not context.has_documentation and context.project_type in ("library", "api"):
242
+ recommendations.append(
243
+ AIRecommendation(
244
+ category="documentation",
245
+ priority="medium",
246
+ title="Add Documentation",
247
+ description="No documentation found. Good documentation improves adoption and maintenance.",
248
+ reasoning="Well-documented projects get 3x more contributors and have better longevity",
249
+ confidence=0.7,
250
+ ),
251
+ )
252
+
253
+ return recommendations
254
+
255
+ def _has_test_directory(self) -> bool:
256
+ test_dirs = ["tests", "test", "testing"]
257
+ return any((self.project_root / dirname).exists() for dirname in test_dirs)
258
+
259
+ def _get_current_coverage(self) -> float:
260
+ from contextlib import suppress
261
+
262
+ with suppress(Exception):
263
+ coverage_file = self.project_root / ".coverage"
264
+ if coverage_file.exists():
265
+ result = subprocess.run(
266
+ ["uv", "run", "coverage", "report", "--format=json"],
267
+ check=False,
268
+ capture_output=True,
269
+ text=True,
270
+ timeout=10,
271
+ cwd=self.project_root,
272
+ )
273
+
274
+ if result.returncode == 0 and result.stdout:
275
+ data = json.loads(result.stdout)
276
+ return float(data.get("totals", {}).get("percent_covered", 0))
277
+ return 0.0
278
+
279
+ def _count_current_lint_errors(self) -> int:
280
+ from contextlib import suppress
281
+
282
+ with suppress(Exception):
283
+ result = subprocess.run(
284
+ ["uv", "run", "ruff", "check", ".", "--output-format=json"],
285
+ check=False,
286
+ capture_output=True,
287
+ text=True,
288
+ timeout=30,
289
+ cwd=self.project_root,
290
+ )
291
+
292
+ if result.returncode != 0 and result.stdout:
293
+ with suppress(json.JSONDecodeError):
294
+ lint_data = json.loads(result.stdout)
295
+ return len(lint_data) if isinstance(lint_data, list) else 0
296
+ return len(result.stdout.splitlines())
297
+ return 0
298
+
299
+ def _determine_project_size(self) -> str:
300
+ try:
301
+ python_files = list(self.project_root.rglob("*.py"))
302
+ if len(python_files) < 10:
303
+ return "small"
304
+ if len(python_files) < 50:
305
+ return "medium"
306
+ return "large"
307
+ except Exception:
308
+ return "small"
309
+
310
+ def _detect_main_languages(self) -> list[str]:
311
+ languages = []
312
+
313
+ if (self.project_root / "pyproject.toml").exists():
314
+ languages.append("python")
315
+
316
+ if (self.project_root / "package.json").exists():
317
+ languages.append("javascript")
318
+ if any(self.project_root.glob("*.ts")):
319
+ languages.append("typescript")
320
+
321
+ if (self.project_root / "Cargo.toml").exists():
322
+ languages.append("rust")
323
+
324
+ if (self.project_root / "go.mod").exists():
325
+ languages.append("go")
326
+
327
+ return languages or ["python"]
328
+
329
+ def _has_ci_cd_config(self) -> bool:
330
+ ci_files = [
331
+ ".github/workflows",
332
+ ".gitlab-ci.yml",
333
+ "azure-pipelines.yml",
334
+ "Jenkinsfile",
335
+ ".travis.yml",
336
+ ]
337
+ return any((self.project_root / path).exists() for path in ci_files)
338
+
339
+ def _has_documentation(self) -> bool:
340
+ doc_indicators = [
341
+ "README.md",
342
+ "README.rst",
343
+ "docs",
344
+ "documentation",
345
+ "doc",
346
+ ]
347
+ return any((self.project_root / path).exists() for path in doc_indicators)
348
+
349
+ def _determine_project_type(self) -> str:
350
+ try:
351
+ if self.pyproject_path.exists():
352
+ with self.pyproject_path.open("rb") as f:
353
+ data = tomllib.load(f)
354
+
355
+ project_data = data.get("project", {})
356
+ scripts = project_data.get("scripts", {})
357
+
358
+ if scripts or "console_scripts" in project_data.get("entry-points", {}):
359
+ return "cli"
360
+
361
+ dependencies = project_data.get("dependencies", [])
362
+ web_frameworks = ["fastapi", "flask", "django", "starlette"]
363
+ if any(fw in str(dependencies).lower() for fw in web_frameworks):
364
+ return "api"
365
+
366
+ if (self.project_root / "__main__.py").exists():
367
+ return "application"
368
+
369
+ return "library"
370
+ except Exception:
371
+ return "library"
372
+
373
+ def _days_since_last_commit(self) -> int:
374
+ from contextlib import suppress
375
+
376
+ with suppress(Exception):
377
+ result = subprocess.run(
378
+ ["git", "log", "-1", "--format=%ct"],
379
+ check=False,
380
+ capture_output=True,
381
+ text=True,
382
+ timeout=5,
383
+ cwd=self.project_root,
384
+ )
385
+
386
+ if result.returncode == 0 and result.stdout.strip():
387
+ last_commit_timestamp = int(result.stdout.strip())
388
+ current_timestamp = time.time()
389
+ return int((current_timestamp - last_commit_timestamp) / 86400)
390
+ return 0
391
+
392
+ def _detect_security_issues(self) -> list[str]:
393
+ issues = []
394
+ from contextlib import suppress
395
+
396
+ with suppress(Exception):
397
+ result = subprocess.run(
398
+ ["uv", "run", "bandit", "-r", ".", "-f", "json"],
399
+ check=False,
400
+ capture_output=True,
401
+ text=True,
402
+ timeout=30,
403
+ cwd=self.project_root,
404
+ )
405
+
406
+ if result.returncode != 0 and result.stdout:
407
+ with suppress(json.JSONDecodeError):
408
+ bandit_data = json.loads(result.stdout)
409
+ results = bandit_data.get("results", [])
410
+ for issue in results[:5]:
411
+ test_id = issue.get("test_id", "unknown")
412
+ issues.append(f"Security issue: {test_id}")
413
+
414
+ return issues
415
+
416
+ def _get_outdated_dependencies(self) -> list[str]:
417
+ outdated = []
418
+
419
+ from contextlib import suppress
420
+
421
+ with suppress(Exception):
422
+ if self.pyproject_path.exists():
423
+ with self.pyproject_path.open("rb") as f:
424
+ data = tomllib.load(f)
425
+
426
+ dependencies = data.get("project", {}).get("dependencies", [])
427
+
428
+ old_patterns = [" == 1.", " == 0.", " >= 1.", "~ = 1."]
429
+ for dep in dependencies:
430
+ if any(pattern in dep for pattern in old_patterns):
431
+ pkg_name = (
432
+ dep.split(" == ")[0]
433
+ .split(" >= ")[0]
434
+ .split("~ = ")[0]
435
+ .strip()
436
+ )
437
+ outdated.append(pkg_name)
438
+
439
+ return outdated
440
+
441
+ def display_recommendations(self, recommendations: list[AIRecommendation]) -> None:
442
+ if not recommendations:
443
+ self.console.print(
444
+ "[green]โœจ Great job! No immediate recommendations.[/green]",
445
+ )
446
+ return
447
+
448
+ self.console.print("\n[bold cyan]๐Ÿค– AI Assistant Recommendations[/bold cyan]")
449
+ self.console.print("[dim]Based on your current project context[/dim]\n")
450
+
451
+ for i, rec in enumerate(recommendations, 1):
452
+ priority_color = {"high": "red", "medium": "yellow", "low": "blue"}.get(
453
+ rec.priority,
454
+ "white",
455
+ )
456
+
457
+ category_emoji = {
458
+ "testing": "๐Ÿงช",
459
+ "code_quality": "๐Ÿ”ง",
460
+ "security": "๐Ÿ”’",
461
+ "maintenance": "๐Ÿ“ฆ",
462
+ "workflow": "โš™๏ธ",
463
+ "documentation": "๐Ÿ“š",
464
+ "architecture": "๐Ÿ—๏ธ",
465
+ "performance": "โšก",
466
+ }.get(rec.category, "๐Ÿ’ก")
467
+
468
+ self.console.print(
469
+ f"[bold]{i}. {category_emoji} {rec.title}[/bold] [{priority_color}]({rec.priority})[/{priority_color}]",
470
+ )
471
+ self.console.print(f" {rec.description}")
472
+
473
+ if rec.action_command:
474
+ self.console.print(
475
+ f" [dim]Run:[/dim] [cyan]{rec.action_command}[/cyan]",
476
+ )
477
+
478
+ if rec.reasoning:
479
+ self.console.print(f" [dim italic]๐Ÿ’ญ {rec.reasoning}[/dim italic]")
480
+
481
+ confidence_bar = "โ–ˆ" * int(rec.confidence * 10) + "โ–’" * (
482
+ 10 - int(rec.confidence * 10)
483
+ )
484
+ self.console.print(
485
+ f" [dim]Confidence: [{confidence_bar}] {rec.confidence:.1%}[/dim]",
486
+ )
487
+
488
+ if i < len(recommendations):
489
+ self.console.print()
490
+
491
+ def get_quick_help(self, query: str) -> str:
492
+ query_lower = query.lower()
493
+
494
+ # Check for more specific patterns first
495
+ if "coverage" in query_lower:
496
+ return "Check test coverage with: python -m crackerjack -t\nView HTML report: uv run coverage html"
497
+
498
+ if "security" in query_lower or "vulnerabilit" in query_lower:
499
+ return "Check security with: python -m crackerjack --check-dependencies\nRun security audit: uv run bandit -r ."
500
+
501
+ if "lint" in query_lower or "format" in query_lower:
502
+ return "Fix code style with: python -m crackerjack\nFor AI-powered fixes: python -m crackerjack --ai-agent"
503
+
504
+ if "test" in query_lower:
505
+ return "Run tests with: python -m crackerjack -t\nFor AI-powered test fixes: python -m crackerjack --ai-agent -t"
506
+
507
+ if "publish" in query_lower or "release" in query_lower:
508
+ return "Publish to PyPI: python -m crackerjack -p patch\nBump version only: python -m crackerjack -b patch"
509
+
510
+ if "clean" in query_lower:
511
+ return "Clean code: python -m crackerjack -x\nNote: Resolve TODOs first before cleaning"
512
+
513
+ if "dashboard" in query_lower or "monitor" in query_lower:
514
+ return "Start monitoring dashboard: python -m crackerjack --dashboard\nStart WebSocket server: python -m crackerjack --start-websocket-server"
515
+
516
+ return "For full help, run: python -m crackerjack --help\nFor AI assistance: python -m crackerjack --ai-agent"