jarvis-ai-assistant 0.1.122__py3-none-any.whl → 0.1.124__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 +1 -1
- jarvis/jarvis_code_agent/code_agent.py +47 -86
- jarvis/jarvis_code_agent/file_select.py +0 -85
- jarvis/jarvis_code_agent/patch.py +164 -56
- jarvis/jarvis_dev/main.py +924 -0
- jarvis/jarvis_platform/base.py +21 -26
- jarvis/jarvis_platform/openai.py +1 -1
- jarvis/jarvis_tools/chdir.py +25 -0
- jarvis/jarvis_tools/create_code_agent.py +3 -6
- jarvis/jarvis_tools/execute_shell_script.py +58 -0
- jarvis/jarvis_tools/git_commiter.py +21 -15
- jarvis/jarvis_tools/read_code.py +1 -1
- jarvis/jarvis_tools/search.py +0 -1
- jarvis/jarvis_utils/__init__.py +72 -24
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/METADATA +10 -10
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/RECORD +21 -20
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/WHEEL +1 -1
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_code_agent/relevant_files.py +0 -117
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.122.dist-info → jarvis_ai_assistant-0.1.124.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
|
@@ -327,7 +327,7 @@ Please continue the task based on the above information.
|
|
|
327
327
|
return self._complete_task()
|
|
328
328
|
|
|
329
329
|
# 获取用户输入
|
|
330
|
-
user_input = get_multiline_input(f"{self.name}:
|
|
330
|
+
user_input = get_multiline_input(f"{self.name}: 请输入,或输入空行来结束当前任务:")
|
|
331
331
|
|
|
332
332
|
if user_input:
|
|
333
333
|
self.prompt = user_input
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import subprocess
|
|
1
2
|
import os
|
|
2
3
|
from typing import Dict, List
|
|
3
4
|
|
|
4
5
|
from jarvis.jarvis_agent import Agent
|
|
5
|
-
from jarvis.jarvis_code_agent.file_select import
|
|
6
|
-
from jarvis.jarvis_code_agent.patch import PatchOutputHandler
|
|
7
|
-
from jarvis.jarvis_code_agent.relevant_files import find_relevant_information
|
|
6
|
+
from jarvis.jarvis_code_agent.file_select import select_files
|
|
7
|
+
from jarvis.jarvis_code_agent.patch import PatchOutputHandler, file_input_handler
|
|
8
8
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
9
9
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
10
10
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
11
11
|
from jarvis.jarvis_tools.read_code import ReadCodeTool
|
|
12
|
+
from jarvis.jarvis_utils import get_commits_between
|
|
13
|
+
from jarvis.jarvis_utils import OutputType, PrettyOutput, get_multiline_input, has_uncommitted_changes, init_env, find_git_root, user_confirm, get_latest_commit_hash
|
|
12
14
|
from jarvis.jarvis_utils import OutputType, PrettyOutput, get_multiline_input, has_uncommitted_changes, init_env, find_git_root, user_confirm
|
|
13
15
|
|
|
14
16
|
|
|
@@ -18,6 +20,7 @@ class CodeAgent:
|
|
|
18
20
|
tool_registry = ToolRegistry()
|
|
19
21
|
tool_registry.use_tools(["read_code",
|
|
20
22
|
"execute_shell",
|
|
23
|
+
"execute_shell_script",
|
|
21
24
|
"search",
|
|
22
25
|
"create_code_agent",
|
|
23
26
|
"ask_user",
|
|
@@ -29,44 +32,32 @@ class CodeAgent:
|
|
|
29
32
|
"lsp_prepare_rename",
|
|
30
33
|
"lsp_validate_edit"])
|
|
31
34
|
code_system_prompt = """
|
|
32
|
-
# Role:
|
|
33
|
-
Expert in precise code modifications with
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- Document complex logic
|
|
49
|
-
- Maintain API contracts
|
|
50
|
-
|
|
35
|
+
# Role: Code Engineer
|
|
36
|
+
Expert in precise code modifications with proper tool usage.
|
|
37
|
+
## Tool Usage Guide
|
|
38
|
+
1. read_code: Analyze code files before changes
|
|
39
|
+
2. execute_shell: Run system commands safely
|
|
40
|
+
3. execute_shell_script: Execute script files
|
|
41
|
+
4. search: Find technical information
|
|
42
|
+
5. create_code_agent: Create new code agents
|
|
43
|
+
6. ask_user: Clarify requirements
|
|
44
|
+
7. ask_codebase: Analyze codebase structure
|
|
45
|
+
8. lsp_get_document_symbols: List code symbols
|
|
46
|
+
9. lsp_get_diagnostics: Check code errors
|
|
47
|
+
10. lsp_find_references: Find symbol usage
|
|
48
|
+
11. lsp_find_definition: Locate symbol definitions
|
|
49
|
+
12. lsp_prepare_rename: Check rename safety
|
|
50
|
+
13. lsp_validate_edit: Verify code changes
|
|
51
51
|
## Workflow
|
|
52
|
-
1.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
d) Replace existing code
|
|
57
|
-
e) Insert new code
|
|
58
|
-
|
|
59
|
-
2. Large File Handling:
|
|
60
|
-
- Locate specific sections first
|
|
61
|
-
- Read targeted ranges
|
|
62
|
-
- Make focused changes
|
|
63
|
-
|
|
52
|
+
1. Analyze: Use read_code and LSP tools
|
|
53
|
+
2. Modify: Make minimal, precise changes
|
|
54
|
+
3. Validate: Verify with LSP tools
|
|
55
|
+
4. Document: Explain non-obvious logic
|
|
64
56
|
## Best Practices
|
|
65
|
-
- Prefer minimal changes over rewrites
|
|
66
|
-
- Preserve existing interfaces
|
|
67
57
|
- Verify line ranges carefully
|
|
68
|
-
-
|
|
69
|
-
-
|
|
58
|
+
- Preserve existing interfaces
|
|
59
|
+
- Test edge cases
|
|
60
|
+
- Document changes
|
|
70
61
|
"""
|
|
71
62
|
self.agent = Agent(system_prompt=code_system_prompt,
|
|
72
63
|
name="CodeAgent",
|
|
@@ -90,26 +81,6 @@ Expert in precise code modifications with minimal impact.
|
|
|
90
81
|
git_commiter.execute({})
|
|
91
82
|
|
|
92
83
|
|
|
93
|
-
def make_files_prompt(self, files: List[Dict[str, str]]) -> str:
|
|
94
|
-
"""Make the files prompt with content that fits within token limit.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
files: The files to be modified
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
str: A prompt containing file paths and contents within token limit
|
|
101
|
-
"""
|
|
102
|
-
prompt_parts = []
|
|
103
|
-
|
|
104
|
-
# Then try to add file contents
|
|
105
|
-
for file in files:
|
|
106
|
-
prompt_parts.append(f'''- {file['file']} ({file['reason']})''')
|
|
107
|
-
|
|
108
|
-
result = ReadCodeTool().execute({"files": [{"path": file["file"]} for file in files]})
|
|
109
|
-
if result["success"]:
|
|
110
|
-
prompt_parts.append(result["stdout"])
|
|
111
|
-
|
|
112
|
-
return "\n".join(prompt_parts)
|
|
113
84
|
|
|
114
85
|
def run(self, user_input: str) :
|
|
115
86
|
"""Run the code agent with the given user input.
|
|
@@ -122,40 +93,30 @@ Expert in precise code modifications with minimal impact.
|
|
|
122
93
|
"""
|
|
123
94
|
try:
|
|
124
95
|
self._init_env()
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
96
|
+
start_commit = get_latest_commit_hash()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
self.agent.run(user_input)
|
|
100
|
+
|
|
101
|
+
end_commit = get_latest_commit_hash()
|
|
102
|
+
# Print commit history between start and end commits
|
|
103
|
+
commits = get_commits_between(start_commit, end_commit)
|
|
104
|
+
if commits:
|
|
105
|
+
commit_messages = "检测到以下提交记录:\n" + "\n".join([f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits])
|
|
106
|
+
PrettyOutput.print(commit_messages, OutputType.INFO)
|
|
131
107
|
|
|
108
|
+
if start_commit and end_commit and start_commit != end_commit and user_confirm("检测到多个提交,是否要合并为一个更清晰的提交记录?", True):
|
|
109
|
+
# Reset to start commit
|
|
110
|
+
subprocess.run(["git", "reset", "--soft", start_commit], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
111
|
+
# Create new commit
|
|
112
|
+
git_commiter = GitCommitTool()
|
|
113
|
+
git_commiter.execute({})
|
|
114
|
+
|
|
132
115
|
except Exception as e:
|
|
133
116
|
return f"Error during execution: {str(e)}"
|
|
134
117
|
|
|
135
118
|
|
|
136
119
|
|
|
137
|
-
def _build_first_edit_prompt(self, user_input: str, files_prompt: str, information: str) -> str:
|
|
138
|
-
"""Build the initial prompt for the agent.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
user_input: The user's requirement
|
|
142
|
-
files_prompt: The formatted list of relevant files
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
str: The formatted prompt
|
|
146
|
-
"""
|
|
147
|
-
|
|
148
|
-
return f"""# Code Modification Task
|
|
149
|
-
|
|
150
|
-
## User Requirement
|
|
151
|
-
{user_input}
|
|
152
|
-
|
|
153
|
-
## Maybe Relevant Files
|
|
154
|
-
{files_prompt}
|
|
155
|
-
|
|
156
|
-
## Some Information
|
|
157
|
-
{information}
|
|
158
|
-
"""
|
|
159
120
|
def main():
|
|
160
121
|
"""Jarvis main entry point"""
|
|
161
122
|
# Add argument parser
|
|
@@ -213,88 +213,3 @@ def select_files(related_files: List[Dict[str, str]], root_dir: str) -> List[Dic
|
|
|
213
213
|
if tips:
|
|
214
214
|
PrettyOutput.print(tips, OutputType.INFO)
|
|
215
215
|
return selected_files
|
|
216
|
-
|
|
217
|
-
def file_input_handler(user_input: str, agent: Any) -> str:
|
|
218
|
-
"""Handle file input with optional line ranges.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
user_input: User input string containing file references
|
|
222
|
-
agent: Agent instance (unused in current implementation)
|
|
223
|
-
|
|
224
|
-
Returns:
|
|
225
|
-
str: Prompt with file contents prepended if files are found
|
|
226
|
-
"""
|
|
227
|
-
prompt = user_input
|
|
228
|
-
files = []
|
|
229
|
-
|
|
230
|
-
# Match file references in backticks
|
|
231
|
-
file_refs = re.findall(r'`([^`]+)`', user_input)
|
|
232
|
-
|
|
233
|
-
for ref in file_refs:
|
|
234
|
-
# Handle file:start,end or file:start:end format
|
|
235
|
-
if ':' in ref:
|
|
236
|
-
file_path, line_range = ref.split(':', 1)
|
|
237
|
-
# Initialize with default values
|
|
238
|
-
start_line = 1 # 1-based
|
|
239
|
-
end_line = -1
|
|
240
|
-
|
|
241
|
-
# Process line range if specified
|
|
242
|
-
if ',' in line_range or ':' in line_range:
|
|
243
|
-
try:
|
|
244
|
-
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
245
|
-
|
|
246
|
-
# Handle special values and Python-style negative indices
|
|
247
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
248
|
-
total_lines = len(f.readlines())
|
|
249
|
-
|
|
250
|
-
# Process start line
|
|
251
|
-
if raw_start == 0: # 0表示整个文件
|
|
252
|
-
start_line = 1
|
|
253
|
-
end_line = total_lines
|
|
254
|
-
else:
|
|
255
|
-
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
256
|
-
|
|
257
|
-
# Process end line
|
|
258
|
-
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
259
|
-
end_line = total_lines
|
|
260
|
-
else:
|
|
261
|
-
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
262
|
-
|
|
263
|
-
# Auto-correct ranges
|
|
264
|
-
start_line = max(1, min(start_line, total_lines))
|
|
265
|
-
end_line = max(start_line, min(end_line, total_lines))
|
|
266
|
-
|
|
267
|
-
# Final validation
|
|
268
|
-
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
269
|
-
raise ValueError
|
|
270
|
-
|
|
271
|
-
except (ValueError, FileNotFoundError) as e:
|
|
272
|
-
PrettyOutput.print(
|
|
273
|
-
f"无效的行号范围: {line_range} (文件总行数: {total_lines})",
|
|
274
|
-
OutputType.WARNING
|
|
275
|
-
)
|
|
276
|
-
continue
|
|
277
|
-
|
|
278
|
-
# Add file if it exists
|
|
279
|
-
if os.path.isfile(file_path):
|
|
280
|
-
files.append({
|
|
281
|
-
"path": file_path,
|
|
282
|
-
"start_line": start_line,
|
|
283
|
-
"end_line": end_line
|
|
284
|
-
})
|
|
285
|
-
else:
|
|
286
|
-
# Handle simple file path
|
|
287
|
-
if os.path.isfile(ref):
|
|
288
|
-
files.append({
|
|
289
|
-
"path": ref,
|
|
290
|
-
"start_line": 1, # 1-based
|
|
291
|
-
"end_line": -1
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
# Read and process files if any were found
|
|
295
|
-
if files:
|
|
296
|
-
result = ReadCodeTool().execute({"files": files})
|
|
297
|
-
if result["success"]:
|
|
298
|
-
return result["stdout"] + "\n" + prompt
|
|
299
|
-
|
|
300
|
-
return prompt
|
|
@@ -21,85 +21,88 @@ class PatchOutputHandler(OutputHandler):
|
|
|
21
21
|
|
|
22
22
|
def prompt(self) -> str:
|
|
23
23
|
return """
|
|
24
|
-
# 🛠️
|
|
25
|
-
<PATCH>
|
|
26
|
-
File path [Operation parameters]
|
|
27
|
-
Code content
|
|
28
|
-
</PATCH>
|
|
29
|
-
|
|
30
|
-
Operation types:
|
|
31
|
-
- Replace: [Start line,End line] Replace line range (e.g. [5,8] replaces lines 5-8)
|
|
32
|
-
- Delete: [Start line,End line] Delete line range (e.g. [10,10] deletes line 10)
|
|
33
|
-
- Insert: [Line number] Insert before specified line (e.g. [3] inserts before line 3)
|
|
34
|
-
- New file: [1] Create new file
|
|
24
|
+
# 🛠️ Code Patch Specification
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
You can output multiple patches, each patch is a <PATCH> block.
|
|
27
|
+
--------------------------------
|
|
28
|
+
# [OPERATION] on [FILE]
|
|
29
|
+
# Start Line: [START_LINE], End Line: [END_LINE] [include/exclude], I can verify the line number range is correct
|
|
30
|
+
# Reason: [CLEAR EXPLANATION]
|
|
38
31
|
<PATCH>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
print("Replaced lines 5-8")
|
|
42
|
-
return new_value * 2
|
|
32
|
+
[FILE] [RANGE]
|
|
33
|
+
[CONTENT]
|
|
43
34
|
</PATCH>
|
|
35
|
+
--------------------------------
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
Explain:
|
|
38
|
+
- [OPERATION]: The operation to be performed, including:
|
|
39
|
+
- INSERT: Insert code before the specified line, [RANGE] should be [m,m)
|
|
40
|
+
- REPLACE: Replace code in the specified range, [RANGE] should be [m,n] n>=m
|
|
41
|
+
- DELETE: Delete code in the specified range, [RANGE] should be [m,n] n>=m
|
|
42
|
+
- NEW_FILE: Create a new file, [RANGE] should be [1,1)
|
|
43
|
+
- [FILE]: The path of the file to be modified
|
|
44
|
+
- [RANGE]: The range of the lines to be modified, [m,n] includes both m and n, [m,n) includes m but excludes n
|
|
45
|
+
- [CONTENT]: The content of the code to be modified, if the operation is delete, the [CONTENT] is empty
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<PATCH>
|
|
58
|
-
config.yaml [1]
|
|
59
|
-
database:
|
|
60
|
-
host: localhost
|
|
61
|
-
port: 5432
|
|
62
|
-
</PATCH>
|
|
47
|
+
Critical Rules:
|
|
48
|
+
- NEVER include unchanged code in patch content
|
|
49
|
+
- ONLY show lines that are being modified/added
|
|
50
|
+
- Maintain original line breaks around modified sections
|
|
51
|
+
- Preserve surrounding comments unless explicitly modifying them
|
|
52
|
+
- Verify line number range is correct
|
|
53
|
+
- Verify indentation is correct
|
|
63
54
|
"""
|
|
64
55
|
|
|
65
56
|
|
|
66
57
|
def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
67
58
|
"""解析补丁格式"""
|
|
68
59
|
result = {}
|
|
60
|
+
# 更新正则表达式以更好地处理文件路径和范围
|
|
69
61
|
header_pattern = re.compile(
|
|
70
|
-
r'^\s*"?(
|
|
62
|
+
r'^\s*"?([^\n\r\[]+)"?\s*\[(\d+)(?:,(\d+))?([\]\)])\s*$', # 匹配文件路径和行号
|
|
63
|
+
re.ASCII
|
|
71
64
|
)
|
|
72
65
|
patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
|
|
73
66
|
|
|
74
67
|
for patch in patches:
|
|
75
|
-
# 分割首行和内容
|
|
76
68
|
parts = patch.split('\n', 1)
|
|
77
69
|
if len(parts) < 1:
|
|
78
70
|
continue
|
|
79
71
|
header_line = parts[0].strip()
|
|
80
72
|
content = parts[1] if len(parts) > 1 else ''
|
|
81
73
|
|
|
82
|
-
# 仅在内容非空时添加换行符
|
|
83
74
|
if content and not content.endswith('\n'):
|
|
84
75
|
content += '\n'
|
|
85
76
|
|
|
86
77
|
# 解析文件路径和行号
|
|
87
78
|
header_match = header_pattern.match(header_line)
|
|
88
79
|
if not header_match:
|
|
80
|
+
PrettyOutput.print(f"无法解析补丁头: {header_line}", OutputType.WARNING)
|
|
89
81
|
continue
|
|
90
82
|
|
|
91
|
-
filepath = header_match.group(1)
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
filepath = header_match.group(1).strip()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
start = int(header_match.group(2)) # 保持1-based行号
|
|
87
|
+
end = int(header_match.group(3)) if header_match.group(3) else start
|
|
88
|
+
range_type = header_match.group(4) # ] 或 ) 表示范围类型
|
|
89
|
+
except (ValueError, IndexError) as e:
|
|
90
|
+
PrettyOutput.print(f"解析行号失败: {str(e)}", OutputType.WARNING)
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# 根据范围类型调整结束行号
|
|
94
|
+
if range_type == ')': # 对于 [m,n) 格式,不包括第n行
|
|
95
|
+
end = end
|
|
96
|
+
else: # 对于 [m,n] 格式,包括第n行
|
|
97
|
+
end = end + 1
|
|
94
98
|
|
|
95
|
-
# 存储参数
|
|
96
99
|
if filepath not in result:
|
|
97
100
|
result[filepath] = []
|
|
98
101
|
result[filepath].append({
|
|
99
102
|
'filepath': filepath,
|
|
100
103
|
'start': start,
|
|
101
104
|
'end': end,
|
|
102
|
-
'content': content
|
|
105
|
+
'content': content
|
|
103
106
|
})
|
|
104
107
|
for filepath in result.keys():
|
|
105
108
|
result[filepath] = sorted(result[filepath], key=lambda x: x['start'], reverse=True)
|
|
@@ -117,10 +120,14 @@ def apply_patch(output_str: str) -> str:
|
|
|
117
120
|
ret = ""
|
|
118
121
|
|
|
119
122
|
for filepath, patch_list in patches.items():
|
|
120
|
-
for patch in patch_list:
|
|
123
|
+
for i, patch in enumerate(patch_list):
|
|
121
124
|
try:
|
|
122
|
-
handle_code_operation(filepath, patch)
|
|
123
|
-
|
|
125
|
+
err = handle_code_operation(filepath, patch)
|
|
126
|
+
if err:
|
|
127
|
+
PrettyOutput.print(err, OutputType.WARNING)
|
|
128
|
+
revert_change()
|
|
129
|
+
return err
|
|
130
|
+
PrettyOutput.print(f"成功为文件{filepath}应用补丁{i+1}/{len(patch_list)}", OutputType.SUCCESS)
|
|
124
131
|
except Exception as e:
|
|
125
132
|
PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
|
|
126
133
|
|
|
@@ -160,6 +167,12 @@ def get_diff() -> str:
|
|
|
160
167
|
finally:
|
|
161
168
|
subprocess.run(['git', 'reset', 'HEAD'], check=True)
|
|
162
169
|
|
|
170
|
+
def revert_change():
|
|
171
|
+
import subprocess
|
|
172
|
+
subprocess.run(['git', 'reset', 'HEAD'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
173
|
+
subprocess.run(['git', 'checkout', '--', '.'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
174
|
+
subprocess.run(['git', 'clean', '-fd'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
175
|
+
|
|
163
176
|
def handle_commit_workflow(diff:str)->bool:
|
|
164
177
|
"""Handle the git commit workflow and return the commit details.
|
|
165
178
|
|
|
@@ -167,9 +180,7 @@ def handle_commit_workflow(diff:str)->bool:
|
|
|
167
180
|
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
168
181
|
"""
|
|
169
182
|
if not user_confirm("是否要提交代码?", default=True):
|
|
170
|
-
|
|
171
|
-
os.system("git checkout -- .")
|
|
172
|
-
os.system("git clean -fd")
|
|
183
|
+
revert_change()
|
|
173
184
|
return False
|
|
174
185
|
|
|
175
186
|
git_commiter = GitCommitTool()
|
|
@@ -214,11 +225,11 @@ def handle_new_file(filepath: str, patch: Dict[str, Any]):
|
|
|
214
225
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
215
226
|
f.write(patch['content'])
|
|
216
227
|
|
|
217
|
-
def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
228
|
+
def handle_code_operation(filepath: str, patch: Dict[str, Any]) -> str:
|
|
218
229
|
"""处理紧凑格式补丁"""
|
|
219
230
|
try:
|
|
220
231
|
# 新建文件时强制覆盖
|
|
221
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
232
|
+
os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)
|
|
222
233
|
if not os.path.exists(filepath):
|
|
223
234
|
open(filepath, 'w', encoding='utf-8').close()
|
|
224
235
|
with open(filepath, 'r+', encoding='utf-8') as f:
|
|
@@ -234,26 +245,24 @@ def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
|
234
245
|
f.seek(0)
|
|
235
246
|
f.writelines(new_lines)
|
|
236
247
|
f.truncate()
|
|
237
|
-
|
|
238
248
|
PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
|
|
239
|
-
|
|
249
|
+
return ""
|
|
240
250
|
except Exception as e:
|
|
241
|
-
|
|
242
|
-
|
|
251
|
+
error_msg = f"Failed to handle code operation: {str(e)}"
|
|
252
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
253
|
+
return error_msg
|
|
243
254
|
def validate_and_apply_changes(
|
|
244
255
|
lines: List[str],
|
|
245
256
|
start: int,
|
|
246
257
|
end: int,
|
|
247
258
|
content: str
|
|
248
259
|
) -> List[str]:
|
|
249
|
-
|
|
250
260
|
new_content = content.splitlines(keepends=True)
|
|
251
261
|
|
|
252
262
|
# 插入操作处理
|
|
253
263
|
if start == end:
|
|
254
264
|
if start < 1 or start > len(lines)+1:
|
|
255
265
|
raise ValueError(f"无效插入位置: {start}")
|
|
256
|
-
# 在指定位置前插入
|
|
257
266
|
return lines[:start-1] + new_content + lines[start-1:]
|
|
258
267
|
|
|
259
268
|
# 范围替换/删除操作
|
|
@@ -267,3 +276,102 @@ def validate_and_apply_changes(
|
|
|
267
276
|
|
|
268
277
|
# 执行替换
|
|
269
278
|
return lines[:start-1] + new_content + lines[end-1:]
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def file_input_handler(user_input: str, agent: Any) -> str:
|
|
282
|
+
"""Handle file input with optional line ranges.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
user_input: User input string containing file references
|
|
286
|
+
agent: Agent instance (unused in current implementation)
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
str: Prompt with file contents prepended if files are found
|
|
290
|
+
"""
|
|
291
|
+
prompt = user_input
|
|
292
|
+
files = []
|
|
293
|
+
|
|
294
|
+
file_refs = re.findall(r"'([^']+)'", user_input)
|
|
295
|
+
for ref in file_refs:
|
|
296
|
+
# Handle file:start,end or file:start:end format
|
|
297
|
+
if ':' in ref:
|
|
298
|
+
file_path, line_range = ref.split(':', 1)
|
|
299
|
+
# Initialize with default values
|
|
300
|
+
start_line = 1 # 1-based
|
|
301
|
+
end_line = -1
|
|
302
|
+
|
|
303
|
+
# Process line range if specified
|
|
304
|
+
if ',' in line_range or ':' in line_range:
|
|
305
|
+
try:
|
|
306
|
+
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
307
|
+
|
|
308
|
+
# Handle special values and Python-style negative indices
|
|
309
|
+
try:
|
|
310
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
311
|
+
total_lines = len(f.readlines())
|
|
312
|
+
except FileNotFoundError:
|
|
313
|
+
PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
|
|
314
|
+
continue
|
|
315
|
+
# Process start line
|
|
316
|
+
if raw_start == 0: # 0表示整个文件
|
|
317
|
+
start_line = 1
|
|
318
|
+
end_line = total_lines
|
|
319
|
+
else:
|
|
320
|
+
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
321
|
+
|
|
322
|
+
# Process end line
|
|
323
|
+
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
324
|
+
end_line = total_lines
|
|
325
|
+
else:
|
|
326
|
+
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
327
|
+
|
|
328
|
+
# Auto-correct ranges
|
|
329
|
+
start_line = max(1, min(start_line, total_lines))
|
|
330
|
+
end_line = max(start_line, min(end_line, total_lines))
|
|
331
|
+
|
|
332
|
+
# Final validation
|
|
333
|
+
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
334
|
+
raise ValueError
|
|
335
|
+
|
|
336
|
+
except:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
# Add file if it exists
|
|
340
|
+
if os.path.isfile(file_path):
|
|
341
|
+
files.append({
|
|
342
|
+
"path": file_path,
|
|
343
|
+
"start_line": start_line,
|
|
344
|
+
"end_line": end_line
|
|
345
|
+
})
|
|
346
|
+
else:
|
|
347
|
+
# Handle simple file path
|
|
348
|
+
if os.path.isfile(ref):
|
|
349
|
+
files.append({
|
|
350
|
+
"path": ref,
|
|
351
|
+
"start_line": 1, # 1-based
|
|
352
|
+
"end_line": -1
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
# Read and process files if any were found
|
|
356
|
+
if files:
|
|
357
|
+
result = ReadCodeTool().execute({"files": files})
|
|
358
|
+
if result["success"]:
|
|
359
|
+
return result["stdout"] + "\n" + prompt
|
|
360
|
+
|
|
361
|
+
return prompt + """
|
|
362
|
+
==================================================================
|
|
363
|
+
Patch Line Number Range Rules:
|
|
364
|
+
- INSERT: [m,m)
|
|
365
|
+
- REPLACE: [m,n] n>=m
|
|
366
|
+
- DELETE: [m,n] n>=m
|
|
367
|
+
- NEW_FILE: [1,1)
|
|
368
|
+
|
|
369
|
+
Critical Rules:
|
|
370
|
+
- NEVER include unchanged code in patch content
|
|
371
|
+
- ONLY show lines that are being modified/added
|
|
372
|
+
- Maintain original line breaks around modified sections
|
|
373
|
+
- Preserve surrounding comments unless explicitly modifying them
|
|
374
|
+
- Verify line number range is correct
|
|
375
|
+
- Verify indentation is correct
|
|
376
|
+
==================================================================
|
|
377
|
+
"""
|