jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.6__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 (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +6 -12
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -5,13 +5,14 @@
5
5
  """
6
6
 
7
7
 
8
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
9
+ import json
8
10
  import os
9
11
  import re
10
- import yaml
11
- from typing import List, Optional, Dict, Any, Set
12
+ from typing import List, Optional, Any
12
13
 
14
+ from rich.console import Console
13
15
  from jarvis.jarvis_platform.registry import PlatformRegistry
14
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
15
16
  from jarvis.jarvis_utils.config import get_normal_platform_name, get_normal_model_name
16
17
  from jarvis.jarvis_code_agent.utils import get_project_overview
17
18
 
@@ -60,6 +61,7 @@ class ContextRecommender:
60
61
 
61
62
  # 优先根据 model_group 获取配置(确保配置一致性)
62
63
  # 如果 model_group 存在,强制使用它来解析,避免使用 parent_model 中可能不一致的值
64
+ # 使用cheap平台,上下文推荐可以降低成本
63
65
  if model_group:
64
66
  try:
65
67
  platform_name = get_normal_platform_name(model_group)
@@ -72,10 +74,10 @@ class ContextRecommender:
72
74
  if platform_name:
73
75
  self.llm_model = registry.create_platform(platform_name)
74
76
  if self.llm_model is None:
75
- # 如果创建失败,使用默认平台
76
- self.llm_model = registry.get_normal_platform()
77
+ # 如果创建失败,使用cheap平台
78
+ self.llm_model = registry.get_cheap_platform()
77
79
  else:
78
- self.llm_model = registry.get_normal_platform()
80
+ self.llm_model = registry.get_cheap_platform()
79
81
 
80
82
  # 先设置模型组(如果从父Agent获取到),因为 model_group 可能会影响模型名称的解析
81
83
  if model_group and self.llm_model:
@@ -111,37 +113,66 @@ class ContextRecommender:
111
113
  Returns:
112
114
  ContextRecommendation: 推荐的上下文信息
113
115
  """
114
- # 1. 使用LLM提取关键词(仅提取关键词)
115
- keywords = self._extract_keywords_with_llm(user_input)
116
+ print("🔍 开始智能上下文推荐分析...")
117
+
118
+ # 0. 检查并填充符号表(如果为空)
119
+ self._ensure_symbol_table_loaded()
120
+
121
+ # 1. 使用LLM生成相关符号名
122
+ model_name = self.llm_model.name() if self.llm_model else "LLM"
123
+ print(f"📝 正在使用{model_name}生成相关符号名...")
124
+ symbol_names = self._extract_symbol_names_with_llm(user_input)
125
+ if symbol_names:
126
+ print(f"✅ 生成 {len(symbol_names)} 个符号名: {', '.join(symbol_names[:5])}{'...' if len(symbol_names) > 5 else ''}")
127
+ else:
128
+ print("⚠️ 未能生成符号名,将使用基础搜索策略")
116
129
 
117
130
  # 2. 初始化推荐结果
118
131
  recommended_symbols: List[Symbol] = []
119
132
 
120
- # 3. 基于关键词进行符号查找和文本查找,然后使用LLM挑选关联度高的条目(主要推荐方式)
121
- if keywords:
122
- # 3.1 使用关键词进行符号查找和文本查找,找到所有候选符号及其位置
123
- candidate_symbols = self._search_symbols_by_keywords(keywords)
124
- candidate_symbols_from_text = self._search_text_by_keywords(keywords)
133
+ # 3. 基于符号名进行符号查找,然后使用LLM挑选关联度高的条目(主要推荐方式)
134
+ if symbol_names:
135
+ # 3.1 使用符号名进行精确查找,找到所有候选符号及其位置
136
+ print("🔎 正在基于符号名搜索相关符号...")
137
+ candidate_symbols = self._search_symbols_by_names(symbol_names)
125
138
 
126
- # 合并候选符号(去重)
127
- all_candidates = {}
128
- for symbol in candidate_symbols + candidate_symbols_from_text:
129
- # 使用 (file_path, name, line_start) 作为唯一键
130
- key = (symbol.file_path, symbol.name, symbol.line_start)
131
- if key not in all_candidates:
132
- all_candidates[key] = symbol
139
+ print(f"📊 符号名匹配: {len(candidate_symbols)} 个候选")
133
140
 
134
- candidate_symbols_list = list(all_candidates.values())
141
+ candidate_symbols_list = candidate_symbols
142
+ print(f"📦 共 {len(candidate_symbols_list)} 个候选符号")
135
143
 
136
144
  # 3.2 使用LLM从候选符号中挑选关联度高的条目
137
145
  if candidate_symbols_list:
146
+ model_name = self.llm_model.name() if self.llm_model else "LLM"
147
+ print(f"🤖 正在使用{model_name}从 {len(candidate_symbols_list)} 个候选符号中筛选最相关的条目...")
138
148
  selected_symbols = self._select_relevant_symbols_with_llm(
139
- user_input, keywords, candidate_symbols_list
149
+ user_input, symbol_names, candidate_symbols_list
140
150
  )
141
151
  recommended_symbols.extend(selected_symbols)
142
-
143
- # 4. 限制符号数量
144
- final_symbols = recommended_symbols[:10]
152
+ print(f"✅ {model_name}筛选完成,选中 {len(selected_symbols)} 个相关符号")
153
+ else:
154
+ print("⚠️ 没有找到候选符号")
155
+ else:
156
+ print("⚠️ 无符号名可用,跳过符号推荐")
157
+
158
+ # 4. 对推荐符号去重(基于 name + file_path + line_start)
159
+ seen = set()
160
+ unique_symbols = []
161
+ for symbol in recommended_symbols:
162
+ key = (symbol.name, symbol.file_path, symbol.line_start)
163
+ if key not in seen:
164
+ seen.add(key)
165
+ unique_symbols.append(symbol)
166
+
167
+ if len(unique_symbols) < len(recommended_symbols):
168
+ print(f"🔄 去重: {len(recommended_symbols)} -> {len(unique_symbols)} 个符号")
169
+
170
+ # 5. 限制符号数量
171
+ final_symbols = unique_symbols[:10]
172
+ if len(unique_symbols) > 10:
173
+ print(f"📌 推荐结果已限制为前 10 个符号(共 {len(unique_symbols)} 个)")
174
+
175
+ print(f"✨ 上下文推荐完成,共推荐 {len(final_symbols)} 个符号")
145
176
 
146
177
  return ContextRecommendation(
147
178
  recommended_symbols=final_symbols,
@@ -155,166 +186,274 @@ class ContextRecommender:
155
186
  """
156
187
  return get_project_overview(self.context_manager.project_root)
157
188
 
158
- def _extract_keywords_with_llm(self, user_input: str) -> List[str]:
159
- """使用LLM提取关键词(仅提取关键词)
189
+ def _ensure_symbol_table_loaded(self) -> None:
190
+ """确保符号表已加载(如果为空则扫描项目文件)
191
+
192
+ 在推荐上下文之前,需要确保符号表已经被填充。
193
+ 如果符号表为空,则扫描项目文件并填充符号表。
194
+ """
195
+ # 检查符号表是否为空
196
+ if not self.context_manager.symbol_table.symbols_by_name:
197
+ print("📚 符号表为空,开始扫描项目文件构建符号表...")
198
+ self._build_symbol_table()
199
+ else:
200
+ symbol_count = sum(len(symbols) for symbols in self.context_manager.symbol_table.symbols_by_name.values())
201
+ print(f"📚 符号表已就绪,包含 {symbol_count} 个符号")
202
+
203
+ def _build_symbol_table(self) -> None:
204
+ """扫描项目文件并构建符号表
205
+
206
+ 遍历项目目录,提取所有支持语言的符号。
207
+ """
208
+ import os
209
+ from .language_support import detect_language, get_symbol_extractor
210
+ from .file_ignore import filter_walk_dirs
211
+
212
+ console = Console()
213
+ project_root = self.context_manager.project_root
214
+ files_scanned = 0
215
+ symbols_added = 0
216
+ files_with_symbols = 0
217
+ files_skipped = 0
218
+
219
+ # 用于清除行的最大宽度(终端通常80-120字符,使用100作为安全值)
220
+ max_line_width = 100
221
+
222
+ # 快速统计总文件数(用于进度显示)
223
+ console.print("📊 正在统计项目文件...", end="")
224
+ total_files = 0
225
+ for root, dirs, files in os.walk(project_root):
226
+ dirs[:] = filter_walk_dirs(dirs)
227
+ for file in files:
228
+ file_path = os.path.join(root, file)
229
+ language = detect_language(file_path)
230
+ if language and get_symbol_extractor(language):
231
+ total_files += 1
232
+ console.print(" 完成") # 统计完成,换行
233
+
234
+ # 进度反馈间隔(每处理这么多文件输出一次,最多每10个文件输出一次)
235
+ # progress_interval = max(1, min(total_files // 20, 10)) if total_files > 0 else 10
236
+
237
+ if total_files > 0:
238
+ console.print(f"📁 发现 {total_files} 个代码文件,开始扫描...")
239
+ else:
240
+ console.print("⚠️ 未发现可扫描的代码文件", style="yellow")
241
+ return
242
+
243
+ # 辅助函数:生成固定宽度的进度字符串(避免残留字符)
244
+ def format_progress_msg(current_file: str, scanned: int, total: int, symbols: int, skipped: int) -> str:
245
+ progress_pct = (scanned * 100) // total if total > 0 else 0
246
+ base_msg = f"⏳ 扫描进度: {scanned}/{total} ({progress_pct}%)"
247
+ if symbols > 0:
248
+ base_msg += f",已提取 {symbols} 个符号"
249
+ if skipped > 0:
250
+ base_msg += f",跳过 {skipped}"
251
+ base_msg += f" | {current_file}"
252
+ # 填充空格到固定宽度,清除旧内容
253
+ if len(base_msg) < max_line_width:
254
+ base_msg += " " * (max_line_width - len(base_msg))
255
+ return base_msg
256
+
257
+ # 遍历项目目录
258
+ for root, dirs, files in os.walk(project_root):
259
+ # 过滤需要忽略的目录
260
+ dirs[:] = filter_walk_dirs(dirs)
261
+
262
+ for file in files:
263
+ file_path = os.path.join(root, file)
264
+
265
+ # 检测语言
266
+ language = detect_language(file_path)
267
+ if not language:
268
+ continue
269
+
270
+ # 获取符号提取器
271
+ extractor = get_symbol_extractor(language)
272
+ if not extractor:
273
+ continue
274
+
275
+ # 获取相对路径用于显示(限制长度)
276
+ try:
277
+ rel_path = os.path.relpath(file_path, project_root)
278
+ # 如果路径太长,只显示文件名
279
+ if len(rel_path) > 40:
280
+ rel_path = "..." + rel_path[-37:]
281
+ except Exception:
282
+ rel_path = file
283
+
284
+ # 读取文件内容(跳过超大文件,避免内存问题)
285
+ try:
286
+ # 检查文件大小(超过 1MB 的文件跳过)
287
+ file_size = os.path.getsize(file_path)
288
+ if file_size > 1024 * 1024: # 1MB
289
+ files_skipped += 1
290
+ # 实时更新进度(不换行,文件名在最后)
291
+ msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
292
+ console.print(msg, end="\r")
293
+ continue
294
+
295
+ # 显示当前正在扫描的文件
296
+ msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
297
+ console.print(msg, end="\r")
298
+
299
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
300
+ content = f.read()
301
+ if not content:
302
+ continue
303
+
304
+ # 提取符号
305
+ symbols = extractor.extract_symbols(file_path, content)
306
+ if symbols:
307
+ files_with_symbols += 1
308
+ for symbol in symbols:
309
+ # 不立即保存缓存,批量添加以提高性能
310
+ self.context_manager.symbol_table.add_symbol(symbol, save_to_cache=False)
311
+ symbols_added += 1
312
+
313
+ # 更新文件修改时间
314
+ try:
315
+ self.context_manager.symbol_table._file_mtimes[file_path] = os.path.getmtime(file_path)
316
+ except Exception:
317
+ pass
318
+
319
+ files_scanned += 1
320
+
321
+ # 实时更新进度(不换行,文件名在最后)
322
+ msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
323
+ console.print(msg, end="\r")
324
+ except Exception:
325
+ # 跳过无法读取的文件
326
+ files_skipped += 1
327
+ # 实时更新进度(不换行,文件名在最后)
328
+ msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
329
+ console.print(msg, end="\r")
330
+ continue
331
+
332
+ # 完成时显示100%进度,然后换行并显示最终结果
333
+ if total_files > 0:
334
+ # 清除进度行
335
+ console.print(" " * max_line_width, end="\r")
336
+ console.print() # 换行
337
+
338
+ # 批量保存缓存(扫描完成后一次性保存,提高性能)
339
+ try:
340
+ console.print("💾 正在保存符号表缓存...", end="\r")
341
+ self.context_manager.symbol_table.save_cache()
342
+ console.print("💾 符号表缓存已保存")
343
+ except Exception as e:
344
+ console.print(f"⚠️ 保存符号表缓存失败: {e}", style="yellow")
345
+
346
+ skip_msg = f",跳过 {files_skipped} 个文件" if files_skipped > 0 else ""
347
+ console.print(
348
+ f"✅ 符号表构建完成: 扫描 {files_scanned} 个文件{skip_msg},提取 {symbols_added} 个符号(来自 {files_with_symbols} 个文件)",
349
+ style="green"
350
+ )
351
+
352
+ def _extract_symbol_names_with_llm(self, user_input: str) -> List[str]:
353
+ """使用LLM生成相关符号名
160
354
 
161
355
  Args:
162
356
  user_input: 用户输入
163
357
 
164
358
  Returns:
165
- 关键词列表
359
+ 符号名列表
166
360
  """
167
- # 获取项目概况
361
+ # 获取项目概况和符号表信息
168
362
  project_overview = self._get_project_overview()
169
363
 
170
- prompt = f"""分析以下代码编辑任务,提取关键词。关键词应该是与任务相关的核心概念、技术术语、功能模块等。
364
+ # 获取所有可用的符号名(用于参考)
365
+ all_symbol_names = list(self.context_manager.symbol_table.symbols_by_name.keys())
366
+ symbol_names_sample = sorted(all_symbol_names)[:50] # 取前50个作为示例
367
+
368
+ prompt = f"""分析代码编辑任务,生成5-15个可能相关的符号名(函数名、类名、变量名等)。
171
369
 
172
370
  {project_overview}
173
371
 
174
- 任务描述:
175
- {user_input}
372
+ 任务描述:{user_input}
373
+
374
+ 符号名示例:{', '.join(symbol_names_sample[:30])}{'...' if len(symbol_names_sample) > 30 else ''}
176
375
 
177
- 请提取5-10个关键词,以YAML数组格式返回,并用<KEYWORDS>标签包裹。
178
- 只返回关键词数组,不要包含其他文字。
376
+ 要求:与任务直接相关,符合命名规范,尽量具体。
179
377
 
180
- 示例格式:
181
- <KEYWORDS>
182
- - data processing
183
- - validation
184
- - error handling
185
- - API endpoint
186
- - authentication
187
- </KEYWORDS>
378
+ 以Jsonnet数组格式返回,用<SYMBOL_NAMES>标签包裹。示例:
379
+ <SYMBOL_NAMES>
380
+ ["processData", "validateInput", "handleError"]
381
+ </SYMBOL_NAMES>
188
382
  """
189
383
 
190
384
  try:
191
385
  response = self._call_llm(prompt)
192
- # 从<KEYWORDS>标签中提取内容
386
+ # 从<SYMBOL_NAMES>标签中提取内容
193
387
  response = response.strip()
194
- yaml_match = re.search(r'<KEYWORDS>\s*(.*?)\s*</KEYWORDS>', response, re.DOTALL)
195
- if yaml_match:
196
- yaml_content = yaml_match.group(1).strip()
388
+ json_match = re.search(r'<SYMBOL_NAMES>\s*(.*?)\s*</SYMBOL_NAMES>', response, re.DOTALL)
389
+ if json_match:
390
+ json_content = json_match.group(1).strip()
197
391
  else:
198
392
  # 如果没有找到标签,尝试清理markdown代码块
199
- if response.startswith("```yaml"):
393
+ if response.startswith("```json"):
200
394
  response = response[7:]
201
395
  elif response.startswith("```"):
202
396
  response = response[3:]
203
397
  if response.endswith("```"):
204
398
  response = response[:-3]
205
- yaml_content = response.strip()
399
+ json_content = response.strip()
206
400
 
207
- keywords = yaml.safe_load(yaml_content)
208
- if not isinstance(keywords, list):
401
+ symbol_names = json_loads(json_content)
402
+ if not isinstance(symbol_names, list):
403
+ print("⚠️ LLM返回的符号名格式不正确,期望 Jsonnet 数组格式")
209
404
  return []
210
405
 
211
- # 过滤空字符串和过短的关键词
212
- keywords = [k.strip() for k in keywords if k and isinstance(k, str) and len(k.strip()) > 1]
213
- return keywords
406
+ # 过滤空字符串和过短的符号名
407
+ original_count = len(symbol_names)
408
+ symbol_names = [name.strip() for name in symbol_names if name and isinstance(name, str) and len(name.strip()) > 0]
409
+ if original_count != len(symbol_names):
410
+ print(f"📋 过滤后保留 {len(symbol_names)} 个有效符号名(原始 {original_count} 个)")
411
+ return symbol_names
214
412
  except Exception as e:
215
413
  # 解析失败,返回空列表
216
- PrettyOutput.print(f"LLM关键词提取失败: {e}", OutputType.WARNING)
414
+ print(f"LLM符号名生成失败: {e}")
217
415
  return []
218
-
219
- def _search_symbols_by_keywords(self, keywords: List[str]) -> List[Symbol]:
220
- """基于关键词在符号表中查找相关符号
416
+ def _search_symbols_by_names(self, symbol_names: List[str]) -> List[Symbol]:
417
+ """基于符号名在符号表中精确查找相关符号
221
418
 
222
419
  Args:
223
- keywords: 关键词列表
420
+ symbol_names: 符号名列表
224
421
 
225
422
  Returns:
226
423
  候选符号列表
227
424
  """
228
- if not keywords:
425
+ if not symbol_names:
229
426
  return []
230
427
 
231
428
  found_symbols: List[Symbol] = []
232
- keywords_lower = [k.lower() for k in keywords]
233
429
  found_symbol_keys = set() # 用于去重,使用 (file_path, name, line_start) 作为键
234
430
 
235
- # 遍历所有符号,查找名称或签名中包含关键词的符号
431
+ # 创建符号名映射(支持大小写不敏感匹配)
432
+ symbol_names_lower = {name.lower(): name for name in symbol_names}
433
+
434
+ # 遍历所有符号,精确匹配符号名
236
435
  for symbol_name, symbols in self.context_manager.symbol_table.symbols_by_name.items():
237
436
  symbol_name_lower = symbol_name.lower()
238
437
 
239
- # 检查符号名称是否包含任何关键词
240
- name_matched = False
241
- for keyword in keywords_lower:
242
- if keyword in symbol_name_lower:
243
- # 找到匹配的符号,添加所有同名符号(可能有重载)
244
- for symbol in symbols:
245
- key = (symbol.file_path, symbol.name, symbol.line_start)
246
- if key not in found_symbol_keys:
247
- found_symbols.append(symbol)
248
- found_symbol_keys.add(key)
249
- name_matched = True
250
- break
251
-
252
- # 如果名称不匹配,检查符号签名是否包含关键词
253
- if not name_matched:
438
+ # 精确匹配:检查符号名是否在目标列表中(大小写不敏感)
439
+ if symbol_name_lower in symbol_names_lower:
440
+ # 找到匹配的符号,添加所有同名符号(可能有重载)
254
441
  for symbol in symbols:
255
- if symbol.signature:
256
- signature_lower = symbol.signature.lower()
257
- for keyword in keywords_lower:
258
- if keyword in signature_lower:
259
- key = (symbol.file_path, symbol.name, symbol.line_start)
260
- if key not in found_symbol_keys:
261
- found_symbols.append(symbol)
262
- found_symbol_keys.add(key)
263
- break
264
-
265
- return found_symbols
266
-
267
- def _search_text_by_keywords(self, keywords: List[str]) -> List[Symbol]:
268
- """基于关键词在文件内容中进行文本查找,找到相关符号
269
-
270
- Args:
271
- keywords: 关键词列表
272
-
273
- Returns:
274
- 候选符号列表(在包含关键词的文件中找到的符号)
275
- """
276
- if not keywords:
277
- return []
278
-
279
- found_symbols: List[Symbol] = []
280
- keywords_lower = [k.lower() for k in keywords]
281
-
282
- # 获取所有已分析的文件
283
- all_files = set()
284
- for symbol_name, symbols in self.context_manager.symbol_table.symbols_by_name.items():
285
- for symbol in symbols:
286
- all_files.add(symbol.file_path)
287
-
288
- # 在文件内容中搜索关键词
289
- for file_path in all_files:
290
- content = self.context_manager._get_file_content(file_path)
291
- if not content:
292
- continue
293
-
294
- content_lower = content.lower()
295
-
296
- # 检查文件内容是否包含任何关键词
297
- file_matches = False
298
- for keyword in keywords_lower:
299
- if keyword in content_lower:
300
- file_matches = True
301
- break
302
-
303
- if file_matches:
304
- # 获取该文件中的所有符号
305
- file_symbols = self.context_manager.symbol_table.get_file_symbols(file_path)
306
- found_symbols.extend(file_symbols)
442
+ key = (symbol.file_path, symbol.name, symbol.line_start)
443
+ if key not in found_symbol_keys:
444
+ found_symbols.append(symbol)
445
+ found_symbol_keys.add(key)
307
446
 
308
447
  return found_symbols
309
448
 
310
449
  def _select_relevant_symbols_with_llm(
311
- self, user_input: str, keywords: List[str], candidate_symbols: List[Symbol]
450
+ self, user_input: str, symbol_names: List[str], candidate_symbols: List[Symbol]
312
451
  ) -> List[Symbol]:
313
452
  """使用LLM从候选符号中挑选关联度高的条目
314
453
 
315
454
  Args:
316
455
  user_input: 用户输入/任务描述
317
- keywords: 关键词列表
456
+ symbol_names: 符号名列表
318
457
  candidate_symbols: 候选符号列表(包含位置信息)
319
458
 
320
459
  Returns:
@@ -325,6 +464,8 @@ class ContextRecommender:
325
464
 
326
465
  # 限制候选符号数量,避免prompt过长
327
466
  candidates_to_consider = candidate_symbols[:100] # 最多100个候选
467
+ if len(candidate_symbols) > 100:
468
+ print(f"📌 候选符号数量较多({len(candidate_symbols)} 个),限制为前 100 个进行LLM筛选")
328
469
 
329
470
  # 构建带编号的符号信息列表(包含位置信息)
330
471
  symbol_info_list = []
@@ -342,25 +483,17 @@ class ContextRecommender:
342
483
  # 获取项目概况
343
484
  project_overview = self._get_project_overview()
344
485
 
345
- prompt = f"""根据以下任务描述和关键词,从候选符号列表中选择最相关的符号。
486
+ prompt = f"""根据任务描述和生成的符号名,从候选符号列表中选择最相关的10-20个符号。
346
487
 
347
488
  {project_overview}
348
489
 
349
490
  任务描述:{user_input}
350
- 关键词:{', '.join(keywords)}
351
-
352
- 候选符号列表(已编号,包含位置信息):
353
- {yaml.dump(symbol_info_list, allow_unicode=True, default_flow_style=False)}
354
-
355
- 请返回最相关的10-20个符号的序号(YAML数组格式),按相关性排序,并用<SELECTED_INDICES>标签包裹。
491
+ 生成的符号名:{', '.join(symbol_names)}
492
+ 候选符号列表(已编号):{json.dumps(symbol_info_list, ensure_ascii=False, indent=2)}
356
493
 
357
- 只返回序号数组,例如:
494
+ 返回最相关符号的序号(Jsonnet数组),按相关性排序,用<SELECTED_INDICES>标签包裹。示例:
358
495
  <SELECTED_INDICES>
359
- - 3
360
- - 7
361
- - 12
362
- - 15
363
- - 23
496
+ [3, 7, 12, 15, 23]
364
497
  </SELECTED_INDICES>
365
498
  """
366
499
 
@@ -368,35 +501,52 @@ class ContextRecommender:
368
501
  response = self._call_llm(prompt)
369
502
  # 从<SELECTED_INDICES>标签中提取内容
370
503
  response = response.strip()
371
- yaml_match = re.search(r'<SELECTED_INDICES>\s*(.*?)\s*</SELECTED_INDICES>', response, re.DOTALL)
372
- if yaml_match:
373
- yaml_content = yaml_match.group(1).strip()
504
+ json_match = re.search(r'<SELECTED_INDICES>\s*(.*?)\s*</SELECTED_INDICES>', response, re.DOTALL)
505
+ if json_match:
506
+ json_content = json_match.group(1).strip()
374
507
  else:
375
508
  # 如果没有找到标签,尝试清理markdown代码块
376
- if response.startswith("```yaml"):
509
+ if response.startswith("```json"):
377
510
  response = response[7:]
378
511
  elif response.startswith("```"):
379
512
  response = response[3:]
380
513
  if response.endswith("```"):
381
514
  response = response[:-3]
382
- yaml_content = response.strip()
515
+ json_content = response.strip()
383
516
 
384
- selected_indices = yaml.safe_load(yaml_content)
517
+ selected_indices = json_loads(json_content)
385
518
  if not isinstance(selected_indices, list):
519
+ print("⚠️ LLM返回的符号序号格式不正确,期望 Jsonnet 数组格式")
386
520
  return []
387
521
 
522
+ print(f"📋 LLM返回了 {len(selected_indices)} 个符号序号")
523
+
388
524
  # 根据序号查找对应的符号对象
389
525
  selected_symbols = []
526
+ invalid_indices = []
390
527
  for idx in selected_indices:
391
528
  # 序号从1开始,转换为列表索引(从0开始)
392
529
  if isinstance(idx, int) and 1 <= idx <= len(candidates_to_consider):
393
530
  symbol = candidates_to_consider[idx - 1]
394
531
  selected_symbols.append(symbol)
532
+ else:
533
+ invalid_indices.append(idx)
534
+
535
+ if invalid_indices:
536
+ print(f"⚠️ 发现 {len(invalid_indices)} 个无效序号: {invalid_indices[:5]}{'...' if len(invalid_indices) > 5 else ''}")
537
+
538
+ if selected_symbols:
539
+ # 统计选中的符号类型分布
540
+ kind_count = {}
541
+ for symbol in selected_symbols:
542
+ kind_count[symbol.kind] = kind_count.get(symbol.kind, 0) + 1
543
+ kind_summary = ", ".join([f"{kind}: {count}" for kind, count in sorted(kind_count.items())])
544
+ print(f"📊 选中符号类型分布: {kind_summary}")
395
545
 
396
546
  return selected_symbols
397
547
  except Exception as e:
398
548
  # 解析失败,返回空列表
399
- PrettyOutput.print(f"LLM符号筛选失败: {e}", OutputType.WARNING)
549
+ print(f"LLM符号筛选失败: {e}")
400
550
  return []
401
551
 
402
552
  def _call_llm(self, prompt: str) -> str:
@@ -415,12 +565,16 @@ class ContextRecommender:
415
565
  # 使用chat_until_success方法(BasePlatform的标准接口)
416
566
  if hasattr(self.llm_model, 'chat_until_success'):
417
567
  response = self.llm_model.chat_until_success(prompt)
418
- return str(response)
568
+ response_str = str(response)
569
+ if response_str:
570
+ response_length = len(response_str)
571
+ print(f"💬 LLM响应长度: {response_length} 字符")
572
+ return response_str
419
573
  else:
420
574
  # 如果不支持chat_until_success,抛出异常
421
575
  raise ValueError("LLM model does not support chat_until_success interface")
422
576
  except Exception as e:
423
- PrettyOutput.print(f"LLM调用失败: {e}", OutputType.WARNING)
577
+ print(f"LLM调用失败: {e}")
424
578
  raise
425
579
 
426
580
  def format_recommendation(self, recommendation: ContextRecommendation) -> str: