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.

Files changed (39) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +12 -12
  3. jarvis/jarvis_code_agent/main.py +26 -27
  4. jarvis/jarvis_codebase/main.py +3 -3
  5. jarvis/jarvis_coder/git_utils.py +4 -4
  6. jarvis/jarvis_coder/main.py +2 -8
  7. jarvis/jarvis_coder/patch_handler.py +153 -75
  8. jarvis/jarvis_platform/main.py +2 -2
  9. jarvis/jarvis_rag/main.py +2 -2
  10. jarvis/jarvis_smart_shell/main.py +6 -4
  11. jarvis/models/kimi.py +2 -2
  12. jarvis/models/openai.py +1 -1
  13. jarvis/models/registry.py +35 -12
  14. jarvis/tools/ask_user.py +6 -3
  15. jarvis/tools/chdir.py +9 -5
  16. jarvis/tools/create_code_sub_agent.py +2 -1
  17. jarvis/tools/create_sub_agent.py +2 -1
  18. jarvis/tools/execute_code_modification.py +4 -6
  19. jarvis/tools/execute_shell.py +2 -2
  20. jarvis/tools/file_operation.py +10 -5
  21. jarvis/tools/find_files.py +119 -0
  22. jarvis/tools/generate_tool.py +27 -25
  23. jarvis/tools/methodology.py +13 -7
  24. jarvis/tools/rag.py +9 -5
  25. jarvis/tools/read_webpage.py +4 -2
  26. jarvis/tools/registry.py +25 -15
  27. jarvis/tools/search.py +18 -15
  28. jarvis/tools/select_code_files.py +2 -5
  29. jarvis/tools/thinker.py +7 -5
  30. jarvis/utils.py +53 -34
  31. {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/METADATA +9 -8
  32. jarvis_ai_assistant-0.1.101.dist-info/RECORD +51 -0
  33. jarvis/tools/codebase_qa.py +0 -72
  34. jarvis/tools/find_related_files.py +0 -86
  35. jarvis_ai_assistant-0.1.99.dist-info/RECORD +0 -52
  36. {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/LICENSE +0 -0
  37. {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/WHEEL +0 -0
  38. {jarvis_ai_assistant-0.1.99.dist-info → jarvis_ai_assistant-0.1.101.dist-info}/entry_points.txt +0 -0
  39. {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
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.99"
3
+ __version__ = "0.1.101"
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, load_env_from_file
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("~/.jarvis_methodology")
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
- load_env_from_file()
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()
@@ -1,5 +1,6 @@
1
1
  from jarvis.agent import Agent
2
- from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, load_env_from_file
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
- * Use shell commands to find patterns:
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
- Generate a detailed modification plan based on user requirements and actual code conditions.
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 codebase_search to find relevant files
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
- - codebase_search: Find relevant files
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
- load_env_from_file()
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:
@@ -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 load_env_from_file
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
- load_env_from_file()
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-codebase")
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)
@@ -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-* pattern
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-* pattern
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-* to .gitignore", OutputType.INFO)
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"):
@@ -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, load_env_from_file, while_success
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
- load_env_from_file()
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, old_code: str, new_code: str):
14
- self.old_code = old_code
15
- self.new_code = new_code
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[Tuple[str, str, str]]: Patch list, each patch is a tuple of (format, file path, patch content)
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
- ret.append(Patch(m.group(1), m.group(3)))
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 patch"""
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
- file_content = open(file_path, "r", encoding="utf-8").read()
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
- if patch.old_code == "" and patch.new_code == "":
117
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Delete file {file_path}", OutputType.INFO)
118
- file_content = ""
119
- os.system(f"git rm {file_path}")
120
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
121
- elif patch.old_code == "":
122
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Replace file {file_path} content: \n{patch.new_code}", OutputType.INFO)
123
- file_content = patch.new_code
124
- open(file_path, "w", encoding="utf-8").write(patch.new_code)
125
- os.system(f"git add {file_path}")
126
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
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
- PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: File original content: \n{patch.old_code}\nReplace with: \n{patch.new_code}", OutputType.INFO)
129
- if file_content.find(patch.old_code) == -1:
130
- PrettyOutput.print(f"File {file_path} does not contain {patch.old_code}", OutputType.WARNING)
131
- os.system(f"git reset {file_path}")
132
- os.system(f"git checkout -- {file_path}")
133
- return False
134
- else:
135
- file_content = file_content.replace(patch.old_code, patch.new_code, 1)
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
- content = open(file_path, "r", encoding="utf-8").read()
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
- 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, and current file's modification plan. The output format should be as follows:
169
- <PATCH>
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
- return False
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
- confirm = get_single_line_input("\nKeep these changes? (y/n) [y]: ").lower() or "y"
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"
@@ -1,5 +1,5 @@
1
1
  from jarvis.models.registry import PlatformRegistry
2
- from jarvis.utils import PrettyOutput, OutputType, load_env_from_file, get_multiline_input
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
- load_env_from_file()
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 load_env_from_file
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
- load_env_from_file()
140
+ init_env()
141
141
  self.root_dir = root_dir
142
142
  os.chdir(self.root_dir)
143
143