mcli-framework 7.0.0__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 mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from mcli.lib.logger.logger import get_logger
|
|
9
|
+
|
|
10
|
+
from .ai_service import GitCommitAIService
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GitCommitWorkflow:
|
|
16
|
+
"""Workflow for automatic git commit message generation"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, repo_path: Optional[str] = None, use_ai: bool = True):
|
|
19
|
+
self.repo_path = Path(repo_path) if repo_path else Path.cwd()
|
|
20
|
+
self.use_ai = use_ai
|
|
21
|
+
self.ai_service = GitCommitAIService() if use_ai else None
|
|
22
|
+
self.validate_git_repo()
|
|
23
|
+
|
|
24
|
+
def validate_git_repo(self):
|
|
25
|
+
"""Validate that we're in a git repository"""
|
|
26
|
+
git_dir = self.repo_path / ".git"
|
|
27
|
+
if not git_dir.exists():
|
|
28
|
+
raise ValueError(f"Not a git repository: {self.repo_path}")
|
|
29
|
+
|
|
30
|
+
def get_git_status(self) -> Dict[str, Any]:
|
|
31
|
+
"""Get current git status"""
|
|
32
|
+
try:
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
["git", "status", "--porcelain"],
|
|
35
|
+
cwd=self.repo_path,
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
check=True,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
status_lines = result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
42
|
+
|
|
43
|
+
changes = {"modified": [], "added": [], "deleted": [], "renamed": [], "untracked": []}
|
|
44
|
+
|
|
45
|
+
for line in status_lines:
|
|
46
|
+
if len(line) < 3:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
status_code = line[:2]
|
|
50
|
+
filename = line[3:]
|
|
51
|
+
|
|
52
|
+
if status_code[0] == "M" or status_code[1] == "M":
|
|
53
|
+
changes["modified"].append(filename)
|
|
54
|
+
elif status_code[0] == "A":
|
|
55
|
+
changes["added"].append(filename)
|
|
56
|
+
elif status_code[0] == "D":
|
|
57
|
+
changes["deleted"].append(filename)
|
|
58
|
+
elif status_code[0] == "R":
|
|
59
|
+
changes["renamed"].append(filename)
|
|
60
|
+
elif status_code == "??":
|
|
61
|
+
changes["untracked"].append(filename)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"has_changes": len(status_lines) > 0,
|
|
65
|
+
"changes": changes,
|
|
66
|
+
"total_files": len(status_lines),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
except subprocess.CalledProcessError as e:
|
|
70
|
+
raise RuntimeError(f"Failed to get git status: {e}")
|
|
71
|
+
|
|
72
|
+
def get_git_diff(self) -> str:
|
|
73
|
+
"""Get git diff for all changes"""
|
|
74
|
+
try:
|
|
75
|
+
# Get diff for staged and unstaged changes
|
|
76
|
+
staged_result = subprocess.run(
|
|
77
|
+
["git", "diff", "--cached"],
|
|
78
|
+
cwd=self.repo_path,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
check=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
unstaged_result = subprocess.run(
|
|
85
|
+
["git", "diff"], cwd=self.repo_path, capture_output=True, text=True, check=True
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
diff_content = ""
|
|
89
|
+
if staged_result.stdout:
|
|
90
|
+
diff_content += "=== STAGED CHANGES ===\n" + staged_result.stdout + "\n"
|
|
91
|
+
if unstaged_result.stdout:
|
|
92
|
+
diff_content += "=== UNSTAGED CHANGES ===\n" + unstaged_result.stdout + "\n"
|
|
93
|
+
|
|
94
|
+
return diff_content
|
|
95
|
+
|
|
96
|
+
except subprocess.CalledProcessError as e:
|
|
97
|
+
raise RuntimeError(f"Failed to get git diff: {e}")
|
|
98
|
+
|
|
99
|
+
def stage_all_changes(self) -> bool:
|
|
100
|
+
"""Stage all changes for commit"""
|
|
101
|
+
try:
|
|
102
|
+
subprocess.run(["git", "add", "."], cwd=self.repo_path, check=True)
|
|
103
|
+
logger.info("All changes staged successfully")
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
except subprocess.CalledProcessError as e:
|
|
107
|
+
logger.error(f"Failed to stage changes: {e}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def generate_commit_message(self, changes: Dict[str, Any], diff_content: str) -> str:
|
|
111
|
+
"""Generate a commit message using AI or fallback to rule-based generation"""
|
|
112
|
+
|
|
113
|
+
if self.use_ai and self.ai_service:
|
|
114
|
+
try:
|
|
115
|
+
logger.info("Generating AI-powered commit message...")
|
|
116
|
+
ai_message = self.ai_service.generate_commit_message(changes, diff_content)
|
|
117
|
+
if ai_message:
|
|
118
|
+
return ai_message
|
|
119
|
+
else:
|
|
120
|
+
logger.warning("AI service returned empty message, falling back to rule-based")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"AI commit message generation failed: {e}")
|
|
123
|
+
logger.info("Falling back to rule-based commit message generation")
|
|
124
|
+
|
|
125
|
+
# Fallback to rule-based generation
|
|
126
|
+
return self._generate_rule_based_message(changes)
|
|
127
|
+
|
|
128
|
+
def _generate_rule_based_message(self, changes: Dict[str, Any]) -> str:
|
|
129
|
+
"""Generate rule-based commit message (original implementation)"""
|
|
130
|
+
summary_parts = []
|
|
131
|
+
|
|
132
|
+
# Analyze file changes
|
|
133
|
+
if changes["changes"]["added"]:
|
|
134
|
+
if len(changes["changes"]["added"]) == 1:
|
|
135
|
+
summary_parts.append(f"Add {changes['changes']['added'][0]}")
|
|
136
|
+
else:
|
|
137
|
+
summary_parts.append(f"Add {len(changes['changes']['added'])} new files")
|
|
138
|
+
|
|
139
|
+
if changes["changes"]["modified"]:
|
|
140
|
+
if len(changes["changes"]["modified"]) == 1:
|
|
141
|
+
summary_parts.append(f"Update {changes['changes']['modified'][0]}")
|
|
142
|
+
else:
|
|
143
|
+
summary_parts.append(f"Update {len(changes['changes']['modified'])} files")
|
|
144
|
+
|
|
145
|
+
if changes["changes"]["deleted"]:
|
|
146
|
+
if len(changes["changes"]["deleted"]) == 1:
|
|
147
|
+
summary_parts.append(f"Remove {changes['changes']['deleted'][0]}")
|
|
148
|
+
else:
|
|
149
|
+
summary_parts.append(f"Remove {len(changes['changes']['deleted'])} files")
|
|
150
|
+
|
|
151
|
+
if changes["changes"]["renamed"]:
|
|
152
|
+
summary_parts.append(f"Rename {len(changes['changes']['renamed'])} files")
|
|
153
|
+
|
|
154
|
+
# Create commit message
|
|
155
|
+
if not summary_parts:
|
|
156
|
+
commit_message = "Update repository files"
|
|
157
|
+
else:
|
|
158
|
+
commit_message = ", ".join(summary_parts)
|
|
159
|
+
|
|
160
|
+
# Add more context based on file patterns
|
|
161
|
+
modified_files = changes["changes"]["modified"] + changes["changes"]["added"]
|
|
162
|
+
|
|
163
|
+
# Check for specific file types
|
|
164
|
+
if any(".py" in f for f in modified_files):
|
|
165
|
+
commit_message += " (Python changes)"
|
|
166
|
+
elif any(".js" in f or ".ts" in f for f in modified_files):
|
|
167
|
+
commit_message += " (JavaScript/TypeScript changes)"
|
|
168
|
+
elif any(".md" in f for f in modified_files):
|
|
169
|
+
commit_message += " (Documentation changes)"
|
|
170
|
+
elif any(
|
|
171
|
+
"requirements" in f or "package.json" in f or "Cargo.toml" in f for f in modified_files
|
|
172
|
+
):
|
|
173
|
+
commit_message += " (Dependencies changes)"
|
|
174
|
+
|
|
175
|
+
return commit_message
|
|
176
|
+
|
|
177
|
+
def create_commit(self, message: str) -> bool:
|
|
178
|
+
"""Create a git commit with the given message"""
|
|
179
|
+
try:
|
|
180
|
+
subprocess.run(["git", "commit", "-m", message], cwd=self.repo_path, check=True)
|
|
181
|
+
logger.info(f"Commit created successfully: {message}")
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
except subprocess.CalledProcessError as e:
|
|
185
|
+
logger.error(f"Failed to create commit: {e}")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
def run_auto_commit(self) -> Dict[str, Any]:
|
|
189
|
+
"""Run the complete auto-commit workflow"""
|
|
190
|
+
result = {
|
|
191
|
+
"success": False,
|
|
192
|
+
"message": "",
|
|
193
|
+
"commit_hash": None,
|
|
194
|
+
"changes_summary": {},
|
|
195
|
+
"error": None,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# Check git status
|
|
200
|
+
status = self.get_git_status()
|
|
201
|
+
result["changes_summary"] = status
|
|
202
|
+
|
|
203
|
+
if not status["has_changes"]:
|
|
204
|
+
result["message"] = "No changes to commit"
|
|
205
|
+
result["success"] = True
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
# Get diff content
|
|
209
|
+
diff_content = self.get_git_diff()
|
|
210
|
+
|
|
211
|
+
# Stage all changes
|
|
212
|
+
if not self.stage_all_changes():
|
|
213
|
+
result["error"] = "Failed to stage changes"
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
# Generate commit message
|
|
217
|
+
commit_message = self.generate_commit_message(status, diff_content)
|
|
218
|
+
result["message"] = commit_message
|
|
219
|
+
|
|
220
|
+
# Create commit
|
|
221
|
+
if self.create_commit(commit_message):
|
|
222
|
+
# Get commit hash
|
|
223
|
+
try:
|
|
224
|
+
hash_result = subprocess.run(
|
|
225
|
+
["git", "rev-parse", "HEAD"],
|
|
226
|
+
cwd=self.repo_path,
|
|
227
|
+
capture_output=True,
|
|
228
|
+
text=True,
|
|
229
|
+
check=True,
|
|
230
|
+
)
|
|
231
|
+
result["commit_hash"] = hash_result.stdout.strip()
|
|
232
|
+
except:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
result["success"] = True
|
|
236
|
+
else:
|
|
237
|
+
result["error"] = "Failed to create commit"
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
result["error"] = str(e)
|
|
241
|
+
logger.error(f"Auto-commit workflow failed: {e}")
|
|
242
|
+
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@click.group(name="git-commit")
|
|
247
|
+
def git_commit_cli():
|
|
248
|
+
"""AI-powered git commit message generation"""
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@git_commit_cli.command()
|
|
253
|
+
@click.option(
|
|
254
|
+
"--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
|
|
255
|
+
)
|
|
256
|
+
@click.option(
|
|
257
|
+
"--dry-run", is_flag=True, help="Show what would be committed without actually committing"
|
|
258
|
+
)
|
|
259
|
+
@click.option("--no-ai", is_flag=True, help="Disable AI-powered commit message generation")
|
|
260
|
+
@click.option("--model", type=str, help="Override AI model for commit message generation")
|
|
261
|
+
def auto(repo_path: Optional[str], dry_run: bool, no_ai: bool, model: Optional[str]):
|
|
262
|
+
"""Automatically stage changes and create commit with AI-generated message"""
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
workflow = GitCommitWorkflow(repo_path, use_ai=not no_ai)
|
|
266
|
+
|
|
267
|
+
# Override AI model if specified
|
|
268
|
+
if not no_ai and model and workflow.ai_service:
|
|
269
|
+
workflow.ai_service.model_name = model
|
|
270
|
+
|
|
271
|
+
if dry_run:
|
|
272
|
+
# Just show what would happen
|
|
273
|
+
status = workflow.get_git_status()
|
|
274
|
+
|
|
275
|
+
if not status["has_changes"]:
|
|
276
|
+
click.echo("No changes to commit")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
click.echo("Changes that would be staged and committed:")
|
|
280
|
+
changes = status["changes"]
|
|
281
|
+
|
|
282
|
+
if changes["untracked"]:
|
|
283
|
+
click.echo(f" New files ({len(changes['untracked'])}):")
|
|
284
|
+
for file in changes["untracked"]:
|
|
285
|
+
click.echo(f" + {file}")
|
|
286
|
+
|
|
287
|
+
if changes["modified"]:
|
|
288
|
+
click.echo(f" Modified files ({len(changes['modified'])}):")
|
|
289
|
+
for file in changes["modified"]:
|
|
290
|
+
click.echo(f" M {file}")
|
|
291
|
+
|
|
292
|
+
if changes["deleted"]:
|
|
293
|
+
click.echo(f" Deleted files ({len(changes['deleted'])}):")
|
|
294
|
+
for file in changes["deleted"]:
|
|
295
|
+
click.echo(f" - {file}")
|
|
296
|
+
|
|
297
|
+
# Generate and show commit message
|
|
298
|
+
diff_content = workflow.get_git_diff()
|
|
299
|
+
commit_message = workflow.generate_commit_message(status, diff_content)
|
|
300
|
+
click.echo(f"\nProposed commit message: {commit_message}")
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
# Actually run the workflow
|
|
304
|
+
result = workflow.run_auto_commit()
|
|
305
|
+
|
|
306
|
+
if result["success"]:
|
|
307
|
+
if result["commit_hash"]:
|
|
308
|
+
click.echo(f"✅ Commit created successfully: {result['commit_hash'][:8]}")
|
|
309
|
+
else:
|
|
310
|
+
click.echo("✅ Commit created successfully")
|
|
311
|
+
|
|
312
|
+
click.echo(f"Message: {result['message']}")
|
|
313
|
+
|
|
314
|
+
# Show summary
|
|
315
|
+
changes = result["changes_summary"]["changes"]
|
|
316
|
+
total = sum(len(files) for files in changes.values())
|
|
317
|
+
click.echo(f"Files changed: {total}")
|
|
318
|
+
|
|
319
|
+
else:
|
|
320
|
+
click.echo(f"❌ Failed to create commit: {result.get('error', 'Unknown error')}")
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
click.echo(f"❌ Error: {e}")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@git_commit_cli.command()
|
|
327
|
+
@click.option(
|
|
328
|
+
"--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
|
|
329
|
+
)
|
|
330
|
+
def status(repo_path: Optional[str]):
|
|
331
|
+
"""Show current git repository status"""
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
workflow = GitCommitWorkflow(repo_path)
|
|
335
|
+
status_info = workflow.get_git_status()
|
|
336
|
+
|
|
337
|
+
if not status_info["has_changes"]:
|
|
338
|
+
click.echo("✅ Working tree is clean")
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
click.echo("📋 Repository Status:")
|
|
342
|
+
changes = status_info["changes"]
|
|
343
|
+
|
|
344
|
+
if changes["untracked"]:
|
|
345
|
+
click.echo(f"\n📄 Untracked files ({len(changes['untracked'])}):")
|
|
346
|
+
for file in changes["untracked"]:
|
|
347
|
+
click.echo(f" ?? {file}")
|
|
348
|
+
|
|
349
|
+
if changes["modified"]:
|
|
350
|
+
click.echo(f"\n✏️ Modified files ({len(changes['modified'])}):")
|
|
351
|
+
for file in changes["modified"]:
|
|
352
|
+
click.echo(f" M {file}")
|
|
353
|
+
|
|
354
|
+
if changes["added"]:
|
|
355
|
+
click.echo(f"\n➕ Added files ({len(changes['added'])}):")
|
|
356
|
+
for file in changes["added"]:
|
|
357
|
+
click.echo(f" A {file}")
|
|
358
|
+
|
|
359
|
+
if changes["deleted"]:
|
|
360
|
+
click.echo(f"\n🗑️ Deleted files ({len(changes['deleted'])}):")
|
|
361
|
+
for file in changes["deleted"]:
|
|
362
|
+
click.echo(f" D {file}")
|
|
363
|
+
|
|
364
|
+
if changes["renamed"]:
|
|
365
|
+
click.echo(f"\n🔄 Renamed files ({len(changes['renamed'])}):")
|
|
366
|
+
for file in changes["renamed"]:
|
|
367
|
+
click.echo(f" R {file}")
|
|
368
|
+
|
|
369
|
+
click.echo(f"\nTotal files with changes: {status_info['total_files']}")
|
|
370
|
+
|
|
371
|
+
except Exception as e:
|
|
372
|
+
click.echo(f"❌ Error: {e}")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@git_commit_cli.command()
|
|
376
|
+
@click.argument("message")
|
|
377
|
+
@click.option(
|
|
378
|
+
"--repo-path", type=click.Path(), help="Path to git repository (default: current directory)"
|
|
379
|
+
)
|
|
380
|
+
@click.option("--stage-all", is_flag=True, help="Stage all changes before committing")
|
|
381
|
+
def commit(message: str, repo_path: Optional[str], stage_all: bool):
|
|
382
|
+
"""Create a commit with a custom message"""
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
workflow = GitCommitWorkflow(repo_path)
|
|
386
|
+
|
|
387
|
+
if stage_all:
|
|
388
|
+
if not workflow.stage_all_changes():
|
|
389
|
+
click.echo("❌ Failed to stage changes")
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
if workflow.create_commit(message):
|
|
393
|
+
click.echo(f"✅ Commit created: {message}")
|
|
394
|
+
else:
|
|
395
|
+
click.echo("❌ Failed to create commit")
|
|
396
|
+
|
|
397
|
+
except Exception as e:
|
|
398
|
+
click.echo(f"❌ Error: {e}")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@git_commit_cli.command()
|
|
402
|
+
@click.option("--model", type=str, help="Override AI model for testing")
|
|
403
|
+
def test_ai(model: Optional[str]):
|
|
404
|
+
"""Test the AI service for commit message generation"""
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
from .ai_service import GitCommitAIService
|
|
408
|
+
|
|
409
|
+
ai_service = GitCommitAIService()
|
|
410
|
+
|
|
411
|
+
# Override model if specified
|
|
412
|
+
if model:
|
|
413
|
+
ai_service.model_name = model
|
|
414
|
+
|
|
415
|
+
click.echo(f"🧪 Testing AI service with model: {ai_service.model_name}")
|
|
416
|
+
click.echo(f"🌐 Ollama base URL: {ai_service.ollama_base_url}")
|
|
417
|
+
click.echo(f"🌡️ Temperature: {ai_service.temperature}")
|
|
418
|
+
|
|
419
|
+
# Test AI service
|
|
420
|
+
if ai_service.test_ai_service():
|
|
421
|
+
click.echo("✅ AI service test passed!")
|
|
422
|
+
else:
|
|
423
|
+
click.echo("❌ AI service test failed!")
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
click.echo(f"❌ Error testing AI service: {e}")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
if __name__ == "__main__":
|
|
430
|
+
git_commit_cli()
|