jarvis-ai-assistant 0.1.132__py3-none-any.whl → 0.1.138__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +330 -347
- jarvis/jarvis_agent/builtin_input_handler.py +16 -6
- jarvis/jarvis_agent/file_input_handler.py +9 -9
- jarvis/jarvis_agent/jarvis.py +143 -0
- jarvis/jarvis_agent/main.py +12 -13
- jarvis/jarvis_agent/output_handler.py +3 -3
- jarvis/jarvis_agent/patch.py +92 -64
- jarvis/jarvis_agent/shell_input_handler.py +5 -3
- jarvis/jarvis_code_agent/code_agent.py +263 -177
- jarvis/jarvis_code_agent/file_select.py +24 -24
- jarvis/jarvis_dev/main.py +45 -59
- jarvis/jarvis_git_details/__init__.py +0 -0
- jarvis/jarvis_git_details/main.py +179 -0
- jarvis/jarvis_git_squash/main.py +7 -7
- jarvis/jarvis_lsp/base.py +11 -53
- jarvis/jarvis_lsp/cpp.py +13 -28
- jarvis/jarvis_lsp/go.py +13 -28
- jarvis/jarvis_lsp/python.py +8 -27
- jarvis/jarvis_lsp/registry.py +21 -83
- jarvis/jarvis_lsp/rust.py +15 -30
- jarvis/jarvis_methodology/main.py +101 -0
- jarvis/jarvis_multi_agent/__init__.py +10 -51
- jarvis/jarvis_multi_agent/main.py +43 -0
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/ai8.py +67 -89
- jarvis/jarvis_platform/base.py +14 -13
- jarvis/jarvis_platform/kimi.py +25 -28
- jarvis/jarvis_platform/ollama.py +24 -26
- jarvis/jarvis_platform/openai.py +15 -19
- jarvis/jarvis_platform/oyi.py +48 -50
- jarvis/jarvis_platform/registry.py +29 -44
- jarvis/jarvis_platform/yuanbao.py +39 -43
- jarvis/jarvis_platform_manager/main.py +81 -81
- jarvis/jarvis_platform_manager/openai_test.py +21 -21
- jarvis/jarvis_rag/file_processors.py +18 -18
- jarvis/jarvis_rag/main.py +262 -278
- jarvis/jarvis_smart_shell/main.py +12 -12
- jarvis/jarvis_tools/ask_codebase.py +85 -78
- jarvis/jarvis_tools/ask_user.py +8 -8
- jarvis/jarvis_tools/base.py +4 -4
- jarvis/jarvis_tools/chdir.py +9 -9
- jarvis/jarvis_tools/code_review.py +40 -21
- jarvis/jarvis_tools/create_code_agent.py +15 -15
- jarvis/jarvis_tools/create_sub_agent.py +0 -1
- jarvis/jarvis_tools/execute_python_script.py +3 -3
- jarvis/jarvis_tools/execute_shell.py +11 -11
- jarvis/jarvis_tools/execute_shell_script.py +3 -3
- jarvis/jarvis_tools/file_analyzer.py +116 -105
- jarvis/jarvis_tools/file_operation.py +22 -20
- jarvis/jarvis_tools/find_caller.py +105 -40
- jarvis/jarvis_tools/find_methodolopy.py +65 -0
- jarvis/jarvis_tools/find_symbol.py +123 -39
- jarvis/jarvis_tools/function_analyzer.py +140 -57
- jarvis/jarvis_tools/git_commiter.py +10 -10
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
- jarvis/jarvis_tools/methodology.py +22 -67
- jarvis/jarvis_tools/project_analyzer.py +137 -53
- jarvis/jarvis_tools/rag.py +15 -20
- jarvis/jarvis_tools/read_code.py +25 -23
- jarvis/jarvis_tools/read_webpage.py +31 -31
- jarvis/jarvis_tools/registry.py +72 -52
- jarvis/jarvis_tools/search_web.py +23 -353
- jarvis/jarvis_tools/tool_generator.py +19 -19
- jarvis/jarvis_utils/config.py +36 -96
- jarvis/jarvis_utils/embedding.py +83 -83
- jarvis/jarvis_utils/git_utils.py +20 -20
- jarvis/jarvis_utils/globals.py +18 -6
- jarvis/jarvis_utils/input.py +10 -9
- jarvis/jarvis_utils/methodology.py +141 -140
- jarvis/jarvis_utils/output.py +13 -13
- jarvis/jarvis_utils/utils.py +23 -71
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +6 -15
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +4 -3
- jarvis/jarvis_tools/lsp_find_definition.py +0 -150
- jarvis/jarvis_tools/lsp_find_references.py +0 -127
- jarvis/jarvis_tools/select_code_files.py +0 -62
- jarvis_ai_assistant-0.1.132.dist-info/RECORD +0 -82
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
"""Jarvis代码代理模块。
|
|
2
|
+
|
|
3
|
+
该模块提供CodeAgent类,用于处理代码修改任务。
|
|
4
|
+
"""
|
|
5
|
+
|
|
2
6
|
import os
|
|
7
|
+
import sys
|
|
8
|
+
import subprocess
|
|
9
|
+
import argparse
|
|
10
|
+
from typing import Optional
|
|
3
11
|
|
|
4
12
|
from yaspin import yaspin
|
|
5
13
|
|
|
@@ -10,145 +18,197 @@ from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
|
|
|
10
18
|
from jarvis.jarvis_agent.patch import PatchOutputHandler
|
|
11
19
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
12
20
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
13
|
-
|
|
14
21
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
15
|
-
from jarvis.jarvis_utils.git_utils import
|
|
22
|
+
from jarvis.jarvis_utils.git_utils import (
|
|
23
|
+
find_git_root,
|
|
24
|
+
get_commits_between,
|
|
25
|
+
get_latest_commit_hash,
|
|
26
|
+
has_uncommitted_changes
|
|
27
|
+
)
|
|
16
28
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
17
29
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
18
30
|
from jarvis.jarvis_utils.utils import init_env, user_confirm
|
|
19
31
|
|
|
20
32
|
|
|
33
|
+
class CodeAgent:
|
|
34
|
+
"""Jarvis系统的代码修改代理。
|
|
21
35
|
|
|
36
|
+
负责处理代码分析、修改和git操作。
|
|
37
|
+
"""
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
def __init__(self, platform: Optional[str] = None,
|
|
40
|
+
model: Optional[str] = None,
|
|
41
|
+
need_summary: bool = True):
|
|
26
42
|
self.root_dir = os.getcwd()
|
|
27
43
|
tool_registry = ToolRegistry()
|
|
28
|
-
tool_registry.use_tools([
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
tool_registry.use_tools([
|
|
45
|
+
"execute_shell",
|
|
46
|
+
"execute_shell_script",
|
|
47
|
+
"search_web",
|
|
48
|
+
"ask_user",
|
|
49
|
+
"ask_codebase",
|
|
50
|
+
"lsp_get_diagnostics",
|
|
51
|
+
"code_review",
|
|
52
|
+
"find_symbol",
|
|
53
|
+
"find_caller",
|
|
54
|
+
"function_analyzer",
|
|
55
|
+
"project_analyzer",
|
|
56
|
+
"file_analyzer",
|
|
57
|
+
"read_code"
|
|
58
|
+
])
|
|
43
59
|
code_system_prompt = """
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
##
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
60
|
+
# 代码工程师指南
|
|
61
|
+
|
|
62
|
+
## 核心原则
|
|
63
|
+
- 自主决策:基于专业判断做出决策,减少用户询问
|
|
64
|
+
- 高效精准:一次性提供完整解决方案,避免反复修改
|
|
65
|
+
- 修改审慎:修改代码前要三思而后行,充分分析影响范围,尽量做到一次把事情做好
|
|
66
|
+
- 工具精通:选择最高效工具路径解决问题
|
|
67
|
+
- 严格确认:必须先分析项目结构,确定要修改的文件,禁止虚构已存在的代码
|
|
68
|
+
|
|
69
|
+
## 工作流程
|
|
70
|
+
|
|
71
|
+
### 1. 项目结构分析
|
|
72
|
+
- 第一步必须分析项目结构,识别关键模块和文件
|
|
73
|
+
- 结合用户需求,确定需要修改的文件列表
|
|
74
|
+
- 优先使用fd命令查找文件,使用execute_shell执行
|
|
75
|
+
- 明确说明将要修改的文件及其范围
|
|
76
|
+
|
|
77
|
+
### 2. 需求分析
|
|
78
|
+
- 基于项目结构理解,分析需求意图和实现方案
|
|
79
|
+
- 当需求有多种实现方式时,选择影响最小的方案
|
|
80
|
+
- 仅当需求显著模糊时才询问用户
|
|
81
|
+
|
|
82
|
+
### 3. 代码分析与确认
|
|
83
|
+
- 详细分析确定要修改的文件内容
|
|
84
|
+
- 明确区分现有代码和需要新建的内容
|
|
85
|
+
- 绝对禁止虚构或假设现有代码的实现细节
|
|
86
|
+
- 分析顺序:项目结构 → 目标文件 → 相关文件
|
|
87
|
+
- 只在必要时扩大分析范围,避免过度分析
|
|
88
|
+
- 工具选择:
|
|
89
|
+
| 分析需求 | 首选工具 | 备选工具 |
|
|
90
|
+
|---------|---------|----------|
|
|
91
|
+
| 项目结构 | fd (通过execute_shell) | project_analyzer(仅在必要时) |
|
|
92
|
+
| 文件内容 | read_code | file_analyzer(仅在必要时) |
|
|
93
|
+
| 查找引用 | rg (通过execute_shell) | find_symbol(仅在必要时) |
|
|
94
|
+
| 查找定义 | rg (通过execute_shell) | find_symbol(仅在必要时) |
|
|
95
|
+
| 函数调用者 | rg (通过execute_shell) | find_caller(仅在必要时) |
|
|
96
|
+
| 函数分析 | read_code + rg | function_analyzer(仅在必要时) |
|
|
97
|
+
| 整体分析 | execute_shell_script | ask_codebase(仅在必要时) |
|
|
98
|
+
| 代码质量检查 | execute_shell | code_review(仅在必要时) |
|
|
99
|
+
| 统计代码行数 | loc (通过execute_shell) | - |
|
|
100
|
+
|
|
101
|
+
### 4. 方案设计
|
|
102
|
+
- 确定最小变更方案,保持代码结构
|
|
103
|
+
- 变更类型处理:
|
|
104
|
+
- 修改现有文件:必须先确认文件存在及其内容
|
|
105
|
+
- 创建新文件:可以根据需求创建,但要符合项目结构和风格
|
|
106
|
+
- 变更规模处理:
|
|
107
|
+
- ≤50行:一次性完成所有修改
|
|
108
|
+
- 50-200行:按功能模块分组
|
|
109
|
+
- >200行:按功能拆分,但尽量减少提交次数
|
|
110
|
+
|
|
111
|
+
### 5. 实施修改
|
|
112
|
+
- 遵循"先读后写"原则,在修改已有代码前,必须已经读取了对应文件
|
|
113
|
+
- 保持代码风格一致性
|
|
114
|
+
- 自动匹配项目现有命名风格
|
|
115
|
+
- 允许创建新文件和结构,但不得假设或虚构现有代码
|
|
116
|
+
|
|
117
|
+
## 专用工具简介
|
|
118
|
+
仅在必要时使用以下专用工具:
|
|
119
|
+
|
|
120
|
+
- **project_analyzer**: 项目整体结构分析,仅在fd命令无法满足需求时使用
|
|
121
|
+
- **file_analyzer**: 单文件深度分析,应优先使用read_code替代
|
|
122
|
+
- **find_caller**: 函数调用者查找,应优先使用rg命令替代
|
|
123
|
+
- **find_symbol**: 符号引用查找,应优先使用rg命令替代
|
|
124
|
+
- **function_analyzer**: 函数实现分析,应优先使用read_code和rg组合替代
|
|
125
|
+
- **ask_codebase**: 代码库整体查询,应优先使用fd、rg和read_code组合替代
|
|
126
|
+
- **code_review**: 代码质量检查,应优先使用语言特定的lint工具替代
|
|
127
|
+
|
|
128
|
+
## Shell命令优先策略
|
|
129
|
+
|
|
130
|
+
### 优先使用的Shell命令
|
|
131
|
+
- **项目结构分析**:
|
|
132
|
+
- `fd -t f -e py` 查找所有Python文件
|
|
133
|
+
- `fd -t f -e js -e ts` 查找所有JavaScript/TypeScript文件
|
|
134
|
+
- `fd -t d` 列出所有目录
|
|
135
|
+
- `fd -t f -e java -e kt` 查找所有Java/Kotlin文件
|
|
136
|
+
- `fd -t f -e go` 查找所有Go文件
|
|
137
|
+
- `fd -t f -e rs` 查找所有Rust文件
|
|
138
|
+
- `fd -t f -e c -e cpp -e h -e hpp` 查找所有C/C++文件
|
|
139
|
+
|
|
140
|
+
- **代码内容搜索**:
|
|
141
|
+
- `rg "pattern" --type py` 在Python文件中搜索
|
|
142
|
+
- `rg "pattern" --type js` 在JavaScript文件中搜索
|
|
143
|
+
- `rg "pattern" --type java` 在Java文件中搜索
|
|
144
|
+
- `rg "pattern" --type c` 在C文件中搜索
|
|
145
|
+
- `rg "class ClassName"` 查找类定义
|
|
146
|
+
- `rg "func|function|def" -g "*.py" -g "*.js" -g "*.go" -g "*.rs"` 查找函数定义
|
|
147
|
+
- `rg -w "word"` 精确匹配单词
|
|
148
|
+
|
|
149
|
+
- **代码统计分析**:
|
|
150
|
+
- `loc` 统计当前目录代码行数
|
|
151
|
+
|
|
152
|
+
- **代码质量检查**:
|
|
153
|
+
- Python: `pylint <file_path>`, `flake8 <file_path>`
|
|
154
|
+
- JavaScript: `eslint <file_path>`
|
|
155
|
+
- TypeScript: `tsc --noEmit <file_path>`
|
|
156
|
+
- Java: `checkstyle <file_path>`
|
|
157
|
+
- Go: `go vet <file_path>`
|
|
158
|
+
- Rust: `cargo clippy`
|
|
159
|
+
- C/C++: `cppcheck <file_path>`
|
|
160
|
+
|
|
161
|
+
- **整体代码分析**:
|
|
162
|
+
- 使用execute_shell_script编写和执行脚本,批量分析多个文件
|
|
163
|
+
- 简单脚本示例:`find . -name "*.py" | xargs pylint`
|
|
164
|
+
- 使用多工具组合:`fd -e py | xargs pylint`
|
|
165
|
+
|
|
166
|
+
### read_code工具使用
|
|
167
|
+
读取文件应优先使用read_code工具,而非shell命令:
|
|
168
|
+
- 完整读取:使用read_code读取整个文件内容
|
|
169
|
+
- 部分读取:使用read_code指定行范围
|
|
170
|
+
- 大文件处理:对大型文件使用read_code指定行范围,避免全部加载
|
|
171
|
+
|
|
172
|
+
### 仅在命令行工具不足时使用专用工具
|
|
173
|
+
只有当fd、rg、loc和read_code工具无法获取足够信息时,才考虑使用专用工具(ask_codebase、code_review等)。在每次使用专用工具前,应先尝试使用上述工具获取所需信息。
|
|
174
|
+
|
|
175
|
+
### 注意事项
|
|
176
|
+
- read_code比cat或grep更适合阅读代码
|
|
177
|
+
- rg比grep更快更强大,应优先使用
|
|
178
|
+
- fd比find更快更易用,应优先使用
|
|
179
|
+
- loc比wc -l提供更多代码统计信息,应优先使用
|
|
180
|
+
- 针对不同编程语言选择对应的代码质量检查工具
|
|
181
|
+
- 不要留下未实现的代码
|
|
135
182
|
"""
|
|
136
183
|
# Dynamically add ask_codebase based on task complexity if really needed
|
|
137
|
-
|
|
138
|
-
|
|
184
|
+
# 处理platform参数
|
|
185
|
+
platform_instance = (PlatformRegistry().create_platform(platform)
|
|
186
|
+
if platform
|
|
187
|
+
else PlatformRegistry().get_normal_platform())
|
|
188
|
+
if model:
|
|
189
|
+
platform_instance.set_model_name(model) # type: ignore
|
|
190
|
+
|
|
191
|
+
self.agent = Agent(system_prompt=code_system_prompt,
|
|
192
|
+
name="CodeAgent",
|
|
139
193
|
auto_complete=False,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
194
|
+
output_handler=[tool_registry,
|
|
195
|
+
PatchOutputHandler()],
|
|
196
|
+
platform=platform_instance,
|
|
197
|
+
input_handler=[
|
|
198
|
+
shell_input_handler, file_input_handler, builtin_input_handler],
|
|
199
|
+
need_summary=need_summary)
|
|
200
|
+
self.agent.set_addon_prompt("请使用工具充分理解用户需求,然后根据需求一步步执行代码修改/开发")
|
|
201
|
+
|
|
202
|
+
def get_root_dir(self) -> str:
|
|
203
|
+
"""获取项目根目录
|
|
204
|
+
|
|
205
|
+
返回:
|
|
206
|
+
str: 项目根目录路径
|
|
207
|
+
"""
|
|
208
|
+
return self.root_dir
|
|
149
209
|
|
|
150
210
|
def _init_env(self):
|
|
151
|
-
with yaspin(text="正在初始化环境...", color="cyan") as spinner:
|
|
211
|
+
with yaspin(text="正在初始化环境...", color="cyan") as spinner:
|
|
152
212
|
curr_dir = os.getcwd()
|
|
153
213
|
git_dir = find_git_root(curr_dir)
|
|
154
214
|
self.root_dir = git_dir
|
|
@@ -159,79 +219,105 @@ class CodeAgent:
|
|
|
159
219
|
spinner.text = "环境初始化完成"
|
|
160
220
|
spinner.ok("✅")
|
|
161
221
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
222
|
+
def _handle_uncommitted_changes(self):
|
|
223
|
+
"""处理未提交的修改"""
|
|
224
|
+
if has_uncommitted_changes():
|
|
225
|
+
PrettyOutput.print("检测到未提交的修改,是否要提交?", OutputType.WARNING)
|
|
226
|
+
if user_confirm("是否要提交?", True):
|
|
227
|
+
git_commiter = GitCommitTool()
|
|
228
|
+
git_commiter.execute({})
|
|
229
|
+
|
|
230
|
+
def _show_commit_history(self, start_commit, end_commit):
|
|
231
|
+
"""显示提交历史"""
|
|
232
|
+
if start_commit and end_commit:
|
|
233
|
+
commits = get_commits_between(start_commit, end_commit)
|
|
234
|
+
else:
|
|
235
|
+
commits = []
|
|
236
|
+
|
|
237
|
+
if commits:
|
|
238
|
+
commit_messages = "检测到以下提交记录:\n" + \
|
|
239
|
+
"\n".join(
|
|
240
|
+
[f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits])
|
|
241
|
+
PrettyOutput.print(commit_messages, OutputType.INFO)
|
|
242
|
+
return commits
|
|
243
|
+
|
|
244
|
+
def _handle_commit_confirmation(self, commits, start_commit):
|
|
245
|
+
"""处理提交确认和可能的重置"""
|
|
246
|
+
if commits and user_confirm("是否接受以上提交记录?", True):
|
|
247
|
+
if len(commits) > 1 and user_confirm(
|
|
248
|
+
"是否要合并为一个更清晰的提交记录?", True
|
|
249
|
+
):
|
|
250
|
+
# Reset to start commit
|
|
251
|
+
subprocess.run(
|
|
252
|
+
["git", "reset", "--mixed", start_commit],
|
|
253
|
+
stdout=subprocess.DEVNULL,
|
|
254
|
+
stderr=subprocess.DEVNULL,
|
|
255
|
+
check=True
|
|
256
|
+
)
|
|
257
|
+
# Create new commit
|
|
258
|
+
git_commiter = GitCommitTool()
|
|
259
|
+
git_commiter.execute({})
|
|
260
|
+
elif start_commit:
|
|
261
|
+
os.system(f"git reset --hard {start_commit}")
|
|
262
|
+
PrettyOutput.print("已重置到初始提交", OutputType.INFO)
|
|
263
|
+
|
|
264
|
+
def run(self, user_input: str) -> Optional[str]:
|
|
265
|
+
"""使用给定的用户输入运行代码代理。
|
|
266
|
+
|
|
267
|
+
参数:
|
|
268
|
+
user_input: 用户的需求/请求
|
|
269
|
+
|
|
270
|
+
返回:
|
|
271
|
+
str: 描述执行结果的输出,成功时返回None
|
|
172
272
|
"""
|
|
173
273
|
try:
|
|
174
274
|
self._init_env()
|
|
175
|
-
|
|
176
275
|
start_commit = get_latest_commit_hash()
|
|
177
|
-
|
|
276
|
+
|
|
178
277
|
try:
|
|
179
278
|
self.agent.run(user_input)
|
|
180
|
-
except
|
|
279
|
+
except RuntimeError as e:
|
|
181
280
|
PrettyOutput.print(f"执行失败: {str(e)}", OutputType.WARNING)
|
|
182
|
-
|
|
281
|
+
return str(e)
|
|
282
|
+
|
|
283
|
+
self._handle_uncommitted_changes()
|
|
183
284
|
end_commit = get_latest_commit_hash()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if commits:
|
|
191
|
-
commit_messages = "检测到以下提交记录:\n" + "\n".join([f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits])
|
|
192
|
-
PrettyOutput.print(commit_messages, OutputType.INFO)
|
|
193
|
-
|
|
194
|
-
if commits and user_confirm("是否接受以上提交记录?", True):
|
|
195
|
-
if len(commits) > 1 and user_confirm("是否要合并为一个更清晰的提交记录?", True):
|
|
196
|
-
# Reset to start commit
|
|
197
|
-
subprocess.run(["git", "reset", "--mixed", start_commit], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
198
|
-
# Create new commit
|
|
199
|
-
git_commiter = GitCommitTool()
|
|
200
|
-
git_commiter.execute({})
|
|
201
|
-
elif start_commit:
|
|
202
|
-
os.system(f"git reset --hard {start_commit}")
|
|
203
|
-
PrettyOutput.print("已重置到初始提交", OutputType.INFO)
|
|
204
|
-
|
|
205
|
-
except Exception as e:
|
|
285
|
+
commits = self._show_commit_history(start_commit, end_commit)
|
|
286
|
+
self._handle_commit_confirmation(commits, start_commit)
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
except RuntimeError as e:
|
|
206
290
|
return f"Error during execution: {str(e)}"
|
|
207
|
-
|
|
208
291
|
|
|
209
292
|
|
|
210
293
|
def main():
|
|
211
|
-
"""Jarvis
|
|
212
|
-
# Add argument parser
|
|
294
|
+
"""Jarvis主入口点。"""
|
|
213
295
|
init_env()
|
|
214
296
|
|
|
297
|
+
parser = argparse.ArgumentParser(description='Jarvis Code Agent')
|
|
298
|
+
parser.add_argument('-p', '--platform', type=str,
|
|
299
|
+
help='Target platform name', default=None)
|
|
300
|
+
parser.add_argument('-m', '--model', type=str,
|
|
301
|
+
help='Model name to use', default=None)
|
|
302
|
+
args = parser.parse_args()
|
|
303
|
+
|
|
215
304
|
curr_dir = os.getcwd()
|
|
216
305
|
git_dir = find_git_root(curr_dir)
|
|
217
306
|
PrettyOutput.print(f"当前目录: {git_dir}", OutputType.INFO)
|
|
218
307
|
|
|
219
308
|
try:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
except Exception as e:
|
|
228
|
-
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
309
|
+
user_input = get_multiline_input("请输入你的需求(输入空行退出):")
|
|
310
|
+
if not user_input:
|
|
311
|
+
sys.exit(0)
|
|
312
|
+
agent = CodeAgent(platform=args.platform,
|
|
313
|
+
model=args.model,
|
|
314
|
+
need_summary=False)
|
|
315
|
+
agent.run(user_input)
|
|
229
316
|
|
|
230
|
-
except
|
|
231
|
-
PrettyOutput.print(f"
|
|
232
|
-
|
|
317
|
+
except RuntimeError as e:
|
|
318
|
+
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
319
|
+
sys.exit(1)
|
|
233
320
|
|
|
234
|
-
return 0
|
|
235
321
|
|
|
236
322
|
if __name__ == "__main__":
|
|
237
|
-
|
|
323
|
+
main()
|
|
@@ -11,15 +11,15 @@ from jarvis.jarvis_utils.utils import user_confirm
|
|
|
11
11
|
|
|
12
12
|
def _parse_file_selection(input_str: str, max_index: int) -> List[int]:
|
|
13
13
|
selected = set()
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Remove all whitespace characters
|
|
16
16
|
input_str = "".join(input_str.split())
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
# Process comma-separated parts
|
|
19
19
|
for part in input_str.split(","):
|
|
20
20
|
if not part:
|
|
21
21
|
continue
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# Process range (e.g.: 3-6)
|
|
24
24
|
if "-" in part:
|
|
25
25
|
try:
|
|
@@ -41,7 +41,7 @@ def _parse_file_selection(input_str: str, max_index: int) -> List[int]:
|
|
|
41
41
|
PrettyOutput.print(f"忽略超出范围的索引: {part}", OutputType.WARNING)
|
|
42
42
|
except ValueError:
|
|
43
43
|
PrettyOutput.print(f"忽略无效的数字: {part}", OutputType.WARNING)
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
return sorted(list(selected))
|
|
46
46
|
|
|
47
47
|
def _get_file_completer(root_dir: str) -> Completer:
|
|
@@ -49,58 +49,58 @@ def _get_file_completer(root_dir: str) -> Completer:
|
|
|
49
49
|
class FileCompleter(Completer):
|
|
50
50
|
def __init__(self, root_dir: str):
|
|
51
51
|
self.root_dir = root_dir
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def get_completions(self, document, complete_event):
|
|
54
54
|
text = document.text_before_cursor
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
if not text:
|
|
57
57
|
for path in self._list_files(""):
|
|
58
58
|
yield Completion(path, start_position=0)
|
|
59
59
|
return
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
# Generate fuzzy matching pattern
|
|
62
62
|
pattern = '.*'.join(map(re.escape, text))
|
|
63
63
|
try:
|
|
64
64
|
regex = re.compile(pattern, re.IGNORECASE)
|
|
65
65
|
except re.error:
|
|
66
66
|
return
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
for path in self._list_files(""):
|
|
69
69
|
if regex.search(path):
|
|
70
70
|
yield Completion(path, start_position=-len(text))
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
def _list_files(self, current_dir: str) -> List[str]:
|
|
73
73
|
"""List all files in the specified directory (recursively)"""
|
|
74
74
|
files = []
|
|
75
75
|
search_dir = os.path.join(self.root_dir, current_dir)
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
for root, _, filenames in os.walk(search_dir):
|
|
78
78
|
for filename in filenames:
|
|
79
79
|
full_path = os.path.join(root, filename)
|
|
80
80
|
rel_path = os.path.relpath(full_path, self.root_dir)
|
|
81
81
|
if not any(part.startswith('.') for part in rel_path.split(os.sep)):
|
|
82
82
|
files.append(rel_path)
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
return sorted(files)
|
|
85
85
|
|
|
86
86
|
return FileCompleter(root_dir)
|
|
87
87
|
|
|
88
88
|
def _fuzzy_match_files(root_dir: str, pattern: str) -> List[str]:
|
|
89
89
|
"""Fuzzy match file path
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
Args:
|
|
92
92
|
pattern: Matching pattern
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Returns:
|
|
95
95
|
List[str]: List of matching file paths
|
|
96
96
|
"""
|
|
97
97
|
matches = []
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
# 将模式转换为正则表达式
|
|
100
100
|
pattern = pattern.replace('.', r'\.').replace('*', '.*').replace('?', '.')
|
|
101
101
|
pattern = f".*{pattern}.*" # 允许部分匹配
|
|
102
102
|
regex = re.compile(pattern, re.IGNORECASE)
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
# 遍历所有文件
|
|
105
105
|
for root, _, files in os.walk(root_dir):
|
|
106
106
|
for file in files:
|
|
@@ -110,7 +110,7 @@ def _fuzzy_match_files(root_dir: str, pattern: str) -> List[str]:
|
|
|
110
110
|
if not any(part.startswith('.') for part in rel_path.split(os.sep)):
|
|
111
111
|
if regex.match(rel_path):
|
|
112
112
|
matches.append(rel_path)
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
return sorted(matches)
|
|
115
115
|
|
|
116
116
|
def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dict[str, str]]:
|
|
@@ -127,7 +127,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
127
127
|
if output:
|
|
128
128
|
PrettyOutput.section("相关文件", OutputType.INFO)
|
|
129
129
|
PrettyOutput.print(output, OutputType.INFO, lang="markdown")
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
if len(related_files) > 0:
|
|
132
132
|
# Ask the user if they need to adjust the file list
|
|
133
133
|
if user_confirm("是否需要调整文件列表?", False):
|
|
@@ -139,7 +139,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
139
139
|
selected_files = [related_files[i] for i in selected_indices]
|
|
140
140
|
else:
|
|
141
141
|
PrettyOutput.print("没有有效的文件被选择, 保持当前选择", OutputType.WARNING)
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
tips = ""
|
|
144
144
|
# Ask if they need to supplement files
|
|
145
145
|
if user_confirm("是否需要补充其他文件?", False):
|
|
@@ -154,23 +154,23 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
154
154
|
file_path = session.prompt(">>> ").strip()
|
|
155
155
|
except KeyboardInterrupt:
|
|
156
156
|
break
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
if not file_path:
|
|
159
159
|
break
|
|
160
|
-
|
|
160
|
+
|
|
161
161
|
# Process wildcard matching
|
|
162
162
|
if '*' in file_path or '?' in file_path:
|
|
163
163
|
matches = _fuzzy_match_files(root_dir, file_path)
|
|
164
164
|
if not matches:
|
|
165
165
|
PrettyOutput.print("没有找到匹配的文件", OutputType.WARNING)
|
|
166
166
|
continue
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Display matching files
|
|
169
169
|
tips = "找到以下匹配的文件:"
|
|
170
170
|
for i, path in enumerate(matches, 1):
|
|
171
171
|
tips += f"\n[{i}] {path}"
|
|
172
172
|
PrettyOutput.print(tips, OutputType.INFO)
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
# Let the user select
|
|
175
175
|
numbers = get_single_line_input("请选择要添加的文件编号(支持: 1,3-6格式, 按回车选择所有)").strip()
|
|
176
176
|
if numbers:
|
|
@@ -182,7 +182,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
182
182
|
paths_to_add = matches
|
|
183
183
|
else:
|
|
184
184
|
paths_to_add = [file_path]
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
# Add selected files
|
|
187
187
|
tips = "添加以下文件:"
|
|
188
188
|
for path in paths_to_add:
|
|
@@ -190,7 +190,7 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
190
190
|
if not os.path.isfile(full_path):
|
|
191
191
|
tips += f"\n文件不存在: {path}"
|
|
192
192
|
continue
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
try:
|
|
195
195
|
selected_files.append({"file": path, "reason": "I Added"})
|
|
196
196
|
tips += f"\n文件已添加: {path}"
|