cloneloop 0.1.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.
Files changed (118) hide show
  1. cloneloop-0.1.0.dist-info/METADATA +392 -0
  2. cloneloop-0.1.0.dist-info/RECORD +118 -0
  3. cloneloop-0.1.0.dist-info/WHEEL +4 -0
  4. cloneloop-0.1.0.dist-info/entry_points.txt +5 -0
  5. mcp_ai_supervisor/__init__.py +52 -0
  6. mcp_ai_supervisor/__main__.py +551 -0
  7. mcp_ai_supervisor/debug.py +15 -0
  8. mcp_ai_supervisor/desktop_app/__init__.py +29 -0
  9. mcp_ai_supervisor/desktop_app/desktop_app.py +390 -0
  10. mcp_ai_supervisor/helpers/__init__.py +4 -0
  11. mcp_ai_supervisor/helpers/feedback_helpers.py +140 -0
  12. mcp_ai_supervisor/helpers/image_helpers.py +73 -0
  13. mcp_ai_supervisor/i18n.py +14 -0
  14. mcp_ai_supervisor/log_writer.py +16 -0
  15. mcp_ai_supervisor/logging/__init__.py +8 -0
  16. mcp_ai_supervisor/logging/log_parser.py +293 -0
  17. mcp_ai_supervisor/logging/log_tailer.py +367 -0
  18. mcp_ai_supervisor/py.typed +0 -0
  19. mcp_ai_supervisor/server.py +654 -0
  20. mcp_ai_supervisor/utils/__init__.py +28 -0
  21. mcp_ai_supervisor/utils/error_handler.py +10 -0
  22. mcp_ai_supervisor/utils/memory_monitor.py +11 -0
  23. mcp_ai_supervisor/utils/paths.py +7 -0
  24. mcp_ai_supervisor/utils/resource_manager.py +15 -0
  25. mcp_ai_supervisor/utils/structure_checker.py +291 -0
  26. mcp_ai_supervisor/web/__init__.py +26 -0
  27. mcp_ai_supervisor/web/constants/__init__.py +8 -0
  28. mcp_ai_supervisor/web/constants/message_codes.py +173 -0
  29. mcp_ai_supervisor/web/core/__init__.py +30 -0
  30. mcp_ai_supervisor/web/core/agent_bridge.py +957 -0
  31. mcp_ai_supervisor/web/core/auto_responder.py +332 -0
  32. mcp_ai_supervisor/web/core/context_collector.py +124 -0
  33. mcp_ai_supervisor/web/core/conversation_recorder.py +751 -0
  34. mcp_ai_supervisor/web/core/delegate_manager.py +1193 -0
  35. mcp_ai_supervisor/web/core/feedback_mediator.py +259 -0
  36. mcp_ai_supervisor/web/core/message_channel.py +551 -0
  37. mcp_ai_supervisor/web/core/response_builder.py +333 -0
  38. mcp_ai_supervisor/web/core/scene_detector.py +184 -0
  39. mcp_ai_supervisor/web/core/task_state.py +194 -0
  40. mcp_ai_supervisor/web/locales/en/translation.json +643 -0
  41. mcp_ai_supervisor/web/locales/zh-CN/translation.json +629 -0
  42. mcp_ai_supervisor/web/locales/zh-TW/translation.json +648 -0
  43. mcp_ai_supervisor/web/main.py +747 -0
  44. mcp_ai_supervisor/web/managers/__init__.py +8 -0
  45. mcp_ai_supervisor/web/managers/desktop_manager.py +228 -0
  46. mcp_ai_supervisor/web/managers/server_manager.py +170 -0
  47. mcp_ai_supervisor/web/managers/session_manager.py +515 -0
  48. mcp_ai_supervisor/web/managers/workbench_connector.py +186 -0
  49. mcp_ai_supervisor/web/models/__init__.py +13 -0
  50. mcp_ai_supervisor/web/models/feedback_result.py +16 -0
  51. mcp_ai_supervisor/web/models/feedback_session.py +938 -0
  52. mcp_ai_supervisor/web/models/session_cleaner.py +245 -0
  53. mcp_ai_supervisor/web/models/session_commands.py +213 -0
  54. mcp_ai_supervisor/web/models/session_resolver.py +158 -0
  55. mcp_ai_supervisor/web/models/session_timer.py +242 -0
  56. mcp_ai_supervisor/web/routes/__init__.py +12 -0
  57. mcp_ai_supervisor/web/routes/main_routes.py +965 -0
  58. mcp_ai_supervisor/web/routes/settings_routes.py +195 -0
  59. mcp_ai_supervisor/web/routes/ws_handlers.py +270 -0
  60. mcp_ai_supervisor/web/static/css/audio-management.css +545 -0
  61. mcp_ai_supervisor/web/static/css/notification-settings.css +152 -0
  62. mcp_ai_supervisor/web/static/css/prompt-management.css +566 -0
  63. mcp_ai_supervisor/web/static/css/session-management.css +1428 -0
  64. mcp_ai_supervisor/web/static/css/styles.css +2267 -0
  65. mcp_ai_supervisor/web/static/favicon.ico +0 -0
  66. mcp_ai_supervisor/web/static/icon-192.png +0 -0
  67. mcp_ai_supervisor/web/static/icon.svg +11 -0
  68. mcp_ai_supervisor/web/static/index.html +37 -0
  69. mcp_ai_supervisor/web/static/js/app.js +1721 -0
  70. mcp_ai_supervisor/web/static/js/i18n.js +376 -0
  71. mcp_ai_supervisor/web/static/js/modules/audio/audio-manager.js +610 -0
  72. mcp_ai_supervisor/web/static/js/modules/audio/audio-settings-ui.js +732 -0
  73. mcp_ai_supervisor/web/static/js/modules/connection-monitor.js +435 -0
  74. mcp_ai_supervisor/web/static/js/modules/constants/message-codes.js +168 -0
  75. mcp_ai_supervisor/web/static/js/modules/countdown-manager.js +273 -0
  76. mcp_ai_supervisor/web/static/js/modules/drag-drop-handler.js +677 -0
  77. mcp_ai_supervisor/web/static/js/modules/file-upload-manager.js +555 -0
  78. mcp_ai_supervisor/web/static/js/modules/image-handler.js +199 -0
  79. mcp_ai_supervisor/web/static/js/modules/logger.js +404 -0
  80. mcp_ai_supervisor/web/static/js/modules/notification/notification-manager.js +360 -0
  81. mcp_ai_supervisor/web/static/js/modules/notification/notification-settings.js +344 -0
  82. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-input-buttons.js +427 -0
  83. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-manager.js +414 -0
  84. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-modal.js +458 -0
  85. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-settings-ui.js +524 -0
  86. mcp_ai_supervisor/web/static/js/modules/session/session-data-manager.js +1042 -0
  87. mcp_ai_supervisor/web/static/js/modules/session/session-details-modal.js +594 -0
  88. mcp_ai_supervisor/web/static/js/modules/session/session-ui-renderer.js +836 -0
  89. mcp_ai_supervisor/web/static/js/modules/session-manager.js +1059 -0
  90. mcp_ai_supervisor/web/static/js/modules/settings-manager.js +1002 -0
  91. mcp_ai_supervisor/web/static/js/modules/tab-manager.js +235 -0
  92. mcp_ai_supervisor/web/static/js/modules/textarea-height-manager.js +267 -0
  93. mcp_ai_supervisor/web/static/js/modules/ui-manager.js +578 -0
  94. mcp_ai_supervisor/web/static/js/modules/utils/dom-utils.js +392 -0
  95. mcp_ai_supervisor/web/static/js/modules/utils/status-utils.js +403 -0
  96. mcp_ai_supervisor/web/static/js/modules/utils/time-utils.js +440 -0
  97. mcp_ai_supervisor/web/static/js/modules/utils.js +557 -0
  98. mcp_ai_supervisor/web/static/js/modules/websocket-manager.js +875 -0
  99. mcp_ai_supervisor/web/static/js/modules/workbench-notification.js +103 -0
  100. mcp_ai_supervisor/web/static/js/vendor/marked.min.js +6 -0
  101. mcp_ai_supervisor/web/static/js/vendor/purify.min.js +3 -0
  102. mcp_ai_supervisor/web/templates/components/image-upload.html +43 -0
  103. mcp_ai_supervisor/web/templates/components/settings-card.html +58 -0
  104. mcp_ai_supervisor/web/templates/components/status-indicator.html +31 -0
  105. mcp_ai_supervisor/web/templates/components/toggle-switch.html +19 -0
  106. mcp_ai_supervisor/web/templates/feedback.html +1198 -0
  107. mcp_ai_supervisor/web/templates/index.html +378 -0
  108. mcp_ai_supervisor/web/utils/__init__.py +12 -0
  109. mcp_ai_supervisor/web/utils/browser.py +35 -0
  110. mcp_ai_supervisor/web/utils/compression_config.py +195 -0
  111. mcp_ai_supervisor/web/utils/compression_monitor.py +314 -0
  112. mcp_ai_supervisor/web/utils/ide_opener.py +286 -0
  113. mcp_ai_supervisor/web/utils/network.py +66 -0
  114. mcp_ai_supervisor/web/utils/port_manager.py +340 -0
  115. mcp_ai_supervisor/web/utils/session_cleanup_manager.py +543 -0
  116. mcp_ai_supervisor/web/utils/workbench_client.py +899 -0
  117. mcp_ai_supervisor/workbench/__init__.py +5 -0
  118. mcp_ai_supervisor/workbench/__main__.py +5 -0
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 自动回复生成器模块(降级方案 D7)
4
+ ================================
5
+
6
+ 当 Agent(qodercli)不可用时的降级回复生成器。
7
+
8
+ - MCP_AGENT_ENABLED=false → 直接走 AutoResponder
9
+ - Agent 调用失败/超时 → 降级走 AutoResponder
10
+
11
+ 主要功能:
12
+ - 检测问句类型(确认、建议、选择、澄清等)
13
+ - 生成代码自检提示(当检测到代码变更时)
14
+ - 加载项目规范(.cursor/rules/ 或 .qoder/rules/)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from enum import Enum
20
+ from pathlib import Path
21
+
22
+ from .scene_detector import SceneResult
23
+ from ...debug import server_debug_log as debug_log
24
+
25
+
26
+ class QuestionType(Enum):
27
+ """问句类型枚举"""
28
+
29
+ NONE = "none" # 非问句
30
+ CONFIRMATION = "confirmation" # 确认类(是否、要不要)
31
+ SUGGESTION = "suggestion" # 建议类(我建议、推荐)
32
+ CHOICE = "choice" # 选择类(A还是B)
33
+ CLARIFICATION = "clarification" # 澄清类(什么意思、为什么)
34
+
35
+
36
+ class AutoResponder:
37
+ """
38
+ 自动回复生成器
39
+
40
+ 负责在 auto 模式下自动生成回复内容,包括:
41
+ - 检测问句类型并生成对应回复
42
+ - 检测代码变更并生成自检提示
43
+ - 加载项目规范并追加到响应中
44
+ """
45
+
46
+ def generate(
47
+ self,
48
+ scene: SceneResult,
49
+ summary: str,
50
+ intention: str,
51
+ project_dir: str = "",
52
+ ) -> str:
53
+ """
54
+ 生成自动回复内容
55
+
56
+ Args:
57
+ scene: 场景检测结果
58
+ summary: 工作摘要
59
+ intention: AI 意图/回复内容
60
+ project_dir: 项目目录路径(可选)
61
+
62
+ Returns:
63
+ str: 自动生成的回复内容
64
+
65
+ 逻辑:
66
+ - 如果 scene.has_code_changes 为 True:返回代码自检提示
67
+ - 否则:检测问句类型,返回对应回复
68
+ """
69
+ if scene.has_code_changes:
70
+ return self._build_code_review_prompt(scene, project_dir)
71
+
72
+ question_type = self._detect_question(summary, intention)
73
+ return self._get_response_for_question_type(question_type)
74
+
75
+ def _detect_question(self, summary: str, intention: str) -> QuestionType:
76
+ """
77
+ 检测问句类型
78
+
79
+ Args:
80
+ summary: 工作摘要
81
+ intention: AI 意图/回复内容
82
+
83
+ Returns:
84
+ QuestionType: 检测到的问句类型
85
+
86
+ 检测逻辑:
87
+ 1. 获取 intention 或 summary 的最后 300 字符
88
+ 2. 检测关键词匹配
89
+ 3. 检测问号标记
90
+ 4. 返回匹配的类型
91
+ """
92
+ # 获取最后 300 字符用于分析
93
+ text = intention if intention else summary
94
+ if not text:
95
+ return QuestionType.NONE
96
+
97
+ # 取最后 300 字符
98
+ analysis_text = text[-300:] if len(text) > 300 else text
99
+ analysis_text_lower = analysis_text.lower()
100
+
101
+ # 确认类关键词
102
+ confirmation_keywords = [
103
+ "是否",
104
+ "要不要",
105
+ "确认",
106
+ "可以吗",
107
+ "行吗",
108
+ "好吗",
109
+ "对吗",
110
+ "shall",
111
+ "should we",
112
+ "confirm",
113
+ ]
114
+
115
+ # 建议类关键词
116
+ suggestion_keywords = [
117
+ "建议",
118
+ "推荐",
119
+ "suggest",
120
+ "recommend",
121
+ "方案",
122
+ ]
123
+
124
+ # 选择类关键词
125
+ choice_keywords = [
126
+ "还是",
127
+ "或者",
128
+ "a方案",
129
+ "b方案",
130
+ "方案一",
131
+ "方案二",
132
+ "which",
133
+ "choose",
134
+ "option",
135
+ ]
136
+
137
+ # 澄清类关键词
138
+ clarification_keywords = [
139
+ "什么意思",
140
+ "为什么",
141
+ "怎么",
142
+ "如何",
143
+ "能解释",
144
+ "what",
145
+ "why",
146
+ "how",
147
+ "explain",
148
+ ]
149
+
150
+ # 检测关键词
151
+ for keyword in confirmation_keywords:
152
+ if keyword in analysis_text_lower:
153
+ return QuestionType.CONFIRMATION
154
+
155
+ for keyword in suggestion_keywords:
156
+ if keyword in analysis_text_lower:
157
+ return QuestionType.SUGGESTION
158
+
159
+ for keyword in choice_keywords:
160
+ if keyword in analysis_text_lower:
161
+ return QuestionType.CHOICE
162
+
163
+ for keyword in clarification_keywords:
164
+ if keyword in analysis_text_lower:
165
+ return QuestionType.CLARIFICATION
166
+
167
+ # 检测问号
168
+ has_question_mark = "?" in analysis_text or "?" in analysis_text
169
+
170
+ # 如果只有问号没有关键词,返回澄清类
171
+ if has_question_mark:
172
+ return QuestionType.CLARIFICATION
173
+
174
+ # 否则返回非问句
175
+ return QuestionType.NONE
176
+
177
+ def _get_response_for_question_type(self, question_type: QuestionType) -> str:
178
+ """
179
+ 根据问句类型返回对应回复
180
+
181
+ Args:
182
+ question_type: 问句类型
183
+
184
+ Returns:
185
+ str: 对应的回复内容
186
+ """
187
+ responses = {
188
+ QuestionType.CONFIRMATION: "是的,请继续执行。",
189
+ QuestionType.SUGGESTION: "好的,按照你建议的方案继续执行。",
190
+ QuestionType.CHOICE: "请根据你的专业判断选择最佳方案。",
191
+ QuestionType.CLARIFICATION: "请根据你的专业判断决定。",
192
+ QuestionType.NONE: "✅ 本次检查通过,请继续执行。",
193
+ }
194
+ return responses.get(question_type, "✅ 本次检查通过,请继续执行。")
195
+
196
+ def _build_code_review_prompt(
197
+ self, scene: SceneResult, project_dir: str = ""
198
+ ) -> str:
199
+ """
200
+ 构建代码自检提示
201
+
202
+ Args:
203
+ scene: 场景检测结果
204
+ project_dir: 项目目录路径(可选)
205
+
206
+ Returns:
207
+ str: 代码自检提示文本
208
+
209
+ 包含内容:
210
+ 1. 变更文件列表(最多10个)
211
+ 2. 4个自检检查点
212
+ 3. 项目规范(如果存在)
213
+ """
214
+ # 列出变更文件(最多10个)
215
+ changed_files = scene.changed_files[:10]
216
+ file_list_text = "\n".join(f"- {f}" for f in changed_files)
217
+
218
+ if len(scene.changed_files) > 10:
219
+ file_list_text += f"\n...等共 {len(scene.changed_files)} 个文件"
220
+
221
+ # 构建自检提示
222
+ prompt = f"""━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223
+ 🔍 【代码自检提示】
224
+
225
+ 检测到代码变更,请自检以下内容:
226
+
227
+ 📁 变更文件:
228
+ {file_list_text}
229
+
230
+ ✅ 请检查:
231
+ 1. 功能完整性:是否完成了要求的所有功能?
232
+ 2. 代码质量:是否有明显的 bug 或安全问题?
233
+ 3. 向后兼容:是否破坏了现有功能?
234
+ 4. 测试覆盖:关键逻辑是否有测试?
235
+
236
+ """
237
+
238
+ # 加载项目规范
239
+ project_rules = self._load_project_rules(project_dir)
240
+ if project_rules:
241
+ prompt += f"""📋 项目规范:
242
+ {project_rules}
243
+
244
+ """
245
+
246
+ prompt += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
247
+
248
+ return prompt
249
+
250
+ def _load_project_rules(self, project_dir: str) -> str:
251
+ """
252
+ 加载项目规范
253
+
254
+ Args:
255
+ project_dir: 项目目录路径
256
+
257
+ Returns:
258
+ str: 项目规范内容(限制2000字符),如果不存在或出错则返回空字符串
259
+
260
+ 加载逻辑:
261
+ 1. 优先检查 .cursor/rules/ 目录
262
+ 2. 其次检查 .qoder/rules/ 目录
263
+ 3. 读取所有 .md、.mdc、.txt 文件
264
+ 4. 拼接内容,限制总长度 2000 字符
265
+ """
266
+ if not project_dir:
267
+ return ""
268
+
269
+ try:
270
+ project_path = Path(project_dir)
271
+ if not project_path.exists():
272
+ debug_log(f"项目目录不存在: {project_dir}")
273
+ return ""
274
+
275
+ # 优先检查 .cursor/rules/
276
+ rules_dirs = [
277
+ project_path / ".cursor" / "rules",
278
+ project_path / ".qoder" / "rules",
279
+ ]
280
+
281
+ rules_dir = None
282
+ for dir_path in rules_dirs:
283
+ if dir_path.exists() and dir_path.is_dir():
284
+ rules_dir = dir_path
285
+ debug_log(f"找到项目规范目录: {rules_dir}")
286
+ break
287
+
288
+ if not rules_dir:
289
+ debug_log("未找到项目规范目录")
290
+ return ""
291
+
292
+ # 读取所有 .md、.mdc、.txt 文件
293
+ rule_files = []
294
+ for ext in [".md", ".mdc", ".txt"]:
295
+ rule_files.extend(rules_dir.glob(f"*{ext}"))
296
+
297
+ if not rule_files:
298
+ debug_log("项目规范目录中没有找到规则文件")
299
+ return ""
300
+
301
+ # 拼接内容
302
+ content_parts = []
303
+ total_length = 0
304
+ max_length = 2000
305
+
306
+ for rule_file in sorted(rule_files):
307
+ try:
308
+ file_content = rule_file.read_text(encoding="utf-8")
309
+ file_length = len(file_content)
310
+
311
+ # 如果加上这个文件会超过限制,只取部分内容
312
+ if total_length + file_length > max_length:
313
+ remaining = max_length - total_length
314
+ if remaining > 100: # 至少保留100字符才有意义
315
+ content_parts.append(file_content[:remaining])
316
+ content_parts.append("\n...")
317
+ break
318
+
319
+ content_parts.append(f"# {rule_file.name}\n{file_content}\n")
320
+ total_length += file_length + len(rule_file.name) + 3
321
+
322
+ except Exception as e:
323
+ debug_log(f"读取规则文件失败 {rule_file}: {e}")
324
+ continue
325
+
326
+ result = "\n".join(content_parts)
327
+ debug_log(f"成功加载项目规范,总长度: {len(result)} 字符")
328
+ return result
329
+
330
+ except Exception as e:
331
+ debug_log(f"加载项目规范时出错: {e}")
332
+ return ""
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 项目上下文收集器模块
4
+ ===================
5
+
6
+ 为 Agent 提供完整的监督上下文,包括:
7
+ - 项目规则和技能目录路径(D64:不读内容,传路径)
8
+ - AI 当前轮行为数据(summary/intention/changed_files)
9
+ - 任务状态迭代信息
10
+
11
+ 设计决策参考:
12
+ - D25: 仅传当前轮 summary/intention
13
+ - D36: diff 统一传路径委托 Agent
14
+ - D52: changed_files_all 上限 200
15
+ - D64: Prompt 只传路径和元数据
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import os
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ContextCollector:
27
+ """项目上下文收集器,为 Agent 提供完整的监督上下文"""
28
+
29
+ MAX_CHANGED_FILES = 200
30
+
31
+ def __init__(self, project_dir: str) -> None:
32
+ self.project_dir = project_dir
33
+ self.project_rules_dir: str = ""
34
+ self.project_skills_dirs: list[str] = []
35
+ self.sub_agent_skill_paths: list[str] = []
36
+ self.current_summary: str = ""
37
+ self.current_intention: str = ""
38
+ self.changed_files_all: list[str] = []
39
+ self._current_changed: list[str] = []
40
+ self._dirs_scanned: bool = False
41
+
42
+ def update(
43
+ self,
44
+ summary: str,
45
+ intention: str,
46
+ changed_files: list[str] | None,
47
+ ) -> None:
48
+ """每轮调用时更新当前轮数据(D25: 不累计历史 summary/intention)"""
49
+ logger.debug(
50
+ "[ContextCollector] update: summary=%d intention=%d files=%d",
51
+ len(summary or ""),
52
+ len(intention or ""),
53
+ len(changed_files or []),
54
+ )
55
+ self.current_summary = summary
56
+ self.current_intention = intention
57
+ self._current_changed = list(changed_files) if changed_files else []
58
+ # D52/R23: 有序列表去重,保留最后一次出现的位置
59
+ for f in self._current_changed:
60
+ if f in self.changed_files_all:
61
+ self.changed_files_all.remove(f)
62
+ self.changed_files_all.append(f)
63
+ if len(self.changed_files_all) > self.MAX_CHANGED_FILES:
64
+ self.changed_files_all = self.changed_files_all[-self.MAX_CHANGED_FILES :]
65
+
66
+ async def collect(self, task_state) -> dict:
67
+ """收集完整上下文,输出结构化 dict(D64: 只输出路径元数据)。"""
68
+ logger.info("[ContextCollector] collect 开始: project=%s", self.project_dir)
69
+ if not self._dirs_scanned:
70
+ self._scan_project_dirs()
71
+ self._dirs_scanned = True
72
+ logger.debug(
73
+ "[ContextCollector] collect 完成: rules=%s, all_files=%d",
74
+ self.project_rules_dir or "N/A",
75
+ len(self.changed_files_all),
76
+ )
77
+ return {
78
+ "project_dir": self.project_dir,
79
+ "original_task": task_state.original_task,
80
+ "retry_count": task_state.retry_count,
81
+ "task_phase": task_state.task_phase.value,
82
+ "completion_attempts": task_state.agent_deny_count,
83
+ "agent_deny_count": task_state.agent_deny_count,
84
+ "current_summary": self.current_summary,
85
+ "current_intention": self.current_intention,
86
+ "changed_files_this_round": list(self._current_changed),
87
+ "changed_files_all": list(self.changed_files_all),
88
+ "project_rules_dir": self.project_rules_dir,
89
+ "project_skills_dirs": self.project_skills_dirs,
90
+ "sub_agent_skill_paths": list(self.sub_agent_skill_paths),
91
+ }
92
+
93
+ # ─── 项目目录扫描(T1.1)──────────────────────────────
94
+
95
+ def _scan_project_dirs(self) -> None:
96
+ """扫描项目目录,记录规则和技能目录路径(D64: 只传路径,不读内容)"""
97
+ logger.debug("[ContextCollector] 扫描项目目录: %s", self.project_dir)
98
+ rules_dir = os.path.join(self.project_dir, ".cursor", "rules")
99
+ if os.path.isdir(rules_dir):
100
+ self.project_rules_dir = rules_dir
101
+
102
+ for skills_dir in [
103
+ os.path.join(self.project_dir, ".cursor", "skills"),
104
+ os.path.join(self.project_dir, ".agents", "skills"),
105
+ ]:
106
+ if os.path.isdir(skills_dir):
107
+ self.project_skills_dirs.append(skills_dir)
108
+
109
+ self.sub_agent_skill_paths = []
110
+ scan_dirs = list(self.project_skills_dirs)
111
+ global_qoder_skills = os.path.join(os.path.expanduser("~"), ".qoder", "skills")
112
+ if os.path.isdir(global_qoder_skills) and global_qoder_skills not in scan_dirs:
113
+ scan_dirs.append(global_qoder_skills)
114
+
115
+ seen_skill_names: set[str] = set()
116
+ for skills_dir in scan_dirs:
117
+ if not os.path.isdir(skills_dir):
118
+ continue
119
+ for entry in sorted(os.listdir(skills_dir)):
120
+ base_name = entry.removeprefix("tbj_")
121
+ skill_file = os.path.join(skills_dir, entry, "SKILL.md")
122
+ if os.path.isfile(skill_file) and base_name not in seen_skill_names:
123
+ self.sub_agent_skill_paths.append(skill_file)
124
+ seen_skill_names.add(base_name)