aider-ce 0.88.20__py3-none-any.whl → 0.88.38__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.
Files changed (113) hide show
  1. aider/__init__.py +1 -1
  2. aider/_version.py +2 -2
  3. aider/args.py +63 -43
  4. aider/coders/agent_coder.py +331 -79
  5. aider/coders/agent_prompts.py +3 -15
  6. aider/coders/architect_coder.py +21 -5
  7. aider/coders/base_coder.py +661 -413
  8. aider/coders/base_prompts.py +6 -3
  9. aider/coders/chat_chunks.py +39 -17
  10. aider/commands.py +79 -15
  11. aider/diffs.py +10 -9
  12. aider/exceptions.py +1 -1
  13. aider/helpers/coroutines.py +8 -0
  14. aider/helpers/requests.py +45 -0
  15. aider/history.py +5 -0
  16. aider/io.py +179 -25
  17. aider/main.py +86 -35
  18. aider/models.py +16 -8
  19. aider/queries/tree-sitter-language-pack/c-tags.scm +3 -0
  20. aider/queries/tree-sitter-language-pack/clojure-tags.scm +5 -0
  21. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +5 -0
  22. aider/queries/tree-sitter-language-pack/cpp-tags.scm +3 -0
  23. aider/queries/tree-sitter-language-pack/csharp-tags.scm +6 -0
  24. aider/queries/tree-sitter-language-pack/dart-tags.scm +5 -0
  25. aider/queries/tree-sitter-language-pack/elixir-tags.scm +5 -0
  26. aider/queries/tree-sitter-language-pack/elm-tags.scm +3 -0
  27. aider/queries/tree-sitter-language-pack/go-tags.scm +7 -0
  28. aider/queries/tree-sitter-language-pack/java-tags.scm +6 -0
  29. aider/queries/tree-sitter-language-pack/javascript-tags.scm +8 -0
  30. aider/queries/tree-sitter-language-pack/lua-tags.scm +5 -0
  31. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +3 -0
  32. aider/queries/tree-sitter-language-pack/python-tags.scm +10 -0
  33. aider/queries/tree-sitter-language-pack/r-tags.scm +6 -0
  34. aider/queries/tree-sitter-language-pack/ruby-tags.scm +5 -0
  35. aider/queries/tree-sitter-language-pack/rust-tags.scm +3 -0
  36. aider/queries/tree-sitter-language-pack/solidity-tags.scm +1 -1
  37. aider/queries/tree-sitter-language-pack/swift-tags.scm +4 -1
  38. aider/queries/tree-sitter-languages/c-tags.scm +3 -0
  39. aider/queries/tree-sitter-languages/c_sharp-tags.scm +6 -0
  40. aider/queries/tree-sitter-languages/cpp-tags.scm +3 -0
  41. aider/queries/tree-sitter-languages/dart-tags.scm +2 -1
  42. aider/queries/tree-sitter-languages/elixir-tags.scm +5 -0
  43. aider/queries/tree-sitter-languages/elm-tags.scm +3 -0
  44. aider/queries/tree-sitter-languages/fortran-tags.scm +3 -0
  45. aider/queries/tree-sitter-languages/go-tags.scm +6 -0
  46. aider/queries/tree-sitter-languages/haskell-tags.scm +2 -0
  47. aider/queries/tree-sitter-languages/java-tags.scm +6 -0
  48. aider/queries/tree-sitter-languages/javascript-tags.scm +8 -0
  49. aider/queries/tree-sitter-languages/julia-tags.scm +2 -2
  50. aider/queries/tree-sitter-languages/kotlin-tags.scm +3 -0
  51. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +6 -0
  52. aider/queries/tree-sitter-languages/php-tags.scm +6 -0
  53. aider/queries/tree-sitter-languages/python-tags.scm +10 -0
  54. aider/queries/tree-sitter-languages/ruby-tags.scm +5 -0
  55. aider/queries/tree-sitter-languages/rust-tags.scm +3 -0
  56. aider/queries/tree-sitter-languages/scala-tags.scm +2 -3
  57. aider/queries/tree-sitter-languages/typescript-tags.scm +3 -0
  58. aider/queries/tree-sitter-languages/zig-tags.scm +20 -3
  59. aider/repomap.py +71 -11
  60. aider/resources/model-metadata.json +27335 -635
  61. aider/resources/model-settings.yml +190 -0
  62. aider/scrape.py +2 -0
  63. aider/tools/__init__.py +2 -0
  64. aider/tools/command.py +84 -94
  65. aider/tools/command_interactive.py +95 -110
  66. aider/tools/delete_block.py +131 -159
  67. aider/tools/delete_line.py +97 -132
  68. aider/tools/delete_lines.py +120 -160
  69. aider/tools/extract_lines.py +288 -312
  70. aider/tools/finished.py +30 -43
  71. aider/tools/git_branch.py +107 -109
  72. aider/tools/git_diff.py +44 -56
  73. aider/tools/git_log.py +39 -53
  74. aider/tools/git_remote.py +37 -51
  75. aider/tools/git_show.py +33 -47
  76. aider/tools/git_status.py +30 -44
  77. aider/tools/grep.py +214 -242
  78. aider/tools/indent_lines.py +175 -201
  79. aider/tools/insert_block.py +220 -253
  80. aider/tools/list_changes.py +65 -80
  81. aider/tools/ls.py +64 -80
  82. aider/tools/make_editable.py +57 -73
  83. aider/tools/make_readonly.py +50 -66
  84. aider/tools/remove.py +64 -80
  85. aider/tools/replace_all.py +96 -109
  86. aider/tools/replace_line.py +118 -156
  87. aider/tools/replace_lines.py +160 -197
  88. aider/tools/replace_text.py +159 -160
  89. aider/tools/show_numbered_context.py +115 -141
  90. aider/tools/thinking.py +52 -0
  91. aider/tools/undo_change.py +78 -91
  92. aider/tools/update_todo_list.py +130 -138
  93. aider/tools/utils/base_tool.py +64 -0
  94. aider/tools/utils/output.py +118 -0
  95. aider/tools/view.py +38 -54
  96. aider/tools/view_files_matching.py +131 -134
  97. aider/tools/view_files_with_symbol.py +108 -120
  98. aider/urls.py +1 -1
  99. aider/versioncheck.py +4 -3
  100. aider/website/docs/config/adv-model-settings.md +237 -0
  101. aider/website/docs/config/agent-mode.md +36 -3
  102. aider/website/docs/config/model-aliases.md +2 -1
  103. aider/website/docs/faq.md +6 -11
  104. aider/website/docs/languages.md +2 -2
  105. aider/website/docs/more/infinite-output.md +27 -0
  106. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/METADATA +112 -70
  107. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/RECORD +112 -107
  108. aider_ce-0.88.38.dist-info/entry_points.txt +6 -0
  109. aider_ce-0.88.20.dist-info/entry_points.txt +0 -2
  110. /aider/tools/{tool_utils.py → utils/helpers.py} +0 -0
  111. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/WHEEL +0 -0
  112. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/licenses/LICENSE.txt +0 -0
  113. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/top_level.txt +0 -0
