jarvis-ai-assistant 0.1.121__py3-none-any.whl → 0.1.123__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_code_agent/code_agent.py +62 -59
- jarvis/jarvis_code_agent/file_select.py +7 -6
- jarvis/jarvis_code_agent/patch.py +207 -330
- jarvis/jarvis_code_agent/relevant_files.py +2 -4
- jarvis/jarvis_dev/main.py +106 -60
- jarvis/jarvis_platform/base.py +20 -25
- jarvis/jarvis_platform/kimi.py +0 -2
- jarvis/jarvis_platform/openai.py +1 -1
- jarvis/jarvis_tools/file_operation.py +0 -3
- jarvis/jarvis_tools/read_code.py +2 -3
- jarvis/jarvis_utils/__init__.py +15 -8
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/METADATA +6 -5
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/RECORD +18 -18
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.121.dist-info → jarvis_ai_assistant-0.1.123.dist-info}/top_level.txt +0 -0
|
@@ -21,276 +21,162 @@ class PatchOutputHandler(OutputHandler):
|
|
|
21
21
|
|
|
22
22
|
def prompt(self) -> str:
|
|
23
23
|
return """
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
1.
|
|
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
|
-
<DELETE>
|
|
117
|
-
File: src/utils.py
|
|
118
|
-
Lines: [9,13]
|
|
119
|
-
</DELETE>
|
|
120
|
-
|
|
121
|
-
## MOVE_FILE Example
|
|
122
|
-
<MOVE_FILE>
|
|
123
|
-
File: src/old_dir/file.py
|
|
124
|
-
NewPath: src/new_dir/file.py
|
|
125
|
-
</MOVE_FILE>
|
|
126
|
-
|
|
127
|
-
## REMOVE_FILE Example
|
|
128
|
-
<REMOVE_FILE>
|
|
129
|
-
File: src/obsolete.py
|
|
130
|
-
</REMOVE_FILE>
|
|
131
|
-
|
|
132
|
-
# 🚨 Critical Requirements
|
|
133
|
-
1. One change per block
|
|
134
|
-
2. Use correct operation type
|
|
135
|
-
3. Match existing code style
|
|
136
|
-
4. Preserve indentation levels
|
|
137
|
-
5. Exact file paths required
|
|
138
|
-
6. Handle edge cases properly
|
|
139
|
-
7. Include error handling
|
|
140
|
-
8. Maintain code consistency
|
|
141
|
-
|
|
142
|
-
# 🚫 Invalid Format Examples
|
|
143
|
-
## BAD EXAMPLE 1 - Do not use diff format
|
|
144
|
-
<REPLACE>
|
|
145
|
-
File: src/file.py
|
|
146
|
-
Lines: [5,8)
|
|
147
|
-
- old_line_1
|
|
148
|
-
+ new_line_1
|
|
149
|
-
</REPLACE>
|
|
150
|
-
|
|
151
|
-
## BAD EXAMPLE 2 - Do not include previous and new tags
|
|
152
|
-
<REPLACE>
|
|
153
|
-
File: src/file.py
|
|
154
|
-
Lines: [10,12]
|
|
155
|
-
<PREVIOUS>
|
|
156
|
-
old_code
|
|
157
|
-
</PREVIOUS>
|
|
158
|
-
<NEW>
|
|
159
|
-
new_code
|
|
160
|
-
</NEW>
|
|
161
|
-
</REPLACE>
|
|
162
|
-
|
|
163
|
-
## BAD EXAMPLE 3 - Do not use comment to explain
|
|
164
|
-
<REPLACE>
|
|
165
|
-
File: src/file.py
|
|
166
|
-
Lines: [15,18]
|
|
167
|
-
# Replace the following code
|
|
168
|
-
old_function()
|
|
169
|
-
# With the new implementation
|
|
170
|
-
new_function()
|
|
171
|
-
</REPLACE>
|
|
24
|
+
# 🛠️ Code Patch Specification
|
|
25
|
+
When making changes, you MUST:
|
|
26
|
+
1. Explain each modification BEFORE the <PATCH> block using:
|
|
27
|
+
# [OPERATION] on [FILE]: Lines X-Y
|
|
28
|
+
# Reason: [CLEAR EXPLANATION]
|
|
29
|
+
2. Maintain original code style and compatibility:
|
|
30
|
+
- Preserve existing indentation levels
|
|
31
|
+
- Keep surrounding empty lines
|
|
32
|
+
- Match variable naming conventions
|
|
33
|
+
- Maintain API compatibility
|
|
34
|
+
3. Follow the exact patch format below
|
|
35
|
+
4. Use separate <PATCH> blocks for different files
|
|
36
|
+
5. Include ONLY modified lines in content
|
|
37
|
+
|
|
38
|
+
<PATCH>
|
|
39
|
+
File path [Range]
|
|
40
|
+
Code content
|
|
41
|
+
</PATCH>
|
|
42
|
+
|
|
43
|
+
Critical Rules:
|
|
44
|
+
- NEVER include unchanged code in patch content
|
|
45
|
+
- ONLY show lines that are being modified/added
|
|
46
|
+
- Maintain original line breaks around modified sections
|
|
47
|
+
- Preserve surrounding comments unless explicitly modifying them
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
# ======== REPLACE ========
|
|
51
|
+
# GOOD: Only modified lines
|
|
52
|
+
# REPLACE in src/app.py: Lines 5-8
|
|
53
|
+
# Reason: Update calculation formula
|
|
54
|
+
<PATCH>
|
|
55
|
+
src/app.py [5,8]
|
|
56
|
+
result = (base_value * 1.15) + tax
|
|
57
|
+
logger.debug("New calculation applied")
|
|
58
|
+
</PATCH>
|
|
59
|
+
|
|
60
|
+
# BAD: Includes unchanged lines
|
|
61
|
+
<PATCH>
|
|
62
|
+
src/app.py [5,8]
|
|
63
|
+
def calculate():
|
|
64
|
+
# Original comment (should not be included)
|
|
65
|
+
result = (base_value * 1.15) + tax
|
|
66
|
+
return result # Original line
|
|
67
|
+
</PATCH>
|
|
68
|
+
|
|
69
|
+
# ======== INSERT ========
|
|
70
|
+
# GOOD: Insert single line
|
|
71
|
+
# INSERT in utils/logger.py: Before line 3
|
|
72
|
+
# Reason: Add initialization check
|
|
73
|
+
<PATCH>
|
|
74
|
+
utils/logger.py [3]
|
|
75
|
+
if not _initialized: initialize()
|
|
76
|
+
</PATCH>
|
|
77
|
+
|
|
78
|
+
# BAD: Extra empty lines
|
|
79
|
+
<PATCH>
|
|
80
|
+
utils/logger.py [3]
|
|
81
|
+
|
|
82
|
+
if not _initialized:
|
|
83
|
+
initialize()
|
|
84
|
+
|
|
85
|
+
</PATCH>
|
|
86
|
+
|
|
87
|
+
# ======== NEW FILE ========
|
|
88
|
+
# GOOD: Complete minimal content
|
|
89
|
+
# NEW FILE in config/settings.yaml: Create new config
|
|
90
|
+
<PATCH>
|
|
91
|
+
config/settings.yaml [1]
|
|
92
|
+
database:
|
|
93
|
+
host: localhost
|
|
94
|
+
port: 5432
|
|
95
|
+
</PATCH>
|
|
96
|
+
|
|
97
|
+
# BAD: Placeholder content
|
|
98
|
+
<PATCH>
|
|
99
|
+
config/settings.yaml [1]
|
|
100
|
+
TODO: Add configuration
|
|
101
|
+
</PATCH>
|
|
102
|
+
|
|
103
|
+
# ======== DELETE ========
|
|
104
|
+
# GOOD: Empty content for deletion
|
|
105
|
+
# DELETE in src/old.py: Lines 10-12
|
|
106
|
+
# Reason: Remove deprecated function
|
|
107
|
+
<PATCH>
|
|
108
|
+
src/old.py [10,12]
|
|
109
|
+
</PATCH>
|
|
110
|
+
|
|
111
|
+
# BAD: Comment in delete operation
|
|
112
|
+
<PATCH>
|
|
113
|
+
src/old.py [10,12]
|
|
114
|
+
# Remove these lines
|
|
115
|
+
</PATCH>
|
|
172
116
|
"""
|
|
173
117
|
|
|
174
118
|
|
|
175
119
|
def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
176
|
-
"""
|
|
120
|
+
"""解析补丁格式"""
|
|
177
121
|
result = {}
|
|
178
|
-
|
|
122
|
+
header_pattern = re.compile(
|
|
123
|
+
r'^\s*"?(.+?)"?\s*\[(\d+)(?:,(\d+))?\]\s*$' # Match file path and line number
|
|
124
|
+
)
|
|
125
|
+
patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
|
|
179
126
|
|
|
180
|
-
for
|
|
181
|
-
|
|
182
|
-
|
|
127
|
+
for patch in patches:
|
|
128
|
+
# 分割首行和内容
|
|
129
|
+
parts = patch.split('\n', 1)
|
|
130
|
+
if len(parts) < 1:
|
|
183
131
|
continue
|
|
132
|
+
header_line = parts[0].strip()
|
|
133
|
+
content = parts[1] if len(parts) > 1 else ''
|
|
134
|
+
|
|
135
|
+
# 仅在内容非空时添加换行符
|
|
136
|
+
if content and not content.endswith('\n'):
|
|
137
|
+
content += '\n'
|
|
184
138
|
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
if not
|
|
139
|
+
# 解析文件路径和行号
|
|
140
|
+
header_match = header_pattern.match(header_line)
|
|
141
|
+
if not header_match:
|
|
188
142
|
continue
|
|
189
|
-
filepath = file_match.group(1).strip()
|
|
190
|
-
|
|
191
|
-
# Initialize line numbers
|
|
192
|
-
start_line = end_line = 0
|
|
193
|
-
|
|
194
|
-
# Parse line numbers based on operation type
|
|
195
|
-
if patch_type in ['REPLACE', 'DELETE']:
|
|
196
|
-
# 增强正则表达式兼容性
|
|
197
|
-
line_match = re.match(
|
|
198
|
-
r"^Lines:\s*\[\s*(\d+)\s*(?:,\s*(\d+)\s*)?([\]\)])\s*$", # 支持单数字格式
|
|
199
|
-
lines[1].strip(), # 去除前后空格
|
|
200
|
-
re.IGNORECASE
|
|
201
|
-
)
|
|
202
|
-
if line_match:
|
|
203
|
-
start_line = int(line_match.group(1))
|
|
204
|
-
end_value = int(line_match.group(2) or line_match.group(1)) # 第二个数字不存在时使用第一个
|
|
205
|
-
bracket_type = line_match.group(3).strip()
|
|
206
|
-
|
|
207
|
-
# 根据括号类型处理区间
|
|
208
|
-
if bracket_type == ')': # [m,n)
|
|
209
|
-
end_line = end_value - 1
|
|
210
|
-
else: # [m,n]
|
|
211
|
-
end_line = end_value
|
|
212
|
-
|
|
213
|
-
# 确保 end_line >= start_line
|
|
214
|
-
end_line = max(end_line, start_line)
|
|
215
|
-
else:
|
|
216
|
-
PrettyOutput.print(f"无法解析行号格式: {lines[1]}", OutputType.WARNING)
|
|
217
|
-
continue
|
|
218
|
-
elif patch_type == 'INSERT':
|
|
219
|
-
line_match = re.match(r"Line:\s*(\d+)", lines[1])
|
|
220
|
-
if line_match:
|
|
221
|
-
start_line = int(line_match.group(1)) # 1-based
|
|
222
|
-
end_line = start_line
|
|
223
|
-
elif patch_type == 'MOVE_FILE':
|
|
224
|
-
new_path_match = re.match(r"NewPath:\s*([^\s]+)", lines[1])
|
|
225
|
-
if new_path_match:
|
|
226
|
-
new_path = new_path_match.group(1).strip()
|
|
227
|
-
else:
|
|
228
|
-
continue
|
|
229
|
-
|
|
230
|
-
# Get content (after metadata)
|
|
231
|
-
if patch_type in ['REPLACE', 'DELETE']:
|
|
232
|
-
content_start = 2 # File + Lines
|
|
233
|
-
elif patch_type == 'INSERT':
|
|
234
|
-
content_start = 2 # File + Line
|
|
235
|
-
elif patch_type == 'NEW_FILE':
|
|
236
|
-
content_start = 1 # File
|
|
237
|
-
elif patch_type == 'MOVE_FILE':
|
|
238
|
-
content_start = 2 # File + NewPath
|
|
239
|
-
elif patch_type == 'REMOVE_FILE':
|
|
240
|
-
content_start = 1 # File
|
|
241
|
-
|
|
242
|
-
content_lines = lines[content_start:]
|
|
243
|
-
# 保留原始缩进和空行
|
|
244
|
-
content = '\n'.join(content_lines).rstrip('\n') + '\n' # 保留末尾换行
|
|
245
143
|
|
|
144
|
+
filepath = header_match.group(1)
|
|
145
|
+
start = int(header_match.group(2)) # 保持1-based行号
|
|
146
|
+
end = int(header_match.group(3)) + 1 if header_match.group(3) else start
|
|
147
|
+
|
|
148
|
+
# 存储参数
|
|
246
149
|
if filepath not in result:
|
|
247
150
|
result[filepath] = []
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
else:
|
|
257
|
-
result[filepath].append({
|
|
258
|
-
'type': patch_type,
|
|
259
|
-
'start_line': start_line,
|
|
260
|
-
'end_line': end_line,
|
|
261
|
-
'content': content
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
# Sort patches by start line in reverse order to apply from bottom to top
|
|
265
|
-
for filepath in result:
|
|
266
|
-
result[filepath].sort(key=lambda x: x.get('start_line', 0), reverse=True)
|
|
267
|
-
|
|
151
|
+
result[filepath].append({
|
|
152
|
+
'filepath': filepath,
|
|
153
|
+
'start': start,
|
|
154
|
+
'end': end,
|
|
155
|
+
'content': content # 保留原始内容(可能为空)
|
|
156
|
+
})
|
|
157
|
+
for filepath in result.keys():
|
|
158
|
+
result[filepath] = sorted(result[filepath], key=lambda x: x['start'], reverse=True)
|
|
268
159
|
return result
|
|
269
160
|
|
|
270
161
|
|
|
271
162
|
def apply_patch(output_str: str) -> str:
|
|
272
163
|
"""Apply patches to files"""
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
for patch in patch_info:
|
|
279
|
-
patch_type = patch['type']
|
|
280
|
-
|
|
281
|
-
if patch_type == 'MOVE_FILE':
|
|
282
|
-
handle_move_file(filepath, patch)
|
|
283
|
-
elif patch_type == 'NEW_FILE':
|
|
284
|
-
handle_new_file(filepath, patch)
|
|
285
|
-
elif patch_type == 'REMOVE_FILE':
|
|
286
|
-
handle_remove_file(filepath)
|
|
287
|
-
else:
|
|
288
|
-
handle_code_operation(filepath, patch)
|
|
289
|
-
|
|
290
|
-
except Exception as e:
|
|
291
|
-
PrettyOutput.print(f"应用 {patch_type} 操作到 {filepath} 失败: {str(e)}", OutputType.ERROR)
|
|
292
|
-
continue
|
|
164
|
+
try:
|
|
165
|
+
patches = _parse_patch(output_str)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
|
|
168
|
+
return ""
|
|
293
169
|
|
|
170
|
+
ret = ""
|
|
171
|
+
|
|
172
|
+
for filepath, patch_list in patches.items():
|
|
173
|
+
for patch in patch_list:
|
|
174
|
+
try:
|
|
175
|
+
handle_code_operation(filepath, patch)
|
|
176
|
+
PrettyOutput.print(f"成功处理 操作", OutputType.SUCCESS)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
|
|
179
|
+
|
|
294
180
|
if has_uncommitted_changes():
|
|
295
181
|
diff = get_diff()
|
|
296
182
|
if handle_commit_workflow(diff):
|
|
@@ -308,15 +194,25 @@ def apply_patch(output_str: str) -> str:
|
|
|
308
194
|
if user_input:
|
|
309
195
|
ret += "\n" + user_input
|
|
310
196
|
else:
|
|
311
|
-
ret
|
|
197
|
+
ret = ""
|
|
312
198
|
|
|
313
199
|
return ret # Ensure a string is always returned
|
|
314
200
|
|
|
315
|
-
def get_diff()->str:
|
|
316
|
-
os.system
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
201
|
+
def get_diff() -> str:
|
|
202
|
+
"""使用更安全的subprocess代替os.system"""
|
|
203
|
+
import subprocess
|
|
204
|
+
try:
|
|
205
|
+
subprocess.run(['git', 'add', '.'], check=True)
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
['git', 'diff', 'HEAD'],
|
|
208
|
+
capture_output=True,
|
|
209
|
+
text=True,
|
|
210
|
+
check=True
|
|
211
|
+
)
|
|
212
|
+
return result.stdout
|
|
213
|
+
finally:
|
|
214
|
+
subprocess.run(['git', 'reset', 'HEAD'], check=True)
|
|
215
|
+
|
|
320
216
|
def handle_commit_workflow(diff:str)->bool:
|
|
321
217
|
"""Handle the git commit workflow and return the commit details.
|
|
322
218
|
|
|
@@ -324,9 +220,10 @@ def handle_commit_workflow(diff:str)->bool:
|
|
|
324
220
|
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
325
221
|
"""
|
|
326
222
|
if not user_confirm("是否要提交代码?", default=True):
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
223
|
+
import subprocess
|
|
224
|
+
subprocess.run(['git', 'reset', 'HEAD'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
225
|
+
subprocess.run(['git', 'checkout', '--', '.'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
226
|
+
subprocess.run(['git', 'clean', '-fd'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
330
227
|
return False
|
|
331
228
|
|
|
332
229
|
git_commiter = GitCommitTool()
|
|
@@ -365,82 +262,62 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
|
365
262
|
return result
|
|
366
263
|
# New handler functions below ▼▼▼
|
|
367
264
|
|
|
368
|
-
def handle_move_file(filepath: str, patch: Dict[str, Any]):
|
|
369
|
-
"""Handle file moving operation"""
|
|
370
|
-
new_path = patch['new_path']
|
|
371
|
-
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
|
372
|
-
if os.path.exists(filepath):
|
|
373
|
-
os.rename(filepath, new_path)
|
|
374
|
-
PrettyOutput.print(f"成功移动文件 {filepath} -> {new_path}", OutputType.SUCCESS)
|
|
375
|
-
else:
|
|
376
|
-
PrettyOutput.print(f"源文件不存在: {filepath}", OutputType.WARNING)
|
|
377
|
-
|
|
378
265
|
def handle_new_file(filepath: str, patch: Dict[str, Any]):
|
|
379
|
-
"""
|
|
380
|
-
new_content = patch.get('content', '').splitlines(keepends=True)
|
|
381
|
-
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
382
|
-
new_content[-1] += '\n'
|
|
383
|
-
|
|
266
|
+
"""统一参数格式处理新文件"""
|
|
384
267
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
385
268
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
386
|
-
f.
|
|
387
|
-
PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
|
|
388
|
-
|
|
389
|
-
def handle_remove_file(filepath: str):
|
|
390
|
-
"""Handle file removal"""
|
|
391
|
-
if os.path.exists(filepath):
|
|
392
|
-
os.remove(filepath)
|
|
393
|
-
PrettyOutput.print(f"成功删除文件 {filepath}", OutputType.SUCCESS)
|
|
394
|
-
else:
|
|
395
|
-
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
269
|
+
f.write(patch['content'])
|
|
396
270
|
|
|
397
271
|
def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
398
|
-
"""
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
272
|
+
"""处理紧凑格式补丁"""
|
|
273
|
+
try:
|
|
274
|
+
# 新建文件时强制覆盖
|
|
275
|
+
os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)
|
|
276
|
+
if not os.path.exists(filepath):
|
|
277
|
+
open(filepath, 'w', encoding='utf-8').close()
|
|
278
|
+
with open(filepath, 'r+', encoding='utf-8') as f:
|
|
279
|
+
lines = f.readlines()
|
|
280
|
+
|
|
281
|
+
new_lines = validate_and_apply_changes(
|
|
282
|
+
lines,
|
|
283
|
+
patch['start'],
|
|
284
|
+
patch['end'],
|
|
285
|
+
patch['content']
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
f.seek(0)
|
|
289
|
+
f.writelines(new_lines)
|
|
290
|
+
f.truncate()
|
|
415
291
|
|
|
416
|
-
|
|
417
|
-
lines = f.readlines()
|
|
418
|
-
validate_and_apply_changes(patch_type, lines, start_line, end_line, new_content)
|
|
419
|
-
f.seek(0)
|
|
420
|
-
f.writelines(lines)
|
|
421
|
-
f.truncate()
|
|
292
|
+
PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
|
|
422
293
|
|
|
423
|
-
|
|
294
|
+
except Exception as e:
|
|
295
|
+
PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
|
|
424
296
|
|
|
425
297
|
def validate_and_apply_changes(
|
|
426
|
-
patch_type: str,
|
|
427
298
|
lines: List[str],
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
):
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
299
|
+
start: int,
|
|
300
|
+
end: int,
|
|
301
|
+
content: str
|
|
302
|
+
) -> List[str]:
|
|
303
|
+
|
|
304
|
+
new_content = content.splitlines(keepends=True)
|
|
305
|
+
|
|
306
|
+
# 插入操作处理
|
|
307
|
+
if start == end:
|
|
308
|
+
if start < 1 or start > len(lines)+1:
|
|
309
|
+
raise ValueError(f"无效插入位置: {start}")
|
|
310
|
+
# 在指定位置前插入
|
|
311
|
+
return lines[:start-1] + new_content + lines[start-1:]
|
|
312
|
+
|
|
313
|
+
# 范围替换/删除操作
|
|
314
|
+
if start > end:
|
|
315
|
+
raise ValueError(f"起始行{start}不能大于结束行{end}")
|
|
316
|
+
|
|
317
|
+
max_line = len(lines)
|
|
318
|
+
# 自动修正行号范围
|
|
319
|
+
start = max(1, min(start, max_line+1))
|
|
320
|
+
end = max(start, min(end, max_line+1))
|
|
321
|
+
|
|
322
|
+
# 执行替换
|
|
323
|
+
return lines[:start-1] + new_content + lines[end-1:]
|
|
@@ -60,14 +60,12 @@ Identify customization options:
|
|
|
60
60
|
- "Should we create a new class?"
|
|
61
61
|
|
|
62
62
|
# 🎨 Question Template
|
|
63
|
-
|
|
63
|
+
3-5 specific questions about existing implementations:
|
|
64
64
|
<QUESTION>
|
|
65
|
-
[3-5 specific questions about existing implementations:
|
|
66
65
|
1. System architecture question
|
|
67
66
|
2. Implementation details question
|
|
68
|
-
3. Integration or extension question
|
|
67
|
+
3. Integration or extension question
|
|
69
68
|
</QUESTION>
|
|
70
|
-
```
|
|
71
69
|
|
|
72
70
|
# 🔎 Investigation Focus
|
|
73
71
|
1. Current System
|