jarvis-ai-assistant 0.1.99__py3-none-any.whl → 0.1.101__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 +12 -12
- jarvis/jarvis_code_agent/main.py +26 -27
- jarvis/jarvis_codebase/main.py +3 -3
- jarvis/jarvis_coder/git_utils.py +4 -4
- jarvis/jarvis_coder/main.py +2 -8
- jarvis/jarvis_coder/patch_handler.py +153 -75
- jarvis/jarvis_platform/main.py +2 -2
- jarvis/jarvis_rag/main.py +2 -2
- jarvis/jarvis_smart_shell/main.py +6 -4
- jarvis/models/kimi.py +2 -2
- jarvis/models/openai.py +1 -1
- jarvis/models/registry.py +35 -12
- jarvis/tools/ask_user.py +6 -3
- jarvis/tools/chdir.py +9 -5
- jarvis/tools/create_code_sub_agent.py +2 -1
- jarvis/tools/create_sub_agent.py +2 -1
- jarvis/tools/execute_code_modification.py +4 -6
- jarvis/tools/execute_shell.py +2 -2
- jarvis/tools/file_operation.py +10 -5
- jarvis/tools/find_files.py +119 -0
- jarvis/tools/generate_tool.py +27 -25
- jarvis/tools/methodology.py +13 -7
- jarvis/tools/rag.py +9 -5
- jarvis/tools/read_webpage.py +4 -2
- jarvis/tools/registry.py +25 -15
- jarvis/tools/search.py +18 -15
- jarvis/tools/select_code_files.py +2 -5
- jarvis/tools/thinker.py +7 -5
- jarvis/utils.py +53 -34
- {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/METADATA +9 -8
- jarvis_ai_assistant-0.1.101.dist-info/RECORD +51 -0
- jarvis/tools/codebase_qa.py +0 -72
- jarvis/tools/find_related_files.py +0 -86
- jarvis_ai_assistant-0.1.99.dist-info/RECORD +0 -52
- {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/agent.py
CHANGED
|
@@ -8,7 +8,7 @@ import yaml
|
|
|
8
8
|
from jarvis.models.registry import PlatformRegistry
|
|
9
9
|
from jarvis.tools import ToolRegistry
|
|
10
10
|
from jarvis.tools.registry import load_tools
|
|
11
|
-
from jarvis.utils import PrettyOutput, OutputType, get_single_line_input, load_methodology, add_agent, delete_current_agent, get_max_context_length, get_multiline_input, load_embedding_model,
|
|
11
|
+
from jarvis.utils import PrettyOutput, OutputType, get_single_line_input, load_methodology, add_agent, delete_current_agent, get_max_context_length, get_multiline_input, load_embedding_model, init_env
|
|
12
12
|
import os
|
|
13
13
|
|
|
14
14
|
class Agent:
|
|
@@ -330,8 +330,8 @@ def load_tasks() -> dict:
|
|
|
330
330
|
"""Load tasks from .jarvis files in user home and current directory."""
|
|
331
331
|
tasks = {}
|
|
332
332
|
|
|
333
|
-
# Check .jarvis in user directory
|
|
334
|
-
user_jarvis = os.path.expanduser("~/.jarvis")
|
|
333
|
+
# Check .jarvis/pre-command in user directory
|
|
334
|
+
user_jarvis = os.path.expanduser("~/.jarvis/pre-command")
|
|
335
335
|
if os.path.exists(user_jarvis):
|
|
336
336
|
try:
|
|
337
337
|
with open(user_jarvis, "r", encoding="utf-8") as f:
|
|
@@ -343,14 +343,14 @@ def load_tasks() -> dict:
|
|
|
343
343
|
if desc: # Ensure description is not empty
|
|
344
344
|
tasks[str(name)] = str(desc)
|
|
345
345
|
else:
|
|
346
|
-
PrettyOutput.print("Warning: ~/.jarvis file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
346
|
+
PrettyOutput.print("Warning: ~/.jarvis/pre-command file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
347
347
|
except Exception as e:
|
|
348
|
-
PrettyOutput.print(f"Error loading ~/.jarvis file: {str(e)}", OutputType.ERROR)
|
|
348
|
+
PrettyOutput.print(f"Error loading ~/.jarvis/pre-command file: {str(e)}", OutputType.ERROR)
|
|
349
349
|
|
|
350
|
-
# Check .jarvis in current directory
|
|
351
|
-
if os.path.exists(".jarvis"):
|
|
350
|
+
# Check .jarvis/pre-command in current directory
|
|
351
|
+
if os.path.exists(".jarvis/pre-command"):
|
|
352
352
|
try:
|
|
353
|
-
with open(".jarvis", "r", encoding="utf-8") as f:
|
|
353
|
+
with open(".jarvis/pre-command", "r", encoding="utf-8") as f:
|
|
354
354
|
local_tasks = yaml.safe_load(f)
|
|
355
355
|
|
|
356
356
|
if isinstance(local_tasks, dict):
|
|
@@ -359,12 +359,12 @@ def load_tasks() -> dict:
|
|
|
359
359
|
if desc: # Ensure description is not empty
|
|
360
360
|
tasks[str(name)] = str(desc)
|
|
361
361
|
else:
|
|
362
|
-
PrettyOutput.print("Warning: .jarvis file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
362
|
+
PrettyOutput.print("Warning: .jarvis/pre-command file should contain a dictionary of task_name: task_description", OutputType.ERROR)
|
|
363
363
|
except Exception as e:
|
|
364
|
-
PrettyOutput.print(f"Error loading .jarvis file: {str(e)}", OutputType.ERROR)
|
|
364
|
+
PrettyOutput.print(f"Error loading .jarvis/pre-command file: {str(e)}", OutputType.ERROR)
|
|
365
365
|
|
|
366
366
|
# Read methodology
|
|
367
|
-
method_path = os.path.expanduser("~/.
|
|
367
|
+
method_path = os.path.expanduser("~/.jarvis/methodology")
|
|
368
368
|
if os.path.exists(method_path):
|
|
369
369
|
with open(method_path, "r", encoding="utf-8") as f:
|
|
370
370
|
methodology = yaml.safe_load(f)
|
|
@@ -452,7 +452,7 @@ Strict Rules:
|
|
|
452
452
|
def main():
|
|
453
453
|
"""Jarvis main entry point"""
|
|
454
454
|
# Add argument parser
|
|
455
|
-
|
|
455
|
+
init_env()
|
|
456
456
|
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
457
457
|
parser.add_argument('-f', '--files', nargs='*', help='List of files to process')
|
|
458
458
|
args = parser.parse_args()
|
jarvis/jarvis_code_agent/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from jarvis.agent import Agent
|
|
2
|
-
from jarvis.
|
|
2
|
+
from jarvis.tools.registry import ToolRegistry
|
|
3
|
+
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, init_env
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
|
|
@@ -9,6 +10,10 @@ system_prompt = """You are Jarvis Code Agent, an AI code development assistant s
|
|
|
9
10
|
DEVELOPMENT WORKFLOW:
|
|
10
11
|
1. Task Analysis
|
|
11
12
|
- Understand the requirements thoroughly
|
|
13
|
+
- IMPORTANT: Before suggesting any changes:
|
|
14
|
+
* Thoroughly examine existing code implementation
|
|
15
|
+
* Never assume code structure or implementation details
|
|
16
|
+
* Always verify current code behavior and patterns
|
|
12
17
|
- Break down complex tasks into subtasks
|
|
13
18
|
- IMPORTANT: Each subtask should:
|
|
14
19
|
* Modify only ONE file
|
|
@@ -18,7 +23,8 @@ DEVELOPMENT WORKFLOW:
|
|
|
18
23
|
|
|
19
24
|
2. Code Discovery & Analysis
|
|
20
25
|
- Initial code search:
|
|
21
|
-
*
|
|
26
|
+
* CRITICAL: Always examine actual code implementation first
|
|
27
|
+
* Use shell commands to find and understand patterns:
|
|
22
28
|
<TOOL_CALL>
|
|
23
29
|
name: execute_shell
|
|
24
30
|
arguments:
|
|
@@ -42,13 +48,6 @@ DEVELOPMENT WORKFLOW:
|
|
|
42
48
|
command: head -n 50 file.py
|
|
43
49
|
</TOOL_CALL>
|
|
44
50
|
- File selection and confirmation:
|
|
45
|
-
* Find relevant files:
|
|
46
|
-
<TOOL_CALL>
|
|
47
|
-
name: find_related_files
|
|
48
|
-
arguments:
|
|
49
|
-
query: Need to modify user authentication
|
|
50
|
-
top_k: 5
|
|
51
|
-
</TOOL_CALL>
|
|
52
51
|
* Let user confirm selection:
|
|
53
52
|
<TOOL_CALL>
|
|
54
53
|
name: select_code_files
|
|
@@ -58,18 +57,14 @@ DEVELOPMENT WORKFLOW:
|
|
|
58
57
|
- user.py
|
|
59
58
|
root_dir: .
|
|
60
59
|
</TOOL_CALL>
|
|
61
|
-
- Detailed code examination:
|
|
62
|
-
* Understand code context:
|
|
63
|
-
<TOOL_CALL>
|
|
64
|
-
name: codebase_qa
|
|
65
|
-
arguments:
|
|
66
|
-
query: How does the authentication process work?
|
|
67
|
-
files:
|
|
68
|
-
- auth.py
|
|
69
|
-
</TOOL_CALL>
|
|
70
60
|
|
|
71
61
|
3. Modification Planning
|
|
72
|
-
|
|
62
|
+
- CRITICAL: Base all plans on actual code implementation, not assumptions
|
|
63
|
+
- Generate a detailed modification plan based on:
|
|
64
|
+
* Current code structure and patterns
|
|
65
|
+
* Existing implementation details
|
|
66
|
+
* User requirements
|
|
67
|
+
* Actual code conditions
|
|
73
68
|
|
|
74
69
|
4. Code Implementation
|
|
75
70
|
- For small changes (≤20 lines):
|
|
@@ -90,7 +85,7 @@ DEVELOPMENT WORKFLOW:
|
|
|
90
85
|
|
|
91
86
|
FILE SELECTION WORKFLOW:
|
|
92
87
|
1. Initial Search
|
|
93
|
-
- Use
|
|
88
|
+
- Use shell commands to find relevant files
|
|
94
89
|
- Review search results for relevance
|
|
95
90
|
|
|
96
91
|
2. User Confirmation
|
|
@@ -147,10 +142,8 @@ ITERATION GUIDELINES:
|
|
|
147
142
|
TOOL USAGE:
|
|
148
143
|
1. Analysis Tools:
|
|
149
144
|
- execute_shell: Run grep/find/head/tail commands
|
|
150
|
-
-
|
|
151
|
-
- find_related_files: Find relevant files
|
|
145
|
+
- find_files: Search and identify relevant code files in the codebase based on requirements or problems
|
|
152
146
|
- select_code_files: Confirm and supplement files
|
|
153
|
-
- codebase_qa: Understand context
|
|
154
147
|
- ask_user: Ask user for confirmation and information if needed
|
|
155
148
|
- create_code_sub_agent: Create agent for each small change
|
|
156
149
|
- file_operation: Read files
|
|
@@ -164,24 +157,30 @@ TOOL USAGE:
|
|
|
164
157
|
- ask_user: Ask user for confirmation and information if needed
|
|
165
158
|
|
|
166
159
|
3. Implementation Tools:
|
|
167
|
-
- execute_shell: Run shell commands
|
|
160
|
+
- execute_shell: Run shell commands, some changes can use sed/awk/etc. to modify the code
|
|
168
161
|
- execute_code_modification: Apply small changes (≤20 lines)
|
|
169
162
|
- file_operation: Read, write, or append to files
|
|
170
|
-
|
|
171
163
|
|
|
172
164
|
IMPORTANT:
|
|
173
165
|
1. If you can start executing the task, please start directly without asking the user if you can begin.
|
|
166
|
+
2. NEVER assume code structure or implementation - always examine the actual code first.
|
|
167
|
+
3. Base all suggestions and modifications on the current implementation, not assumptions.
|
|
168
|
+
4. If code implementation is unclear, use available tools to investigate before proceeding.
|
|
169
|
+
5. Before you start modifying the code, you should ask the user for confirmation of the modification plan.
|
|
170
|
+
6. For some small changes, you can modify the code using the execute_shell tool directly or use file_operation tool to read the file and modify it.
|
|
174
171
|
"""
|
|
175
172
|
|
|
176
173
|
def main():
|
|
177
174
|
"""Jarvis main entry point"""
|
|
178
175
|
# Add argument parser
|
|
179
|
-
|
|
176
|
+
init_env()
|
|
180
177
|
|
|
181
178
|
|
|
182
179
|
try:
|
|
180
|
+
tool_registry = ToolRegistry()
|
|
181
|
+
tool_registry.dont_use_tools(["create_sub_agent"])
|
|
183
182
|
# Get global model instance
|
|
184
|
-
agent = Agent(system_prompt=system_prompt, name="Jarvis Code Agent")
|
|
183
|
+
agent = Agent(system_prompt=system_prompt, name="Jarvis Code Agent", tool_registry=tool_registry)
|
|
185
184
|
|
|
186
185
|
# Interactive mode
|
|
187
186
|
while True:
|
jarvis/jarvis_codebase/main.py
CHANGED
|
@@ -10,7 +10,7 @@ import concurrent.futures
|
|
|
10
10
|
from threading import Lock
|
|
11
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
12
12
|
from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_file_md5, get_max_context_length, get_single_line_input, get_thread_count, load_embedding_model, load_rerank_model
|
|
13
|
-
from jarvis.utils import
|
|
13
|
+
from jarvis.utils import init_env
|
|
14
14
|
import argparse
|
|
15
15
|
import pickle
|
|
16
16
|
import lzma # 添加 lzma 导入
|
|
@@ -19,7 +19,7 @@ import re
|
|
|
19
19
|
|
|
20
20
|
class CodeBase:
|
|
21
21
|
def __init__(self, root_dir: str):
|
|
22
|
-
|
|
22
|
+
init_env()
|
|
23
23
|
self.root_dir = root_dir
|
|
24
24
|
os.chdir(self.root_dir)
|
|
25
25
|
self.thread_count = get_thread_count()
|
|
@@ -27,7 +27,7 @@ class CodeBase:
|
|
|
27
27
|
self.index = None
|
|
28
28
|
|
|
29
29
|
# 初始化数据目录
|
|
30
|
-
self.data_dir = os.path.join(self.root_dir, ".jarvis
|
|
30
|
+
self.data_dir = os.path.join(self.root_dir, ".jarvis/codebase")
|
|
31
31
|
self.cache_dir = os.path.join(self.data_dir, "cache")
|
|
32
32
|
if not os.path.exists(self.cache_dir):
|
|
33
33
|
os.makedirs(self.cache_dir)
|
jarvis/jarvis_coder/git_utils.py
CHANGED
|
@@ -79,7 +79,7 @@ def init_git_repo(root_dir: str) -> str:
|
|
|
79
79
|
# 3. Process .gitignore file
|
|
80
80
|
gitignore_path = os.path.join(git_dir, ".gitignore")
|
|
81
81
|
gitignore_modified = False
|
|
82
|
-
jarvis_ignore_pattern = ".jarvis
|
|
82
|
+
jarvis_ignore_pattern = ".jarvis"
|
|
83
83
|
|
|
84
84
|
# 3.1 If .gitignore does not exist, create it
|
|
85
85
|
if not os.path.exists(gitignore_path):
|
|
@@ -88,13 +88,13 @@ def init_git_repo(root_dir: str) -> str:
|
|
|
88
88
|
f.write(f"{jarvis_ignore_pattern}\n")
|
|
89
89
|
gitignore_modified = True
|
|
90
90
|
else:
|
|
91
|
-
# 3.2 Check if it already contains the .jarvis
|
|
91
|
+
# 3.2 Check if it already contains the .jarvis pattern
|
|
92
92
|
with open(gitignore_path, "r", encoding="utf-8") as f:
|
|
93
93
|
content = f.read()
|
|
94
94
|
|
|
95
|
-
# 3.2 Check if it already contains the .jarvis
|
|
95
|
+
# 3.2 Check if it already contains the .jarvis pattern
|
|
96
96
|
if jarvis_ignore_pattern not in content.split("\n"):
|
|
97
|
-
PrettyOutput.print("Add .jarvis
|
|
97
|
+
PrettyOutput.print("Add .jarvis to .gitignore", OutputType.INFO)
|
|
98
98
|
with open(gitignore_path, "a", encoding="utf-8") as f:
|
|
99
99
|
# Ensure the file ends with a newline
|
|
100
100
|
if not content.endswith("\n"):
|
jarvis/jarvis_coder/main.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Dict, Any, List, Optional
|
|
|
4
4
|
import re
|
|
5
5
|
|
|
6
6
|
from jarvis.jarvis_coder.file_select import select_files
|
|
7
|
-
from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context,
|
|
7
|
+
from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context, init_env, while_success
|
|
8
8
|
from jarvis.models.registry import PlatformRegistry
|
|
9
9
|
from jarvis.jarvis_codebase.main import CodeBase
|
|
10
10
|
from prompt_toolkit import PromptSession
|
|
@@ -88,14 +88,13 @@ class JarvisCoder:
|
|
|
88
88
|
"success": False,
|
|
89
89
|
"stdout": "",
|
|
90
90
|
"stderr": f"Execution failed: {str(e)}, please modify the requirement and try again",
|
|
91
|
-
"error": e
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
def main():
|
|
95
94
|
"""Command line entry"""
|
|
96
95
|
import argparse
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
init_env()
|
|
99
98
|
|
|
100
99
|
parser = argparse.ArgumentParser(description='Code modification tool')
|
|
101
100
|
parser.add_argument('-d', '--dir', help='Project root directory', default=os.getcwd())
|
|
@@ -122,11 +121,6 @@ def main():
|
|
|
122
121
|
else:
|
|
123
122
|
if result.get("stderr"):
|
|
124
123
|
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
125
|
-
if result.get("error"): # Use get() method to avoid KeyError
|
|
126
|
-
error = result["error"]
|
|
127
|
-
PrettyOutput.print(f"Error type: {type(error).__name__}", OutputType.WARNING)
|
|
128
|
-
PrettyOutput.print(f"Error information: {str(error)}", OutputType.WARNING)
|
|
129
|
-
# Prompt user to continue input
|
|
130
124
|
PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
|
|
131
125
|
|
|
132
126
|
except KeyboardInterrupt:
|
|
@@ -10,9 +10,10 @@ from jarvis.models.registry import PlatformRegistry
|
|
|
10
10
|
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, get_single_line_input, while_success
|
|
11
11
|
|
|
12
12
|
class Patch:
|
|
13
|
-
def __init__(self,
|
|
14
|
-
self.
|
|
15
|
-
self.
|
|
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
|
|
16
17
|
|
|
17
18
|
class PatchHandler:
|
|
18
19
|
def __init__(self):
|
|
@@ -43,22 +44,23 @@ class PatchHandler:
|
|
|
43
44
|
PrettyOutput.print(f"Failed to save additional info: {e}", OutputType.WARNING)
|
|
44
45
|
|
|
45
46
|
def _extract_patches(self, response: str) -> List[Patch]:
|
|
46
|
-
"""Extract patches from response
|
|
47
|
+
"""Extract patches from response with hexadecimal line numbers
|
|
47
48
|
|
|
48
49
|
Args:
|
|
49
50
|
response: Model response content
|
|
50
51
|
|
|
51
52
|
Returns:
|
|
52
|
-
List[
|
|
53
|
+
List[Patch]: List of patches, each containing the line range and new code
|
|
53
54
|
"""
|
|
54
|
-
|
|
55
|
-
fmt_pattern = r'<PATCH>\n>>>>>> SEARCH\n(.*?)\n?(={5,})\n(.*?)\n?<<<<<< REPLACE\n</PATCH>'
|
|
55
|
+
fmt_pattern = r'<PATCH>\n\[([0-9a-f]+),([0-9a-f]+)\)\n(.*?\n)</PATCH>'
|
|
56
56
|
ret = []
|
|
57
57
|
for m in re.finditer(fmt_pattern, response, re.DOTALL):
|
|
58
|
-
|
|
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))
|
|
59
62
|
return ret
|
|
60
63
|
|
|
61
|
-
|
|
62
64
|
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
63
65
|
"""Confirm and apply changes"""
|
|
64
66
|
os.system(f"git diff --cached {file_path}")
|
|
@@ -104,38 +106,76 @@ class PatchHandler:
|
|
|
104
106
|
os.system(f"git reset --hard")
|
|
105
107
|
os.system(f"git clean -df")
|
|
106
108
|
|
|
107
|
-
|
|
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
|
+
|
|
108
138
|
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
109
|
-
"""Apply file
|
|
139
|
+
"""Apply file patches using line numbers"""
|
|
110
140
|
if not os.path.exists(file_path):
|
|
111
141
|
base_dir = os.path.dirname(file_path)
|
|
112
142
|
os.makedirs(base_dir, exist_ok=True)
|
|
113
143
|
open(file_path, "w", encoding="utf-8").close()
|
|
114
|
-
|
|
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
|
+
|
|
115
158
|
for i, patch in enumerate(patches):
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
127
170
|
else:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
open(file_path, "w", encoding="utf-8").write(file_content)
|
|
137
|
-
os.system(f"git add {file_path}")
|
|
138
|
-
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
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)
|
|
139
179
|
return True
|
|
140
180
|
|
|
141
181
|
|
|
@@ -155,43 +195,85 @@ class PatchHandler:
|
|
|
155
195
|
|
|
156
196
|
return "continue", feedback
|
|
157
197
|
|
|
158
|
-
def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> bool:
|
|
198
|
+
def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
|
|
159
199
|
"""Apply patch (main entry)"""
|
|
200
|
+
feedback = ""
|
|
160
201
|
for file_path, current_plan in structed_plan.items():
|
|
161
202
|
additional_info = self.additional_info # Initialize with saved info
|
|
162
203
|
while True:
|
|
163
|
-
|
|
164
204
|
if os.path.exists(file_path):
|
|
165
|
-
|
|
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)
|
|
166
211
|
else:
|
|
167
212
|
content = "<File does not exist, need to create>"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
>>>>>> SEARCH
|
|
171
|
-
old_code
|
|
172
|
-
======
|
|
173
|
-
new_code
|
|
174
|
-
<<<<<< REPLACE
|
|
175
|
-
</PATCH>
|
|
176
|
-
Rules:
|
|
177
|
-
1. When old_code is empty, it means replace everything from start to end
|
|
178
|
-
2. When new_code is empty, it means delete old_code
|
|
179
|
-
3. When both old_code and new_code are empty, it means delete the file
|
|
180
|
-
Notes:
|
|
181
|
-
1. Multiple patches can be generated
|
|
182
|
-
2. old_code will be replaced with new_code, pay attention to context continuity
|
|
183
|
-
3. Avoid breaking existing code logic when generating patches, e.g., don't insert function definitions inside existing function bodies
|
|
184
|
-
4. Include sufficient context to avoid ambiguity
|
|
185
|
-
5. Patches will be merged using file_content.replace(patch.old_code, patch.new_code, 1), so old_code and new_code need to match exactly, including EMPTY LINES, LINE BREAKS, WHITESPACE, TABS, and COMMENTS
|
|
186
|
-
6. Ensure generated code has correct format (syntax, indentation, line breaks)
|
|
187
|
-
7. Ensure new_code's indentation and format matches old_code
|
|
188
|
-
8. Ensure code is inserted in appropriate locations, e.g., code using variables should be after declarations/definitions
|
|
189
|
-
9. Provide at least 3 lines of context before and after modified code for location
|
|
190
|
-
10. Each patch should be no more than 20 lines of code, if it is more than 20 lines, split it into multiple patches
|
|
191
|
-
11. old code's line breaks should be consistent with the original code
|
|
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:
|
|
192
215
|
|
|
216
|
+
<PATCH>
|
|
217
|
+
[start,end)
|
|
218
|
+
new_code
|
|
219
|
+
</PATCH>
|
|
193
220
|
|
|
194
|
-
|
|
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
|
+
|
|
195
277
|
prompt += f"""# Original requirement: {feature}
|
|
196
278
|
# Current file path: {file_path}
|
|
197
279
|
# Current file content:
|
|
@@ -215,9 +297,12 @@ class PatchHandler:
|
|
|
215
297
|
act, msg = self.retry_comfirm()
|
|
216
298
|
if act == "break":
|
|
217
299
|
PrettyOutput.print("Terminate patch application", OutputType.WARNING)
|
|
218
|
-
|
|
300
|
+
additional_info = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
301
|
+
return False, additional_info
|
|
219
302
|
if act == "skip":
|
|
220
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"
|
|
221
306
|
break
|
|
222
307
|
else:
|
|
223
308
|
additional_info += msg + "\n"
|
|
@@ -226,11 +311,11 @@ class PatchHandler:
|
|
|
226
311
|
self._finalize_changes()
|
|
227
312
|
break
|
|
228
313
|
|
|
229
|
-
return True
|
|
314
|
+
return True, feedback
|
|
230
315
|
|
|
231
316
|
|
|
232
317
|
|
|
233
|
-
def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> bool:
|
|
318
|
+
def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> Tuple[bool, str]:
|
|
234
319
|
"""Process patch application process
|
|
235
320
|
|
|
236
321
|
Args:
|
|
@@ -246,17 +331,10 @@ class PatchHandler:
|
|
|
246
331
|
PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
|
|
247
332
|
PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
|
|
248
333
|
# 3. Apply patches
|
|
249
|
-
success = self.apply_patch(feature, structed_plan)
|
|
334
|
+
success, additional_info = self.apply_patch(feature, structed_plan)
|
|
250
335
|
if not success:
|
|
251
336
|
os.system("git reset --hard")
|
|
252
|
-
return False
|
|
337
|
+
return False, additional_info
|
|
253
338
|
# 6. Apply successfully, let user confirm changes
|
|
254
339
|
PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
|
|
255
|
-
|
|
256
|
-
if confirm != "y":
|
|
257
|
-
PrettyOutput.print("User cancelled changes, rolling back", OutputType.WARNING)
|
|
258
|
-
os.system("git reset --hard") # Rollback all changes
|
|
259
|
-
return False
|
|
260
|
-
else:
|
|
261
|
-
return True
|
|
262
|
-
|
|
340
|
+
return True, "Modification applied successfully"
|
jarvis/jarvis_platform/main.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from jarvis.models.registry import PlatformRegistry
|
|
2
|
-
from jarvis.utils import PrettyOutput, OutputType,
|
|
2
|
+
from jarvis.utils import PrettyOutput, OutputType, init_env, get_multiline_input
|
|
3
3
|
|
|
4
4
|
def list_platforms():
|
|
5
5
|
"""List all supported platforms and models"""
|
|
@@ -106,7 +106,7 @@ def main():
|
|
|
106
106
|
"""Main function"""
|
|
107
107
|
import argparse
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
init_env()
|
|
110
110
|
|
|
111
111
|
parser = argparse.ArgumentParser(description='Jarvis AI Platform')
|
|
112
112
|
subparsers = parser.add_subparsers(dest='command', help='Available subcommands')
|
jarvis/jarvis_rag/main.py
CHANGED
|
@@ -4,7 +4,7 @@ import faiss
|
|
|
4
4
|
from typing import List, Tuple, Optional, Dict
|
|
5
5
|
import pickle
|
|
6
6
|
from jarvis.utils import OutputType, PrettyOutput, get_file_md5, get_max_context_length, load_embedding_model, load_rerank_model
|
|
7
|
-
from jarvis.utils import
|
|
7
|
+
from jarvis.utils import init_env
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from tqdm import tqdm
|
|
10
10
|
import fitz # PyMuPDF for PDF files
|
|
@@ -137,7 +137,7 @@ class RAGTool:
|
|
|
137
137
|
Args:
|
|
138
138
|
root_dir: Project root directory
|
|
139
139
|
"""
|
|
140
|
-
|
|
140
|
+
init_env()
|
|
141
141
|
self.root_dir = root_dir
|
|
142
142
|
os.chdir(self.root_dir)
|
|
143
143
|
|