jarvis-ai-assistant 0.3.30__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- 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 +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- 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 +34 -10
- 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 +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- 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/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- 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 +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- 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 +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- 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 +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +139 -0
- 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 +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
"""智能上下文推荐器。
|
|
2
|
+
|
|
3
|
+
使用LLM进行语义理解,提供更准确的上下文推荐。
|
|
4
|
+
完全基于LLM实现,不依赖硬编码规则。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from typing import List, Optional, Any
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
16
|
+
from jarvis.jarvis_utils.config import get_normal_platform_name, get_normal_model_name
|
|
17
|
+
from jarvis.jarvis_code_agent.utils import get_project_overview
|
|
18
|
+
|
|
19
|
+
from .context_recommender import ContextRecommendation
|
|
20
|
+
from .context_manager import ContextManager
|
|
21
|
+
from .symbol_extractor import Symbol
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ContextRecommender:
|
|
25
|
+
"""智能上下文推荐器。
|
|
26
|
+
|
|
27
|
+
使用LLM进行语义理解,根据编辑意图推荐相关的上下文信息。
|
|
28
|
+
完全基于LLM实现,提供语义级别的推荐,而非简单的关键词匹配。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, context_manager: ContextManager, parent_model: Optional[Any] = None):
|
|
32
|
+
"""初始化上下文推荐器
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
context_manager: 上下文管理器
|
|
36
|
+
parent_model: 父Agent的模型实例,用于获取模型配置(平台名称、模型名称、模型组等)
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: 如果无法创建LLM模型
|
|
40
|
+
"""
|
|
41
|
+
self.context_manager = context_manager
|
|
42
|
+
|
|
43
|
+
# 自己创建LLM模型实例,使用父Agent的配置
|
|
44
|
+
try:
|
|
45
|
+
registry = PlatformRegistry.get_global_platform_registry()
|
|
46
|
+
|
|
47
|
+
# 从父Agent的model获取配置
|
|
48
|
+
platform_name = None
|
|
49
|
+
model_name = None
|
|
50
|
+
model_group = None
|
|
51
|
+
|
|
52
|
+
if parent_model:
|
|
53
|
+
try:
|
|
54
|
+
# 优先获取 model_group,因为它包含了完整的配置信息
|
|
55
|
+
model_group = getattr(parent_model, 'model_group', None)
|
|
56
|
+
platform_name = parent_model.platform_name()
|
|
57
|
+
model_name = parent_model.name()
|
|
58
|
+
except Exception:
|
|
59
|
+
# 如果获取失败,使用默认配置
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# 优先根据 model_group 获取配置(确保配置一致性)
|
|
63
|
+
# 如果 model_group 存在,强制使用它来解析,避免使用 parent_model 中可能不一致的值
|
|
64
|
+
# 使用cheap平台,上下文推荐可以降低成本
|
|
65
|
+
if model_group:
|
|
66
|
+
try:
|
|
67
|
+
platform_name = get_normal_platform_name(model_group)
|
|
68
|
+
model_name = get_normal_model_name(model_group)
|
|
69
|
+
except Exception:
|
|
70
|
+
# 如果从 model_group 解析失败,回退到从 parent_model 获取的值
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# 创建平台实例
|
|
74
|
+
if platform_name:
|
|
75
|
+
self.llm_model = registry.create_platform(platform_name)
|
|
76
|
+
if self.llm_model is None:
|
|
77
|
+
# 如果创建失败,使用cheap平台
|
|
78
|
+
self.llm_model = registry.get_cheap_platform()
|
|
79
|
+
else:
|
|
80
|
+
self.llm_model = registry.get_cheap_platform()
|
|
81
|
+
|
|
82
|
+
# 先设置模型组(如果从父Agent获取到),因为 model_group 可能会影响模型名称的解析
|
|
83
|
+
if model_group and self.llm_model:
|
|
84
|
+
try:
|
|
85
|
+
self.llm_model.set_model_group(model_group)
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# 然后设置模型名称(如果从父Agent或model_group获取到)
|
|
90
|
+
if model_name and self.llm_model:
|
|
91
|
+
try:
|
|
92
|
+
self.llm_model.set_model_name(model_name)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
# 设置抑制输出,因为这是后台任务
|
|
97
|
+
if self.llm_model:
|
|
98
|
+
self.llm_model.set_suppress_output(True)
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError("无法创建LLM模型实例")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise ValueError(f"无法创建LLM模型: {e}")
|
|
103
|
+
|
|
104
|
+
def recommend_context(
|
|
105
|
+
self,
|
|
106
|
+
user_input: str,
|
|
107
|
+
) -> ContextRecommendation:
|
|
108
|
+
"""根据编辑意图推荐上下文
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
user_input: 用户输入/任务描述
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
ContextRecommendation: 推荐的上下文信息
|
|
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("⚠️ 未能生成符号名,将使用基础搜索策略")
|
|
129
|
+
|
|
130
|
+
# 2. 初始化推荐结果
|
|
131
|
+
recommended_symbols: List[Symbol] = []
|
|
132
|
+
|
|
133
|
+
# 3. 基于符号名进行符号查找,然后使用LLM挑选关联度高的条目(主要推荐方式)
|
|
134
|
+
if symbol_names:
|
|
135
|
+
# 3.1 使用符号名进行精确查找,找到所有候选符号及其位置
|
|
136
|
+
print("🔎 正在基于符号名搜索相关符号...")
|
|
137
|
+
candidate_symbols = self._search_symbols_by_names(symbol_names)
|
|
138
|
+
|
|
139
|
+
print(f"📊 符号名匹配: {len(candidate_symbols)} 个候选")
|
|
140
|
+
|
|
141
|
+
candidate_symbols_list = candidate_symbols
|
|
142
|
+
print(f"📦 共 {len(candidate_symbols_list)} 个候选符号")
|
|
143
|
+
|
|
144
|
+
# 3.2 使用LLM从候选符号中挑选关联度高的条目
|
|
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)} 个候选符号中筛选最相关的条目...")
|
|
148
|
+
selected_symbols = self._select_relevant_symbols_with_llm(
|
|
149
|
+
user_input, symbol_names, candidate_symbols_list
|
|
150
|
+
)
|
|
151
|
+
recommended_symbols.extend(selected_symbols)
|
|
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)} 个符号")
|
|
176
|
+
|
|
177
|
+
return ContextRecommendation(
|
|
178
|
+
recommended_symbols=final_symbols,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def _get_project_overview(self) -> str:
|
|
182
|
+
"""获取项目概况信息
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
项目概况字符串
|
|
186
|
+
"""
|
|
187
|
+
return get_project_overview(self.context_manager.project_root)
|
|
188
|
+
|
|
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生成相关符号名
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
user_input: 用户输入
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
符号名列表
|
|
360
|
+
"""
|
|
361
|
+
# 获取项目概况和符号表信息
|
|
362
|
+
project_overview = self._get_project_overview()
|
|
363
|
+
|
|
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个可能相关的符号名(函数名、类名、变量名等)。
|
|
369
|
+
|
|
370
|
+
{project_overview}
|
|
371
|
+
|
|
372
|
+
任务描述:{user_input}
|
|
373
|
+
|
|
374
|
+
符号名示例:{', '.join(symbol_names_sample[:30])}{'...' if len(symbol_names_sample) > 30 else ''}
|
|
375
|
+
|
|
376
|
+
要求:与任务直接相关,符合命名规范,尽量具体。
|
|
377
|
+
|
|
378
|
+
以Jsonnet数组格式返回,用<SYMBOL_NAMES>标签包裹。示例:
|
|
379
|
+
<SYMBOL_NAMES>
|
|
380
|
+
["processData", "validateInput", "handleError"]
|
|
381
|
+
</SYMBOL_NAMES>
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
response = self._call_llm(prompt)
|
|
386
|
+
# 从<SYMBOL_NAMES>标签中提取内容
|
|
387
|
+
response = response.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()
|
|
391
|
+
else:
|
|
392
|
+
# 如果没有找到标签,尝试清理markdown代码块
|
|
393
|
+
if response.startswith("```json"):
|
|
394
|
+
response = response[7:]
|
|
395
|
+
elif response.startswith("```"):
|
|
396
|
+
response = response[3:]
|
|
397
|
+
if response.endswith("```"):
|
|
398
|
+
response = response[:-3]
|
|
399
|
+
json_content = response.strip()
|
|
400
|
+
|
|
401
|
+
symbol_names = json_loads(json_content)
|
|
402
|
+
if not isinstance(symbol_names, list):
|
|
403
|
+
print("⚠️ LLM返回的符号名格式不正确,期望 Jsonnet 数组格式")
|
|
404
|
+
return []
|
|
405
|
+
|
|
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
|
|
412
|
+
except Exception as e:
|
|
413
|
+
# 解析失败,返回空列表
|
|
414
|
+
print(f"❌ LLM符号名生成失败: {e}")
|
|
415
|
+
return []
|
|
416
|
+
def _search_symbols_by_names(self, symbol_names: List[str]) -> List[Symbol]:
|
|
417
|
+
"""基于符号名在符号表中精确查找相关符号
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
symbol_names: 符号名列表
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
候选符号列表
|
|
424
|
+
"""
|
|
425
|
+
if not symbol_names:
|
|
426
|
+
return []
|
|
427
|
+
|
|
428
|
+
found_symbols: List[Symbol] = []
|
|
429
|
+
found_symbol_keys = set() # 用于去重,使用 (file_path, name, line_start) 作为键
|
|
430
|
+
|
|
431
|
+
# 创建符号名映射(支持大小写不敏感匹配)
|
|
432
|
+
symbol_names_lower = {name.lower(): name for name in symbol_names}
|
|
433
|
+
|
|
434
|
+
# 遍历所有符号,精确匹配符号名
|
|
435
|
+
for symbol_name, symbols in self.context_manager.symbol_table.symbols_by_name.items():
|
|
436
|
+
symbol_name_lower = symbol_name.lower()
|
|
437
|
+
|
|
438
|
+
# 精确匹配:检查符号名是否在目标列表中(大小写不敏感)
|
|
439
|
+
if symbol_name_lower in symbol_names_lower:
|
|
440
|
+
# 找到匹配的符号,添加所有同名符号(可能有重载)
|
|
441
|
+
for symbol in 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)
|
|
446
|
+
|
|
447
|
+
return found_symbols
|
|
448
|
+
|
|
449
|
+
def _select_relevant_symbols_with_llm(
|
|
450
|
+
self, user_input: str, symbol_names: List[str], candidate_symbols: List[Symbol]
|
|
451
|
+
) -> List[Symbol]:
|
|
452
|
+
"""使用LLM从候选符号中挑选关联度高的条目
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
user_input: 用户输入/任务描述
|
|
456
|
+
symbol_names: 符号名列表
|
|
457
|
+
candidate_symbols: 候选符号列表(包含位置信息)
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
选中的符号列表
|
|
461
|
+
"""
|
|
462
|
+
if not candidate_symbols:
|
|
463
|
+
return []
|
|
464
|
+
|
|
465
|
+
# 限制候选符号数量,避免prompt过长
|
|
466
|
+
candidates_to_consider = candidate_symbols[:100] # 最多100个候选
|
|
467
|
+
if len(candidate_symbols) > 100:
|
|
468
|
+
print(f"📌 候选符号数量较多({len(candidate_symbols)} 个),限制为前 100 个进行LLM筛选")
|
|
469
|
+
|
|
470
|
+
# 构建带编号的符号信息列表(包含位置信息)
|
|
471
|
+
symbol_info_list = []
|
|
472
|
+
for idx, symbol in enumerate(candidates_to_consider, start=1):
|
|
473
|
+
symbol_info = {
|
|
474
|
+
"序号": idx,
|
|
475
|
+
"name": symbol.name,
|
|
476
|
+
"kind": symbol.kind,
|
|
477
|
+
"file": os.path.relpath(symbol.file_path, self.context_manager.project_root),
|
|
478
|
+
"line": symbol.line_start,
|
|
479
|
+
"signature": symbol.signature or "",
|
|
480
|
+
}
|
|
481
|
+
symbol_info_list.append(symbol_info)
|
|
482
|
+
|
|
483
|
+
# 获取项目概况
|
|
484
|
+
project_overview = self._get_project_overview()
|
|
485
|
+
|
|
486
|
+
prompt = f"""根据任务描述和生成的符号名,从候选符号列表中选择最相关的10-20个符号。
|
|
487
|
+
|
|
488
|
+
{project_overview}
|
|
489
|
+
|
|
490
|
+
任务描述:{user_input}
|
|
491
|
+
生成的符号名:{', '.join(symbol_names)}
|
|
492
|
+
候选符号列表(已编号):{json.dumps(symbol_info_list, ensure_ascii=False, indent=2)}
|
|
493
|
+
|
|
494
|
+
返回最相关符号的序号(Jsonnet数组),按相关性排序,用<SELECTED_INDICES>标签包裹。示例:
|
|
495
|
+
<SELECTED_INDICES>
|
|
496
|
+
[3, 7, 12, 15, 23]
|
|
497
|
+
</SELECTED_INDICES>
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
response = self._call_llm(prompt)
|
|
502
|
+
# 从<SELECTED_INDICES>标签中提取内容
|
|
503
|
+
response = response.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()
|
|
507
|
+
else:
|
|
508
|
+
# 如果没有找到标签,尝试清理markdown代码块
|
|
509
|
+
if response.startswith("```json"):
|
|
510
|
+
response = response[7:]
|
|
511
|
+
elif response.startswith("```"):
|
|
512
|
+
response = response[3:]
|
|
513
|
+
if response.endswith("```"):
|
|
514
|
+
response = response[:-3]
|
|
515
|
+
json_content = response.strip()
|
|
516
|
+
|
|
517
|
+
selected_indices = json_loads(json_content)
|
|
518
|
+
if not isinstance(selected_indices, list):
|
|
519
|
+
print("⚠️ LLM返回的符号序号格式不正确,期望 Jsonnet 数组格式")
|
|
520
|
+
return []
|
|
521
|
+
|
|
522
|
+
print(f"📋 LLM返回了 {len(selected_indices)} 个符号序号")
|
|
523
|
+
|
|
524
|
+
# 根据序号查找对应的符号对象
|
|
525
|
+
selected_symbols = []
|
|
526
|
+
invalid_indices = []
|
|
527
|
+
for idx in selected_indices:
|
|
528
|
+
# 序号从1开始,转换为列表索引(从0开始)
|
|
529
|
+
if isinstance(idx, int) and 1 <= idx <= len(candidates_to_consider):
|
|
530
|
+
symbol = candidates_to_consider[idx - 1]
|
|
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}")
|
|
545
|
+
|
|
546
|
+
return selected_symbols
|
|
547
|
+
except Exception as e:
|
|
548
|
+
# 解析失败,返回空列表
|
|
549
|
+
print(f"❌ LLM符号筛选失败: {e}")
|
|
550
|
+
return []
|
|
551
|
+
|
|
552
|
+
def _call_llm(self, prompt: str) -> str:
|
|
553
|
+
"""调用LLM生成响应
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
prompt: 提示词
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
LLM生成的响应文本
|
|
560
|
+
"""
|
|
561
|
+
if not self.llm_model:
|
|
562
|
+
raise ValueError("LLM model not available")
|
|
563
|
+
|
|
564
|
+
try:
|
|
565
|
+
# 使用chat_until_success方法(BasePlatform的标准接口)
|
|
566
|
+
if hasattr(self.llm_model, 'chat_until_success'):
|
|
567
|
+
response = self.llm_model.chat_until_success(prompt)
|
|
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
|
|
573
|
+
else:
|
|
574
|
+
# 如果不支持chat_until_success,抛出异常
|
|
575
|
+
raise ValueError("LLM model does not support chat_until_success interface")
|
|
576
|
+
except Exception as e:
|
|
577
|
+
print(f"❌ LLM调用失败: {e}")
|
|
578
|
+
raise
|
|
579
|
+
|
|
580
|
+
def format_recommendation(self, recommendation: ContextRecommendation) -> str:
|
|
581
|
+
"""格式化推荐结果为可读文本
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
recommendation: 推荐结果
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
格式化的文本
|
|
588
|
+
"""
|
|
589
|
+
if not recommendation.recommended_symbols:
|
|
590
|
+
return ""
|
|
591
|
+
|
|
592
|
+
lines = ["\n💡 智能上下文推荐:"]
|
|
593
|
+
lines.append("─" * 60)
|
|
594
|
+
|
|
595
|
+
# 输出:符号在文件中的位置
|
|
596
|
+
symbols_str = "\n ".join(
|
|
597
|
+
f"• 符号 `{s.name}` ({s.kind}) 位于文件 {os.path.relpath(s.file_path, self.context_manager.project_root)} 第 {s.line_start} 行"
|
|
598
|
+
for s in recommendation.recommended_symbols
|
|
599
|
+
)
|
|
600
|
+
lines.append(f"🔗 推荐符号位置 ({len(recommendation.recommended_symbols)}个):\n {symbols_str}")
|
|
601
|
+
|
|
602
|
+
lines.append("─" * 60)
|
|
603
|
+
lines.append("") # 空行
|
|
604
|
+
|
|
605
|
+
return "\n".join(lines)
|