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.

@@ -21,276 +21,162 @@ class PatchOutputHandler(OutputHandler):
21
21
 
22
22
  def prompt(self) -> str:
23
23
  return """
24
- # 🔄 REPLACE: Modify existing code
25
- <REPLACE>
26
- File: path/to/file
27
- Lines: [start,end] or [start,end)
28
- [new content]
29
- ...
30
- </REPLACE>
31
-
32
- # INSERT: Add new code
33
- <INSERT>
34
- File: path/to/file
35
- Line: position
36
- [new content]
37
- ...
38
- </INSERT>
39
-
40
- # 🗑️ DELETE: Remove existing code
41
- <DELETE>
42
- File: path/to/file
43
- Lines: [start,end] or [start,end)
44
- </DELETE>
45
-
46
- # 🆕 NEW_FILE: Create new file
47
- <NEW_FILE>
48
- File: path/to/file
49
- [new content]
50
- ...
51
- </NEW_FILE>
52
-
53
- # ➡️ MOVE_FILE: Relocate a file
54
- <MOVE_FILE>
55
- File: path/to/source/file
56
- NewPath: path/to/destination/file
57
- </MOVE_FILE>
58
-
59
- # ❌ REMOVE_FILE: Delete entire file
60
- <REMOVE_FILE>
61
- File: path/to/file
62
- </REMOVE_FILE>
63
-
64
- # 📋 Formatting Rules
65
- 1. File Paths
66
- - Use relative paths from project root
67
- - Must be exact and case-sensitive
68
- - Example: src/module/file.py
69
-
70
- 2. Line Numbers
71
- - Format: [start,end] (inclusive) or [start,end) (right-exclusive)
72
- - 1-based line numbers
73
- - Single number for INSERT
74
- - Omit for NEW_FILE/REMOVE_FILE
75
-
76
- 3. Content
77
- - Maintain original indentation
78
- - Follow existing code style
79
-
80
- # 📌 Usage Examples
81
- ## REPLACE Example (Closed Interval)
82
- <REPLACE>
83
- File: src/utils.py
84
- Lines: [9,13]
85
- def updated_function():
86
- # Replaces lines 9-13 inclusive
87
- return "new_implementation"
88
- </REPLACE>
89
-
90
- ## REPLACE Example (Left-Closed Right-Open)
91
- <REPLACE>
92
- File: src/calculator.py
93
- Lines: [5,8)
94
- def new_calculation():
95
- # Replaces lines 5-7 (excludes line 8)
96
- return 42
97
- </REPLACE>
98
-
99
- ## INSERT Example
100
- <INSERT>
101
- File: src/main.py
102
- Line: 19
103
- # Inserted before line 19
104
- new_feature()
105
- </INSERT>
106
-
107
- ## NEW_FILE Example
108
- <NEW_FILE>
109
- File: src/new_module.py
110
- # New file creation
111
- def feature_entry():
112
- pass
113
- </NEW_FILE>
114
-
115
- ## DELETE Example
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
- """Parse patches from string with optimized format"""
120
+ """解析补丁格式"""
177
121
  result = {}
178
- patches = re.findall(r"<(REPLACE|INSERT|DELETE|NEW_FILE|REMOVE_FILE|MOVE_FILE)>(.*?)</\1>", patch_str, re.DOTALL)
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 patch_type, patch in patches:
181
- lines = patch.strip().split('\n')
182
- if not lines:
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
- # Parse file path
186
- file_match = re.match(r"File:\s*([^\s]+)", lines[0])
187
- if not file_match:
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
- # Handle MOVE_FILE specially
250
- if patch_type == 'MOVE_FILE':
251
- result[filepath].append({
252
- 'type': patch_type,
253
- 'new_path': new_path,
254
- 'content': content
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
- patches = _parse_patch(output_str)
274
- ret = "" # Initialize return value
275
-
276
- for filepath, patch_info in patches.items():
277
- try:
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 += "Please check the patch again"
197
+ ret = ""
312
198
 
313
199
  return ret # Ensure a string is always returned
314
200
 
315
- def get_diff()->str:
316
- os.system("git add .")
317
- diff = os.popen("git diff HEAD").read()
318
- os.system("git reset HEAD")
319
- return diff
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
- os.system("git reset HEAD")
328
- os.system("git checkout -- .")
329
- os.system("git clean -fd")
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
- """Handle new file creation"""
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.writelines(new_content)
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
- """Handle code modification operations (REPLACE/INSERT/DELETE)"""
399
- patch_type = patch['type']
400
- start_line = patch.get('start_line', 0)
401
- end_line = patch.get('end_line', 0)
402
- new_content = patch.get('content', '').splitlines(keepends=True)
403
-
404
- if not new_content:
405
- new_content = ['']
406
-
407
- PrettyOutput.print(f"patch_type: {patch_type}\nstart_line: {start_line}\nend_line: {end_line}\nnew_content:\n{''.join(new_content)}", OutputType.INFO)
408
-
409
- if new_content and new_content[-1] and new_content[-1][-1] != '\n':
410
- new_content[-1] += '\n'
411
-
412
- if not os.path.exists(filepath):
413
- PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
414
- return
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
- with open(filepath, 'r+', encoding='utf-8') as f:
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
- PrettyOutput.print(f"成功对 {filepath} 执行 {patch_type} 操作", OutputType.SUCCESS)
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
- start_line: int,
429
- end_line: int,
430
- new_content: List[str]
431
- ):
432
- """Validate and apply code changes to in-memory file content"""
433
- if patch_type in ['REPLACE', 'DELETE']:
434
- if start_line < 1 or end_line > len(lines) or start_line > end_line:
435
- raise ValueError(f"Invalid line range [{start_line}, {end_line}] (total lines: {len(lines)})")
436
-
437
- if patch_type == 'REPLACE':
438
- lines[start_line-1:end_line] = new_content
439
- else: # DELETE
440
- lines[start_line-1:end_line] = []
441
-
442
- elif patch_type == 'INSERT':
443
- if start_line < 1 or start_line > len(lines) + 1:
444
- raise ValueError(f"Invalid insertion position [{start_line}] (valid range: 1-{len(lines)+1})")
445
-
446
- lines[start_line-1:start_line-1] = new_content
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