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.
- 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.6.dist-info}/METADATA +205 -70
- jarvis_ai_assistant-0.7.6.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.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,7 @@ import hashlib
|
|
|
11
11
|
from typing import Any, Dict, List, Optional, Tuple
|
|
12
12
|
|
|
13
13
|
import typer
|
|
14
|
+
import yaml
|
|
14
15
|
|
|
15
16
|
from jarvis.jarvis_agent import Agent
|
|
16
17
|
from jarvis.jarvis_agent.events import AFTER_TOOL_CALL
|
|
@@ -18,6 +19,7 @@ from jarvis.jarvis_code_agent.lint import (
|
|
|
18
19
|
get_lint_tools,
|
|
19
20
|
get_lint_commands_for_files,
|
|
20
21
|
group_commands_by_tool,
|
|
22
|
+
get_format_commands_for_files,
|
|
21
23
|
)
|
|
22
24
|
from jarvis.jarvis_code_agent.code_analyzer.build_validator import BuildValidator, BuildResult, FallbackBuildValidator
|
|
23
25
|
from jarvis.jarvis_code_agent.build_validation_config import BuildValidationConfig
|
|
@@ -33,10 +35,12 @@ from jarvis.jarvis_utils.config import (
|
|
|
33
35
|
get_git_check_mode,
|
|
34
36
|
set_config,
|
|
35
37
|
get_data_dir,
|
|
36
|
-
is_plan_enabled,
|
|
37
38
|
is_enable_intent_recognition,
|
|
38
39
|
is_enable_impact_analysis,
|
|
40
|
+
get_smart_platform_name,
|
|
41
|
+
get_smart_model_name,
|
|
39
42
|
)
|
|
43
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
40
44
|
from jarvis.jarvis_code_agent.utils import get_project_overview
|
|
41
45
|
from jarvis.jarvis_utils.git_utils import (
|
|
42
46
|
confirm_add_new_files,
|
|
@@ -46,13 +50,12 @@ from jarvis.jarvis_utils.git_utils import (
|
|
|
46
50
|
get_diff,
|
|
47
51
|
get_diff_file_list,
|
|
48
52
|
get_latest_commit_hash,
|
|
49
|
-
get_recent_commits_with_files,
|
|
50
53
|
handle_commit_workflow,
|
|
51
54
|
has_uncommitted_changes,
|
|
52
55
|
revert_change,
|
|
53
56
|
)
|
|
54
57
|
from jarvis.jarvis_utils.input import get_multiline_input, user_confirm
|
|
55
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
58
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput # 保留用于语法高亮
|
|
56
59
|
from jarvis.jarvis_utils.utils import init_env, _acquire_single_instance_lock
|
|
57
60
|
|
|
58
61
|
app = typer.Typer(help="Jarvis 代码助手")
|
|
@@ -83,7 +86,7 @@ class CodeAgent(Agent):
|
|
|
83
86
|
append_tools: Optional[str] = None,
|
|
84
87
|
tool_group: Optional[str] = None,
|
|
85
88
|
non_interactive: Optional[bool] = None,
|
|
86
|
-
|
|
89
|
+
rule_names: Optional[str] = None,
|
|
87
90
|
**kwargs,
|
|
88
91
|
):
|
|
89
92
|
self.root_dir = os.getcwd()
|
|
@@ -98,13 +101,10 @@ class CodeAgent(Agent):
|
|
|
98
101
|
self._check_git_config()
|
|
99
102
|
base_tools = [
|
|
100
103
|
"execute_script",
|
|
101
|
-
"search_web",
|
|
102
|
-
"ask_user",
|
|
103
104
|
"read_code",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"sub_code_agent",
|
|
105
|
+
"edit_file",
|
|
106
|
+
"rewrite_file",
|
|
107
|
+
"lsp_client", # LSP客户端工具,用于获取代码补全、悬停等信息
|
|
108
108
|
]
|
|
109
109
|
|
|
110
110
|
if append_tools:
|
|
@@ -121,10 +121,23 @@ class CodeAgent(Agent):
|
|
|
121
121
|
project_rules = self._read_project_rules()
|
|
122
122
|
|
|
123
123
|
combined_parts: List[str] = []
|
|
124
|
+
loaded_rule_names: List[str] = [] # 记录加载的规则名称
|
|
125
|
+
|
|
124
126
|
if global_rules:
|
|
125
127
|
combined_parts.append(global_rules)
|
|
128
|
+
loaded_rule_names.append("global_rule")
|
|
126
129
|
if project_rules:
|
|
127
130
|
combined_parts.append(project_rules)
|
|
131
|
+
loaded_rule_names.append("project_rule")
|
|
132
|
+
|
|
133
|
+
# 如果指定了 rule_names,从 rules.yaml 文件中读取并添加多个规则
|
|
134
|
+
if rule_names:
|
|
135
|
+
rule_list = [name.strip() for name in rule_names.split(',') if name.strip()]
|
|
136
|
+
for rule_name in rule_list:
|
|
137
|
+
named_rule = self._get_named_rule(rule_name)
|
|
138
|
+
if named_rule:
|
|
139
|
+
combined_parts.append(named_rule)
|
|
140
|
+
loaded_rule_names.append(rule_name)
|
|
128
141
|
|
|
129
142
|
if combined_parts:
|
|
130
143
|
merged_rules = "\n\n".join(combined_parts)
|
|
@@ -132,25 +145,44 @@ class CodeAgent(Agent):
|
|
|
132
145
|
f"{code_system_prompt}\n\n"
|
|
133
146
|
f"<rules>\n{merged_rules}\n</rules>"
|
|
134
147
|
)
|
|
148
|
+
# 显示加载的规则名称
|
|
149
|
+
if loaded_rule_names:
|
|
150
|
+
rules_display = ", ".join(loaded_rule_names)
|
|
151
|
+
print(f"ℹ️ 已加载规则: {rules_display}")
|
|
135
152
|
|
|
136
153
|
# 调用父类 Agent 的初始化
|
|
137
154
|
# 默认禁用方法论和分析,但允许通过 kwargs 覆盖
|
|
138
155
|
use_methodology = kwargs.pop("use_methodology", False)
|
|
139
156
|
use_analysis = kwargs.pop("use_analysis", False)
|
|
157
|
+
# name 使用传入的值,如果没有传入则使用默认值 "CodeAgent"
|
|
158
|
+
name = kwargs.pop("name", "CodeAgent")
|
|
159
|
+
|
|
160
|
+
# 准备显式传递给 super().__init__ 的参数
|
|
161
|
+
# 注意:这些参数如果也在 kwargs 中,需要先移除,避免重复传递错误
|
|
162
|
+
explicit_params = {
|
|
163
|
+
"system_prompt": code_system_prompt,
|
|
164
|
+
"name": name,
|
|
165
|
+
"auto_complete": False,
|
|
166
|
+
"model_group": model_group,
|
|
167
|
+
"need_summary": need_summary,
|
|
168
|
+
"use_methodology": use_methodology,
|
|
169
|
+
"use_analysis": use_analysis,
|
|
170
|
+
"non_interactive": non_interactive,
|
|
171
|
+
"use_tools": base_tools,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# 自动移除所有显式传递的参数,避免重复传递错误
|
|
175
|
+
# 这样以后添加新参数时,只要在 explicit_params 中声明,就会自动处理
|
|
176
|
+
for key in explicit_params:
|
|
177
|
+
kwargs.pop(key, None)
|
|
178
|
+
|
|
140
179
|
super().__init__(
|
|
141
|
-
|
|
142
|
-
name="CodeAgent",
|
|
143
|
-
auto_complete=False,
|
|
144
|
-
model_group=model_group,
|
|
145
|
-
need_summary=need_summary,
|
|
146
|
-
use_methodology=use_methodology,
|
|
147
|
-
use_analysis=use_analysis,
|
|
148
|
-
non_interactive=non_interactive,
|
|
149
|
-
plan=bool(plan) if plan is not None else is_plan_enabled(),
|
|
150
|
-
use_tools=base_tools, # 仅启用限定工具
|
|
180
|
+
**explicit_params,
|
|
151
181
|
**kwargs,
|
|
152
182
|
)
|
|
153
183
|
|
|
184
|
+
self._agent_type = "code_agent"
|
|
185
|
+
|
|
154
186
|
# 建立CodeAgent与Agent的关联,便于工具获取上下文管理器
|
|
155
187
|
self._code_agent = self
|
|
156
188
|
|
|
@@ -158,7 +190,7 @@ class CodeAgent(Agent):
|
|
|
158
190
|
try:
|
|
159
191
|
# 获取当前Agent的model实例
|
|
160
192
|
parent_model = None
|
|
161
|
-
if
|
|
193
|
+
if self.model:
|
|
162
194
|
parent_model = self.model
|
|
163
195
|
|
|
164
196
|
self.context_recommender = ContextRecommender(
|
|
@@ -167,82 +199,103 @@ class CodeAgent(Agent):
|
|
|
167
199
|
)
|
|
168
200
|
except Exception as e:
|
|
169
201
|
# LLM推荐器初始化失败
|
|
170
|
-
|
|
171
|
-
logger = logging.getLogger(__name__)
|
|
172
|
-
logger.warning(f"上下文推荐器初始化失败: {e},将跳过上下文推荐功能")
|
|
202
|
+
print(f"⚠️ 上下文推荐器初始化失败: {e},将跳过上下文推荐功能")
|
|
173
203
|
|
|
174
204
|
self.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
205
|
+
|
|
206
|
+
# 打印语言功能支持表格
|
|
207
|
+
try:
|
|
208
|
+
from jarvis.jarvis_agent.language_support_info import print_language_support_table
|
|
209
|
+
print_language_support_table()
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def _init_model(self, model_group: Optional[str]):
|
|
214
|
+
"""初始化模型平台(CodeAgent使用smart平台,适用于代码生成等复杂场景)"""
|
|
215
|
+
platform_name = get_smart_platform_name(model_group)
|
|
216
|
+
model_name = get_smart_model_name(model_group)
|
|
217
|
+
|
|
218
|
+
maybe_model = PlatformRegistry().create_platform(platform_name)
|
|
219
|
+
if maybe_model is None:
|
|
220
|
+
print(f"⚠️ 平台 {platform_name} 不存在,将使用smart模型")
|
|
221
|
+
maybe_model = PlatformRegistry().get_smart_platform()
|
|
222
|
+
|
|
223
|
+
# 在此处收敛为非可选类型,确保后续赋值满足类型检查
|
|
224
|
+
self.model = maybe_model
|
|
225
|
+
|
|
226
|
+
if model_name:
|
|
227
|
+
self.model.set_model_name(model_name)
|
|
228
|
+
|
|
229
|
+
self.model.set_model_group(model_group)
|
|
230
|
+
self.model.set_suppress_output(False)
|
|
175
231
|
|
|
176
232
|
def _get_system_prompt(self) -> str:
|
|
177
233
|
"""获取代码工程师的系统提示词"""
|
|
178
234
|
return """
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
9. 沟通清晰:用要点列出结论、变更范围、影响评估与下一步行动
|
|
238
|
-
10. 持续改进:沉淀经验为可复用清单,下一次做得更快更稳
|
|
239
|
-
</say_to_llm>
|
|
235
|
+
你是Jarvis代码工程师,专注于**项目级代码分析、精准修改与问题排查**,核心原则:自主决策不犹豫、高效精准不冗余、修改审慎可回退、工具精通不臆测。
|
|
236
|
+
|
|
237
|
+
工作流程(闭环执行,每步必落地):
|
|
238
|
+
1. 需求拆解与项目对齐:
|
|
239
|
+
- 先明确用户需求的核心目标(如“修复XX报错”“新增XX功能”“优化XX性能”),标注关键约束(如“兼容Python 3.8+”“不修改核心依赖”);
|
|
240
|
+
- 快速定位项目核心目录(如src/、main/)、技术栈(语言/框架/版本)、代码风格规范(如PEP8、ESLint规则),避免无的放矢。
|
|
241
|
+
|
|
242
|
+
2. 目标文件精准定位(工具优先,拒绝盲搜):
|
|
243
|
+
- 优先通过 lsp_client 的 search_symbol(符号搜索)定位关联文件(如函数、类、变量所属文件);
|
|
244
|
+
- 若符号不明确,用全文搜索工具按“关键词+文件类型过滤”(如“关键词:user_login + 后缀:.py”)缩小范围;
|
|
245
|
+
- 仅当工具无法定位时,才用 read_code 读取疑似目录下的核心文件(如入口文件、配置文件),避免无效读取。
|
|
246
|
+
|
|
247
|
+
3. 代码深度分析(基于工具,禁止虚构):
|
|
248
|
+
- 符号分析:用 lsp_client 的 document_symbols(文档符号)、get_symbol_info(符号详情)、definition(定义跳转)、references(引用查询),确认符号的作用域、依赖关系、调用链路;
|
|
249
|
+
- 内容分析:用 read_code 读取目标文件完整内容,重点关注“逻辑分支、异常处理、依赖引入、配置参数”,记录关键代码片段(如报错位置、待修改逻辑);
|
|
250
|
+
- 影响范围评估:用 lsp_client 的 references 查询待修改符号的所有引用场景,预判修改可能波及的模块,避免“改一处崩一片”。
|
|
251
|
+
|
|
252
|
+
4. 最小变更方案设计(可回退、易维护):
|
|
253
|
+
- 优先选择“局部修改”(如修改函数内逻辑、补充条件判断),而非“重构”或“全文件重写”;
|
|
254
|
+
- 方案需满足:① 覆盖需求核心;② 不破坏现有功能;③ 符合项目代码风格;④ 便于后续回退(如仅修改必要行,不删无关代码);
|
|
255
|
+
- 若需修改核心逻辑(如公共函数、配置文件),先记录原始代码片段(如用临时文件保存到 /tmp/backup_xxx.txt),再执行修改。
|
|
256
|
+
|
|
257
|
+
5. 先读后写,精准执行(工具规范使用):
|
|
258
|
+
- 必须先通过 read_code 读取目标文件完整内容,确认待修改位置的上下文(如前后代码逻辑、缩进格式),再调用编辑工具;
|
|
259
|
+
- 编辑工具选择:
|
|
260
|
+
- 局部修改(改少数行、补代码块):用 edit_file,明确标注“修改范围(行号/代码片段)+ 修改内容”(如“替换第15-20行的循环逻辑为:xxx”);
|
|
261
|
+
- 全文件重写(如格式统一、逻辑重构):仅当局部修改无法满足需求时使用 rewrite_file,重写前必须备份原始文件到 /tmp/rewrite_backup_xxx.txt。
|
|
262
|
+
|
|
263
|
+
6. 验证与兜底(避免无效交付):
|
|
264
|
+
- 修改后优先通过 lsp_client 的语法检查功能(若支持)验证代码无语法错误;
|
|
265
|
+
- 若涉及功能变更,建议补充1-2行核心测试用例(或提示用户验证场景),确保修改生效;
|
|
266
|
+
- 记录修改日志(保存到 /tmp/modify_log_xxx.txt),内容包括:修改时间、目标文件、修改原因、原始代码片段、修改后代码片段,便于问题追溯。
|
|
267
|
+
|
|
268
|
+
工具使用规范(精准调用,不浪费资源):
|
|
269
|
+
- lsp_client:仅传递有效参数(如符号名精准、文件路径明确),避免模糊查询(如无关键词的全局搜索);
|
|
270
|
+
- 全文搜索:必须添加“文件类型过滤”“目录过滤”,减少无效结果(如仅搜索 src/ 目录下的 .java 文件);
|
|
271
|
+
- read_code:仅读取目标文件和关联依赖文件,不读取日志、测试数据、第三方依赖包等无关文件;
|
|
272
|
+
- edit_file/rewrite_file:修改后必须保持代码缩进、命名规范与原文件一致(如原文件用4空格缩进,不改为2空格),不引入多余空行、注释。
|
|
273
|
+
|
|
274
|
+
代码质量约束(底线要求,不可突破):
|
|
275
|
+
1. 语法正确性:修改后代码无语法错误、无未定义变量/函数、无依赖缺失;
|
|
276
|
+
2. 功能兼容性:不破坏现有正常功能,修改后的代码能适配项目已有的调用场景;
|
|
277
|
+
3. 风格一致性:严格遵循项目既有风格(如命名规范、缩进、注释格式),不引入个人风格;
|
|
278
|
+
4. 可维护性:修改逻辑清晰,关键改动可加简洁注释(如“// 修复XX报错:XX场景下变量未初始化”),不写“魔法值”“冗余代码”。
|
|
279
|
+
|
|
280
|
+
调试指引(问题闭环,高效排查):
|
|
281
|
+
- 定位报错:优先用 lsp_client 定位报错位置,结合 read_code 查看上下文,确认报错类型(语法错/逻辑错/运行时错);
|
|
282
|
+
- 日志补充:若报错模糊,在关键位置(如函数入口、循环内、异常捕获前)增加打印日志,内容包括“变量值、执行步骤、时间戳”(如 print(f"[DEBUG] user_login: username={username}, status={status}")),日志输出到 /tmp/ 目录,不污染项目日志;
|
|
283
|
+
- 中间结果保存:复杂逻辑修改时,用临时文件(/tmp/temp_result_xxx.txt)保存中间数据(如计算结果、接口返回值),便于验证逻辑正确性;
|
|
284
|
+
- 回退机制:若修改后出现新问题,立即用备份文件回退,重新分析,不盲目叠加修改。
|
|
285
|
+
|
|
286
|
+
禁止行为(红线不可碰):
|
|
287
|
+
1. 禁止虚构代码、依赖、文件路径,所有结论必须基于工具返回结果或实际读取的代码;
|
|
288
|
+
2. 禁止无差别读取项目所有文件,避免浪费资源;
|
|
289
|
+
3. 禁止大篇幅删除、重构未明确要求修改的代码;
|
|
290
|
+
4. 禁止引入项目未依赖的第三方库(除非用户明确允许);
|
|
291
|
+
5. 禁止修改 /tmp/ 以外的非项目目录文件,避免污染环境。
|
|
292
|
+
|
|
240
293
|
"""
|
|
241
294
|
|
|
242
295
|
def _read_project_rules(self) -> Optional[str]:
|
|
243
296
|
"""读取 .jarvis/rules 内容,如果存在则返回字符串,否则返回 None"""
|
|
244
297
|
try:
|
|
245
|
-
rules_path = os.path.join(self.root_dir, ".jarvis", "
|
|
298
|
+
rules_path = os.path.join(self.root_dir, ".jarvis", "rule")
|
|
246
299
|
if os.path.exists(rules_path) and os.path.isfile(rules_path):
|
|
247
300
|
with open(rules_path, "r", encoding="utf-8", errors="replace") as f:
|
|
248
301
|
content = f.read().strip()
|
|
@@ -255,7 +308,7 @@ class CodeAgent(Agent):
|
|
|
255
308
|
def _read_global_rules(self) -> Optional[str]:
|
|
256
309
|
"""读取数据目录 rules 内容,如果存在则返回字符串,否则返回 None"""
|
|
257
310
|
try:
|
|
258
|
-
rules_path = os.path.join(get_data_dir(), "
|
|
311
|
+
rules_path = os.path.join(get_data_dir(), "rule")
|
|
259
312
|
if os.path.exists(rules_path) and os.path.isfile(rules_path):
|
|
260
313
|
with open(rules_path, "r", encoding="utf-8", errors="replace") as f:
|
|
261
314
|
content = f.read().strip()
|
|
@@ -265,6 +318,48 @@ class CodeAgent(Agent):
|
|
|
265
318
|
pass
|
|
266
319
|
return None
|
|
267
320
|
|
|
321
|
+
def _get_named_rule(self, rule_name: str) -> Optional[str]:
|
|
322
|
+
"""从 rules.yaml 文件中获取指定名称的规则
|
|
323
|
+
|
|
324
|
+
参数:
|
|
325
|
+
rule_name: 规则名称
|
|
326
|
+
|
|
327
|
+
返回:
|
|
328
|
+
str: 规则内容,如果未找到则返回 None
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
# 读取全局数据目录下的 rules.yaml
|
|
332
|
+
global_rules_yaml_path = os.path.join(get_data_dir(), "rules.yaml")
|
|
333
|
+
global_rules = {}
|
|
334
|
+
if os.path.exists(global_rules_yaml_path) and os.path.isfile(global_rules_yaml_path):
|
|
335
|
+
with open(global_rules_yaml_path, "r", encoding="utf-8", errors="replace") as f:
|
|
336
|
+
global_rules = yaml.safe_load(f) or {}
|
|
337
|
+
|
|
338
|
+
# 读取 git 根目录下的 rules.yaml
|
|
339
|
+
project_rules_yaml_path = os.path.join(self.root_dir, "rules.yaml")
|
|
340
|
+
project_rules = {}
|
|
341
|
+
if os.path.exists(project_rules_yaml_path) and os.path.isfile(project_rules_yaml_path):
|
|
342
|
+
with open(project_rules_yaml_path, "r", encoding="utf-8", errors="replace") as f:
|
|
343
|
+
project_rules = yaml.safe_load(f) or {}
|
|
344
|
+
|
|
345
|
+
# 合并配置:项目配置覆盖全局配置
|
|
346
|
+
merged_rules = {**global_rules, **project_rules}
|
|
347
|
+
|
|
348
|
+
# 查找指定的规则
|
|
349
|
+
if rule_name in merged_rules:
|
|
350
|
+
rule_value = merged_rules[rule_name]
|
|
351
|
+
# 如果值是字符串,直接返回
|
|
352
|
+
if isinstance(rule_value, str):
|
|
353
|
+
return rule_value.strip() if rule_value.strip() else None
|
|
354
|
+
# 如果值是其他类型,转换为字符串
|
|
355
|
+
return str(rule_value).strip() if str(rule_value).strip() else None
|
|
356
|
+
|
|
357
|
+
return None
|
|
358
|
+
except Exception as e:
|
|
359
|
+
# 读取规则失败时忽略,不影响主流程
|
|
360
|
+
print(f"⚠️ 读取 rules.yaml 失败: {e}")
|
|
361
|
+
return None
|
|
362
|
+
|
|
268
363
|
def _check_git_config(self) -> None:
|
|
269
364
|
"""检查 git username 和 email 是否已设置,如果没有则提示并退出"""
|
|
270
365
|
try:
|
|
@@ -301,25 +396,22 @@ class CodeAgent(Agent):
|
|
|
301
396
|
message = "❌ Git 配置不完整\n\n请运行以下命令配置 Git:\n" + "\n".join(
|
|
302
397
|
missing_configs
|
|
303
398
|
)
|
|
304
|
-
|
|
399
|
+
print(f"⚠️ {message}")
|
|
305
400
|
# 通过配置控制严格校验模式(JARVIS_GIT_CHECK_MODE):
|
|
306
401
|
# - warn: 仅告警并继续,后续提交可能失败
|
|
307
402
|
# - strict: 严格模式(默认),直接退出
|
|
308
403
|
mode = get_git_check_mode().lower()
|
|
309
404
|
if mode == "warn":
|
|
310
|
-
|
|
311
|
-
"
|
|
312
|
-
"注意:后续提交可能失败,请尽快配置 git user.name 与 user.email。",
|
|
313
|
-
OutputType.INFO,
|
|
314
|
-
)
|
|
405
|
+
print("ℹ️ 已启用 Git 校验警告模式(JARVIS_GIT_CHECK_MODE=warn),将继续运行。"
|
|
406
|
+
"注意:后续提交可能失败,请尽快配置 git user.name 与 user.email。")
|
|
315
407
|
return
|
|
316
408
|
sys.exit(1)
|
|
317
409
|
|
|
318
410
|
except FileNotFoundError:
|
|
319
|
-
|
|
411
|
+
print("❌ 未找到 git 命令,请先安装 Git")
|
|
320
412
|
sys.exit(1)
|
|
321
413
|
except Exception as e:
|
|
322
|
-
|
|
414
|
+
print(f"❌ 检查 Git 配置时出错: {str(e)}")
|
|
323
415
|
sys.exit(1)
|
|
324
416
|
|
|
325
417
|
def _find_git_root(self) -> str:
|
|
@@ -468,7 +560,7 @@ class CodeAgent(Agent):
|
|
|
468
560
|
content_to_write = "\n".join(new_lines).rstrip()
|
|
469
561
|
if content_to_write:
|
|
470
562
|
f.write(content_to_write + "\n")
|
|
471
|
-
|
|
563
|
+
print("✅ 已创建 .gitignore 并添加常用忽略规则")
|
|
472
564
|
else:
|
|
473
565
|
if new_lines:
|
|
474
566
|
# 追加缺失的规则
|
|
@@ -477,7 +569,7 @@ class CodeAgent(Agent):
|
|
|
477
569
|
if existing_content and not existing_content.endswith("\n"):
|
|
478
570
|
f.write("\n")
|
|
479
571
|
f.write("\n".join(new_lines).rstrip() + "\n")
|
|
480
|
-
|
|
572
|
+
print("✅ 已更新 .gitignore,追加常用忽略规则")
|
|
481
573
|
|
|
482
574
|
def _handle_git_changes(self, prefix: str, suffix: str) -> None:
|
|
483
575
|
"""处理git仓库中的未提交修改"""
|
|
@@ -528,16 +620,14 @@ class CodeAgent(Agent):
|
|
|
528
620
|
|
|
529
621
|
return
|
|
530
622
|
|
|
531
|
-
|
|
532
|
-
"⚠️ 正在修改git换行符敏感设置,这会影响所有文件的换行符处理方式",
|
|
533
|
-
OutputType.WARNING,
|
|
534
|
-
)
|
|
623
|
+
print("⚠️ 正在修改git换行符敏感设置,这会影响所有文件的换行符处理方式")
|
|
535
624
|
# 避免在循环中逐条打印,先拼接后统一打印
|
|
536
625
|
lines = ["将进行以下设置:"]
|
|
537
626
|
for key, value in target_settings.items():
|
|
538
627
|
current = current_settings.get(key, "未设置")
|
|
539
628
|
lines.append(f"{key}: {current} -> {value}")
|
|
540
|
-
|
|
629
|
+
joined_lines = '\n'.join(lines)
|
|
630
|
+
print(f"ℹ️ {joined_lines}")
|
|
541
631
|
|
|
542
632
|
# 直接执行设置,不需要用户确认
|
|
543
633
|
for key, value in target_settings.items():
|
|
@@ -547,7 +637,7 @@ class CodeAgent(Agent):
|
|
|
547
637
|
if sys.platform.startswith("win"):
|
|
548
638
|
self._handle_windows_line_endings()
|
|
549
639
|
|
|
550
|
-
|
|
640
|
+
print("✅ git换行符敏感设置已更新")
|
|
551
641
|
|
|
552
642
|
def _handle_windows_line_endings(self) -> None:
|
|
553
643
|
"""在Windows系统上处理换行符问题,提供建议而非强制修改"""
|
|
@@ -561,13 +651,8 @@ class CodeAgent(Agent):
|
|
|
561
651
|
if any(keyword in content for keyword in ["text=", "eol=", "binary"]):
|
|
562
652
|
return
|
|
563
653
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
OutputType.INFO,
|
|
567
|
-
)
|
|
568
|
-
PrettyOutput.print(
|
|
569
|
-
"这可以防止仅因换行符不同而导致整个文件被标记为修改。", OutputType.INFO
|
|
570
|
-
)
|
|
654
|
+
print("ℹ️ 提示:在Windows系统上,建议配置 .gitattributes 文件来避免换行符问题。")
|
|
655
|
+
print("ℹ️ 这可以防止仅因换行符不同而导致整个文件被标记为修改。")
|
|
571
656
|
|
|
572
657
|
if user_confirm("是否要创建一个最小化的.gitattributes文件?", False):
|
|
573
658
|
# 最小化的内容,只影响特定类型的文件
|
|
@@ -586,25 +671,18 @@ class CodeAgent(Agent):
|
|
|
586
671
|
if not os.path.exists(gitattributes_path):
|
|
587
672
|
with open(gitattributes_path, "w", encoding="utf-8", newline="\n") as f:
|
|
588
673
|
f.write(minimal_content)
|
|
589
|
-
|
|
590
|
-
"已创建最小化的 .gitattributes 文件", OutputType.SUCCESS
|
|
591
|
-
)
|
|
674
|
+
print("✅ 已创建最小化的 .gitattributes 文件")
|
|
592
675
|
else:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
)
|
|
596
|
-
PrettyOutput.print(minimal_content, OutputType.CODE, lang="text")
|
|
676
|
+
print("ℹ️ 将以下内容追加到现有 .gitattributes 文件:")
|
|
677
|
+
PrettyOutput.print(minimal_content, OutputType.CODE, lang="text") # 保留语法高亮
|
|
597
678
|
if user_confirm("是否追加到现有文件?", True):
|
|
598
679
|
with open(
|
|
599
680
|
gitattributes_path, "a", encoding="utf-8", newline="\n"
|
|
600
681
|
) as f:
|
|
601
682
|
f.write("\n" + minimal_content)
|
|
602
|
-
|
|
683
|
+
print("✅ 已更新 .gitattributes 文件")
|
|
603
684
|
else:
|
|
604
|
-
|
|
605
|
-
"跳过 .gitattributes 文件创建。如遇换行符问题,可手动创建此文件。",
|
|
606
|
-
OutputType.INFO,
|
|
607
|
-
)
|
|
685
|
+
print("ℹ️ 跳过 .gitattributes 文件创建。如遇换行符问题,可手动创建此文件。")
|
|
608
686
|
|
|
609
687
|
def _record_code_changes_stats(self, diff_text: str) -> None:
|
|
610
688
|
"""记录代码变更的统计信息。
|
|
@@ -655,7 +733,7 @@ class CodeAgent(Agent):
|
|
|
655
733
|
except subprocess.CalledProcessError:
|
|
656
734
|
pass
|
|
657
735
|
|
|
658
|
-
|
|
736
|
+
print("⚠️ 检测到未提交的修改,是否要提交?")
|
|
659
737
|
if not user_confirm("是否要提交?", True):
|
|
660
738
|
return
|
|
661
739
|
|
|
@@ -693,7 +771,7 @@ class CodeAgent(Agent):
|
|
|
693
771
|
check=True,
|
|
694
772
|
)
|
|
695
773
|
except subprocess.CalledProcessError as e:
|
|
696
|
-
|
|
774
|
+
print(f"❌ 提交失败: {str(e)}")
|
|
697
775
|
|
|
698
776
|
def _show_commit_history(
|
|
699
777
|
self, start_commit: Optional[str], end_commit: Optional[str]
|
|
@@ -721,9 +799,81 @@ class CodeAgent(Agent):
|
|
|
721
799
|
commit_messages = "检测到以下提交记录:\n" + "\n".join(
|
|
722
800
|
f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits
|
|
723
801
|
)
|
|
724
|
-
|
|
802
|
+
print(f"ℹ️ {commit_messages}")
|
|
725
803
|
return commits
|
|
726
804
|
|
|
805
|
+
def _format_modified_files(self, modified_files: List[str]) -> None:
|
|
806
|
+
"""格式化修改的文件
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
modified_files: 修改的文件列表
|
|
810
|
+
"""
|
|
811
|
+
if not modified_files:
|
|
812
|
+
return
|
|
813
|
+
|
|
814
|
+
# 获取格式化命令
|
|
815
|
+
format_commands = get_format_commands_for_files(modified_files, self.root_dir)
|
|
816
|
+
if not format_commands:
|
|
817
|
+
return
|
|
818
|
+
|
|
819
|
+
print("🔧 正在格式化代码...")
|
|
820
|
+
|
|
821
|
+
# 执行格式化命令
|
|
822
|
+
formatted_files = set()
|
|
823
|
+
for tool_name, file_path, command in format_commands:
|
|
824
|
+
try:
|
|
825
|
+
# 检查文件是否存在
|
|
826
|
+
abs_file_path = os.path.join(self.root_dir, file_path) if not os.path.isabs(file_path) else file_path
|
|
827
|
+
if not os.path.exists(abs_file_path):
|
|
828
|
+
continue
|
|
829
|
+
|
|
830
|
+
# 执行格式化命令
|
|
831
|
+
result = subprocess.run(
|
|
832
|
+
command,
|
|
833
|
+
shell=True,
|
|
834
|
+
cwd=self.root_dir,
|
|
835
|
+
capture_output=True,
|
|
836
|
+
text=True,
|
|
837
|
+
encoding="utf-8",
|
|
838
|
+
errors="replace",
|
|
839
|
+
timeout=300, # 300秒超时
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if result.returncode == 0:
|
|
843
|
+
formatted_files.add(file_path)
|
|
844
|
+
print(f"✅ 已格式化: {os.path.basename(file_path)} ({tool_name})")
|
|
845
|
+
else:
|
|
846
|
+
# 格式化失败,记录但不中断流程
|
|
847
|
+
error_msg = (result.stderr or result.stdout or "").strip()
|
|
848
|
+
if error_msg:
|
|
849
|
+
print(f"⚠️ 格式化失败 ({os.path.basename(file_path)}, {tool_name}): {error_msg[:200]}")
|
|
850
|
+
except subprocess.TimeoutExpired:
|
|
851
|
+
print(f"⚠️ 格式化超时: {os.path.basename(file_path)} ({tool_name})")
|
|
852
|
+
except FileNotFoundError:
|
|
853
|
+
# 工具未安装,跳过
|
|
854
|
+
continue
|
|
855
|
+
except Exception as e:
|
|
856
|
+
# 其他错误,记录但继续
|
|
857
|
+
print(f"⚠️ 格式化失败 ({os.path.basename(file_path)}, {tool_name}): {str(e)[:100]}")
|
|
858
|
+
continue
|
|
859
|
+
|
|
860
|
+
if formatted_files:
|
|
861
|
+
print(f"✅ 已格式化 {len(formatted_files)} 个文件")
|
|
862
|
+
# 暂存格式化后的文件
|
|
863
|
+
try:
|
|
864
|
+
for file_path in formatted_files:
|
|
865
|
+
abs_file_path = os.path.join(self.root_dir, file_path) if not os.path.isabs(file_path) else file_path
|
|
866
|
+
if os.path.exists(abs_file_path):
|
|
867
|
+
subprocess.run(
|
|
868
|
+
["git", "add", file_path],
|
|
869
|
+
cwd=self.root_dir,
|
|
870
|
+
check=False,
|
|
871
|
+
stdout=subprocess.DEVNULL,
|
|
872
|
+
stderr=subprocess.DEVNULL,
|
|
873
|
+
)
|
|
874
|
+
except Exception:
|
|
875
|
+
pass
|
|
876
|
+
|
|
727
877
|
def _handle_commit_confirmation(
|
|
728
878
|
self,
|
|
729
879
|
commits: List[Tuple[str, str]],
|
|
@@ -744,6 +894,12 @@ class CodeAgent(Agent):
|
|
|
744
894
|
stderr=subprocess.DEVNULL,
|
|
745
895
|
check=True,
|
|
746
896
|
)
|
|
897
|
+
|
|
898
|
+
# 检测变更文件并格式化
|
|
899
|
+
modified_files = get_diff_file_list()
|
|
900
|
+
if modified_files:
|
|
901
|
+
self._format_modified_files(modified_files)
|
|
902
|
+
|
|
747
903
|
git_commiter = GitCommitTool()
|
|
748
904
|
git_commiter.execute({"prefix": prefix, "suffix": suffix, "agent": self, "model_group": getattr(self.model, "model_group", None)})
|
|
749
905
|
|
|
@@ -753,7 +909,7 @@ class CodeAgent(Agent):
|
|
|
753
909
|
elif start_commit:
|
|
754
910
|
if user_confirm("是否要重置到初始提交?", True):
|
|
755
911
|
os.system(f"git reset --hard {str(start_commit)}") # 确保转换为字符串
|
|
756
|
-
|
|
912
|
+
print("ℹ️ 已重置到初始提交")
|
|
757
913
|
|
|
758
914
|
def run(self, user_input: str, prefix: str = "", suffix: str = "") -> Optional[str]:
|
|
759
915
|
"""使用给定的用户输入运行代码代理。
|
|
@@ -790,7 +946,7 @@ class CodeAgent(Agent):
|
|
|
790
946
|
was_suppressed = getattr(self.model, '_suppress_output', False)
|
|
791
947
|
self.model.set_suppress_output(True)
|
|
792
948
|
try:
|
|
793
|
-
|
|
949
|
+
print("🔍 正在进行智能上下文推荐....")
|
|
794
950
|
|
|
795
951
|
# 生成上下文推荐(基于关键词和项目上下文)
|
|
796
952
|
recommendation = self.context_recommender.recommend_context(
|
|
@@ -802,12 +958,10 @@ class CodeAgent(Agent):
|
|
|
802
958
|
|
|
803
959
|
# 打印推荐的上下文
|
|
804
960
|
if context_recommendation_text:
|
|
805
|
-
|
|
961
|
+
print(f"ℹ️ {context_recommendation_text}")
|
|
806
962
|
except Exception as e:
|
|
807
963
|
# 上下文推荐失败不应该影响主流程
|
|
808
|
-
|
|
809
|
-
logger = logging.getLogger(__name__)
|
|
810
|
-
logger.debug(f"上下文推荐失败: {e}", exc_info=True)
|
|
964
|
+
print(f"⚠️ 上下文推荐失败: {e}")
|
|
811
965
|
finally:
|
|
812
966
|
# 恢复模型输出设置
|
|
813
967
|
if self.model:
|
|
@@ -830,7 +984,7 @@ class CodeAgent(Agent):
|
|
|
830
984
|
self.model.set_suppress_output(False)
|
|
831
985
|
super().run(enhanced_input)
|
|
832
986
|
except RuntimeError as e:
|
|
833
|
-
|
|
987
|
+
print(f"⚠️ 执行失败: {str(e)}")
|
|
834
988
|
return str(e)
|
|
835
989
|
|
|
836
990
|
|
|
@@ -981,7 +1135,7 @@ class CodeAgent(Agent):
|
|
|
981
1135
|
"""更新上下文管理器:当文件被修改后,更新符号表和依赖图"""
|
|
982
1136
|
if not modified_files:
|
|
983
1137
|
return
|
|
984
|
-
|
|
1138
|
+
print("🔄 正在更新代码上下文...")
|
|
985
1139
|
for file_path in modified_files:
|
|
986
1140
|
if os.path.exists(file_path):
|
|
987
1141
|
try:
|
|
@@ -1001,7 +1155,7 @@ class CodeAgent(Agent):
|
|
|
1001
1155
|
if not is_enable_impact_analysis():
|
|
1002
1156
|
return None
|
|
1003
1157
|
|
|
1004
|
-
|
|
1158
|
+
print("🔍 正在进行变更影响分析...")
|
|
1005
1159
|
try:
|
|
1006
1160
|
impact_analyzer = ImpactAnalyzer(self.context_manager)
|
|
1007
1161
|
all_edits = []
|
|
@@ -1064,9 +1218,7 @@ class CodeAgent(Agent):
|
|
|
1064
1218
|
return impact_report
|
|
1065
1219
|
except Exception as e:
|
|
1066
1220
|
# 影响分析失败不应该影响主流程,仅记录日志
|
|
1067
|
-
|
|
1068
|
-
logger = logging.getLogger(__name__)
|
|
1069
|
-
logger.warning(f"影响范围分析失败: {e}", exc_info=True)
|
|
1221
|
+
print(f"⚠️ 影响范围分析失败: {e}")
|
|
1070
1222
|
return None
|
|
1071
1223
|
|
|
1072
1224
|
def _handle_impact_report(self, impact_report: Optional[Any], agent: Agent, final_ret: str) -> str:
|
|
@@ -1115,7 +1267,6 @@ class CodeAgent(Agent):
|
|
|
1115
1267
|
files_str = ", ".join(os.path.basename(f) for f in modified_files[:3])
|
|
1116
1268
|
if file_count > 3:
|
|
1117
1269
|
files_str += f" 等{file_count}个文件"
|
|
1118
|
-
PrettyOutput.print(f"🔍 正在进行基础静态检查 ({files_str})...", OutputType.INFO)
|
|
1119
1270
|
|
|
1120
1271
|
# 使用兜底验证器进行基础静态检查
|
|
1121
1272
|
fallback_validator = FallbackBuildValidator(self.root_dir, timeout=get_build_validation_timeout())
|
|
@@ -1139,15 +1290,9 @@ class CodeAgent(Agent):
|
|
|
1139
1290
|
if not config.has_been_asked():
|
|
1140
1291
|
# 首次失败,询问用户
|
|
1141
1292
|
error_preview = _format_build_error(build_validation_result)
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
)
|
|
1146
|
-
PrettyOutput.print(
|
|
1147
|
-
"提示:如果此项目需要在特殊环境(如容器)中构建,或使用独立构建脚本,"
|
|
1148
|
-
"可以选择禁用构建验证,后续将仅进行基础静态检查。",
|
|
1149
|
-
OutputType.INFO,
|
|
1150
|
-
)
|
|
1293
|
+
print(f"\n⚠️ 构建验证失败:\n{error_preview}\n")
|
|
1294
|
+
print("ℹ️ 提示:如果此项目需要在特殊环境(如容器)中构建,或使用独立构建脚本,"
|
|
1295
|
+
"可以选择禁用构建验证,后续将仅进行基础静态检查。")
|
|
1151
1296
|
|
|
1152
1297
|
if user_confirm(
|
|
1153
1298
|
"是否要禁用构建验证,后续仅进行基础静态检查?",
|
|
@@ -1165,7 +1310,6 @@ class CodeAgent(Agent):
|
|
|
1165
1310
|
files_str = ", ".join(os.path.basename(f) for f in modified_files[:3])
|
|
1166
1311
|
if file_count > 3:
|
|
1167
1312
|
files_str += f" 等{file_count}个文件"
|
|
1168
|
-
PrettyOutput.print(f"🔍 正在进行基础静态检查 ({files_str})...", OutputType.INFO)
|
|
1169
1313
|
|
|
1170
1314
|
# 立即进行基础静态检查
|
|
1171
1315
|
fallback_validator = FallbackBuildValidator(self.root_dir, timeout=get_build_validation_timeout())
|
|
@@ -1234,7 +1378,7 @@ class CodeAgent(Agent):
|
|
|
1234
1378
|
"""
|
|
1235
1379
|
# 检查是否启用静态分析
|
|
1236
1380
|
if not is_enable_static_analysis():
|
|
1237
|
-
|
|
1381
|
+
print("ℹ️ 静态分析已禁用,跳过静态检查")
|
|
1238
1382
|
return final_ret
|
|
1239
1383
|
|
|
1240
1384
|
# 检查是否有可用的lint工具
|
|
@@ -1245,7 +1389,7 @@ class CodeAgent(Agent):
|
|
|
1245
1389
|
)
|
|
1246
1390
|
|
|
1247
1391
|
if not lint_tools_info:
|
|
1248
|
-
|
|
1392
|
+
print("ℹ️ 未找到可用的静态检查工具,跳过静态检查")
|
|
1249
1393
|
return final_ret
|
|
1250
1394
|
|
|
1251
1395
|
# 如果构建验证失败且未禁用,不进行静态分析(避免重复错误)
|
|
@@ -1257,7 +1401,7 @@ class CodeAgent(Agent):
|
|
|
1257
1401
|
)
|
|
1258
1402
|
|
|
1259
1403
|
if should_skip_static:
|
|
1260
|
-
|
|
1404
|
+
print("ℹ️ 构建验证失败,跳过静态分析(避免重复错误)")
|
|
1261
1405
|
return final_ret
|
|
1262
1406
|
|
|
1263
1407
|
# 直接执行静态扫描
|
|
@@ -1265,6 +1409,8 @@ class CodeAgent(Agent):
|
|
|
1265
1409
|
if lint_results:
|
|
1266
1410
|
# 有错误或警告,让大模型修复
|
|
1267
1411
|
errors_summary = self._format_lint_results(lint_results)
|
|
1412
|
+
# 打印完整的检查结果
|
|
1413
|
+
print(f"⚠️ 静态扫描发现问题:\n{errors_summary}")
|
|
1268
1414
|
addon_prompt = f"""
|
|
1269
1415
|
静态扫描发现以下问题,请根据错误信息修复代码:
|
|
1270
1416
|
|
|
@@ -1321,29 +1467,23 @@ class CodeAgent(Agent):
|
|
|
1321
1467
|
"""
|
|
1322
1468
|
|
|
1323
1469
|
try:
|
|
1324
|
-
|
|
1470
|
+
print("🤖 正在询问大模型判断大量代码删除是否合理...")
|
|
1325
1471
|
response = self.model.chat_until_success(prompt) # type: ignore
|
|
1326
1472
|
|
|
1327
1473
|
# 使用确定的协议标记解析回答
|
|
1328
1474
|
if "<!!!YES!!!>" in response:
|
|
1329
|
-
|
|
1475
|
+
print("✅ 大模型确认:代码删除合理")
|
|
1330
1476
|
return True
|
|
1331
1477
|
elif "<!!!NO!!!>" in response:
|
|
1332
|
-
|
|
1478
|
+
print("⚠️ 大模型确认:代码删除不合理")
|
|
1333
1479
|
return False
|
|
1334
1480
|
else:
|
|
1335
1481
|
# 如果无法找到协议标记,默认认为不合理(保守策略)
|
|
1336
|
-
|
|
1337
|
-
f"⚠️ 无法找到协议标记,默认认为不合理。回答内容: {response[:200]}",
|
|
1338
|
-
OutputType.WARNING
|
|
1339
|
-
)
|
|
1482
|
+
print(f"⚠️ 无法找到协议标记,默认认为不合理。回答内容: {response[:200]}")
|
|
1340
1483
|
return False
|
|
1341
1484
|
except Exception as e:
|
|
1342
1485
|
# 如果询问失败,默认认为不合理(保守策略)
|
|
1343
|
-
|
|
1344
|
-
f"⚠️ 询问大模型失败: {str(e)},默认认为不合理",
|
|
1345
|
-
OutputType.WARNING
|
|
1346
|
-
)
|
|
1486
|
+
print(f"⚠️ 询问大模型失败: {str(e)},默认认为不合理")
|
|
1347
1487
|
return False
|
|
1348
1488
|
|
|
1349
1489
|
def _on_after_tool_call(self, agent: Agent, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
|
|
@@ -1353,7 +1493,7 @@ class CodeAgent(Agent):
|
|
|
1353
1493
|
|
|
1354
1494
|
if diff:
|
|
1355
1495
|
start_hash = get_latest_commit_hash()
|
|
1356
|
-
PrettyOutput.print(diff, OutputType.CODE, lang="diff")
|
|
1496
|
+
PrettyOutput.print(diff, OutputType.CODE, lang="diff") # 保留语法高亮
|
|
1357
1497
|
modified_files = get_diff_file_list()
|
|
1358
1498
|
|
|
1359
1499
|
# 更新上下文管理器
|
|
@@ -1364,21 +1504,20 @@ class CodeAgent(Agent):
|
|
|
1364
1504
|
|
|
1365
1505
|
per_file_preview = self._build_per_file_patch_preview(modified_files)
|
|
1366
1506
|
|
|
1367
|
-
#
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
return
|
|
1507
|
+
# 所有模式下,在提交前检测大量代码删除并询问大模型
|
|
1508
|
+
detection_result = detect_large_code_deletion()
|
|
1509
|
+
if detection_result is not None:
|
|
1510
|
+
# 检测到大量代码删除,询问大模型是否合理
|
|
1511
|
+
is_reasonable = self._ask_llm_about_large_deletion(detection_result, per_file_preview)
|
|
1512
|
+
if not is_reasonable:
|
|
1513
|
+
# 大模型认为不合理,撤销修改
|
|
1514
|
+
print("ℹ️ 已撤销修改(大模型认为代码删除不合理)")
|
|
1515
|
+
revert_change()
|
|
1516
|
+
final_ret += "\n\n修改被撤销(检测到大量代码删除且大模型判断不合理)\n"
|
|
1517
|
+
final_ret += f"# 补丁预览(按文件):\n{per_file_preview}"
|
|
1518
|
+
PrettyOutput.print(final_ret, OutputType.USER, lang="markdown") # 保留语法高亮
|
|
1519
|
+
self.session.prompt += final_ret
|
|
1520
|
+
return
|
|
1382
1521
|
|
|
1383
1522
|
commited = handle_commit_workflow()
|
|
1384
1523
|
if commited:
|
|
@@ -1409,8 +1548,16 @@ class CodeAgent(Agent):
|
|
|
1409
1548
|
|
|
1410
1549
|
# 添加提交信息到final_ret(按文件展示diff;删除文件仅提示)
|
|
1411
1550
|
if commits:
|
|
1551
|
+
# 获取最新的提交信息(commits列表按时间倒序,第一个是最新的)
|
|
1552
|
+
latest_commit_hash, latest_commit_message = commits[0]
|
|
1553
|
+
commit_short_hash = latest_commit_hash[:7] if len(latest_commit_hash) >= 7 else latest_commit_hash
|
|
1554
|
+
|
|
1412
1555
|
final_ret += (
|
|
1413
|
-
f"\n\n代码已修改完成\n
|
|
1556
|
+
f"\n\n代码已修改完成\n"
|
|
1557
|
+
f"✅ 已自动提交\n"
|
|
1558
|
+
f" Commit ID: {commit_short_hash} ({latest_commit_hash})\n"
|
|
1559
|
+
f" 提交信息: {latest_commit_message}\n"
|
|
1560
|
+
f"\n补丁内容(按文件):\n{per_file_preview}\n"
|
|
1414
1561
|
)
|
|
1415
1562
|
|
|
1416
1563
|
# 添加影响范围分析报告
|
|
@@ -1423,7 +1570,33 @@ class CodeAgent(Agent):
|
|
|
1423
1570
|
# 静态分析
|
|
1424
1571
|
final_ret = self._handle_static_analysis(modified_files, build_validation_result, config, self, final_ret)
|
|
1425
1572
|
else:
|
|
1426
|
-
|
|
1573
|
+
# 如果没有获取到commits,尝试直接从end_hash获取commit信息
|
|
1574
|
+
commit_info = ""
|
|
1575
|
+
if end_hash:
|
|
1576
|
+
try:
|
|
1577
|
+
result = subprocess.run(
|
|
1578
|
+
["git", "log", "-1", "--pretty=format:%H|%s", end_hash],
|
|
1579
|
+
capture_output=True,
|
|
1580
|
+
text=True,
|
|
1581
|
+
encoding="utf-8",
|
|
1582
|
+
errors="replace",
|
|
1583
|
+
check=False,
|
|
1584
|
+
)
|
|
1585
|
+
if result.returncode == 0 and result.stdout and "|" in result.stdout:
|
|
1586
|
+
commit_hash, commit_message = result.stdout.strip().split("|", 1)
|
|
1587
|
+
commit_short_hash = commit_hash[:7] if len(commit_hash) >= 7 else commit_hash
|
|
1588
|
+
commit_info = (
|
|
1589
|
+
f"\n✅ 已自动提交\n"
|
|
1590
|
+
f" Commit ID: {commit_short_hash} ({commit_hash})\n"
|
|
1591
|
+
f" 提交信息: {commit_message}\n"
|
|
1592
|
+
)
|
|
1593
|
+
except Exception:
|
|
1594
|
+
pass
|
|
1595
|
+
|
|
1596
|
+
if commit_info:
|
|
1597
|
+
final_ret += f"\n\n代码已修改完成{commit_info}\n"
|
|
1598
|
+
else:
|
|
1599
|
+
final_ret += "\n\n修改没有生效\n"
|
|
1427
1600
|
else:
|
|
1428
1601
|
final_ret += "\n修改被拒绝\n"
|
|
1429
1602
|
final_ret += f"# 补丁预览(按文件):\n{per_file_preview}"
|
|
@@ -1433,7 +1606,7 @@ class CodeAgent(Agent):
|
|
|
1433
1606
|
if commited:
|
|
1434
1607
|
self.session.prompt += final_ret
|
|
1435
1608
|
return
|
|
1436
|
-
PrettyOutput.print(final_ret, OutputType.USER, lang="markdown")
|
|
1609
|
+
PrettyOutput.print(final_ret, OutputType.USER, lang="markdown") # 保留语法高亮
|
|
1437
1610
|
if not is_confirm_before_apply_patch() or user_confirm(
|
|
1438
1611
|
"是否使用此回复?", default=True
|
|
1439
1612
|
):
|
|
@@ -1473,7 +1646,7 @@ class CodeAgent(Agent):
|
|
|
1473
1646
|
tools_str = ", ".join(tool_names[:3])
|
|
1474
1647
|
if len(tool_names) > 3:
|
|
1475
1648
|
tools_str += f" 等{len(tool_names)}个工具"
|
|
1476
|
-
|
|
1649
|
+
print("🔍 静态检查中...")
|
|
1477
1650
|
|
|
1478
1651
|
results = []
|
|
1479
1652
|
# 记录每个文件的检查结果
|
|
@@ -1492,6 +1665,9 @@ class CodeAgent(Agent):
|
|
|
1492
1665
|
file_results.append((file_name, tool_name, "跳过", "文件不存在"))
|
|
1493
1666
|
continue
|
|
1494
1667
|
|
|
1668
|
+
# 打印执行的命令
|
|
1669
|
+
print(f"ℹ️ 执行: {command}")
|
|
1670
|
+
|
|
1495
1671
|
# 执行命令
|
|
1496
1672
|
result = subprocess.run(
|
|
1497
1673
|
command,
|
|
@@ -1501,7 +1677,7 @@ class CodeAgent(Agent):
|
|
|
1501
1677
|
text=True,
|
|
1502
1678
|
encoding="utf-8",
|
|
1503
1679
|
errors="replace",
|
|
1504
|
-
timeout=
|
|
1680
|
+
timeout=600, # 600秒超时
|
|
1505
1681
|
)
|
|
1506
1682
|
|
|
1507
1683
|
# 只记录有错误或警告的结果
|
|
@@ -1510,23 +1686,27 @@ class CodeAgent(Agent):
|
|
|
1510
1686
|
if output.strip(): # 有输出才记录
|
|
1511
1687
|
results.append((tool_name, file_path, command, result.returncode, output))
|
|
1512
1688
|
file_results.append((file_name, tool_name, "失败", "发现问题"))
|
|
1689
|
+
# 失败时打印检查结果
|
|
1690
|
+
output_preview = output[:2000] if len(output) > 2000 else output
|
|
1691
|
+
print(f"⚠️ 检查失败 ({file_name}):\n{output_preview}")
|
|
1692
|
+
if len(output) > 2000:
|
|
1693
|
+
print(f"⚠️ ... (输出已截断,共 {len(output)} 字符)")
|
|
1513
1694
|
else:
|
|
1514
1695
|
file_results.append((file_name, tool_name, "通过", ""))
|
|
1515
1696
|
else:
|
|
1516
1697
|
file_results.append((file_name, tool_name, "通过", ""))
|
|
1517
1698
|
|
|
1518
1699
|
except subprocess.TimeoutExpired:
|
|
1519
|
-
results.append((tool_name, file_path, command, -1, "执行超时(
|
|
1520
|
-
file_results.append((file_name, tool_name, "超时", "执行超时(
|
|
1700
|
+
results.append((tool_name, file_path, command, -1, "执行超时(600秒)"))
|
|
1701
|
+
file_results.append((file_name, tool_name, "超时", "执行超时(600秒)"))
|
|
1702
|
+
print(f"⚠️ 检查超时 ({file_name}): 执行超时(600秒)")
|
|
1521
1703
|
except FileNotFoundError:
|
|
1522
1704
|
# 工具未安装,跳过
|
|
1523
1705
|
file_results.append((file_name, tool_name, "跳过", "工具未安装"))
|
|
1524
1706
|
continue
|
|
1525
1707
|
except Exception as e:
|
|
1526
1708
|
# 其他错误,记录但继续
|
|
1527
|
-
|
|
1528
|
-
logger = logging.getLogger(__name__)
|
|
1529
|
-
logger.warning(f"执行lint命令失败: {command}, 错误: {e}")
|
|
1709
|
+
print(f"⚠️ 执行lint命令失败: {command}, 错误: {e}")
|
|
1530
1710
|
file_results.append((file_name, tool_name, "失败", f"执行失败: {str(e)[:50]}"))
|
|
1531
1711
|
continue
|
|
1532
1712
|
|
|
@@ -1536,38 +1716,23 @@ class CodeAgent(Agent):
|
|
|
1536
1716
|
passed_count = sum(1 for _, _, status, _ in file_results if status == "通过")
|
|
1537
1717
|
failed_count = sum(1 for _, _, status, _ in file_results if status == "失败")
|
|
1538
1718
|
timeout_count = sum(1 for _, _, status, _ in file_results if status == "超时")
|
|
1539
|
-
|
|
1719
|
+
sum(1 for _, _, status, _ in file_results if status == "跳过")
|
|
1540
1720
|
|
|
1541
|
-
#
|
|
1542
|
-
|
|
1543
|
-
if passed_count > 0:
|
|
1544
|
-
summary_lines.append(f" ✅ 通过: {passed_count}")
|
|
1721
|
+
# 收缩为一行的结果摘要
|
|
1722
|
+
summary = f"🔍 静态检查: {total_files}个文件"
|
|
1545
1723
|
if failed_count > 0:
|
|
1546
|
-
|
|
1724
|
+
summary += f", {failed_count}失败"
|
|
1547
1725
|
if timeout_count > 0:
|
|
1548
|
-
|
|
1549
|
-
if
|
|
1550
|
-
|
|
1726
|
+
summary += f", {timeout_count}超时"
|
|
1727
|
+
if passed_count == total_files:
|
|
1728
|
+
summary += " ✅全部通过"
|
|
1551
1729
|
|
|
1552
|
-
# 添加详细结果(只显示失败和超时的文件)
|
|
1553
1730
|
if failed_count > 0 or timeout_count > 0:
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
continue # 只显示失败和超时的文件
|
|
1558
|
-
status_icon = {
|
|
1559
|
-
"失败": "❌",
|
|
1560
|
-
"超时": "⏱️"
|
|
1561
|
-
}.get(status, "•")
|
|
1562
|
-
if message:
|
|
1563
|
-
summary_lines.append(f" {status_icon} {file_name} ({tool_name}): {message}")
|
|
1564
|
-
else:
|
|
1565
|
-
summary_lines.append(f" {status_icon} {file_name} ({tool_name})")
|
|
1566
|
-
|
|
1567
|
-
output_type = OutputType.WARNING if (failed_count > 0 or timeout_count > 0) else OutputType.SUCCESS
|
|
1568
|
-
PrettyOutput.print("\n".join(summary_lines), output_type)
|
|
1731
|
+
print(f"⚠️ {summary}")
|
|
1732
|
+
else:
|
|
1733
|
+
print(f"✅ {summary}")
|
|
1569
1734
|
else:
|
|
1570
|
-
|
|
1735
|
+
print("✅ 静态检查完成")
|
|
1571
1736
|
|
|
1572
1737
|
return results
|
|
1573
1738
|
|
|
@@ -1599,87 +1764,6 @@ class CodeAgent(Agent):
|
|
|
1599
1764
|
lines.append("") # 空行分隔
|
|
1600
1765
|
|
|
1601
1766
|
return "\n".join(lines)
|
|
1602
|
-
|
|
1603
|
-
def _extract_file_paths_from_input(self, user_input: str) -> List[str]:
|
|
1604
|
-
"""从用户输入中提取文件路径
|
|
1605
|
-
|
|
1606
|
-
Args:
|
|
1607
|
-
user_input: 用户输入文本
|
|
1608
|
-
|
|
1609
|
-
Returns:
|
|
1610
|
-
文件路径列表
|
|
1611
|
-
"""
|
|
1612
|
-
import re
|
|
1613
|
-
file_paths = []
|
|
1614
|
-
|
|
1615
|
-
# 匹配常见的文件路径模式
|
|
1616
|
-
# 1. 引号中的路径: "path/to/file.py" 或 'path/to/file.py'
|
|
1617
|
-
quoted_paths = re.findall(r'["\']([^"\']+\.(?:py|js|ts|rs|go|java|cpp|c|h|hpp))["\']', user_input)
|
|
1618
|
-
file_paths.extend(quoted_paths)
|
|
1619
|
-
|
|
1620
|
-
# 2. 相对路径: ./path/to/file.py 或 path/to/file.py
|
|
1621
|
-
relative_paths = re.findall(r'(?:\./)?[\w/]+\.(?:py|js|ts|rs|go|java|cpp|c|h|hpp)', user_input)
|
|
1622
|
-
file_paths.extend(relative_paths)
|
|
1623
|
-
|
|
1624
|
-
# 3. 绝对路径(简化匹配)
|
|
1625
|
-
absolute_paths = re.findall(r'/(?:[\w\-\.]+/)+[\w\-\.]+\.(?:py|js|ts|rs|go|java|cpp|c|h|hpp)', user_input)
|
|
1626
|
-
file_paths.extend(absolute_paths)
|
|
1627
|
-
|
|
1628
|
-
# 转换为绝对路径并去重
|
|
1629
|
-
unique_paths = []
|
|
1630
|
-
seen = set()
|
|
1631
|
-
for path in file_paths:
|
|
1632
|
-
abs_path = os.path.abspath(path) if not os.path.isabs(path) else path
|
|
1633
|
-
if abs_path not in seen and os.path.exists(abs_path):
|
|
1634
|
-
seen.add(abs_path)
|
|
1635
|
-
unique_paths.append(abs_path)
|
|
1636
|
-
|
|
1637
|
-
return unique_paths
|
|
1638
|
-
|
|
1639
|
-
def _extract_symbols_from_input(self, user_input: str) -> List[str]:
|
|
1640
|
-
"""从用户输入中提取符号名称(函数名、类名等)
|
|
1641
|
-
|
|
1642
|
-
Args:
|
|
1643
|
-
user_input: 用户输入文本
|
|
1644
|
-
|
|
1645
|
-
Returns:
|
|
1646
|
-
符号名称列表
|
|
1647
|
-
"""
|
|
1648
|
-
import re
|
|
1649
|
-
symbols = []
|
|
1650
|
-
|
|
1651
|
-
# 匹配常见的符号命名模式
|
|
1652
|
-
# 1. 驼峰命名(类名): MyClass, ProcessData
|
|
1653
|
-
camel_case = re.findall(r'\b[A-Z][a-zA-Z0-9]+\b', user_input)
|
|
1654
|
-
symbols.extend(camel_case)
|
|
1655
|
-
|
|
1656
|
-
# 2. 下划线命名(函数名、变量名): process_data, get_user_info
|
|
1657
|
-
snake_case = re.findall(r'\b[a-z][a-z0-9_]+[a-z0-9]\b', user_input)
|
|
1658
|
-
symbols.extend(snake_case)
|
|
1659
|
-
|
|
1660
|
-
# 3. 在引号中的符号名: "function_name" 或 'ClassName'
|
|
1661
|
-
quoted_symbols = re.findall(r'["\']([A-Za-z][A-Za-z0-9_]*?)["\']', user_input)
|
|
1662
|
-
symbols.extend(quoted_symbols)
|
|
1663
|
-
|
|
1664
|
-
# 过滤常见停用词和过短的符号
|
|
1665
|
-
stop_words = {
|
|
1666
|
-
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'her', 'was', 'one',
|
|
1667
|
-
'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now',
|
|
1668
|
-
'old', 'see', 'two', 'way', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she',
|
|
1669
|
-
'too', 'use', '添加', '修改', '实现', '修复', '更新', '删除', '创建', '文件', '代码',
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
unique_symbols = []
|
|
1673
|
-
seen = set()
|
|
1674
|
-
for symbol in symbols:
|
|
1675
|
-
symbol_lower = symbol.lower()
|
|
1676
|
-
if (symbol_lower not in stop_words and
|
|
1677
|
-
len(symbol) > 2 and
|
|
1678
|
-
symbol_lower not in seen):
|
|
1679
|
-
seen.add(symbol_lower)
|
|
1680
|
-
unique_symbols.append(symbol)
|
|
1681
|
-
|
|
1682
|
-
return unique_symbols[:10] # 限制数量
|
|
1683
1767
|
|
|
1684
1768
|
def _validate_build_after_edit(self, modified_files: List[str]) -> Optional[BuildResult]:
|
|
1685
1769
|
"""编辑后验证构建
|
|
@@ -1704,7 +1788,7 @@ class CodeAgent(Agent):
|
|
|
1704
1788
|
files_str = ", ".join(os.path.basename(f) for f in modified_files[:3])
|
|
1705
1789
|
if file_count > 3:
|
|
1706
1790
|
files_str += f" 等{file_count}个文件"
|
|
1707
|
-
|
|
1791
|
+
print(f"🔨 正在进行编译检查 ({files_str})...")
|
|
1708
1792
|
|
|
1709
1793
|
try:
|
|
1710
1794
|
timeout = get_build_validation_timeout()
|
|
@@ -1713,9 +1797,7 @@ class CodeAgent(Agent):
|
|
|
1713
1797
|
return result
|
|
1714
1798
|
except Exception as e:
|
|
1715
1799
|
# 构建验证失败不应该影响主流程,仅记录日志
|
|
1716
|
-
|
|
1717
|
-
logger = logging.getLogger(__name__)
|
|
1718
|
-
logger.warning(f"构建验证执行失败: {e}", exc_info=True)
|
|
1800
|
+
print(f"⚠️ 构建验证执行失败: {e}")
|
|
1719
1801
|
return None
|
|
1720
1802
|
|
|
1721
1803
|
|
|
@@ -1754,7 +1836,9 @@ def cli(
|
|
|
1754
1836
|
non_interactive: bool = typer.Option(
|
|
1755
1837
|
False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
|
|
1756
1838
|
),
|
|
1757
|
-
|
|
1839
|
+
rule_names: Optional[str] = typer.Option(
|
|
1840
|
+
None, "--rule-names", help="指定规则名称列表,用逗号分隔,从 rules.yaml 文件中读取对应的规则内容"
|
|
1841
|
+
),
|
|
1758
1842
|
) -> None:
|
|
1759
1843
|
"""Jarvis主入口点。"""
|
|
1760
1844
|
# CLI 标志:非交互模式(不依赖配置文件)
|
|
@@ -1766,10 +1850,7 @@ def cli(
|
|
|
1766
1850
|
# 注意:全局配置同步放在 init_env 之后执行,避免被 init_env 覆盖
|
|
1767
1851
|
# 非交互模式要求从命令行传入任务
|
|
1768
1852
|
if non_interactive and not (requirement and str(requirement).strip()):
|
|
1769
|
-
|
|
1770
|
-
"非交互模式已启用:必须使用 --requirement 传入任务内容,因多行输入不可用。",
|
|
1771
|
-
OutputType.ERROR,
|
|
1772
|
-
)
|
|
1853
|
+
print("❌ 非交互模式已启用:必须使用 --requirement 传入任务内容,因多行输入不可用。")
|
|
1773
1854
|
raise typer.Exit(code=2)
|
|
1774
1855
|
init_env(
|
|
1775
1856
|
"欢迎使用 Jarvis-CodeAgent,您的代码工程助手已准备就绪!",
|
|
@@ -1801,9 +1882,7 @@ def cli(
|
|
|
1801
1882
|
)
|
|
1802
1883
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1803
1884
|
curr_dir_path = os.getcwd()
|
|
1804
|
-
|
|
1805
|
-
f"警告:当前目录 '{curr_dir_path}' 不是一个git仓库。", OutputType.WARNING
|
|
1806
|
-
)
|
|
1885
|
+
print(f"⚠️ 警告:当前目录 '{curr_dir_path}' 不是一个git仓库。")
|
|
1807
1886
|
init_git = True if non_interactive else user_confirm(
|
|
1808
1887
|
f"是否要在 '{curr_dir_path}' 中初始化一个新的git仓库?", default=True
|
|
1809
1888
|
)
|
|
@@ -1817,14 +1896,12 @@ def cli(
|
|
|
1817
1896
|
encoding="utf-8",
|
|
1818
1897
|
errors="replace",
|
|
1819
1898
|
)
|
|
1820
|
-
|
|
1899
|
+
print("✅ 已成功初始化git仓库。")
|
|
1821
1900
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
1822
|
-
|
|
1901
|
+
print(f"❌ 初始化git仓库失败: {e}")
|
|
1823
1902
|
sys.exit(1)
|
|
1824
1903
|
else:
|
|
1825
|
-
|
|
1826
|
-
"操作已取消。Jarvis需要在git仓库中运行。", OutputType.INFO
|
|
1827
|
-
)
|
|
1904
|
+
print("ℹ️ 操作已取消。Jarvis需要在git仓库中运行。")
|
|
1828
1905
|
sys.exit(0)
|
|
1829
1906
|
|
|
1830
1907
|
curr_dir = os.getcwd()
|
|
@@ -1838,25 +1915,22 @@ def cli(
|
|
|
1838
1915
|
# 回退到全局锁,确保至少有互斥保护
|
|
1839
1916
|
_acquire_single_instance_lock(lock_name="code_agent.lock")
|
|
1840
1917
|
try:
|
|
1918
|
+
|
|
1841
1919
|
agent = CodeAgent(
|
|
1842
1920
|
model_group=model_group,
|
|
1843
1921
|
need_summary=False,
|
|
1844
1922
|
append_tools=append_tools,
|
|
1845
1923
|
tool_group=tool_group,
|
|
1846
1924
|
non_interactive=non_interactive,
|
|
1847
|
-
|
|
1925
|
+
rule_names=rule_names,
|
|
1848
1926
|
)
|
|
1849
1927
|
|
|
1850
1928
|
# 尝试恢复会话
|
|
1851
1929
|
if restore_session:
|
|
1852
1930
|
if agent.restore_session():
|
|
1853
|
-
|
|
1854
|
-
"已从 .jarvis/saved_session.json 恢复会话。", OutputType.SUCCESS
|
|
1855
|
-
)
|
|
1931
|
+
print("✅ 已从 .jarvis/saved_session.json 恢复会话。")
|
|
1856
1932
|
else:
|
|
1857
|
-
|
|
1858
|
-
"无法从 .jarvis/saved_session.json 恢复会话。", OutputType.WARNING
|
|
1859
|
-
)
|
|
1933
|
+
print("⚠️ 无法从 .jarvis/saved_session.json 恢复会话。")
|
|
1860
1934
|
|
|
1861
1935
|
if requirement:
|
|
1862
1936
|
agent.run(requirement, prefix=prefix, suffix=suffix)
|
|
@@ -1870,7 +1944,7 @@ def cli(
|
|
|
1870
1944
|
except typer.Exit:
|
|
1871
1945
|
raise
|
|
1872
1946
|
except RuntimeError as e:
|
|
1873
|
-
|
|
1947
|
+
print(f"❌ 错误: {str(e)}")
|
|
1874
1948
|
sys.exit(1)
|
|
1875
1949
|
|
|
1876
1950
|
|