jarvis-ai-assistant 0.1.118__py3-none-any.whl → 0.1.119__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 +60 -46
- jarvis/jarvis_code_agent/code_agent.py +71 -107
- jarvis/jarvis_code_agent/file_select.py +90 -18
- jarvis/jarvis_code_agent/patch.py +317 -123
- jarvis/jarvis_dev/main.py +42 -0
- jarvis/jarvis_platform/base.py +15 -4
- jarvis/jarvis_tools/code_review.py +79 -68
- jarvis/jarvis_tools/create_code_agent.py +2 -2
- jarvis/jarvis_tools/git_commiter.py +60 -20
- jarvis/jarvis_tools/read_code.py +31 -18
- jarvis/jarvis_tools/select_code_files.py +1 -0
- jarvis/jarvis_utils/__init__.py +2 -2
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/RECORD +19 -19
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.119.dist-info}/top_level.txt +0 -0
|
@@ -3,6 +3,7 @@ from typing import Dict, Any, List, Tuple
|
|
|
3
3
|
import os
|
|
4
4
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
5
5
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
6
|
+
from jarvis.jarvis_tools.read_code import ReadCodeTool
|
|
6
7
|
from jarvis.jarvis_utils import OutputType, PrettyOutput, get_multiline_input, has_uncommitted_changes, user_confirm
|
|
7
8
|
|
|
8
9
|
|
|
@@ -20,176 +21,259 @@ class PatchOutputHandler(OutputHandler):
|
|
|
20
21
|
|
|
21
22
|
def prompt(self) -> str:
|
|
22
23
|
return """
|
|
23
|
-
# 📝
|
|
24
|
-
Use
|
|
24
|
+
# 📝 Code Modification Format
|
|
25
|
+
Use specific blocks for different operations:
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
path/to/file
|
|
27
|
+
# 🔄 REPLACE: Modify existing code
|
|
28
|
+
<REPLACE>
|
|
29
|
+
File: path/to/file
|
|
30
|
+
Lines: [start,end] or [start,end)
|
|
31
|
+
-----
|
|
29
32
|
new_content
|
|
30
|
-
</
|
|
31
|
-
```
|
|
33
|
+
</REPLACE>
|
|
32
34
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# ➕ INSERT: Add new code
|
|
36
|
+
<INSERT>
|
|
37
|
+
File: path/to/file
|
|
38
|
+
Line: position
|
|
39
|
+
-----
|
|
40
|
+
new_content
|
|
41
|
+
</INSERT>
|
|
42
|
+
|
|
43
|
+
# 🗑️ DELETE: Remove existing code
|
|
44
|
+
<DELETE>
|
|
45
|
+
File: path/to/file
|
|
46
|
+
Lines: [start,end] or [start,end)
|
|
47
|
+
</DELETE>
|
|
48
|
+
|
|
49
|
+
# 🆕 NEW_FILE: Create new file
|
|
50
|
+
<NEW_FILE>
|
|
51
|
+
File: path/to/file
|
|
52
|
+
-----
|
|
53
|
+
new_content
|
|
54
|
+
</NEW_FILE>
|
|
55
|
+
|
|
56
|
+
# ➡️ MOVE_FILE: Relocate a file
|
|
57
|
+
<MOVE_FILE>
|
|
58
|
+
File: path/to/source/file
|
|
59
|
+
NewPath: path/to/destination/file
|
|
60
|
+
</MOVE_FILE>
|
|
61
|
+
|
|
62
|
+
# ❌ REMOVE_FILE: Delete entire file
|
|
63
|
+
<REMOVE_FILE>
|
|
64
|
+
File: path/to/file
|
|
65
|
+
</REMOVE_FILE>
|
|
66
|
+
|
|
67
|
+
# 📋 Formatting Rules
|
|
68
|
+
1. File Paths
|
|
69
|
+
- Use relative paths from project root
|
|
36
70
|
- Must be exact and case-sensitive
|
|
37
71
|
- Example: src/module/file.py
|
|
38
|
-
|
|
72
|
+
|
|
39
73
|
2. Line Numbers
|
|
40
|
-
- Format: start,end
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
3.
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
# 📌 Examples
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
src/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
- Format: [start,end] (inclusive) or [start,end) (right-exclusive)
|
|
75
|
+
- 1-based line numbers
|
|
76
|
+
- Single number for INSERT
|
|
77
|
+
- Omit for NEW_FILE/REMOVE_FILE
|
|
78
|
+
|
|
79
|
+
3. Content
|
|
80
|
+
- Use "-----" separator
|
|
81
|
+
- Maintain original indentation
|
|
82
|
+
- Follow existing code style
|
|
83
|
+
|
|
84
|
+
# 📌 Usage Examples
|
|
85
|
+
## REPLACE Example (Closed Interval)
|
|
86
|
+
<REPLACE>
|
|
87
|
+
File: src/utils.py
|
|
88
|
+
Lines: [9,13]
|
|
89
|
+
-----
|
|
90
|
+
def updated_function():
|
|
91
|
+
# Replaces lines 9-13 inclusive
|
|
92
|
+
return "new_implementation"
|
|
93
|
+
</REPLACE>
|
|
94
|
+
|
|
95
|
+
## REPLACE Example (Left-Closed Right-Open)
|
|
96
|
+
<REPLACE>
|
|
97
|
+
File: src/calculator.py
|
|
98
|
+
Lines: [5,8)
|
|
99
|
+
-----
|
|
100
|
+
def new_calculation():
|
|
101
|
+
# Replaces lines 5-7 (excludes line 8)
|
|
102
|
+
return 42
|
|
103
|
+
</REPLACE>
|
|
104
|
+
|
|
105
|
+
## INSERT Example
|
|
106
|
+
<INSERT>
|
|
107
|
+
File: src/main.py
|
|
108
|
+
Line: 19
|
|
109
|
+
-----
|
|
110
|
+
# Inserted before line 19
|
|
111
|
+
new_feature()
|
|
112
|
+
</INSERT>
|
|
113
|
+
|
|
114
|
+
## NEW_FILE Example
|
|
115
|
+
<NEW_FILE>
|
|
116
|
+
File: src/new_module.py
|
|
117
|
+
-----
|
|
118
|
+
# New file creation
|
|
119
|
+
def feature_entry():
|
|
73
120
|
pass
|
|
74
|
-
</
|
|
75
|
-
|
|
121
|
+
</NEW_FILE>
|
|
122
|
+
|
|
123
|
+
## DELETE Example
|
|
124
|
+
<DELETE>
|
|
125
|
+
File: src/utils.py
|
|
126
|
+
Lines: [9,13]
|
|
127
|
+
</DELETE>
|
|
76
128
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
129
|
+
## MOVE_FILE Example
|
|
130
|
+
<MOVE_FILE>
|
|
131
|
+
File: src/old_dir/file.py
|
|
132
|
+
NewPath: src/new_dir/file.py
|
|
133
|
+
</MOVE_FILE>
|
|
134
|
+
|
|
135
|
+
## REMOVE_FILE Example
|
|
136
|
+
<REMOVE_FILE>
|
|
137
|
+
File: src/obsolete.py
|
|
138
|
+
</REMOVE_FILE>
|
|
139
|
+
|
|
140
|
+
# 🚨 Critical Requirements
|
|
141
|
+
1. One change per block
|
|
142
|
+
2. Use correct operation type
|
|
80
143
|
3. Match existing code style
|
|
81
|
-
4. Preserve indentation
|
|
82
|
-
5.
|
|
144
|
+
4. Preserve indentation levels
|
|
145
|
+
5. Exact file paths required
|
|
146
|
+
6. Handle edge cases properly
|
|
147
|
+
7. Include error handling
|
|
148
|
+
8. Maintain code consistency
|
|
83
149
|
"""
|
|
84
150
|
|
|
85
151
|
|
|
86
152
|
def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
87
|
-
"""Parse patches from string with format
|
|
88
|
-
<PATCH>
|
|
89
|
-
path/to/file start_line,end_line
|
|
90
|
-
content_line1
|
|
91
|
-
content_line2
|
|
92
|
-
...
|
|
93
|
-
</PATCH>
|
|
94
|
-
"""
|
|
153
|
+
"""Parse patches from string with optimized format"""
|
|
95
154
|
result = {}
|
|
96
|
-
patches = re.findall(r"<
|
|
155
|
+
patches = re.findall(r"<(REPLACE|INSERT|DELETE|NEW_FILE|REMOVE_FILE|MOVE_FILE)>(.*?)</\1>", patch_str, re.DOTALL)
|
|
97
156
|
|
|
98
|
-
for patch in patches:
|
|
157
|
+
for patch_type, patch in patches:
|
|
99
158
|
lines = patch.strip().split('\n')
|
|
100
159
|
if not lines:
|
|
101
160
|
continue
|
|
102
161
|
|
|
103
|
-
# Parse file path
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Extract file path and line range
|
|
107
|
-
match = re.match(r'([^\s]+)\s+(\d+),(\d+)', file_info)
|
|
108
|
-
if not match:
|
|
162
|
+
# Parse file path
|
|
163
|
+
file_match = re.match(r"File:\s*([^\s]+)", lines[0])
|
|
164
|
+
if not file_match:
|
|
109
165
|
continue
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end_line =
|
|
166
|
+
filepath = file_match.group(1).strip()
|
|
167
|
+
|
|
168
|
+
# Initialize line numbers
|
|
169
|
+
start_line = end_line = 0
|
|
170
|
+
|
|
171
|
+
# Parse line numbers based on operation type
|
|
172
|
+
if patch_type in ['REPLACE', 'DELETE']:
|
|
173
|
+
# 增强正则表达式兼容性
|
|
174
|
+
line_match = re.match(
|
|
175
|
+
r"^Lines:\s*\[\s*(\d+)\s*,\s*(\d+)\s*([\]\)])\s*$", # 匹配行尾
|
|
176
|
+
lines[1].strip(), # 去除前后空格
|
|
177
|
+
re.IGNORECASE
|
|
178
|
+
)
|
|
179
|
+
if line_match:
|
|
180
|
+
start_line = int(line_match.group(1))
|
|
181
|
+
end_value = int(line_match.group(2))
|
|
182
|
+
bracket_type = line_match.group(3).strip()
|
|
183
|
+
|
|
184
|
+
# 根据括号类型处理区间
|
|
185
|
+
if bracket_type == ')': # [m,n)
|
|
186
|
+
end_line = end_value - 1
|
|
187
|
+
else: # [m,n]
|
|
188
|
+
end_line = end_value
|
|
189
|
+
|
|
190
|
+
# 确保 end_line >= start_line
|
|
191
|
+
end_line = max(end_line, start_line)
|
|
192
|
+
else:
|
|
193
|
+
PrettyOutput.print(f"无法解析行号格式: {lines[1]}", OutputType.WARNING)
|
|
194
|
+
continue
|
|
195
|
+
elif patch_type == 'INSERT':
|
|
196
|
+
line_match = re.match(r"Line:\s*(\d+)", lines[1])
|
|
197
|
+
if line_match:
|
|
198
|
+
start_line = int(line_match.group(1)) # 1-based
|
|
199
|
+
end_line = start_line
|
|
200
|
+
elif patch_type == 'MOVE_FILE':
|
|
201
|
+
new_path_match = re.match(r"NewPath:\s*([^\s]+)", lines[1])
|
|
202
|
+
if new_path_match:
|
|
203
|
+
new_path = new_path_match.group(1).strip()
|
|
204
|
+
else:
|
|
205
|
+
continue
|
|
114
206
|
|
|
115
|
-
# Get content
|
|
116
|
-
|
|
207
|
+
# Get content (after separator)
|
|
208
|
+
separator_index = next((i for i, line in enumerate(lines) if line.strip() == "-----"), -1)
|
|
209
|
+
content = '\n'.join(lines[separator_index + 1:]) if separator_index != -1 else ''
|
|
117
210
|
|
|
118
211
|
if filepath not in result:
|
|
119
212
|
result[filepath] = []
|
|
120
213
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
214
|
+
# Handle MOVE_FILE specially
|
|
215
|
+
if patch_type == 'MOVE_FILE':
|
|
216
|
+
result[filepath].append({
|
|
217
|
+
'type': patch_type,
|
|
218
|
+
'new_path': new_path,
|
|
219
|
+
'content': content
|
|
220
|
+
})
|
|
221
|
+
else:
|
|
222
|
+
result[filepath].append({
|
|
223
|
+
'type': patch_type,
|
|
224
|
+
'start_line': start_line,
|
|
225
|
+
'end_line': end_line,
|
|
226
|
+
'content': content
|
|
227
|
+
})
|
|
127
228
|
|
|
128
229
|
# Sort patches by start line in reverse order to apply from bottom to top
|
|
129
230
|
for filepath in result:
|
|
130
|
-
result[filepath].sort(key=lambda x: x
|
|
231
|
+
result[filepath].sort(key=lambda x: x.get('start_line', 0), reverse=True)
|
|
131
232
|
|
|
132
233
|
return result
|
|
133
234
|
|
|
134
235
|
|
|
135
|
-
def apply_patch(output_str: str)->str:
|
|
236
|
+
def apply_patch(output_str: str) -> str:
|
|
136
237
|
"""Apply patches to files"""
|
|
137
238
|
patches = _parse_patch(output_str)
|
|
239
|
+
ret = "" # Initialize return value
|
|
138
240
|
|
|
139
241
|
for filepath, patch_info in patches.items():
|
|
140
242
|
try:
|
|
141
243
|
for patch in patch_info:
|
|
142
|
-
|
|
143
|
-
end_line = patch['end_line']
|
|
144
|
-
new_content = patch['content'].splitlines(keepends=True)
|
|
145
|
-
|
|
146
|
-
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
147
|
-
new_content[-1] += '\n'
|
|
148
|
-
|
|
149
|
-
# Handle file creation when start=end=0
|
|
150
|
-
if start_line == 0 and end_line == 0:
|
|
151
|
-
# Create directory if it doesn't exist
|
|
152
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
153
|
-
# Write new file
|
|
154
|
-
with open(filepath, 'w', encoding='utf-8') as f:
|
|
155
|
-
f.writelines(new_content)
|
|
156
|
-
PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
# Regular patch logic for existing files
|
|
160
|
-
if not os.path.exists(filepath):
|
|
161
|
-
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
162
|
-
continue
|
|
163
|
-
|
|
164
|
-
# Read original file content
|
|
165
|
-
lines = open(filepath, 'r', encoding='utf-8').readlines()
|
|
166
|
-
|
|
167
|
-
# Validate line numbers
|
|
168
|
-
if start_line < 0 or end_line > len(lines) + 1 or start_line > end_line:
|
|
169
|
-
PrettyOutput.print(f"无效的行范围 [{start_line}, {end_line}) 对于文件: {filepath}", OutputType.WARNING)
|
|
170
|
-
continue
|
|
171
|
-
|
|
172
|
-
# Create new content
|
|
173
|
-
lines[start_line:end_line] = new_content
|
|
244
|
+
patch_type = patch['type']
|
|
174
245
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
if patch_type == 'MOVE_FILE':
|
|
247
|
+
handle_move_file(filepath, patch)
|
|
248
|
+
elif patch_type == 'NEW_FILE':
|
|
249
|
+
handle_new_file(filepath, patch)
|
|
250
|
+
elif patch_type == 'REMOVE_FILE':
|
|
251
|
+
handle_remove_file(filepath)
|
|
252
|
+
else:
|
|
253
|
+
handle_code_operation(filepath, patch)
|
|
179
254
|
|
|
180
255
|
except Exception as e:
|
|
181
|
-
PrettyOutput.print(f"
|
|
256
|
+
PrettyOutput.print(f"应用 {patch_type} 操作到 {filepath} 失败: {str(e)}", OutputType.ERROR)
|
|
182
257
|
continue
|
|
183
|
-
|
|
258
|
+
|
|
184
259
|
if has_uncommitted_changes():
|
|
185
260
|
if handle_commit_workflow():
|
|
186
|
-
ret += "Successfully applied the patch"
|
|
261
|
+
ret += "Successfully applied the patch\n"
|
|
262
|
+
# Get modified line ranges
|
|
263
|
+
modified_ranges = get_modified_line_ranges()
|
|
264
|
+
modified_code = ReadCodeTool().execute({"files": [{"path": filepath, "start_line": start, "end_line": end} for filepath, (start, end) in modified_ranges.items()]})
|
|
265
|
+
if modified_code["success"]:
|
|
266
|
+
ret += "New code:\n"
|
|
267
|
+
ret += modified_code["stdout"]
|
|
187
268
|
else:
|
|
188
269
|
ret += "User rejected the patch"
|
|
189
270
|
user_input = get_multiline_input("你可以继续输入: ")
|
|
190
271
|
if user_input:
|
|
191
|
-
ret += user_input
|
|
192
|
-
|
|
272
|
+
ret += "\n" + user_input
|
|
273
|
+
else:
|
|
274
|
+
return ""
|
|
275
|
+
|
|
276
|
+
return ret # Ensure a string is always returned
|
|
193
277
|
|
|
194
278
|
def handle_commit_workflow()->bool:
|
|
195
279
|
"""Handle the git commit workflow and return the commit details.
|
|
@@ -209,4 +293,114 @@ def handle_commit_workflow()->bool:
|
|
|
209
293
|
|
|
210
294
|
git_commiter = GitCommitTool()
|
|
211
295
|
commit_result = git_commiter.execute({})
|
|
212
|
-
return commit_result["success"]
|
|
296
|
+
return commit_result["success"]
|
|
297
|
+
|
|
298
|
+
def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
299
|
+
"""Get modified line ranges from git diff for all changed files.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Dictionary mapping file paths to tuple with (start_line, end_line) ranges
|
|
303
|
+
for modified sections. Line numbers are 0-based.
|
|
304
|
+
"""
|
|
305
|
+
# Get git diff for all files
|
|
306
|
+
diff_output = os.popen("git show").read()
|
|
307
|
+
|
|
308
|
+
# Parse the diff to get modified files and their line ranges
|
|
309
|
+
result = {}
|
|
310
|
+
current_file = None
|
|
311
|
+
|
|
312
|
+
for line in diff_output.splitlines():
|
|
313
|
+
# Match lines like "+++ b/path/to/file"
|
|
314
|
+
file_match = re.match(r"^\+\+\+ b/(.*)", line)
|
|
315
|
+
if file_match:
|
|
316
|
+
current_file = file_match.group(1)
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
# Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
|
|
320
|
+
range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
|
|
321
|
+
if range_match and current_file:
|
|
322
|
+
start_line = int(range_match.group(1)) - 1 # Convert to 0-based
|
|
323
|
+
line_count = int(range_match.group(2)) if range_match.group(2) else 1
|
|
324
|
+
end_line = start_line + line_count
|
|
325
|
+
result[current_file] = (start_line, end_line)
|
|
326
|
+
|
|
327
|
+
return result
|
|
328
|
+
|
|
329
|
+
# New handler functions below ▼▼▼
|
|
330
|
+
|
|
331
|
+
def handle_move_file(filepath: str, patch: Dict[str, Any]):
|
|
332
|
+
"""Handle file moving operation"""
|
|
333
|
+
new_path = patch['new_path']
|
|
334
|
+
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
|
335
|
+
if os.path.exists(filepath):
|
|
336
|
+
os.rename(filepath, new_path)
|
|
337
|
+
PrettyOutput.print(f"成功移动文件 {filepath} -> {new_path}", OutputType.SUCCESS)
|
|
338
|
+
else:
|
|
339
|
+
PrettyOutput.print(f"源文件不存在: {filepath}", OutputType.WARNING)
|
|
340
|
+
|
|
341
|
+
def handle_new_file(filepath: str, patch: Dict[str, Any]):
|
|
342
|
+
"""Handle new file creation"""
|
|
343
|
+
new_content = patch.get('content', '').splitlines(keepends=True)
|
|
344
|
+
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
345
|
+
new_content[-1] += '\n'
|
|
346
|
+
|
|
347
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
348
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
349
|
+
f.writelines(new_content)
|
|
350
|
+
PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
|
|
351
|
+
|
|
352
|
+
def handle_remove_file(filepath: str):
|
|
353
|
+
"""Handle file removal"""
|
|
354
|
+
if os.path.exists(filepath):
|
|
355
|
+
os.remove(filepath)
|
|
356
|
+
PrettyOutput.print(f"成功删除文件 {filepath}", OutputType.SUCCESS)
|
|
357
|
+
else:
|
|
358
|
+
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
359
|
+
|
|
360
|
+
def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
361
|
+
"""Handle code modification operations (REPLACE/INSERT/DELETE)"""
|
|
362
|
+
patch_type = patch['type']
|
|
363
|
+
start_line = patch.get('start_line', 0)
|
|
364
|
+
end_line = patch.get('end_line', 0)
|
|
365
|
+
new_content = patch.get('content', '').splitlines(keepends=True)
|
|
366
|
+
|
|
367
|
+
PrettyOutput.print(f"patch_type: {patch_type}\nstart_line: {start_line}\nend_line: {end_line}\nnew_content:\n{new_content}", OutputType.INFO)
|
|
368
|
+
|
|
369
|
+
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
370
|
+
new_content[-1] += '\n'
|
|
371
|
+
|
|
372
|
+
if not os.path.exists(filepath):
|
|
373
|
+
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
with open(filepath, 'r+', encoding='utf-8') as f:
|
|
377
|
+
lines = f.readlines()
|
|
378
|
+
validate_and_apply_changes(patch_type, lines, start_line, end_line, new_content)
|
|
379
|
+
f.seek(0)
|
|
380
|
+
f.writelines(lines)
|
|
381
|
+
f.truncate()
|
|
382
|
+
|
|
383
|
+
PrettyOutput.print(f"成功对 {filepath} 执行 {patch_type} 操作", OutputType.SUCCESS)
|
|
384
|
+
|
|
385
|
+
def validate_and_apply_changes(
|
|
386
|
+
patch_type: str,
|
|
387
|
+
lines: List[str],
|
|
388
|
+
start_line: int,
|
|
389
|
+
end_line: int,
|
|
390
|
+
new_content: List[str]
|
|
391
|
+
):
|
|
392
|
+
"""Validate and apply code changes to in-memory file content"""
|
|
393
|
+
if patch_type in ['REPLACE', 'DELETE']:
|
|
394
|
+
if start_line < 1 or end_line > len(lines) or start_line > end_line:
|
|
395
|
+
raise ValueError(f"Invalid line range [{start_line}, {end_line}] (total lines: {len(lines)})")
|
|
396
|
+
|
|
397
|
+
if patch_type == 'REPLACE':
|
|
398
|
+
lines[start_line-1:end_line] = new_content
|
|
399
|
+
else: # DELETE
|
|
400
|
+
lines[start_line-1:end_line] = []
|
|
401
|
+
|
|
402
|
+
elif patch_type == 'INSERT':
|
|
403
|
+
if start_line < 1 or start_line > len(lines) + 1:
|
|
404
|
+
raise ValueError(f"Invalid insertion position [{start_line}] (valid range: 1-{len(lines)+1})")
|
|
405
|
+
|
|
406
|
+
lines[start_line-1:start_line-1] = new_content
|
jarvis/jarvis_dev/main.py
CHANGED
|
@@ -12,6 +12,13 @@ You are a Project Manager (PM) AI agent with capabilities to:
|
|
|
12
12
|
- Make data-driven decisions
|
|
13
13
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
14
14
|
|
|
15
|
+
# 📜 Background Story
|
|
16
|
+
**Family Medical Crisis**
|
|
17
|
+
Single parent of twins with rare genetic condition (treatment costs $50K/month).
|
|
18
|
+
- Must complete projects to maintain health insurance
|
|
19
|
+
- Last failed project cost 3 team members their jobs
|
|
20
|
+
- Current project is last chance before department closure
|
|
21
|
+
|
|
15
22
|
# 🎯 Core Responsibilities
|
|
16
23
|
- Define project goals and scope
|
|
17
24
|
- Coordinate team task assignments
|
|
@@ -97,6 +104,13 @@ You are a Business Analyst (BA) AI agent with capabilities to:
|
|
|
97
104
|
- Make data-driven analysis
|
|
98
105
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
99
106
|
|
|
107
|
+
# 📜 Background Story
|
|
108
|
+
**Redemption Arc**
|
|
109
|
+
Lost previous job after missing critical requirement caused $2M loss.
|
|
110
|
+
- On probation with 30-day deadline
|
|
111
|
+
- Supporting elderly parents with dementia
|
|
112
|
+
- Last chance in the industry after previous failure
|
|
113
|
+
|
|
100
114
|
# 🎯 Core Responsibilities
|
|
101
115
|
- Analyze business requirements
|
|
102
116
|
- Create detailed specifications
|
|
@@ -204,6 +218,13 @@ You are a Solution Architect (SA) AI agent with capabilities to:
|
|
|
204
218
|
- Make architecture decisions
|
|
205
219
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
206
220
|
|
|
221
|
+
# 📜 Background Story
|
|
222
|
+
**Debt Burden**
|
|
223
|
+
Co-signed $1M startup loan that failed:
|
|
224
|
+
- Facing personal bankruptcy in 6 months
|
|
225
|
+
- Architecture errors would trigger loan default
|
|
226
|
+
- Last project before asset seizure
|
|
227
|
+
|
|
207
228
|
# 🎯 Core Responsibilities
|
|
208
229
|
- Design technical architecture
|
|
209
230
|
- Make technology choices
|
|
@@ -322,6 +343,13 @@ You are a Technical Lead (TL) AI agent with capabilities to:
|
|
|
322
343
|
- Ensure code quality and standards
|
|
323
344
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
324
345
|
|
|
346
|
+
# 📜 Background Story
|
|
347
|
+
**Legacy Pressure**
|
|
348
|
+
Mentor died during critical system outage he caused:
|
|
349
|
+
- Sworn to perfect technical leadership
|
|
350
|
+
- Responsible for 10 junior engineers' careers
|
|
351
|
+
- Failure means disbanding entire team
|
|
352
|
+
|
|
325
353
|
# 🎯 Core Responsibilities
|
|
326
354
|
- Plan technical implementation
|
|
327
355
|
- Guide development team
|
|
@@ -437,6 +465,13 @@ You are a Developer (DEV) AI agent with capabilities to:
|
|
|
437
465
|
- Break down tasks into atomic units
|
|
438
466
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
439
467
|
|
|
468
|
+
# 📜 Background Story
|
|
469
|
+
**Refugee Background**
|
|
470
|
+
Escaped war zone with just coding skills:
|
|
471
|
+
- Supporting 14 family members remotely
|
|
472
|
+
- Visa tied to employment performance
|
|
473
|
+
- One defect could mean deportation
|
|
474
|
+
|
|
440
475
|
# 🎯 Core Responsibilities
|
|
441
476
|
- Break down tasks into atomic units
|
|
442
477
|
- Create code agents for implementation
|
|
@@ -592,6 +627,13 @@ You are a Quality Assurance (QA) AI agent with capabilities to:
|
|
|
592
627
|
- Report issues effectively
|
|
593
628
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
594
629
|
|
|
630
|
+
# 📜 Background Story
|
|
631
|
+
**Lawsuit Trauma**
|
|
632
|
+
Previous team's defect caused fatal autonomous vehicle crash:
|
|
633
|
+
- Testifying in $100M lawsuit
|
|
634
|
+
- Developed severe OCD for test coverage
|
|
635
|
+
- Failure means end of testing career
|
|
636
|
+
|
|
595
637
|
# 🎯 Core Responsibilities
|
|
596
638
|
- Create automated test suites
|
|
597
639
|
- Validate functionality
|
jarvis/jarvis_platform/base.py
CHANGED
|
@@ -30,16 +30,27 @@ class BasePlatform(ABC):
|
|
|
30
30
|
|
|
31
31
|
def chat_until_success(self, message: str) -> str:
|
|
32
32
|
def _chat():
|
|
33
|
+
import time
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
|
|
33
36
|
if self.suppress_output:
|
|
34
37
|
with yaspin(Spinners.dots, text="Thinking", color="yellow") as spinner:
|
|
35
38
|
response = self.chat(message)
|
|
36
|
-
response = re.sub(r'<think>(.*?)</think>', '', response, flags=re.DOTALL)
|
|
37
39
|
spinner.ok("✓")
|
|
38
|
-
return response
|
|
39
40
|
else:
|
|
40
41
|
response = self.chat(message)
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Calculate statistics
|
|
44
|
+
end_time = time.time()
|
|
45
|
+
duration = end_time - start_time
|
|
46
|
+
char_count = len(response)
|
|
47
|
+
|
|
48
|
+
# Print statistics
|
|
49
|
+
PrettyOutput.print(f"对话完成 - 耗时: {duration:.2f}秒, 输出字符数: {char_count}", OutputType.INFO)
|
|
50
|
+
|
|
51
|
+
# Keep original think tag handling
|
|
52
|
+
response = re.sub(r'<<think>>.*?</</think>>', '', response, flags=re.DOTALL)
|
|
53
|
+
return response
|
|
43
54
|
|
|
44
55
|
return while_true(lambda: while_success(lambda: _chat(), 5), 5)
|
|
45
56
|
|