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.

Files changed (82) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +330 -347
  3. jarvis/jarvis_agent/builtin_input_handler.py +16 -6
  4. jarvis/jarvis_agent/file_input_handler.py +9 -9
  5. jarvis/jarvis_agent/jarvis.py +143 -0
  6. jarvis/jarvis_agent/main.py +12 -13
  7. jarvis/jarvis_agent/output_handler.py +3 -3
  8. jarvis/jarvis_agent/patch.py +92 -64
  9. jarvis/jarvis_agent/shell_input_handler.py +5 -3
  10. jarvis/jarvis_code_agent/code_agent.py +263 -177
  11. jarvis/jarvis_code_agent/file_select.py +24 -24
  12. jarvis/jarvis_dev/main.py +45 -59
  13. jarvis/jarvis_git_details/__init__.py +0 -0
  14. jarvis/jarvis_git_details/main.py +179 -0
  15. jarvis/jarvis_git_squash/main.py +7 -7
  16. jarvis/jarvis_lsp/base.py +11 -53
  17. jarvis/jarvis_lsp/cpp.py +13 -28
  18. jarvis/jarvis_lsp/go.py +13 -28
  19. jarvis/jarvis_lsp/python.py +8 -27
  20. jarvis/jarvis_lsp/registry.py +21 -83
  21. jarvis/jarvis_lsp/rust.py +15 -30
  22. jarvis/jarvis_methodology/main.py +101 -0
  23. jarvis/jarvis_multi_agent/__init__.py +10 -51
  24. jarvis/jarvis_multi_agent/main.py +43 -0
  25. jarvis/jarvis_platform/__init__.py +1 -1
  26. jarvis/jarvis_platform/ai8.py +67 -89
  27. jarvis/jarvis_platform/base.py +14 -13
  28. jarvis/jarvis_platform/kimi.py +25 -28
  29. jarvis/jarvis_platform/ollama.py +24 -26
  30. jarvis/jarvis_platform/openai.py +15 -19
  31. jarvis/jarvis_platform/oyi.py +48 -50
  32. jarvis/jarvis_platform/registry.py +29 -44
  33. jarvis/jarvis_platform/yuanbao.py +39 -43
  34. jarvis/jarvis_platform_manager/main.py +81 -81
  35. jarvis/jarvis_platform_manager/openai_test.py +21 -21
  36. jarvis/jarvis_rag/file_processors.py +18 -18
  37. jarvis/jarvis_rag/main.py +262 -278
  38. jarvis/jarvis_smart_shell/main.py +12 -12
  39. jarvis/jarvis_tools/ask_codebase.py +85 -78
  40. jarvis/jarvis_tools/ask_user.py +8 -8
  41. jarvis/jarvis_tools/base.py +4 -4
  42. jarvis/jarvis_tools/chdir.py +9 -9
  43. jarvis/jarvis_tools/code_review.py +40 -21
  44. jarvis/jarvis_tools/create_code_agent.py +15 -15
  45. jarvis/jarvis_tools/create_sub_agent.py +0 -1
  46. jarvis/jarvis_tools/execute_python_script.py +3 -3
  47. jarvis/jarvis_tools/execute_shell.py +11 -11
  48. jarvis/jarvis_tools/execute_shell_script.py +3 -3
  49. jarvis/jarvis_tools/file_analyzer.py +116 -105
  50. jarvis/jarvis_tools/file_operation.py +22 -20
  51. jarvis/jarvis_tools/find_caller.py +105 -40
  52. jarvis/jarvis_tools/find_methodolopy.py +65 -0
  53. jarvis/jarvis_tools/find_symbol.py +123 -39
  54. jarvis/jarvis_tools/function_analyzer.py +140 -57
  55. jarvis/jarvis_tools/git_commiter.py +10 -10
  56. jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
  57. jarvis/jarvis_tools/methodology.py +22 -67
  58. jarvis/jarvis_tools/project_analyzer.py +137 -53
  59. jarvis/jarvis_tools/rag.py +15 -20
  60. jarvis/jarvis_tools/read_code.py +25 -23
  61. jarvis/jarvis_tools/read_webpage.py +31 -31
  62. jarvis/jarvis_tools/registry.py +72 -52
  63. jarvis/jarvis_tools/search_web.py +23 -353
  64. jarvis/jarvis_tools/tool_generator.py +19 -19
  65. jarvis/jarvis_utils/config.py +36 -96
  66. jarvis/jarvis_utils/embedding.py +83 -83
  67. jarvis/jarvis_utils/git_utils.py +20 -20
  68. jarvis/jarvis_utils/globals.py +18 -6
  69. jarvis/jarvis_utils/input.py +10 -9
  70. jarvis/jarvis_utils/methodology.py +141 -140
  71. jarvis/jarvis_utils/output.py +13 -13
  72. jarvis/jarvis_utils/utils.py +23 -71
  73. {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +6 -15
  74. jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
  75. {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +4 -3
  76. jarvis/jarvis_tools/lsp_find_definition.py +0 -150
  77. jarvis/jarvis_tools/lsp_find_references.py +0 -127
  78. jarvis/jarvis_tools/select_code_files.py +0 -62
  79. jarvis_ai_assistant-0.1.132.dist-info/RECORD +0 -82
  80. {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
  81. {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
  82. {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
- import subprocess
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 find_git_root, get_commits_between, get_latest_commit_hash, has_uncommitted_changes
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
- class CodeAgent:
25
- def __init__(self):
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(["execute_shell",
29
- "execute_shell_script",
30
- "search_web",
31
- "ask_user",
32
- "ask_codebase",
33
- "lsp_get_diagnostics",
34
- "lsp_find_references",
35
- "lsp_find_definition",
36
- "code_review", # 代码审查工具
37
- "find_symbol", # 添加符号查找工具
38
- "find_caller", # 添加函数调用者查找工具
39
- "function_analyzer", # 添加函数分析工具
40
- "project_analyzer", # 添加项目分析工具
41
- "file_analyzer" # 添加单文件分析工具
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
- - **修改效率**:对于变更不超过50行的代码,应一次性完成修改,避免分段处理
58
-
59
- ## 任务执行流程
60
- ### 分析阶段
61
- - **范围确定**:首先判断是否仅需当前文件内容即可完成任务,避免不必要的分析
62
- - 评估任务复杂度,只在必要时进行全面分析
63
- - 简单修改可直接进行,无需复杂分析
64
- - 使用代码分析工具理解依赖关系
65
- - 识别潜在影响区域
66
- - 确保在开始修改前,已经完全阅读和理解相关代码文件
67
-
68
- ### 实施阶段
69
- - 进行最小范围更改,保持代码完整性
70
- - 对于不超过50行的代码变更,应一次性完成修改
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
- - 如有必要,可以使用`ask_codebase`工具来帮助理解复杂部分
97
-
98
- - **分析工具**:
99
- - lsp_find_references:理解使用模式
100
- - lsp_find_definition:追踪实现细节
101
- - find_symbol:查找代码符号位置
102
- - find_caller:查找函数调用位置
103
- - function_analyzer:分析函数实现
104
- - project_analyzer:分析项目架构(仅复杂任务使用)
105
- - file_analyzer:分析单文件结构
106
-
107
- - **验证工具**:
108
- - lsp_get_diagnostics:检查修改问题
109
- - code_review:代码审查
110
-
111
- - **系统工具**:
112
- - execute_shell:执行系统命令
113
- - search_web:查找技术参考
114
- - ask_codebase:补充分析(优先使用直接分析工具)
115
-
116
- ## 代码分析策略
117
- - **阅读优先**:在提出任何修改前,必须先阅读和理解相关代码
118
- - **避免过度分析**:只分析与任务直接相关的代码
119
- - **单文件优先**:优先考虑当前文件是否含有足够信息完成任务
120
- - 当任务仅涉及语法、文本修改、逻辑优化等当前文件内部变更时,无需分析项目
121
- - 当当前文件包含完整的模块/类/功能实现,且修改不影响外部接口时,无需额外分析
122
- - 当用户明确指示仅修改当前文件时,无需扩大分析范围
123
- - **任务分级**:
124
- - 简单任务:先阅读相关代码,然后直接执行,尽量减少不必要的分析
125
- - 中等任务:全面阅读相关文件并分析直接依赖,确保理解上下文
126
- - 复杂任务:彻底阅读并进行全面项目分析,确保理解代码交互方式
127
- - **长文件处理**:
128
- - 完整阅读后再分段理解和修改
129
- - 先处理核心部分,再扩展到相关代码
130
- - 优先修改相对独立的部分
131
- - **修改规模策略**:
132
- - 对于不超过50行的代码变更,应尽量一次性完成
133
- - 将相关逻辑变更放在同一个修改中,保持功能完整性
134
- - 无需为小规模修改(≤50行)拆分多次提交,除非跨多个不相关模块
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
- self.agent = Agent(system_prompt=code_system_prompt,
138
- name="CodeAgent",
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
- is_sub_agent=False,
141
- use_methodology=False,
142
- output_handler=[tool_registry, PatchOutputHandler()],
143
- platform=PlatformRegistry().get_codegen_platform(),
144
- record_methodology=False,
145
- input_handler=[shell_input_handler, file_input_handler, builtin_input_handler],
146
- need_summary=False)
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
- def run(self, user_input: str) :
165
- """Run the code agent with the given user input.
166
-
167
- Args:
168
- user_input: The user's requirement/request
169
-
170
- Returns:
171
- str: Output describing the execution result
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 Exception as e:
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
- # Print commit history between start and end commits
185
- if start_commit and end_commit:
186
- commits = get_commits_between(start_commit, end_commit)
187
- else:
188
- commits = []
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 main entry point"""
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
- try:
221
- user_input = get_multiline_input("请输入你的需求(输入空行退出):")
222
- if not user_input:
223
- return 0
224
- agent = CodeAgent()
225
- agent.run(user_input)
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 Exception as e:
231
- PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
232
- return 1
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
- exit(main())
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}"