crackerjack 0.30.3__py3-none-any.whl โ 0.31.7__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 +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -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 +657 -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 +409 -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 +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -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 +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -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 +372 -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 +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -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/coverage_improvement.py +223 -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 +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -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 +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -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.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.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.7.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info โ crackerjack-0.31.7.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"
|