jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.8__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +243 -139
- jarvis/jarvis_agent/agent_manager.py +5 -10
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +265 -15
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +113 -98
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +6 -12
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +77 -14
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +12 -21
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/task_analyzer.py +26 -4
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/web_server.py +55 -20
- jarvis/jarvis_c2rust/__init__.py +5 -5
- jarvis/jarvis_c2rust/cli.py +461 -499
- jarvis/jarvis_c2rust/collector.py +45 -53
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +264 -132
- jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +1592 -395
- jarvis/jarvis_c2rust/transpiler.py +1722 -1064
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +2 -3
- jarvis/jarvis_code_agent/code_agent.py +394 -320
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
- jarvis/jarvis_code_agent/lint.py +258 -27
- jarvis/jarvis_code_agent/utils.py +0 -1
- jarvis/jarvis_code_analysis/code_review.py +19 -24
- jarvis/jarvis_data/config_schema.json +53 -26
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +44 -49
- jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
- jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +79 -61
- jarvis/jarvis_multi_agent/main.py +3 -7
- jarvis/jarvis_platform/base.py +469 -199
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +65 -27
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +31 -42
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +49 -51
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +220 -3520
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/cli.py +29 -6
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +83 -4
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +7 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +1 -1
- jarvis/jarvis_stats/stats.py +7 -7
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +20 -23
- jarvis/jarvis_tools/edit_file.py +1066 -0
- jarvis/jarvis_tools/execute_script.py +42 -21
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +11 -20
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1525 -87
- jarvis/jarvis_tools/read_symbols.py +2 -3
- jarvis/jarvis_tools/read_webpage.py +7 -10
- jarvis/jarvis_tools/registry.py +370 -181
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +105 -0
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +3 -7
- jarvis/jarvis_tools/sub_agent.py +17 -6
- jarvis/jarvis_tools/sub_code_agent.py +14 -16
- jarvis/jarvis_tools/virtual_tty.py +54 -32
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +98 -63
- jarvis/jarvis_utils/embedding.py +5 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +81 -67
- jarvis/jarvis_utils/input.py +24 -49
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +33 -35
- jarvis/jarvis_utils/utils.py +245 -202
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/METADATA +205 -70
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +218 -0
- jarvis/jarvis_agent/edit_file_handler.py +0 -584
- jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
- jarvis/jarvis_agent/task_planner.py +0 -496
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.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
|
|
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.
|
|
77
|
+
# 如果创建失败,使用cheap平台
|
|
78
|
+
self.llm_model = registry.get_cheap_platform()
|
|
77
79
|
else:
|
|
78
|
-
self.llm_model = registry.
|
|
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
|
-
|
|
115
|
-
|
|
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.
|
|
121
|
-
if
|
|
122
|
-
# 3.1
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
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,
|
|
149
|
+
user_input, symbol_names, candidate_symbols_list
|
|
140
150
|
)
|
|
141
151
|
recommended_symbols.extend(selected_symbols)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
159
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
372
|
+
任务描述:{user_input}
|
|
373
|
+
|
|
374
|
+
符号名示例:{', '.join(symbol_names_sample[:30])}{'...' if len(symbol_names_sample) > 30 else ''}
|
|
176
375
|
|
|
177
|
-
|
|
178
|
-
只返回关键词数组,不要包含其他文字。
|
|
376
|
+
要求:与任务直接相关,符合命名规范,尽量具体。
|
|
179
377
|
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
# 从<
|
|
386
|
+
# 从<SYMBOL_NAMES>标签中提取内容
|
|
193
387
|
response = response.strip()
|
|
194
|
-
|
|
195
|
-
if
|
|
196
|
-
|
|
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("```
|
|
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
|
-
|
|
399
|
+
json_content = response.strip()
|
|
206
400
|
|
|
207
|
-
|
|
208
|
-
if not isinstance(
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
414
|
+
print(f"❌ LLM符号名生成失败: {e}")
|
|
217
415
|
return []
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"""基于关键词在符号表中查找相关符号
|
|
416
|
+
def _search_symbols_by_names(self, symbol_names: List[str]) -> List[Symbol]:
|
|
417
|
+
"""基于符号名在符号表中精确查找相关符号
|
|
221
418
|
|
|
222
419
|
Args:
|
|
223
|
-
|
|
420
|
+
symbol_names: 符号名列表
|
|
224
421
|
|
|
225
422
|
Returns:
|
|
226
423
|
候选符号列表
|
|
227
424
|
"""
|
|
228
|
-
if not
|
|
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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
if
|
|
373
|
-
|
|
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("```
|
|
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
|
-
|
|
515
|
+
json_content = response.strip()
|
|
383
516
|
|
|
384
|
-
selected_indices =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
+
print(f"❌ LLM调用失败: {e}")
|
|
424
578
|
raise
|
|
425
579
|
|
|
426
580
|
def format_recommendation(self, recommendation: ContextRecommendation) -> str:
|