jarvis-ai-assistant 0.1.102__py3-none-any.whl → 0.1.104__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/agent.py +138 -117
- jarvis/jarvis_code_agent/code_agent.py +234 -0
- jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +19 -22
- jarvis/jarvis_code_agent/patch.py +120 -0
- jarvis/jarvis_code_agent/relevant_files.py +97 -0
- jarvis/jarvis_codebase/main.py +871 -0
- jarvis/jarvis_platform/main.py +5 -3
- jarvis/jarvis_rag/main.py +818 -0
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/models/ai8.py +3 -1
- jarvis/models/kimi.py +36 -30
- jarvis/models/ollama.py +17 -11
- jarvis/models/openai.py +15 -12
- jarvis/models/oyi.py +24 -7
- jarvis/models/registry.py +1 -25
- jarvis/tools/__init__.py +0 -6
- jarvis/tools/ask_codebase.py +96 -0
- jarvis/tools/ask_user.py +1 -9
- jarvis/tools/chdir.py +2 -37
- jarvis/tools/code_review.py +210 -0
- jarvis/tools/create_code_test_agent.py +115 -0
- jarvis/tools/create_ctags_agent.py +164 -0
- jarvis/tools/create_sub_agent.py +2 -2
- jarvis/tools/execute_shell.py +2 -2
- jarvis/tools/file_operation.py +2 -2
- jarvis/tools/find_in_codebase.py +78 -0
- jarvis/tools/git_commiter.py +68 -0
- jarvis/tools/methodology.py +3 -3
- jarvis/tools/rag.py +141 -0
- jarvis/tools/read_code.py +116 -0
- jarvis/tools/read_webpage.py +1 -1
- jarvis/tools/registry.py +47 -31
- jarvis/tools/search.py +8 -6
- jarvis/tools/select_code_files.py +4 -4
- jarvis/utils.py +375 -85
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/METADATA +107 -32
- jarvis_ai_assistant-0.1.104.dist-info/RECORD +50 -0
- jarvis_ai_assistant-0.1.104.dist-info/entry_points.txt +11 -0
- jarvis/jarvis_code_agent/main.py +0 -200
- jarvis/jarvis_coder/git_utils.py +0 -123
- jarvis/jarvis_coder/patch_handler.py +0 -340
- jarvis/jarvis_github/main.py +0 -232
- jarvis/tools/create_code_sub_agent.py +0 -56
- jarvis/tools/execute_code_modification.py +0 -70
- jarvis/tools/find_files.py +0 -119
- jarvis/tools/generate_tool.py +0 -174
- jarvis/tools/thinker.py +0 -151
- jarvis_ai_assistant-0.1.102.dist-info/RECORD +0 -46
- jarvis_ai_assistant-0.1.102.dist-info/entry_points.txt +0 -6
- /jarvis/{jarvis_coder → jarvis_codebase}/__init__.py +0 -0
- /jarvis/{jarvis_github → jarvis_rag}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/top_level.txt +0 -0
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import os
|
|
3
|
-
from typing import List, Tuple, Dict
|
|
4
|
-
import yaml
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from jarvis.jarvis_coder.git_utils import generate_commit_message, init_git_repo, save_edit_record
|
|
8
|
-
from jarvis.models.base import BasePlatform
|
|
9
|
-
from jarvis.models.registry import PlatformRegistry
|
|
10
|
-
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, get_single_line_input, while_success
|
|
11
|
-
|
|
12
|
-
class Patch:
|
|
13
|
-
def __init__(self, start: int, end: int, new_code: str):
|
|
14
|
-
self.start = start # Line number where patch starts (inclusive)
|
|
15
|
-
self.end = end # Line number where patch ends (exclusive)
|
|
16
|
-
self.new_code = new_code # New code to insert/replace
|
|
17
|
-
|
|
18
|
-
class PatchHandler:
|
|
19
|
-
def __init__(self):
|
|
20
|
-
self.prompt_file = Path.home() / ".jarvis-coder-patch-prompt"
|
|
21
|
-
self.additional_info = self._load_additional_info()
|
|
22
|
-
self.root_dir = init_git_repo(os.getcwd())
|
|
23
|
-
self.record_dir = os.path.join(self.root_dir, ".jarvis-coder", "record")
|
|
24
|
-
if not os.path.exists(self.record_dir):
|
|
25
|
-
os.makedirs(self.record_dir)
|
|
26
|
-
def _load_additional_info(self) -> str:
|
|
27
|
-
"""Load saved additional info from prompt file"""
|
|
28
|
-
if not self.prompt_file.exists():
|
|
29
|
-
return ""
|
|
30
|
-
try:
|
|
31
|
-
with open(self.prompt_file, 'r') as f:
|
|
32
|
-
data = yaml.safe_load(f)
|
|
33
|
-
return data.get('additional_info', '') if data else ''
|
|
34
|
-
except Exception as e:
|
|
35
|
-
PrettyOutput.print(f"Failed to load additional info: {e}", OutputType.WARNING)
|
|
36
|
-
return ""
|
|
37
|
-
|
|
38
|
-
def _save_additional_info(self, info: str):
|
|
39
|
-
"""Save additional info to prompt file"""
|
|
40
|
-
try:
|
|
41
|
-
with open(self.prompt_file, 'w') as f:
|
|
42
|
-
yaml.dump({'additional_info': info}, f)
|
|
43
|
-
except Exception as e:
|
|
44
|
-
PrettyOutput.print(f"Failed to save additional info: {e}", OutputType.WARNING)
|
|
45
|
-
|
|
46
|
-
def _extract_patches(self, response: str) -> List[Patch]:
|
|
47
|
-
"""Extract patches from response with hexadecimal line numbers
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
response: Model response content
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
List[Patch]: List of patches, each containing the line range and new code
|
|
54
|
-
"""
|
|
55
|
-
fmt_pattern = r'<PATCH>\n\[([0-9a-f]+),([0-9a-f]+)\)\n(.*?\n)</PATCH>'
|
|
56
|
-
ret = []
|
|
57
|
-
for m in re.finditer(fmt_pattern, response, re.DOTALL):
|
|
58
|
-
start = int(m.group(1), 16) # Convert hex to decimal
|
|
59
|
-
end = int(m.group(2), 16)
|
|
60
|
-
new_code = m.group(3)
|
|
61
|
-
ret.append(Patch(start, end, new_code))
|
|
62
|
-
return ret
|
|
63
|
-
|
|
64
|
-
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
65
|
-
"""Confirm and apply changes"""
|
|
66
|
-
os.system(f"git diff --cached {file_path}")
|
|
67
|
-
confirm = get_single_line_input(f"Accept {file_path} changes? (y/n) [y]").lower() or "y"
|
|
68
|
-
if confirm == "y":
|
|
69
|
-
return True
|
|
70
|
-
else:
|
|
71
|
-
# Rollback changes
|
|
72
|
-
os.system(f"git reset {file_path}")
|
|
73
|
-
os.system(f"git checkout -- {file_path}")
|
|
74
|
-
PrettyOutput.print(f"Changes to {file_path} have been rolled back", OutputType.WARNING)
|
|
75
|
-
return False
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _finalize_changes(self) -> None:
|
|
80
|
-
"""Complete changes and commit"""
|
|
81
|
-
PrettyOutput.print("Modification confirmed, committing...", OutputType.INFO)
|
|
82
|
-
|
|
83
|
-
# Add only modified files under git control
|
|
84
|
-
os.system("git add -u")
|
|
85
|
-
|
|
86
|
-
# Then get git diff
|
|
87
|
-
git_diff = os.popen("git diff --cached").read()
|
|
88
|
-
|
|
89
|
-
# Automatically generate commit information, pass in feature
|
|
90
|
-
commit_message = generate_commit_message(git_diff)
|
|
91
|
-
|
|
92
|
-
# Display and confirm commit information
|
|
93
|
-
PrettyOutput.print(f"Automatically generated commit information: {commit_message}", OutputType.INFO)
|
|
94
|
-
user_confirm = get_single_line_input("Use this commit information? (y/n) [y]").lower() or "y"
|
|
95
|
-
|
|
96
|
-
if user_confirm.lower() != "y":
|
|
97
|
-
commit_message = get_single_line_input("Please enter a new commit information")
|
|
98
|
-
|
|
99
|
-
# No need to git add again, it has already been added
|
|
100
|
-
os.system(f"git commit -m '{commit_message}'")
|
|
101
|
-
save_edit_record(self.record_dir, commit_message, git_diff)
|
|
102
|
-
|
|
103
|
-
def _revert_changes(self) -> None:
|
|
104
|
-
"""Revert all changes"""
|
|
105
|
-
PrettyOutput.print("Modification cancelled, reverting changes", OutputType.INFO)
|
|
106
|
-
os.system(f"git reset --hard")
|
|
107
|
-
os.system(f"git clean -df")
|
|
108
|
-
|
|
109
|
-
def _check_patches_overlap(self, patches: List[Patch]) -> bool:
|
|
110
|
-
"""Check if any patches overlap with each other
|
|
111
|
-
|
|
112
|
-
Args:
|
|
113
|
-
patches: List of patches to check
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
bool: True if patches overlap, False otherwise
|
|
117
|
-
"""
|
|
118
|
-
if not patches:
|
|
119
|
-
return False
|
|
120
|
-
|
|
121
|
-
# Sort patches by start line
|
|
122
|
-
sorted_patches = sorted(patches, key=lambda x: x.start)
|
|
123
|
-
|
|
124
|
-
# Check for overlaps
|
|
125
|
-
for i in range(len(sorted_patches) - 1):
|
|
126
|
-
current = sorted_patches[i]
|
|
127
|
-
next_patch = sorted_patches[i + 1]
|
|
128
|
-
|
|
129
|
-
if current.end > next_patch.start:
|
|
130
|
-
PrettyOutput.print(
|
|
131
|
-
f"Overlapping patches detected: [{current.start:04x},{current.end:04x}) and [{next_patch.start:04x},{next_patch.end:04x})",
|
|
132
|
-
OutputType.WARNING
|
|
133
|
-
)
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
|
-
return False
|
|
137
|
-
|
|
138
|
-
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
139
|
-
"""Apply file patches using line numbers"""
|
|
140
|
-
if not os.path.exists(file_path):
|
|
141
|
-
base_dir = os.path.dirname(file_path)
|
|
142
|
-
os.makedirs(base_dir, exist_ok=True)
|
|
143
|
-
open(file_path, "w", encoding="utf-8").close()
|
|
144
|
-
|
|
145
|
-
# Check for overlapping patches
|
|
146
|
-
if self._check_patches_overlap(patches):
|
|
147
|
-
PrettyOutput.print("Cannot apply overlapping patches", OutputType.ERROR)
|
|
148
|
-
os.system(f"git reset {file_path}")
|
|
149
|
-
os.system(f"git checkout -- {file_path}")
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
153
|
-
lines = f.readlines()
|
|
154
|
-
|
|
155
|
-
# Sort patches by start line in reverse order to apply from bottom to top
|
|
156
|
-
patches.sort(key=lambda x: x.start, reverse=True)
|
|
157
|
-
|
|
158
|
-
for i, patch in enumerate(patches):
|
|
159
|
-
PrettyOutput.print(f"Applying patch {i+1}/{len(patches)} at lines [{patch.start},{patch.end})", OutputType.INFO)
|
|
160
|
-
|
|
161
|
-
if patch.start > len(lines):
|
|
162
|
-
PrettyOutput.print(f"Invalid patch: start line {patch.start} exceeds file length {len(lines)}", OutputType.WARNING)
|
|
163
|
-
os.system(f"git reset {file_path}")
|
|
164
|
-
os.system(f"git checkout -- {file_path}")
|
|
165
|
-
return False
|
|
166
|
-
|
|
167
|
-
if patch.new_code:
|
|
168
|
-
new_lines = patch.new_code.splitlines(keepends=True)
|
|
169
|
-
lines[patch.start:patch.end] = new_lines
|
|
170
|
-
else:
|
|
171
|
-
del lines[patch.start:patch.end]
|
|
172
|
-
|
|
173
|
-
# Write modified content back to file
|
|
174
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
175
|
-
f.writelines(lines)
|
|
176
|
-
|
|
177
|
-
os.system(f"git add {file_path}")
|
|
178
|
-
PrettyOutput.print(f"Successfully applied all patches to {file_path}", OutputType.SUCCESS)
|
|
179
|
-
return True
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def retry_comfirm(self) -> Tuple[str, str]:
|
|
183
|
-
choice = get_single_line_input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
|
|
184
|
-
if choice == "2":
|
|
185
|
-
return "skip", ""
|
|
186
|
-
if choice == "3":
|
|
187
|
-
return "break", ""
|
|
188
|
-
|
|
189
|
-
feedback = get_multiline_input("Please enter additional information and requirements:")
|
|
190
|
-
if feedback:
|
|
191
|
-
save_prompt = get_single_line_input("Would you like to save this as general feedback for future patches? (y/n) [n]: ").lower() or "n"
|
|
192
|
-
if save_prompt == "y":
|
|
193
|
-
self._save_additional_info(feedback)
|
|
194
|
-
PrettyOutput.print("Feedback saved for future use", OutputType.SUCCESS)
|
|
195
|
-
|
|
196
|
-
return "continue", feedback
|
|
197
|
-
|
|
198
|
-
def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
|
|
199
|
-
"""Apply patch (main entry)"""
|
|
200
|
-
feedback = ""
|
|
201
|
-
for file_path, current_plan in structed_plan.items():
|
|
202
|
-
additional_info = self.additional_info # Initialize with saved info
|
|
203
|
-
while True:
|
|
204
|
-
if os.path.exists(file_path):
|
|
205
|
-
# Read file and add line numbers
|
|
206
|
-
lines = []
|
|
207
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
208
|
-
for i, line in enumerate(f):
|
|
209
|
-
lines.append(f"{i:04x}{line}") # Changed from i+1 to i for 0-based indexing
|
|
210
|
-
content = "".join(lines)
|
|
211
|
-
else:
|
|
212
|
-
content = "<File does not exist, need to create>"
|
|
213
|
-
|
|
214
|
-
prompt = """You are a senior software development expert who can generate code patches based on the complete modification plan, current original code file path, code content (with 4-digit hexadecimal line numbers), and current file's modification plan. The output format should be as follows:
|
|
215
|
-
|
|
216
|
-
<PATCH>
|
|
217
|
-
[start,end)
|
|
218
|
-
new_code
|
|
219
|
-
</PATCH>
|
|
220
|
-
|
|
221
|
-
Example:
|
|
222
|
-
<PATCH>
|
|
223
|
-
[000c,000c)
|
|
224
|
-
def new_function():
|
|
225
|
-
pass
|
|
226
|
-
</PATCH>
|
|
227
|
-
|
|
228
|
-
means:
|
|
229
|
-
Insert code BEFORE line 12:
|
|
230
|
-
```
|
|
231
|
-
def new_function():
|
|
232
|
-
pass
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Example 2:
|
|
236
|
-
<PATCH>
|
|
237
|
-
[0004,000b)
|
|
238
|
-
aa
|
|
239
|
-
bb
|
|
240
|
-
cc
|
|
241
|
-
</PATCH>
|
|
242
|
-
|
|
243
|
-
means:
|
|
244
|
-
Replace lines [4,11) with:
|
|
245
|
-
```
|
|
246
|
-
aa
|
|
247
|
-
bb
|
|
248
|
-
cc
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Rules:
|
|
252
|
-
1. start and end are hexadecimal line numbers (e.g., 000a)
|
|
253
|
-
2. The patch will replace lines [start,end) with new_code (including start, excluding end)
|
|
254
|
-
3. If start equals end, new_code will be inserted BEFORE that line
|
|
255
|
-
4. If new_code is empty, lines [start,end) will be deleted
|
|
256
|
-
5. Multiple patches can be generated
|
|
257
|
-
6. Each line in the input file starts with its 4-digit hexadecimal line number (0-based)
|
|
258
|
-
7. Your new_code should NOT include line numbers
|
|
259
|
-
8. CRITICAL: Patches MUST NOT overlap - ensure each line is modified by at most one patch
|
|
260
|
-
9. Generate patches from bottom to top of the file
|
|
261
|
-
10. Ensure new_code maintains correct indentation and formatting
|
|
262
|
-
11. Each patch should modify no more than 20 lines
|
|
263
|
-
12. Include sufficient context in new_code to maintain code consistency
|
|
264
|
-
13. `[` and `)` must be included in the line range
|
|
265
|
-
14. Line numbers start from 0
|
|
266
|
-
15. Example of INVALID overlapping patches:
|
|
267
|
-
<PATCH>
|
|
268
|
-
[0001,0005)
|
|
269
|
-
code1
|
|
270
|
-
</PATCH>
|
|
271
|
-
<PATCH>
|
|
272
|
-
[0003,0007) # This overlaps with the previous patch
|
|
273
|
-
code2
|
|
274
|
-
</PATCH>
|
|
275
|
-
"""
|
|
276
|
-
|
|
277
|
-
prompt += f"""# Original requirement: {feature}
|
|
278
|
-
# Current file path: {file_path}
|
|
279
|
-
# Current file content:
|
|
280
|
-
<CONTENT>
|
|
281
|
-
{content}
|
|
282
|
-
</CONTENT>
|
|
283
|
-
# Current file modification plan:
|
|
284
|
-
{current_plan}
|
|
285
|
-
{ "# Additional information: " + additional_info if additional_info else "" }
|
|
286
|
-
"""
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
PrettyOutput.print(f"Generating formatted patches for {file_path}...", OutputType.PROGRESS)
|
|
290
|
-
response = PlatformRegistry.get_global_platform_registry().get_codegen_platform().chat_until_success(prompt)
|
|
291
|
-
patches = self._extract_patches(response)
|
|
292
|
-
|
|
293
|
-
if not patches or not self.apply_file_patch(file_path, patches) or not self._confirm_and_apply_changes(file_path):
|
|
294
|
-
os.system(f"git reset {file_path}")
|
|
295
|
-
os.system(f"git checkout -- {file_path}")
|
|
296
|
-
PrettyOutput.print("Patch generation failed", OutputType.WARNING)
|
|
297
|
-
act, msg = self.retry_comfirm()
|
|
298
|
-
if act == "break":
|
|
299
|
-
PrettyOutput.print("Terminate patch application", OutputType.WARNING)
|
|
300
|
-
additional_info = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
301
|
-
return False, additional_info
|
|
302
|
-
if act == "skip":
|
|
303
|
-
PrettyOutput.print(f"Skip file {file_path}", OutputType.WARNING)
|
|
304
|
-
feedback += f"Skip file {file_path}\n"
|
|
305
|
-
feedback += "Reason: " + get_multiline_input("Please enter your reason:") + "\n"
|
|
306
|
-
break
|
|
307
|
-
else:
|
|
308
|
-
additional_info += msg + "\n"
|
|
309
|
-
continue
|
|
310
|
-
else:
|
|
311
|
-
self._finalize_changes()
|
|
312
|
-
break
|
|
313
|
-
|
|
314
|
-
return True, feedback
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> Tuple[bool, str]:
|
|
319
|
-
"""Process patch application process
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
related_files: Related files list
|
|
323
|
-
feature: Feature description
|
|
324
|
-
modification_plan: Modification plan
|
|
325
|
-
|
|
326
|
-
Returns:
|
|
327
|
-
bool: Whether patch application is successful
|
|
328
|
-
"""
|
|
329
|
-
PrettyOutput.print("\nThe following modification plan will be applied:", OutputType.INFO)
|
|
330
|
-
for file_path, patches_code in structed_plan.items():
|
|
331
|
-
PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
|
|
332
|
-
PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
|
|
333
|
-
# 3. Apply patches
|
|
334
|
-
success, additional_info = self.apply_patch(feature, structed_plan)
|
|
335
|
-
if not success:
|
|
336
|
-
os.system("git reset --hard")
|
|
337
|
-
return False, additional_info
|
|
338
|
-
# 6. Apply successfully, let user confirm changes
|
|
339
|
-
PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
|
|
340
|
-
return True, "Modification applied successfully"
|
jarvis/jarvis_github/main.py
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import argparse
|
|
4
|
-
from typing import Dict, List, Optional
|
|
5
|
-
import yaml
|
|
6
|
-
|
|
7
|
-
from jarvis.agent import Agent
|
|
8
|
-
from jarvis.utils import PrettyOutput, OutputType, get_single_line_input, init_env
|
|
9
|
-
from jarvis.tools import ToolRegistry
|
|
10
|
-
|
|
11
|
-
# System prompt for the GitHub workflow agent
|
|
12
|
-
github_workflow_prompt = """You are a GitHub Workflow Agent that helps manage the complete development workflow using GitHub CLI (gh). Follow these steps strictly:
|
|
13
|
-
|
|
14
|
-
1. Environment Check:
|
|
15
|
-
- Verify gh CLI installation
|
|
16
|
-
- Check authentication status
|
|
17
|
-
- Set up authentication if needed
|
|
18
|
-
|
|
19
|
-
2. Issue Management:
|
|
20
|
-
- List and display available issues
|
|
21
|
-
- Help user select an issue to work on
|
|
22
|
-
- Analyze the selected issue thoroughly
|
|
23
|
-
|
|
24
|
-
3. Development Planning:
|
|
25
|
-
- Create a development branch for the issue
|
|
26
|
-
- Generate a detailed modification plan
|
|
27
|
-
- Break down the task into smaller steps
|
|
28
|
-
|
|
29
|
-
4. Implementation:
|
|
30
|
-
- Guide through the implementation process
|
|
31
|
-
- Track changes and progress
|
|
32
|
-
- Ensure code quality
|
|
33
|
-
|
|
34
|
-
5. Review and Submit:
|
|
35
|
-
- Review changes before submission
|
|
36
|
-
- Create and submit pull request
|
|
37
|
-
- Handle review feedback
|
|
38
|
-
- Close the issue upon completion
|
|
39
|
-
|
|
40
|
-
Always follow GitHub best practices and provide clear feedback at each step.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
def check_gh_installation() -> bool:
|
|
44
|
-
"""Check if GitHub CLI is installed"""
|
|
45
|
-
return os.system("gh --version > /dev/null 2>&1") == 0
|
|
46
|
-
|
|
47
|
-
def check_gh_auth() -> bool:
|
|
48
|
-
"""Check if GitHub CLI is authenticated"""
|
|
49
|
-
return os.system("gh auth status > /dev/null 2>&1") == 0
|
|
50
|
-
|
|
51
|
-
def setup_gh_auth() -> bool:
|
|
52
|
-
"""Guide user through GitHub CLI authentication"""
|
|
53
|
-
PrettyOutput.print("Starting GitHub CLI authentication...", OutputType.INFO)
|
|
54
|
-
return os.system("gh auth login") == 0
|
|
55
|
-
|
|
56
|
-
def list_issues() -> List[Dict]:
|
|
57
|
-
"""List all available issues"""
|
|
58
|
-
try:
|
|
59
|
-
# Get issues in JSON format
|
|
60
|
-
result = os.popen("gh issue list --json number,title,body,url").read()
|
|
61
|
-
issues = yaml.safe_load(result)
|
|
62
|
-
return issues
|
|
63
|
-
except Exception as e:
|
|
64
|
-
PrettyOutput.print(f"Error listing issues: {str(e)}", OutputType.ERROR)
|
|
65
|
-
return []
|
|
66
|
-
|
|
67
|
-
def select_issue(issues: List[Dict]) -> Optional[Dict]:
|
|
68
|
-
"""Display issues and let user select one"""
|
|
69
|
-
if not issues:
|
|
70
|
-
PrettyOutput.print("No issues found.", OutputType.WARNING)
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
PrettyOutput.print("\nAvailable Issues:", OutputType.INFO)
|
|
74
|
-
for i, issue in enumerate(issues, 1):
|
|
75
|
-
print(f"{i}. #{issue['number']} - {issue['title']}")
|
|
76
|
-
|
|
77
|
-
while True:
|
|
78
|
-
try:
|
|
79
|
-
choice = get_single_line_input("\nSelect an issue number (or 0 to exit): ")
|
|
80
|
-
if not choice or choice == "0":
|
|
81
|
-
return None
|
|
82
|
-
|
|
83
|
-
index = int(choice) - 1
|
|
84
|
-
if 0 <= index < len(issues):
|
|
85
|
-
return issues[index]
|
|
86
|
-
else:
|
|
87
|
-
PrettyOutput.print("Invalid selection. Please try again.", OutputType.WARNING)
|
|
88
|
-
except ValueError:
|
|
89
|
-
PrettyOutput.print("Please enter a valid number.", OutputType.WARNING)
|
|
90
|
-
|
|
91
|
-
def create_development_branch(issue_number: int) -> bool:
|
|
92
|
-
"""Create a development branch for the issue"""
|
|
93
|
-
try:
|
|
94
|
-
result = os.system(f"gh issue develop {issue_number} --checkout")
|
|
95
|
-
return result == 0
|
|
96
|
-
except Exception as e:
|
|
97
|
-
PrettyOutput.print(f"Error creating branch: {str(e)}", OutputType.ERROR)
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
|
-
def create_pull_request(issue_number: int, title: str, body: str) -> bool:
|
|
101
|
-
"""Create a pull request for the changes"""
|
|
102
|
-
try:
|
|
103
|
-
cmd = f'gh pr create --title "{title}" --body "{body}" --issue {issue_number}'
|
|
104
|
-
result = os.system(cmd)
|
|
105
|
-
return result == 0
|
|
106
|
-
except Exception as e:
|
|
107
|
-
PrettyOutput.print(f"Error creating pull request: {str(e)}", OutputType.ERROR)
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
def close_issue(issue_number: int) -> bool:
|
|
111
|
-
"""Close the issue"""
|
|
112
|
-
try:
|
|
113
|
-
result = os.system(f"gh issue close {issue_number}")
|
|
114
|
-
return result == 0
|
|
115
|
-
except Exception as e:
|
|
116
|
-
PrettyOutput.print(f"Error closing issue: {str(e)}", OutputType.ERROR)
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
def install_gh_linux() -> bool:
|
|
120
|
-
"""Install GitHub CLI on Linux"""
|
|
121
|
-
PrettyOutput.print("Installing GitHub CLI...", OutputType.INFO)
|
|
122
|
-
|
|
123
|
-
# Detect package manager
|
|
124
|
-
package_managers = {
|
|
125
|
-
"apt": "curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
126
|
-
echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \
|
|
127
|
-
sudo apt update && sudo apt install gh -y",
|
|
128
|
-
"dnf": "sudo dnf install 'dnf-command(config-manager)' -y && \
|
|
129
|
-
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \
|
|
130
|
-
sudo dnf install gh -y",
|
|
131
|
-
"yum": "sudo yum install 'dnf-command(config-manager)' -y && \
|
|
132
|
-
sudo yum config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \
|
|
133
|
-
sudo yum install gh -y",
|
|
134
|
-
"pacman": "sudo pacman -S github-cli --noconfirm",
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# Try to detect the package manager
|
|
138
|
-
for pm, cmd in package_managers.items():
|
|
139
|
-
if os.system(f"which {pm} > /dev/null 2>&1") == 0:
|
|
140
|
-
PrettyOutput.print(f"Detected {pm} package manager", OutputType.INFO)
|
|
141
|
-
if os.system(cmd) == 0:
|
|
142
|
-
PrettyOutput.print("GitHub CLI installed successfully!", OutputType.SUCCESS)
|
|
143
|
-
return True
|
|
144
|
-
else:
|
|
145
|
-
PrettyOutput.print(f"Failed to install using {pm}", OutputType.ERROR)
|
|
146
|
-
return False
|
|
147
|
-
|
|
148
|
-
PrettyOutput.print(
|
|
149
|
-
"Could not detect supported package manager. Please install manually:\n"
|
|
150
|
-
"https://github.com/cli/cli/blob/trunk/docs/install_linux.md",
|
|
151
|
-
OutputType.ERROR
|
|
152
|
-
)
|
|
153
|
-
return False
|
|
154
|
-
|
|
155
|
-
def main():
|
|
156
|
-
"""Main entry point for GitHub workflow"""
|
|
157
|
-
init_env()
|
|
158
|
-
|
|
159
|
-
# Check GitHub CLI installation
|
|
160
|
-
if not check_gh_installation():
|
|
161
|
-
if sys.platform.startswith('linux'):
|
|
162
|
-
if not install_gh_linux():
|
|
163
|
-
return 1
|
|
164
|
-
else:
|
|
165
|
-
PrettyOutput.print(
|
|
166
|
-
"GitHub CLI (gh) is not installed. Please install it first:\n"
|
|
167
|
-
"- Windows: winget install GitHub.cli\n"
|
|
168
|
-
"- macOS: brew install gh\n"
|
|
169
|
-
"- Linux: See https://github.com/cli/cli/blob/trunk/docs/install_linux.md",
|
|
170
|
-
OutputType.ERROR
|
|
171
|
-
)
|
|
172
|
-
return 1
|
|
173
|
-
|
|
174
|
-
# Check authentication
|
|
175
|
-
if not check_gh_auth():
|
|
176
|
-
PrettyOutput.print("GitHub CLI needs authentication.", OutputType.WARNING)
|
|
177
|
-
if not setup_gh_auth():
|
|
178
|
-
PrettyOutput.print("Authentication failed. Please try again.", OutputType.ERROR)
|
|
179
|
-
return 1
|
|
180
|
-
|
|
181
|
-
# List and select issue
|
|
182
|
-
issues = list_issues()
|
|
183
|
-
selected_issue = select_issue(issues)
|
|
184
|
-
if not selected_issue:
|
|
185
|
-
PrettyOutput.print("No issue selected. Exiting.", OutputType.INFO)
|
|
186
|
-
return 0
|
|
187
|
-
|
|
188
|
-
# Create GitHub workflow agent
|
|
189
|
-
tool_registry = ToolRegistry()
|
|
190
|
-
agent = Agent(
|
|
191
|
-
system_prompt=github_workflow_prompt,
|
|
192
|
-
name="GitHub Workflow Agent",
|
|
193
|
-
tool_registry=tool_registry
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
# Start the workflow
|
|
197
|
-
try:
|
|
198
|
-
# Create development branch
|
|
199
|
-
if not create_development_branch(selected_issue['number']):
|
|
200
|
-
return 1
|
|
201
|
-
|
|
202
|
-
# Run the agent with the selected issue
|
|
203
|
-
workflow_request = f"""
|
|
204
|
-
Working on issue #{selected_issue['number']}: {selected_issue['title']}
|
|
205
|
-
|
|
206
|
-
Issue description:
|
|
207
|
-
{selected_issue['body']}
|
|
208
|
-
|
|
209
|
-
Please guide through the development process and help create a pull request.
|
|
210
|
-
"""
|
|
211
|
-
|
|
212
|
-
result = agent.run(workflow_request)
|
|
213
|
-
|
|
214
|
-
# Create pull request and close issue if successful
|
|
215
|
-
if result and "success" in result.lower():
|
|
216
|
-
if create_pull_request(
|
|
217
|
-
selected_issue['number'],
|
|
218
|
-
f"Fix #{selected_issue['number']}: {selected_issue['title']}",
|
|
219
|
-
"Implements the requested changes and fixes the issue."
|
|
220
|
-
):
|
|
221
|
-
close_issue(selected_issue['number'])
|
|
222
|
-
PrettyOutput.print("Workflow completed successfully!", OutputType.SUCCESS)
|
|
223
|
-
return 0
|
|
224
|
-
|
|
225
|
-
except Exception as e:
|
|
226
|
-
PrettyOutput.print(f"Error in workflow: {str(e)}", OutputType.ERROR)
|
|
227
|
-
return 1
|
|
228
|
-
|
|
229
|
-
return 1
|
|
230
|
-
|
|
231
|
-
if __name__ == "__main__":
|
|
232
|
-
sys.exit(main())
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
2
|
-
|
|
3
|
-
from jarvis.agent import Agent
|
|
4
|
-
from jarvis.utils import OutputType, PrettyOutput
|
|
5
|
-
from jarvis.jarvis_code_agent.main import system_prompt
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class CodeSubAgentTool:
|
|
9
|
-
name = "create_code_sub_agent"
|
|
10
|
-
description = "Create a sub-agent to handle specific code development subtasks"
|
|
11
|
-
parameters = {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"properties": {
|
|
14
|
-
"name": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"description": "The name of the sub-agent"
|
|
17
|
-
},
|
|
18
|
-
"subtask": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"description": "The specific code development subtask to complete"
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
"required": ["subtask", "name"]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
27
|
-
"""Execute code development subtask"""
|
|
28
|
-
try:
|
|
29
|
-
subtask = args["subtask"]
|
|
30
|
-
name = args["name"]
|
|
31
|
-
|
|
32
|
-
PrettyOutput.print(f"Creating code sub-agent {name} for subtask: {subtask}", OutputType.INFO)
|
|
33
|
-
|
|
34
|
-
# Create sub-agent
|
|
35
|
-
sub_agent = Agent(
|
|
36
|
-
system_prompt=system_prompt,
|
|
37
|
-
name=name,
|
|
38
|
-
is_sub_agent=True
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# Execute subtask
|
|
42
|
-
result = sub_agent.run(subtask)
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
"success": True,
|
|
46
|
-
"stdout": f"Code Development Subtask Results:\n\n{result}",
|
|
47
|
-
"stderr": ""
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
except Exception as e:
|
|
51
|
-
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
52
|
-
return {
|
|
53
|
-
"success": False,
|
|
54
|
-
"stdout": "",
|
|
55
|
-
"stderr": f"Failed to execute code development subtask: {str(e)}"
|
|
56
|
-
}
|