@@ -1,341 +1,317 @@
1
1
  import os
2
2
  import traceback
3
3
 
4
- from .tool_utils import generate_unified_diff_snippet
5
-
6
- schema = {
7
- "type": "function",
8
- "function": {
9
- "name": "ExtractLines",
10
- "description": "Extract lines from a source file and append them to a target file.",
11
- "parameters": {
12
- "type": "object",
13
- "properties": {
14
- "source_file_path": {"type": "string"},
15
- "target_file_path": {"type": "string"},
16
- "start_pattern": {"type": "string"},
17
- "end_pattern": {"type": "string"},
18
- "line_count": {"type": "integer"},
19
- "near_context": {"type": "string"},
20
- "occurrence": {"type": "integer", "default": 1},
21
- "dry_run": {"type": "boolean", "default": False},
4
+ from aider.tools.utils.base_tool import BaseTool
5
+ from aider.tools.utils.helpers import generate_unified_diff_snippet
6
+
7
+
8
+ class Tool(BaseTool):
9
+ NORM_NAME = "extractlines"
10
+ SCHEMA = {
11
+ "type": "function",
12
+ "function": {
13
+ "name": "ExtractLines",
14
+ "description": "Extract lines from a source file and append them to a target file.",
15
+ "parameters": {
16
+ "type": "object",
17
+ "properties": {
18
+ "source_file_path": {"type": "string"},
19
+ "target_file_path": {"type": "string"},
20
+ "start_pattern": {"type": "string"},
21
+ "end_pattern": {"type": "string"},
22
+ "line_count": {"type": "integer"},
23
+ "near_context": {"type": "string"},
24
+ "occurrence": {"type": "integer", "default": 1},
25
+ "dry_run": {"type": "boolean", "default": False},
26
+ },
27
+ "required": ["source_file_path", "target_file_path", "start_pattern"],
22
28
  },
23
- "required": ["source_file_path", "target_file_path", "start_pattern"],
24
29
  },
25
- },
26
- }
27
-
28
- # Normalized tool name for lookup
29
- NORM_NAME = "extractlines"
30
-
31
-
32
- def _execute_extract_lines(
33
- coder,
34
- source_file_path,
35
- target_file_path,
36
- start_pattern,
37
- end_pattern=None,
38
- line_count=None,
39
- near_context=None,
40
- occurrence=1,
41
- dry_run=False,
42
- ):
43
- """
44
- Extract a range of lines from a source file and move them to a target file.
45
-
46
- Parameters:
47
- - coder: The Coder instance
48
- - source_file_path: Path to the file to extract lines from
49
- - target_file_path: Path to the file to append extracted lines to (will be created if needed)
50
- - start_pattern: Pattern marking the start of the block to extract
51
- - end_pattern: Optional pattern marking the end of the block
52
- - line_count: Optional number of lines to extract (alternative to end_pattern)
53
- - near_context: Optional text nearby to help locate the correct instance of the start_pattern
54
- - occurrence: Which occurrence of the start_pattern to use (1-based index, or -1 for last)
55
- - dry_run: If True, simulate the change without modifying files
56
-
57
- Returns a result message.
58
- """
59
- try:
60
- # --- Validate Source File ---
61
- abs_source_path = coder.abs_root_path(source_file_path)
62
- rel_source_path = coder.get_rel_fname(abs_source_path)
63
-
64
- if not os.path.isfile(abs_source_path):
65
- coder.io.tool_error(f"Source file '{source_file_path}' not found")
66
- return "Error: Source file not found"
67
-
68
- if abs_source_path not in coder.abs_fnames:
69
- if abs_source_path in coder.abs_read_only_fnames:
70
- coder.io.tool_error(
71
- f"Source file '{source_file_path}' is read-only. Use MakeEditable first."
72
- )
73
- return "Error: Source file is read-only. Use MakeEditable first."
74
- else:
75
- coder.io.tool_error(f"Source file '{source_file_path}' not in context")
76
- return "Error: Source file not in context"
77
-
78
- # --- Validate Target File ---
79
- abs_target_path = coder.abs_root_path(target_file_path)
80
- rel_target_path = coder.get_rel_fname(abs_target_path)
81
- target_exists = os.path.isfile(abs_target_path)
82
- target_is_editable = abs_target_path in coder.abs_fnames
83
- target_is_readonly = abs_target_path in coder.abs_read_only_fnames
84
-
85
- if target_exists and not target_is_editable:
86
- if target_is_readonly:
87
- coder.io.tool_error(
88
- f"Target file '{target_file_path}' exists but is read-only. Use MakeEditable"
89
- " first."
90
- )
91
- return "Error: Target file exists but is read-only. Use MakeEditable first."
92
- else:
93
- # This case shouldn't happen if file exists, but handle defensively
30
+ }
31
+
32
+ @classmethod
33
+ def execute(
34
+ cls,
35
+ coder,
36
+ source_file_path,
37
+ target_file_path,
38
+ start_pattern,
39
+ end_pattern=None,
40
+ line_count=None,
41
+ near_context=None,
42
+ occurrence=1,
43
+ dry_run=False,
44
+ ):
45
+ """
46
+ Extract a range of lines from a source file and move them to a target file.
47
+
48
+ Parameters:
49
+ - coder: The Coder instance
50
+ - source_file_path: Path to the file to extract lines from
51
+ - target_file_path: Path to the file to append extracted lines to (will be created if needed)
52
+ - start_pattern: Pattern marking the start of the block to extract
53
+ - end_pattern: Optional pattern marking the end of the block
54
+ - line_count: Optional number of lines to extract (alternative to end_pattern)
55
+ - near_context: Optional text nearby to help locate the correct instance of the start_pattern
56
+ - occurrence: Which occurrence of the start_pattern to use (1-based index, or -1 for last)
57
+ - dry_run: If True, simulate the change without modifying files
58
+
59
+ Returns a result message.
60
+ """
61
+ try:
62
+ # --- Validate Source File ---
63
+ abs_source_path = coder.abs_root_path(source_file_path)
64
+ rel_source_path = coder.get_rel_fname(abs_source_path)
65
+
66
+ if not os.path.isfile(abs_source_path):
67
+ coder.io.tool_error(f"Source file '{source_file_path}' not found")
68
+ return "Error: Source file not found"
69
+
70
+ if abs_source_path not in coder.abs_fnames:
71
+ if abs_source_path in coder.abs_read_only_fnames:
72
+ coder.io.tool_error(
73
+ f"Source file '{source_file_path}' is read-only. Use MakeEditable first."
74
+ )
75
+ return "Error: Source file is read-only. Use MakeEditable first."
76
+ else:
77
+ coder.io.tool_error(f"Source file '{source_file_path}' not in context")
78
+ return "Error: Source file not in context"
79
+
80
+ # --- Validate Target File ---
81
+ abs_target_path = coder.abs_root_path(target_file_path)
82
+ rel_target_path = coder.get_rel_fname(abs_target_path)
83
+ target_exists = os.path.isfile(abs_target_path)
84
+ target_is_editable = abs_target_path in coder.abs_fnames
85
+ target_is_readonly = abs_target_path in coder.abs_read_only_fnames
86
+
87
+ if target_exists and not target_is_editable:
88
+ if target_is_readonly:
89
+ coder.io.tool_error(
90
+ f"Target file '{target_file_path}' exists but is read-only. Use"
91
+ " MakeEditable first."
92
+ )
93
+ return "Error: Target file exists but is read-only. Use MakeEditable first."
94
+ else:
95
+ # This case shouldn't happen if file exists, but handle defensively
96
+ coder.io.tool_error(
97
+ f"Target file '{target_file_path}' exists but is not in context. Add it"
98
+ " first."
99
+ )
100
+ return "Error: Target file exists but is not in context."
101
+
102
+ # --- Read Source Content ---
103
+ source_content = coder.io.read_text(abs_source_path)
104
+ if source_content is None:
94
105
  coder.io.tool_error(
95
- f"Target file '{target_file_path}' exists but is not in context. Add it first."
106
+ f"Could not read source file '{source_file_path}' before ExtractLines"
107
+ " operation."
96
108
  )
97
- return "Error: Target file exists but is not in context."
98
-
99
- # --- Read Source Content ---
100
- source_content = coder.io.read_text(abs_source_path)
101
- if source_content is None:
102
- coder.io.tool_error(
103
- f"Could not read source file '{source_file_path}' before ExtractLines operation."
104
- )
105
- return f"Error: Could not read source file '{source_file_path}'"
106
-
107
- # --- Find Extraction Range ---
108
- if end_pattern and line_count:
109
- coder.io.tool_error("Cannot specify both end_pattern and line_count")
110
- return "Error: Cannot specify both end_pattern and line_count"
111
-
112
- source_lines = source_content.splitlines()
113
- original_source_content = source_content
114
-
115
- start_pattern_line_indices = []
116
- for i, line in enumerate(source_lines):
117
- if start_pattern in line:
118
- if near_context:
119
- context_window_start = max(0, i - 5)
120
- context_window_end = min(len(source_lines), i + 6)
121
- context_block = "\n".join(source_lines[context_window_start:context_window_end])
122
- if near_context in context_block:
109
+ return f"Error: Could not read source file '{source_file_path}'"
110
+
111
+ # --- Find Extraction Range ---
112
+ if end_pattern and line_count:
113
+ coder.io.tool_error("Cannot specify both end_pattern and line_count")
114
+ return "Error: Cannot specify both end_pattern and line_count"
115
+
116
+ source_lines = source_content.splitlines()
117
+ original_source_content = source_content
118
+
119
+ start_pattern_line_indices = []
120
+ for i, line in enumerate(source_lines):
121
+ if start_pattern in line:
122
+ if near_context:
123
+ context_window_start = max(0, i - 5)
124
+ context_window_end = min(len(source_lines), i + 6)
125
+ context_block = "\n".join(
126
+ source_lines[context_window_start:context_window_end]
127
+ )
128
+ if near_context in context_block:
129
+ start_pattern_line_indices.append(i)
130
+ else:
123
131
  start_pattern_line_indices.append(i)
124
- else:
125
- start_pattern_line_indices.append(i)
126
-
127
- if not start_pattern_line_indices:
128
- err_msg = f"Start pattern '{start_pattern}' not found"
129
- if near_context:
130
- err_msg += f" near context '{near_context}'"
131
- err_msg += f" in source file '{source_file_path}'."
132
- coder.io.tool_error(err_msg)
133
- return f"Error: {err_msg}"
134
132
 
135
- num_occurrences = len(start_pattern_line_indices)
136
- try:
137
- occurrence = int(occurrence)
138
- if occurrence == -1:
139
- target_idx = num_occurrences - 1
140
- elif occurrence > 0 and occurrence <= num_occurrences:
141
- target_idx = occurrence - 1
142
- else:
143
- err_msg = (
144
- f"Occurrence number {occurrence} is out of range for start pattern"
145
- f" '{start_pattern}'. Found {num_occurrences} occurrences"
146
- )
133
+ if not start_pattern_line_indices:
134
+ err_msg = f"Start pattern '{start_pattern}' not found"
147
135
  if near_context:
148
- err_msg += f" near '{near_context}'"
149
- err_msg += f" in '{source_file_path}'."
136
+ err_msg += f" near context '{near_context}'"
137
+ err_msg += f" in source file '{source_file_path}'."
150
138
  coder.io.tool_error(err_msg)
151
139
  return f"Error: {err_msg}"
152
- except ValueError:
153
- coder.io.tool_error(f"Invalid occurrence value: '{occurrence}'. Must be an integer.")
154
- return f"Error: Invalid occurrence value '{occurrence}'"
155
140
 
156
- start_line = start_pattern_line_indices[target_idx]
157
- occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
158
-
159
- end_line = -1
160
- if end_pattern:
161
- for i in range(start_line, len(source_lines)):
162
- if end_pattern in source_lines[i]:
163
- end_line = i
164
- break
165
- if end_line == -1:
166
- err_msg = (
167
- f"End pattern '{end_pattern}' not found after {occurrence_str}start pattern"
168
- f" '{start_pattern}' (line {start_line + 1}) in '{source_file_path}'."
169
- )
170
- coder.io.tool_error(err_msg)
171
- return f"Error: {err_msg}"
172
- elif line_count:
141
+ num_occurrences = len(start_pattern_line_indices)
173
142
  try:
174
- line_count = int(line_count)
175
- if line_count <= 0:
176
- raise ValueError("Line count must be positive")
177
- end_line = min(start_line + line_count - 1, len(source_lines) - 1)
143
+ occurrence = int(occurrence)
144
+ if occurrence == -1:
145
+ target_idx = num_occurrences - 1
146
+ elif occurrence > 0 and occurrence <= num_occurrences:
147
+ target_idx = occurrence - 1
148
+ else:
149
+ err_msg = (
150
+ f"Occurrence number {occurrence} is out of range for start pattern"
151
+ f" '{start_pattern}'. Found {num_occurrences} occurrences"
152
+ )
153
+ if near_context:
154
+ err_msg += f" near '{near_context}'"
155
+ err_msg += f" in '{source_file_path}'."
156
+ coder.io.tool_error(err_msg)
157
+ return f"Error: {err_msg}"
178
158
  except ValueError:
179
159
  coder.io.tool_error(
180
- f"Invalid line_count value: '{line_count}'. Must be a positive integer."
160
+ f"Invalid occurrence value: '{occurrence}'. Must be an integer."
181
161
  )
182
- return f"Error: Invalid line_count value '{line_count}'"
183
- else:
184
- end_line = start_line # Extract just the start line if no end specified
162
+ return f"Error: Invalid occurrence value '{occurrence}'"
163
+
164
+ start_line = start_pattern_line_indices[target_idx]
165
+ occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
166
+
167
+ end_line = -1
168
+ if end_pattern:
169
+ for i in range(start_line, len(source_lines)):
170
+ if end_pattern in source_lines[i]:
171
+ end_line = i
172
+ break
173
+ if end_line == -1:
174
+ err_msg = (
175
+ f"End pattern '{end_pattern}' not found after {occurrence_str}start"
176
+ f" pattern '{start_pattern}' (line {start_line + 1}) in"
177
+ f" '{source_file_path}'."
178
+ )
179
+ coder.io.tool_error(err_msg)
180
+ return f"Error: {err_msg}"
181
+ elif line_count:
182
+ try:
183
+ line_count = int(line_count)
184
+ if line_count <= 0:
185
+ raise ValueError("Line count must be positive")
186
+ end_line = min(start_line + line_count - 1, len(source_lines) - 1)
187
+ except ValueError:
188
+ coder.io.tool_error(
189
+ f"Invalid line_count value: '{line_count}'. Must be a positive integer."
190
+ )
191
+ return f"Error: Invalid line_count value '{line_count}'"
192
+ else:
193
+ end_line = start_line # Extract just the start line if no end specified
194
+
195
+ # --- Prepare Content Changes ---
196
+ extracted_lines = source_lines[start_line : end_line + 1]
197
+ new_source_lines = source_lines[:start_line] + source_lines[end_line + 1 :]
198
+ new_source_content = "\n".join(new_source_lines)
199
+
200
+ target_content = ""
201
+ if target_exists:
202
+ target_content = coder.io.read_text(abs_target_path)
203
+ if target_content is None:
204
+ coder.io.tool_error(
205
+ f"Could not read existing target file '{target_file_path}'."
206
+ )
207
+ return f"Error: Could not read target file '{target_file_path}'"
208
+ original_target_content = target_content # For tracking
209
+
210
+ # Append extracted lines to target content, ensuring a newline if target wasn't empty
211
+ extracted_block = "\n".join(extracted_lines)
212
+ if target_content and not target_content.endswith("\n"):
213
+ target_content += "\n" # Add newline before appending if needed
214
+ new_target_content = target_content + extracted_block
215
+
216
+ # --- Generate Diffs ---
217
+ source_diff_snippet = generate_unified_diff_snippet(
218
+ original_source_content, new_source_content, rel_source_path
219
+ )
220
+ target_insertion_line = len(target_content.splitlines()) if target_content else 0
221
+ target_diff_snippet = generate_unified_diff_snippet(
222
+ original_target_content, new_target_content, rel_target_path
223
+ )
185
224
 
186
- # --- Prepare Content Changes ---
187
- extracted_lines = source_lines[start_line : end_line + 1]
188
- new_source_lines = source_lines[:start_line] + source_lines[end_line + 1 :]
189
- new_source_content = "\n".join(new_source_lines)
225
+ # --- Handle Dry Run ---
226
+ if dry_run:
227
+ num_extracted = end_line - start_line + 1
228
+ target_action = "append to" if target_exists else "create"
229
+ coder.io.tool_output(
230
+ f"Dry run: Would extract {num_extracted} lines (from {occurrence_str}start"
231
+ f" pattern '{start_pattern}') in {source_file_path} and {target_action}"
232
+ f" {target_file_path}"
233
+ )
234
+ # Provide more informative dry run response with diffs
235
+ return (
236
+ f"Dry run: Would extract {num_extracted} lines from {rel_source_path} and"
237
+ f" {target_action} {rel_target_path}.\nSource Diff"
238
+ f" (Deletion):\n{source_diff_snippet}\nTarget Diff"
239
+ f" (Insertion):\n{target_diff_snippet}"
240
+ )
190
241
 
191
- target_content = ""
192
- if target_exists:
193
- target_content = coder.io.read_text(abs_target_path)
194
- if target_content is None:
195
- coder.io.tool_error(f"Could not read existing target file '{target_file_path}'.")
196
- return f"Error: Could not read target file '{target_file_path}'"
197
- original_target_content = target_content # For tracking
242
+ # --- Apply Changes (Not Dry Run) ---
243
+ coder.io.write_text(abs_source_path, new_source_content)
244
+ coder.io.write_text(abs_target_path, new_target_content)
198
245
 
199
- # Append extracted lines to target content, ensuring a newline if target wasn't empty
200
- extracted_block = "\n".join(extracted_lines)
201
- if target_content and not target_content.endswith("\n"):
202
- target_content += "\n" # Add newline before appending if needed
203
- new_target_content = target_content + extracted_block
246
+ # --- Track Changes ---
247
+ source_change_id = "TRACKING_FAILED"
248
+ target_change_id = "TRACKING_FAILED"
249
+ try:
250
+ source_metadata = {
251
+ "start_line": start_line + 1,
252
+ "end_line": end_line + 1,
253
+ "start_pattern": start_pattern,
254
+ "end_pattern": end_pattern,
255
+ "line_count": line_count,
256
+ "near_context": near_context,
257
+ "occurrence": occurrence,
258
+ "extracted_content": extracted_block,
259
+ "target_file": rel_target_path,
260
+ }
261
+ source_change_id = coder.change_tracker.track_change(
262
+ file_path=rel_source_path,
263
+ change_type="extractlines_source",
264
+ original_content=original_source_content,
265
+ new_content=new_source_content,
266
+ metadata=source_metadata,
267
+ )
268
+ except Exception as track_e:
269
+ coder.io.tool_error(f"Error tracking source change for ExtractLines: {track_e}")
204
270
 
205
- # --- Generate Diffs ---
206
- source_diff_snippet = generate_unified_diff_snippet(
207
- original_source_content, new_source_content, rel_source_path
208
- )
209
- target_insertion_line = len(target_content.splitlines()) if target_content else 0
210
- target_diff_snippet = generate_unified_diff_snippet(
211
- original_target_content, new_target_content, rel_target_path
212
- )
271
+ try:
272
+ target_metadata = {
273
+ "insertion_line": target_insertion_line + 1,
274
+ "inserted_content": extracted_block,
275
+ "source_file": rel_source_path,
276
+ }
277
+ target_change_id = coder.change_tracker.track_change(
278
+ file_path=rel_target_path,
279
+ change_type="extractlines_target",
280
+ original_content=original_target_content,
281
+ new_content=new_target_content,
282
+ metadata=target_metadata,
283
+ )
284
+ except Exception as track_e:
285
+ coder.io.tool_error(f"Error tracking target change for ExtractLines: {track_e}")
286
+
287
+ # --- Update Context ---
288
+ coder.files_edited_by_tools.add(rel_source_path)
289
+ coder.files_edited_by_tools.add(rel_target_path)
290
+
291
+ if not target_exists:
292
+ # Add the newly created file to editable context
293
+ coder.abs_fnames.add(abs_target_path)
294
+ coder.io.tool_output(
295
+ f"✨ Created and added '{target_file_path}' to editable context."
296
+ )
213
297
 
214
- # --- Handle Dry Run ---
215
- if dry_run:
298
+ # --- Return Result ---
216
299
  num_extracted = end_line - start_line + 1
217
- target_action = "append to" if target_exists else "create"
300
+ target_action = "appended to" if target_exists else "created"
218
301
  coder.io.tool_output(
219
- f"Dry run: Would extract {num_extracted} lines (from {occurrence_str}start pattern"
220
- f" '{start_pattern}') in {source_file_path} and {target_action} {target_file_path}"
302
+ f" Extracted {num_extracted} lines from {rel_source_path} (change_id:"
303
+ f" {source_change_id}) and {target_action} {rel_target_path} (change_id:"
304
+ f" {target_change_id})"
221
305
  )
222
- # Provide more informative dry run response with diffs
306
+ # Provide more informative success response with change IDs and diffs
223
307
  return (
224
- f"Dry run: Would extract {num_extracted} lines from {rel_source_path} and"
225
- f" {target_action} {rel_target_path}.\nSource Diff"
226
- f" (Deletion):\n{source_diff_snippet}\nTarget Diff"
308
+ f"Successfully extracted {num_extracted} lines from {rel_source_path} and"
309
+ f" {target_action} {rel_target_path}.\nSource Change ID:"
310
+ f" {source_change_id}\nSource Diff (Deletion):\n{source_diff_snippet}\nTarget"
311
+ f" Change ID: {target_change_id}\nTarget Diff"
227
312
  f" (Insertion):\n{target_diff_snippet}"
228
313
  )
229
314
 
230
- # --- Apply Changes (Not Dry Run) ---
231
- coder.io.write_text(abs_source_path, new_source_content)
232
- coder.io.write_text(abs_target_path, new_target_content)
233
-
234
- # --- Track Changes ---
235
- source_change_id = "TRACKING_FAILED"
236
- target_change_id = "TRACKING_FAILED"
237
- try:
238
- source_metadata = {
239
- "start_line": start_line + 1,
240
- "end_line": end_line + 1,
241
- "start_pattern": start_pattern,
242
- "end_pattern": end_pattern,
243
- "line_count": line_count,
244
- "near_context": near_context,
245
- "occurrence": occurrence,
246
- "extracted_content": extracted_block,
247
- "target_file": rel_target_path,
248
- }
249
- source_change_id = coder.change_tracker.track_change(
250
- file_path=rel_source_path,
251
- change_type="extractlines_source",
252
- original_content=original_source_content,
253
- new_content=new_source_content,
254
- metadata=source_metadata,
255
- )
256
- except Exception as track_e:
257
- coder.io.tool_error(f"Error tracking source change for ExtractLines: {track_e}")
258
-
259
- try:
260
- target_metadata = {
261
- "insertion_line": target_insertion_line + 1,
262
- "inserted_content": extracted_block,
263
- "source_file": rel_source_path,
264
- }
265
- target_change_id = coder.change_tracker.track_change(
266
- file_path=rel_target_path,
267
- change_type="extractlines_target",
268
- original_content=original_target_content,
269
- new_content=new_target_content,
270
- metadata=target_metadata,
271
- )
272
- except Exception as track_e:
273
- coder.io.tool_error(f"Error tracking target change for ExtractLines: {track_e}")
274
-
275
- # --- Update Context ---
276
- coder.files_edited_by_tools.add(rel_source_path)
277
- coder.files_edited_by_tools.add(rel_target_path)
278
-
279
- if not target_exists:
280
- # Add the newly created file to editable context
281
- coder.abs_fnames.add(abs_target_path)
282
- coder.io.tool_output(f"✨ Created and added '{target_file_path}' to editable context.")
283
-
284
- # --- Return Result ---
285
- num_extracted = end_line - start_line + 1
286
- target_action = "appended to" if target_exists else "created"
287
- coder.io.tool_output(
288
- f"✅ Extracted {num_extracted} lines from {rel_source_path} (change_id:"
289
- f" {source_change_id}) and {target_action} {rel_target_path} (change_id:"
290
- f" {target_change_id})"
291
- )
292
- # Provide more informative success response with change IDs and diffs
293
- return (
294
- f"Successfully extracted {num_extracted} lines from {rel_source_path} and"
295
- f" {target_action} {rel_target_path}.\nSource Change ID: {source_change_id}\nSource"
296
- f" Diff (Deletion):\n{source_diff_snippet}\nTarget Change ID:"
297
- f" {target_change_id}\nTarget Diff (Insertion):\n{target_diff_snippet}"
298
- )
299
-
300
- except Exception as e:
301
- coder.io.tool_error(f"Error in ExtractLines: {str(e)}\n{traceback.format_exc()}")
302
- return f"Error: {str(e)}"
303
-
304
-
305
- def process_response(coder, params):
306
- """
307
- Process the ExtractLines tool response.
308
-
309
- Args:
310
- coder: The Coder instance
311
- params: Dictionary of parameters
312
-
313
- Returns:
314
- str: Result message
315
- """
316
- source_file_path = params.get("source_file_path")
317
- target_file_path = params.get("target_file_path")
318
- start_pattern = params.get("start_pattern")
319
- end_pattern = params.get("end_pattern")
320
- line_count = params.get("line_count")
321
- near_context = params.get("near_context")
322
- occurrence = params.get("occurrence", 1)
323
- dry_run = params.get("dry_run", False)
324
-
325
- if source_file_path and target_file_path and start_pattern:
326
- return _execute_extract_lines(
327
- coder,
328
- source_file_path,
329
- target_file_path,
330
- start_pattern,
331
- end_pattern,
332
- line_count,
333
- near_context,
334
- occurrence,
335
- dry_run,
336
- )
337
- else:
338
- return (
339
- "Error: Missing required parameters for ExtractLines (source_file_path,"
340
- " target_file_path, start_pattern)"
341
- )
315
+ except Exception as e:
316
+ coder.io.tool_error(f"Error in ExtractLines: {str(e)}\n{traceback.format_exc()}")
317
+ return f"Error: {str(e)}"