stigmergy 1.0.68 → 1.0.70
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.
- package/README.en.md +306 -300
- package/README.md +469 -301
- package/package.json +97 -81
- package/scripts/publish.js +268 -0
- package/scripts/simple-publish.js +59 -0
- package/src/index.js +12 -0
- package/test/enhanced-main-alignment.test.js +298 -0
- package/test/hook-system-integration-test.js +307 -0
- package/test/natural-language-skills-test.js +320 -0
- package/test/nl-integration-test.js +179 -0
- package/test/parameter-parsing-test.js +143 -0
- package/test/real-test.js +435 -0
- package/test/system-compatibility-test.js +447 -0
- package/test/tdd-fixes-test.js +211 -0
- package/test/third-party-skills-test.js +321 -0
- package/test/tool-selection-integration-test.js +157 -0
- package/test/unit/cli-scanner.test.js +291 -0
- package/test/unit/cross-cli-executor.test.js +399 -0
- package/src/adapters/claude/__init__.py +0 -13
- package/src/adapters/claude/claude_skills_integration.py +0 -609
- package/src/adapters/claude/hook_adapter.py +0 -663
- package/src/adapters/claude/install_claude_integration.py +0 -265
- package/src/adapters/claude/skills_hook_adapter.py +0 -841
- package/src/adapters/claude/standalone_claude_adapter.py +0 -384
- package/src/adapters/cline/__init__.py +0 -20
- package/src/adapters/cline/config.py +0 -108
- package/src/adapters/cline/install_cline_integration.py +0 -617
- package/src/adapters/cline/mcp_server.py +0 -713
- package/src/adapters/cline/standalone_cline_adapter.py +0 -459
- package/src/adapters/codebuddy/__init__.py +0 -13
- package/src/adapters/codebuddy/buddy_adapter.py +0 -1125
- package/src/adapters/codebuddy/install_codebuddy_integration.py +0 -279
- package/src/adapters/codebuddy/skills_hook_adapter.py +0 -672
- package/src/adapters/codebuddy/skills_integration.py +0 -395
- package/src/adapters/codebuddy/standalone_codebuddy_adapter.py +0 -403
- package/src/adapters/codex/__init__.py +0 -11
- package/src/adapters/codex/base.py +0 -46
- package/src/adapters/codex/install_codex_integration.py +0 -311
- package/src/adapters/codex/mcp_server.py +0 -493
- package/src/adapters/codex/natural_language_parser.py +0 -82
- package/src/adapters/codex/slash_command_adapter.py +0 -326
- package/src/adapters/codex/standalone_codex_adapter.py +0 -362
- package/src/adapters/copilot/__init__.py +0 -13
- package/src/adapters/copilot/install_copilot_integration.py +0 -564
- package/src/adapters/copilot/mcp_adapter.py +0 -772
- package/src/adapters/copilot/mcp_server.py +0 -168
- package/src/adapters/copilot/standalone_copilot_adapter.py +0 -114
- package/src/adapters/gemini/__init__.py +0 -13
- package/src/adapters/gemini/extension_adapter.py +0 -690
- package/src/adapters/gemini/install_gemini_integration.py +0 -257
- package/src/adapters/gemini/standalone_gemini_adapter.py +0 -366
- package/src/adapters/iflow/__init__.py +0 -7
- package/src/adapters/iflow/hook_adapter.py +0 -1038
- package/src/adapters/iflow/hook_installer.py +0 -536
- package/src/adapters/iflow/install_iflow_integration.py +0 -271
- package/src/adapters/iflow/official_hook_adapter.py +0 -1272
- package/src/adapters/iflow/standalone_iflow_adapter.py +0 -48
- package/src/adapters/iflow/workflow_adapter.py +0 -793
- package/src/adapters/qoder/hook_installer.py +0 -732
- package/src/adapters/qoder/install_qoder_integration.py +0 -265
- package/src/adapters/qoder/notification_hook_adapter.py +0 -863
- package/src/adapters/qoder/standalone_qoder_adapter.py +0 -48
- package/src/adapters/qwen/__init__.py +0 -17
- package/src/adapters/qwencode/__init__.py +0 -13
- package/src/adapters/qwencode/inheritance_adapter.py +0 -818
- package/src/adapters/qwencode/install_qwencode_integration.py +0 -276
- package/src/adapters/qwencode/standalone_qwencode_adapter.py +0 -399
- package/src/atomic_collaboration_handler.py +0 -461
- package/src/cli_collaboration_agent.py +0 -697
- package/src/collaboration/hooks.py +0 -315
- package/src/core/__init__.py +0 -21
- package/src/core/ai_environment_scanner.py +0 -331
- package/src/core/base_adapter.py +0 -220
- package/src/core/cli_hook_integration.py +0 -406
- package/src/core/cross_cli_executor.py +0 -713
- package/src/core/cross_cli_mapping.py +0 -1165
- package/src/core/cross_platform_encoding.py +0 -365
- package/src/core/cross_platform_safe_cli.py +0 -894
- package/src/core/direct_cli_executor.py +0 -805
- package/src/core/direct_cli_hook_system.py +0 -958
- package/src/core/enhanced_init_processor.py +0 -467
- package/src/core/graceful_cli_executor.py +0 -912
- package/src/core/md_enhancer.py +0 -342
- package/src/core/md_generator.py +0 -619
- package/src/core/models.py +0 -218
- package/src/core/parser.py +0 -108
- package/src/core/real_cli_hook_system.py +0 -852
- package/src/core/real_cross_cli_system.py +0 -925
- package/src/core/verified_cross_cli_system.py +0 -961
- package/src/deploy.js +0 -737
- package/src/enhanced-main.js +0 -626
- package/src/enhanced_deploy.js +0 -303
- package/src/enhanced_universal_cli_setup.py +0 -930
- package/src/kimi_wrapper.py +0 -104
- package/src/main.js +0 -1309
- package/src/shell_integration.py +0 -398
- package/src/simple-main.js +0 -315
- package/src/smart_router_creator.py +0 -323
- package/src/universal_cli_setup.py +0 -1289
- package/src/utils/__init__.py +0 -12
- package/src/utils/cli_detector.py +0 -445
- package/src/utils/file_utils.py +0 -246
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AI CLI 协同钩子系统
|
|
3
|
-
为每个CLI工具提供项目协同感知能力
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import json
|
|
8
|
-
import logging
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Dict, List, Optional, Any
|
|
11
|
-
from datetime import datetime, timezone
|
|
12
|
-
from dataclasses import asdict
|
|
13
|
-
import threading
|
|
14
|
-
import time
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
class CollaborationHook:
|
|
19
|
-
"""协同钩子基类"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, cli_name: str, project_root: Optional[Path] = None):
|
|
22
|
-
self.cli_name = cli_name
|
|
23
|
-
self.project_root = project_root or Path.cwd()
|
|
24
|
-
self.constitution_file = self.project_root / "PROJECT_CONSTITUTION.json"
|
|
25
|
-
self.state_file = self.project_root / "PROJECT_STATE.json"
|
|
26
|
-
self.tasks_file = self.project_root / "TASKS.json"
|
|
27
|
-
self.collaboration_file = self.project_root / "COLLABORATION_LOG.json"
|
|
28
|
-
|
|
29
|
-
self.constitution_cache = {}
|
|
30
|
-
self.state_cache = {}
|
|
31
|
-
self.cache_timestamp = 0
|
|
32
|
-
self.cache_ttl = 30 # 缓存30秒
|
|
33
|
-
|
|
34
|
-
logger.info(f"协同钩子初始化: {cli_name} @ {self.project_root}")
|
|
35
|
-
|
|
36
|
-
def is_project_enabled(self) -> bool:
|
|
37
|
-
"""检查项目是否启用协同功能"""
|
|
38
|
-
if not self.constitution_file.exists():
|
|
39
|
-
return False
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
with open(self.constitution_file, 'r', encoding='utf-8') as f:
|
|
43
|
-
constitution = json.load(f)
|
|
44
|
-
return constitution.get("collaboration_config", {}).get("enabled", False)
|
|
45
|
-
except Exception:
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
def _refresh_cache(self):
|
|
49
|
-
"""刷新缓存"""
|
|
50
|
-
current_time = time.time()
|
|
51
|
-
if current_time - self.cache_timestamp > self.cache_ttl:
|
|
52
|
-
self.cache_timestamp = current_time
|
|
53
|
-
|
|
54
|
-
# 重新加载项目宪法
|
|
55
|
-
if self.constitution_file.exists():
|
|
56
|
-
try:
|
|
57
|
-
with open(self.constitution_file, 'r', encoding='utf-8') as f:
|
|
58
|
-
self.constitution_cache = json.load(f)
|
|
59
|
-
except Exception as e:
|
|
60
|
-
logger.warning(f"加载项目宪法失败: {e}")
|
|
61
|
-
|
|
62
|
-
# 重新加载项目状态
|
|
63
|
-
if self.state_file.exists():
|
|
64
|
-
try:
|
|
65
|
-
with open(self.state_file, 'r', encoding='utf-8') as f:
|
|
66
|
-
self.state_cache = json.load(f)
|
|
67
|
-
except Exception as e:
|
|
68
|
-
logger.warning(f"加载项目状态失败: {e}")
|
|
69
|
-
|
|
70
|
-
def get_project_constitution(self) -> Dict:
|
|
71
|
-
"""获取项目宪法"""
|
|
72
|
-
self._refresh_cache()
|
|
73
|
-
return self.constitution_cache
|
|
74
|
-
|
|
75
|
-
def get_project_state(self) -> Dict:
|
|
76
|
-
"""获取项目状态"""
|
|
77
|
-
self._refresh_cache()
|
|
78
|
-
return self.state_cache
|
|
79
|
-
|
|
80
|
-
def detect_collaboration_intent(self, user_input: str) -> Dict:
|
|
81
|
-
"""检测协作意图"""
|
|
82
|
-
if not self.is_project_enabled():
|
|
83
|
-
return {"intent": False}
|
|
84
|
-
|
|
85
|
-
constitution = self.get_project_constitution()
|
|
86
|
-
collaboration_keywords = constitution.get("cli_preferences", {}).get("collaboration_keywords", [])
|
|
87
|
-
|
|
88
|
-
# 检测协作关键词
|
|
89
|
-
user_input_lower = user_input.lower()
|
|
90
|
-
for keyword in collaboration_keywords:
|
|
91
|
-
if keyword in user_input_lower:
|
|
92
|
-
return {
|
|
93
|
-
"intent": True,
|
|
94
|
-
"keyword": keyword,
|
|
95
|
-
"detected_at": datetime.now(timezone.utc).isoformat()
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {"intent": False}
|
|
99
|
-
|
|
100
|
-
def get_next_task(self) -> Optional[Dict]:
|
|
101
|
-
"""获取下一个任务"""
|
|
102
|
-
if not self.is_project_enabled():
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
# 这里应该调用任务管理器获取任务
|
|
106
|
-
# 为了简化,直接检查任务文件
|
|
107
|
-
if not self.tasks_file.exists():
|
|
108
|
-
return None
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
with open(self.tasks_file, 'r', encoding='utf-8') as f:
|
|
112
|
-
tasks_data = json.load(f)
|
|
113
|
-
|
|
114
|
-
# 获取CLI优先级
|
|
115
|
-
constitution = self.get_project_constitution()
|
|
116
|
-
cli_preferences = constitution.get("cli_preferences", {})
|
|
117
|
-
task_priorities = cli_preferences.get("task_priorities", {}).get(self.cli_name, [])
|
|
118
|
-
|
|
119
|
-
# 查找待处理任务
|
|
120
|
-
pending_tasks = [task for task in tasks_data.get("tasks", [])
|
|
121
|
-
if task.get("status") == "pending"]
|
|
122
|
-
|
|
123
|
-
if not pending_tasks:
|
|
124
|
-
return None
|
|
125
|
-
|
|
126
|
-
# 简单优先级排序
|
|
127
|
-
priority_scores = {"critical": 4, "high": 3, "normal": 2, "low": 1}
|
|
128
|
-
|
|
129
|
-
def task_score(task):
|
|
130
|
-
score = priority_scores.get(task.get("priority", "normal"), 2)
|
|
131
|
-
|
|
132
|
-
# CLI偏好加分
|
|
133
|
-
if task.get("tags"):
|
|
134
|
-
for tag in task.get("tags", []):
|
|
135
|
-
if tag in task_priorities:
|
|
136
|
-
score += 2
|
|
137
|
-
|
|
138
|
-
return score
|
|
139
|
-
|
|
140
|
-
pending_tasks.sort(key=task_score, reverse=True)
|
|
141
|
-
return pending_tasks[0]
|
|
142
|
-
|
|
143
|
-
except Exception as e:
|
|
144
|
-
logger.error(f"获取下一个任务失败: {e}")
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
def create_task(self, title: str, description: str, **kwargs) -> bool:
|
|
148
|
-
"""创建新任务"""
|
|
149
|
-
if not self.is_project_enabled():
|
|
150
|
-
logger.warning("项目未启用协同功能")
|
|
151
|
-
return False
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
# 这里应该调用任务管理器创建任务
|
|
155
|
-
# 为了简化,直接修改任务文件
|
|
156
|
-
tasks_data = {"tasks": [], "next_id": 1}
|
|
157
|
-
|
|
158
|
-
if self.tasks_file.exists():
|
|
159
|
-
with open(self.tasks_file, 'r', encoding='utf-8') as f:
|
|
160
|
-
tasks_data = json.load(f)
|
|
161
|
-
|
|
162
|
-
# 创建新任务
|
|
163
|
-
task_id = f"task_{tasks_data['next_id']:04d}"
|
|
164
|
-
task = {
|
|
165
|
-
"id": task_id,
|
|
166
|
-
"title": title,
|
|
167
|
-
"description": description,
|
|
168
|
-
"status": "pending",
|
|
169
|
-
"priority": "normal",
|
|
170
|
-
"assigned_to": self.cli_name,
|
|
171
|
-
"created_by": self.cli_name,
|
|
172
|
-
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
173
|
-
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
174
|
-
"tags": [],
|
|
175
|
-
"metadata": kwargs
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
tasks_data["tasks"].append(task)
|
|
179
|
-
tasks_data["next_id"] += 1
|
|
180
|
-
|
|
181
|
-
with open(self.tasks_file, 'w', encoding='utf-8') as f:
|
|
182
|
-
json.dump(tasks_data, f, indent=2, ensure_ascii=False)
|
|
183
|
-
|
|
184
|
-
# 记录协作日志
|
|
185
|
-
self._log_activity("task_created", {
|
|
186
|
-
"task_id": task_id,
|
|
187
|
-
"title": title,
|
|
188
|
-
"assigned_to": self.cli_name
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
logger.info(f"✅ 任务创建成功: {task_id}")
|
|
192
|
-
return True
|
|
193
|
-
|
|
194
|
-
except Exception as e:
|
|
195
|
-
logger.error(f"创建任务失败: {e}")
|
|
196
|
-
return False
|
|
197
|
-
|
|
198
|
-
def update_task_status(self, task_id: str, status: str, **kwargs) -> bool:
|
|
199
|
-
"""更新任务状态"""
|
|
200
|
-
if not self.is_project_enabled():
|
|
201
|
-
return False
|
|
202
|
-
|
|
203
|
-
try:
|
|
204
|
-
if not self.tasks_file.exists():
|
|
205
|
-
return False
|
|
206
|
-
|
|
207
|
-
with open(self.tasks_file, 'r', encoding='utf-8') as f:
|
|
208
|
-
tasks_data = json.load(f)
|
|
209
|
-
|
|
210
|
-
# 查找并更新任务
|
|
211
|
-
for task in tasks_data.get("tasks", []):
|
|
212
|
-
if task.get("id") == task_id:
|
|
213
|
-
task["status"] = status
|
|
214
|
-
task["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
215
|
-
task.update(kwargs)
|
|
216
|
-
|
|
217
|
-
# 如果任务完成,记录完成时间
|
|
218
|
-
if status == "completed":
|
|
219
|
-
task["completed_at"] = datetime.now(timezone.utc).isoformat()
|
|
220
|
-
|
|
221
|
-
# 记录协作日志
|
|
222
|
-
self._log_activity("task_completed", {
|
|
223
|
-
"task_id": task_id,
|
|
224
|
-
"title": task.get("title", ""),
|
|
225
|
-
"assigned_to": task.get("assigned_to", "")
|
|
226
|
-
})
|
|
227
|
-
else:
|
|
228
|
-
# 记录协作日志
|
|
229
|
-
self._log_activity("task_status_updated", {
|
|
230
|
-
"task_id": task_id,
|
|
231
|
-
"old_status": task.get("status"),
|
|
232
|
-
"new_status": status,
|
|
233
|
-
"assigned_to": task.get("assigned_to", "")
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
break
|
|
237
|
-
|
|
238
|
-
with open(self.tasks_file, 'w', encoding='utf-8') as f:
|
|
239
|
-
json.dump(tasks_data, f, indent=2, ensure_ascii=False)
|
|
240
|
-
|
|
241
|
-
logger.info(f"✅ 任务状态更新: {task_id} -> {status}")
|
|
242
|
-
return True
|
|
243
|
-
|
|
244
|
-
except Exception as e:
|
|
245
|
-
logger.error(f"更新任务状态失败: {e}")
|
|
246
|
-
return False
|
|
247
|
-
|
|
248
|
-
def _log_activity(self, action: str, details: Dict):
|
|
249
|
-
"""记录协作活动"""
|
|
250
|
-
try:
|
|
251
|
-
log_entry = {
|
|
252
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
253
|
-
"cli": self.cli_name,
|
|
254
|
-
"action": action,
|
|
255
|
-
"details": details
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
logs = []
|
|
259
|
-
if self.collaboration_file.exists():
|
|
260
|
-
with open(self.collaboration_file, 'r', encoding='utf-8') as f:
|
|
261
|
-
logs = json.load(f)
|
|
262
|
-
|
|
263
|
-
logs.append(log_entry)
|
|
264
|
-
|
|
265
|
-
# 限制日志大小
|
|
266
|
-
if len(logs) > 1000:
|
|
267
|
-
logs = logs[-1000:]
|
|
268
|
-
|
|
269
|
-
with open(self.collaboration_file, 'w', encoding='utf-8') as f:
|
|
270
|
-
json.dump(logs, f, indent=2, ensure_ascii=False)
|
|
271
|
-
|
|
272
|
-
except Exception as e:
|
|
273
|
-
logger.error(f"记录协作日志失败: {e}")
|
|
274
|
-
|
|
275
|
-
class CLICollaborationHooks:
|
|
276
|
-
"""CLI协作钩子集合"""
|
|
277
|
-
|
|
278
|
-
_hooks = {}
|
|
279
|
-
_lock = threading.Lock()
|
|
280
|
-
|
|
281
|
-
@classmethod
|
|
282
|
-
def register_hook(cls, cli_name: str, hook: CollaborationHook):
|
|
283
|
-
"""注册协作钩子"""
|
|
284
|
-
with cls._lock:
|
|
285
|
-
cls._hooks[cli_name] = hook
|
|
286
|
-
logger.info(f"注册协作钩子: {cli_name}")
|
|
287
|
-
|
|
288
|
-
@classmethod
|
|
289
|
-
def get_hook(cls, cli_name: str) -> Optional[CollaborationHook]:
|
|
290
|
-
"""获取协作钩子"""
|
|
291
|
-
with cls._lock:
|
|
292
|
-
return cls._hooks.get(cli_name)
|
|
293
|
-
|
|
294
|
-
@classmethod
|
|
295
|
-
def initialize_hook(cls, cli_name: str, project_root: Optional[Path] = None):
|
|
296
|
-
"""初始化协作钩子"""
|
|
297
|
-
if cls.get_hook(cli_name) is None:
|
|
298
|
-
hook = CollaborationHook(cli_name, project_root)
|
|
299
|
-
cls.register_hook(cli_name, hook)
|
|
300
|
-
logger.info(f"初始化协作钩子: {cli_name}")
|
|
301
|
-
return hook
|
|
302
|
-
|
|
303
|
-
return cls.get_hook(cli_name)
|
|
304
|
-
|
|
305
|
-
# 预定义的CLI工具钩子
|
|
306
|
-
def initialize_standard_hooks():
|
|
307
|
-
"""初始化标准CLI工具的协作钩子"""
|
|
308
|
-
|
|
309
|
-
standard_clis = ["claude", "gemini", "codex", "qwencode", "codebuddy", "iflow", "qoder", "copilot"]
|
|
310
|
-
|
|
311
|
-
for cli_name in standard_clis:
|
|
312
|
-
CLICollaborationHooks.initialize_hook(cli_name)
|
|
313
|
-
|
|
314
|
-
# 全局初始化
|
|
315
|
-
initialize_standard_hooks()
|
package/src/core/__init__.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"""
|
|
3
|
-
AI CLI Router - 核心模块
|
|
4
|
-
AI CLI协作系统的核心组件
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
# 导入核心类和函数
|
|
8
|
-
from .models import *
|
|
9
|
-
from .ai_environment_scanner import AIEnvironmentScanner
|
|
10
|
-
from .enhanced_init_processor import EnhancedInitProcessor
|
|
11
|
-
from .md_enhancer import MDDocumentEnhancer
|
|
12
|
-
from .md_generator import MDDocumentGenerator
|
|
13
|
-
|
|
14
|
-
# 版本信息
|
|
15
|
-
__version__ = '1.0.0'
|
|
16
|
-
__all__ = [
|
|
17
|
-
'AIEnvironmentScanner',
|
|
18
|
-
'EnhancedInitProcessor',
|
|
19
|
-
'MDDocumentEnhancer',
|
|
20
|
-
'MDDocumentGenerator'
|
|
21
|
-
]
|
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AI Environment Scanner - AI环境扫描器
|
|
3
|
-
检测项目可用的AI CLI工具和协作环境
|
|
4
|
-
"""
|
|
5
|
-
import os
|
|
6
|
-
import asyncio
|
|
7
|
-
import json
|
|
8
|
-
import yaml
|
|
9
|
-
import subprocess
|
|
10
|
-
import logging
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, List, Optional, Any
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
|
|
15
|
-
from .models import (
|
|
16
|
-
CLIInfo, ProjectCLIInfo, AIEnvironmentInfo, CollaborationGuide,
|
|
17
|
-
CLIStatus, IntegrationType, CLI_CONFIG_MAPPING, COLLABORATION_PROTOCOLS
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class AIEnvironmentScanner:
|
|
24
|
-
"""AI环境扫描器 - 检测项目可用的AI协作工具"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, current_cli: str = None):
|
|
27
|
-
self.current_cli = current_cli
|
|
28
|
-
self.scan_start_time = None
|
|
29
|
-
|
|
30
|
-
async def scan_ai_environment(self, project_path: str) -> AIEnvironmentInfo:
|
|
31
|
-
"""扫描当前项目的AI环境"""
|
|
32
|
-
self.scan_start_time = datetime.now()
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
# 1. 检测全局已安装的CLI工具
|
|
36
|
-
logger.info("开始扫描全局CLI工具...")
|
|
37
|
-
global_clis = await self._scan_global_clis()
|
|
38
|
-
|
|
39
|
-
# 2. 检测项目特定的AI配置
|
|
40
|
-
logger.info("开始扫描项目特定配置...")
|
|
41
|
-
project_clis = await self._scan_project_clis(project_path)
|
|
42
|
-
|
|
43
|
-
# 3. 生成协作指南
|
|
44
|
-
logger.info("生成协作指南...")
|
|
45
|
-
collaboration_guide = await self._generate_collaboration_guide(global_clis, project_clis)
|
|
46
|
-
|
|
47
|
-
scan_duration = (datetime.now() - self.scan_start_time).total_seconds()
|
|
48
|
-
|
|
49
|
-
ai_environment = AIEnvironmentInfo(
|
|
50
|
-
available_clis=global_clis,
|
|
51
|
-
project_specific_clis=project_clis,
|
|
52
|
-
collaboration_guide=collaboration_guide,
|
|
53
|
-
generated_at=self.scan_start_time,
|
|
54
|
-
scan_duration=scan_duration
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
logger.info(f"AI环境扫描完成,耗时: {scan_duration:.2f}秒")
|
|
58
|
-
return ai_environment
|
|
59
|
-
|
|
60
|
-
except Exception as e:
|
|
61
|
-
logger.error(f"AI环境扫描失败: {e}")
|
|
62
|
-
raise
|
|
63
|
-
|
|
64
|
-
async def _scan_global_clis(self) -> Dict[str, CLIInfo]:
|
|
65
|
-
"""扫描全局已安装的AI CLI工具"""
|
|
66
|
-
available_clis = {}
|
|
67
|
-
|
|
68
|
-
scan_tasks = []
|
|
69
|
-
for cli_name, config_mapping in CLI_CONFIG_MAPPING.items():
|
|
70
|
-
task = self._scan_single_cli(cli_name, config_mapping)
|
|
71
|
-
scan_tasks.append(task)
|
|
72
|
-
|
|
73
|
-
# 并行扫描所有CLI工具
|
|
74
|
-
results = await asyncio.gather(*scan_tasks, return_exceptions=True)
|
|
75
|
-
|
|
76
|
-
for i, (cli_name, config_mapping) in enumerate(CLI_CONFIG_MAPPING.items()):
|
|
77
|
-
result = results[i]
|
|
78
|
-
if isinstance(result, Exception):
|
|
79
|
-
logger.warning(f"扫描 {cli_name} 时出错: {result}")
|
|
80
|
-
continue
|
|
81
|
-
|
|
82
|
-
if result:
|
|
83
|
-
available_clis[cli_name] = result
|
|
84
|
-
|
|
85
|
-
logger.info(f"发现 {len(available_clis)} 个可用的CLI工具: {list(available_clis.keys())}")
|
|
86
|
-
return available_clis
|
|
87
|
-
|
|
88
|
-
async def _scan_single_cli(self, cli_name: str, config_mapping) -> Optional[CLIInfo]:
|
|
89
|
-
"""扫描单个CLI工具"""
|
|
90
|
-
try:
|
|
91
|
-
# 1. 检查CLI工具是否可用
|
|
92
|
-
is_available = await self._is_cli_available(cli_name, config_mapping)
|
|
93
|
-
if not is_available:
|
|
94
|
-
return None
|
|
95
|
-
|
|
96
|
-
# 2. 获取版本信息
|
|
97
|
-
version = await self._get_cli_version(cli_name, config_mapping)
|
|
98
|
-
|
|
99
|
-
# 3. 获取能力信息
|
|
100
|
-
capabilities = await self._get_cli_capabilities(cli_name, config_mapping)
|
|
101
|
-
|
|
102
|
-
# 4. 检查配置文件
|
|
103
|
-
config_file_exists = await self._check_config_file(config_mapping.config_file)
|
|
104
|
-
|
|
105
|
-
status = CLIStatus.AVAILABLE if config_file_exists else CLIStatus.CONFIG_MISSING
|
|
106
|
-
|
|
107
|
-
return CLIInfo(
|
|
108
|
-
name=cli_name,
|
|
109
|
-
display_name=self._get_display_name(cli_name),
|
|
110
|
-
version=version,
|
|
111
|
-
integration_type=config_mapping.integration_type,
|
|
112
|
-
status=status,
|
|
113
|
-
capabilities=capabilities,
|
|
114
|
-
config_file=config_mapping.config_file,
|
|
115
|
-
global_doc=config_mapping.global_doc,
|
|
116
|
-
protocols=COLLABORATION_PROTOCOLS["chinese"] + COLLABORATION_PROTOCOLS["english"]
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
except Exception as e:
|
|
120
|
-
logger.warning(f"扫描CLI工具 {cli_name} 失败: {e}")
|
|
121
|
-
return None
|
|
122
|
-
|
|
123
|
-
async def _is_cli_available(self, cli_name: str, config_mapping) -> bool:
|
|
124
|
-
"""检查CLI工具是否可用"""
|
|
125
|
-
try:
|
|
126
|
-
# 检查命令是否存在
|
|
127
|
-
command = config_mapping.version_check_command
|
|
128
|
-
if not command:
|
|
129
|
-
# 如果没有版本检查命令,尝试通用命令
|
|
130
|
-
command = f"{cli_name}-cli --version"
|
|
131
|
-
|
|
132
|
-
result = await asyncio.create_subprocess_shell(
|
|
133
|
-
command,
|
|
134
|
-
stdout=asyncio.subprocess.PIPE,
|
|
135
|
-
stderr=asyncio.subprocess.PIPE
|
|
136
|
-
)
|
|
137
|
-
stdout, stderr = await result.communicate()
|
|
138
|
-
|
|
139
|
-
return result.returncode == 0
|
|
140
|
-
|
|
141
|
-
except Exception:
|
|
142
|
-
return False
|
|
143
|
-
|
|
144
|
-
async def _get_cli_version(self, cli_name: str, config_mapping) -> str:
|
|
145
|
-
"""获取CLI工具版本"""
|
|
146
|
-
try:
|
|
147
|
-
command = config_mapping.version_check_command
|
|
148
|
-
if not command:
|
|
149
|
-
command = f"{cli_name}-cli --version"
|
|
150
|
-
|
|
151
|
-
result = await asyncio.create_subprocess_shell(
|
|
152
|
-
command,
|
|
153
|
-
stdout=asyncio.subprocess.PIPE,
|
|
154
|
-
stderr=asyncio.subprocess.PIPE
|
|
155
|
-
)
|
|
156
|
-
stdout, stderr = await result.communicate()
|
|
157
|
-
|
|
158
|
-
if result.returncode == 0:
|
|
159
|
-
version_output = stdout.decode().strip() or stderr.decode().strip()
|
|
160
|
-
# 提取版本号
|
|
161
|
-
for line in version_output.split('\n'):
|
|
162
|
-
if any(keyword in line.lower() for keyword in ['version', 'v']):
|
|
163
|
-
return line.strip()
|
|
164
|
-
return version_output
|
|
165
|
-
else:
|
|
166
|
-
return "Unknown"
|
|
167
|
-
|
|
168
|
-
except Exception:
|
|
169
|
-
return "Unknown"
|
|
170
|
-
|
|
171
|
-
async def _get_cli_capabilities(self, cli_name: str, config_mapping) -> List[str]:
|
|
172
|
-
"""获取CLI工具能力"""
|
|
173
|
-
# 基于集成类型返回默认能力
|
|
174
|
-
capability_map = {
|
|
175
|
-
IntegrationType.HOOK_SYSTEM: ["钩子系统", "事件处理", "跨工具协调"],
|
|
176
|
-
IntegrationType.EXTENSION_SYSTEM: ["扩展系统", "智能匹配", "多处理器"],
|
|
177
|
-
IntegrationType.CLASS_INHERITANCE: ["插件继承", "代码生成", "方法重写"],
|
|
178
|
-
IntegrationType.WORKFLOW_PIPELINE: ["工作流", "流水线处理", "任务队列"],
|
|
179
|
-
IntegrationType.NOTIFICATION_HOOK: ["通知系统", "实时监控", "事件通知"],
|
|
180
|
-
IntegrationType.MCP_SERVER: ["MCP协议", "服务器集成", "自定义代理"],
|
|
181
|
-
IntegrationType.SLASH_COMMAND: ["斜杠命令", "快速执行", "参数化命令"]
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return capability_map.get(config_mapping.integration_type, ["通用AI助手"])
|
|
185
|
-
|
|
186
|
-
async def _check_config_file(self, config_file_path: str) -> bool:
|
|
187
|
-
"""检查配置文件是否存在"""
|
|
188
|
-
try:
|
|
189
|
-
expanded_path = Path(config_file_path.expanduser())
|
|
190
|
-
return expanded_path.exists() and expanded_path.is_file()
|
|
191
|
-
except Exception:
|
|
192
|
-
return False
|
|
193
|
-
|
|
194
|
-
def _get_display_name(self, cli_name: str) -> str:
|
|
195
|
-
"""获取CLI工具显示名称"""
|
|
196
|
-
display_names = {
|
|
197
|
-
"claude": "Claude CLI",
|
|
198
|
-
"gemini": "Gemini CLI",
|
|
199
|
-
"qwen": "QwenCode CLI",
|
|
200
|
-
"iflow": "iFlow CLI",
|
|
201
|
-
"qoder": "Qoder CLI",
|
|
202
|
-
"codebuddy": "CodeBuddy CLI",
|
|
203
|
-
"copilot": "Copilot CLI",
|
|
204
|
-
"codex": "Codex CLI"
|
|
205
|
-
}
|
|
206
|
-
return display_names.get(cli_name, cli_name.upper())
|
|
207
|
-
|
|
208
|
-
async def _scan_project_clis(self, project_path: str) -> Dict[str, ProjectCLIInfo]:
|
|
209
|
-
"""扫描项目特定的AI配置"""
|
|
210
|
-
project_clis = {}
|
|
211
|
-
|
|
212
|
-
# 检查项目目录下的AI配置文件
|
|
213
|
-
ai_config_files = [
|
|
214
|
-
".ai-config.json",
|
|
215
|
-
".claude-project.json",
|
|
216
|
-
".gemini-project.json",
|
|
217
|
-
".qwen-project.json",
|
|
218
|
-
".iflow-project.json",
|
|
219
|
-
"ai-workflow.yml",
|
|
220
|
-
".ai-cli-unified.json"
|
|
221
|
-
]
|
|
222
|
-
|
|
223
|
-
for config_file in ai_config_files:
|
|
224
|
-
config_path = Path(project_path) / config_file
|
|
225
|
-
if config_path.exists():
|
|
226
|
-
try:
|
|
227
|
-
cli_configs = await self._parse_project_config(config_path)
|
|
228
|
-
project_clis.update(cli_configs)
|
|
229
|
-
logger.info(f"发现项目配置文件: {config_file}")
|
|
230
|
-
except Exception as e:
|
|
231
|
-
logger.warning(f"解析项目配置文件 {config_file} 失败: {e}")
|
|
232
|
-
|
|
233
|
-
logger.info(f"发现 {len(project_clis)} 个项目特定CLI配置")
|
|
234
|
-
return project_clis
|
|
235
|
-
|
|
236
|
-
async def _parse_project_config(self, config_path: Path) -> Dict[str, ProjectCLIInfo]:
|
|
237
|
-
"""解析项目配置文件"""
|
|
238
|
-
project_clis = {}
|
|
239
|
-
|
|
240
|
-
try:
|
|
241
|
-
with open(config_path, 'r', encoding='utf-8') as f:
|
|
242
|
-
if config_path.suffix.lower() in ['.yml', '.yaml']:
|
|
243
|
-
config_data = yaml.safe_load(f)
|
|
244
|
-
else:
|
|
245
|
-
config_data = json.load(f)
|
|
246
|
-
|
|
247
|
-
# 根据配置文件格式解析CLI配置
|
|
248
|
-
if 'ai_tools' in config_data:
|
|
249
|
-
for cli_name, cli_config in config_data['ai_tools'].items():
|
|
250
|
-
project_clis[cli_name] = ProjectCLIInfo(
|
|
251
|
-
cli_name=cli_name,
|
|
252
|
-
project_config=cli_config,
|
|
253
|
-
custom_settings=cli_config.get('custom_settings', {}),
|
|
254
|
-
enabled_features=cli_config.get('enabled_features', [])
|
|
255
|
-
)
|
|
256
|
-
elif 'tools' in config_data:
|
|
257
|
-
# 简化格式
|
|
258
|
-
for cli_config in config_data['tools']:
|
|
259
|
-
cli_name = cli_config.get('name')
|
|
260
|
-
if cli_name:
|
|
261
|
-
project_clis[cli_name] = ProjectCLIInfo(
|
|
262
|
-
cli_name=cli_name,
|
|
263
|
-
project_config=cli_config,
|
|
264
|
-
custom_settings=cli_config.get('custom_settings', {}),
|
|
265
|
-
enabled_features=cli_config.get('enabled_features', [])
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
except Exception as e:
|
|
269
|
-
logger.error(f"解析配置文件失败 {config_path}: {e}")
|
|
270
|
-
raise
|
|
271
|
-
|
|
272
|
-
return project_clis
|
|
273
|
-
|
|
274
|
-
async def _generate_collaboration_guide(self,
|
|
275
|
-
global_clis: Dict[str, CLIInfo],
|
|
276
|
-
project_clis: Dict[str, ProjectCLIInfo]) -> CollaborationGuide:
|
|
277
|
-
"""生成协作指南"""
|
|
278
|
-
|
|
279
|
-
# 合并全局和项目特定的CLI信息
|
|
280
|
-
all_clis = {**global_clis}
|
|
281
|
-
for cli_name, project_cli in project_clis.items():
|
|
282
|
-
if cli_name in all_clis:
|
|
283
|
-
# 增强现有CLI信息
|
|
284
|
-
existing_cli = all_clis[cli_name]
|
|
285
|
-
# 可以在这里添加项目特定的增强信息
|
|
286
|
-
else:
|
|
287
|
-
# 项目特定的CLI可能不在全局列表中
|
|
288
|
-
logger.info(f"项目特定CLI {cli_name} 不在全局列表中")
|
|
289
|
-
|
|
290
|
-
# 确定当前CLI工具
|
|
291
|
-
current_cli = self.current_cli or "claude" # 默认值
|
|
292
|
-
|
|
293
|
-
# 生成可协作的伙伴列表(排除当前CLI)
|
|
294
|
-
peer_clis = {k: v for k, v in all_clis.items() if k != current_cli}
|
|
295
|
-
|
|
296
|
-
# 生成协作示例
|
|
297
|
-
examples = self._generate_collaboration_examples(current_cli, peer_clis)
|
|
298
|
-
|
|
299
|
-
return CollaborationGuide(
|
|
300
|
-
current_cli=current_cli,
|
|
301
|
-
available_peers=peer_clis,
|
|
302
|
-
protocols=COLLABORATION_PROTOCOLS,
|
|
303
|
-
examples=examples
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
def _generate_collaboration_examples(self, current_cli: str, peer_clis: Dict[str, CLIInfo]) -> List[str]:
|
|
307
|
-
"""生成协作示例"""
|
|
308
|
-
examples = []
|
|
309
|
-
|
|
310
|
-
# 基于可用CLI工具生成示例
|
|
311
|
-
if "qwen" in peer_clis:
|
|
312
|
-
examples.append("请用qwen帮我生成单元测试")
|
|
313
|
-
examples.append("调用qwen来重构这个函数")
|
|
314
|
-
|
|
315
|
-
if "gemini" in peer_clis:
|
|
316
|
-
examples.append("用gemini分析代码性能")
|
|
317
|
-
examples.append("让gemini优化这个查询")
|
|
318
|
-
|
|
319
|
-
if "iflow" in peer_clis:
|
|
320
|
-
examples.append("用iflow创建CI/CD工作流")
|
|
321
|
-
examples.append("启动iflow部署流程")
|
|
322
|
-
|
|
323
|
-
if "claude" in peer_clis:
|
|
324
|
-
examples.append("请用claude审查这段代码")
|
|
325
|
-
examples.append("调用claude进行架构分析")
|
|
326
|
-
|
|
327
|
-
# 添加通用示例
|
|
328
|
-
examples.append("请用{tool}帮我{task}")
|
|
329
|
-
examples.append("call {tool} to {task}")
|
|
330
|
-
|
|
331
|
-
return examples
|