aider-ce 0.87.2.dev9__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 aider-ce might be problematic. Click here for more details.

Files changed (264) hide show
  1. aider/__init__.py +20 -0
  2. aider/__main__.py +4 -0
  3. aider/_version.py +34 -0
  4. aider/analytics.py +258 -0
  5. aider/args.py +1014 -0
  6. aider/args_formatter.py +228 -0
  7. aider/change_tracker.py +133 -0
  8. aider/coders/__init__.py +36 -0
  9. aider/coders/architect_coder.py +48 -0
  10. aider/coders/architect_prompts.py +40 -0
  11. aider/coders/ask_coder.py +9 -0
  12. aider/coders/ask_prompts.py +35 -0
  13. aider/coders/base_coder.py +3013 -0
  14. aider/coders/base_prompts.py +87 -0
  15. aider/coders/chat_chunks.py +64 -0
  16. aider/coders/context_coder.py +53 -0
  17. aider/coders/context_prompts.py +75 -0
  18. aider/coders/editblock_coder.py +657 -0
  19. aider/coders/editblock_fenced_coder.py +10 -0
  20. aider/coders/editblock_fenced_prompts.py +143 -0
  21. aider/coders/editblock_func_coder.py +141 -0
  22. aider/coders/editblock_func_prompts.py +27 -0
  23. aider/coders/editblock_prompts.py +177 -0
  24. aider/coders/editor_diff_fenced_coder.py +9 -0
  25. aider/coders/editor_diff_fenced_prompts.py +11 -0
  26. aider/coders/editor_editblock_coder.py +9 -0
  27. aider/coders/editor_editblock_prompts.py +21 -0
  28. aider/coders/editor_whole_coder.py +9 -0
  29. aider/coders/editor_whole_prompts.py +12 -0
  30. aider/coders/help_coder.py +16 -0
  31. aider/coders/help_prompts.py +46 -0
  32. aider/coders/navigator_coder.py +2711 -0
  33. aider/coders/navigator_legacy_prompts.py +338 -0
  34. aider/coders/navigator_prompts.py +530 -0
  35. aider/coders/patch_coder.py +706 -0
  36. aider/coders/patch_prompts.py +161 -0
  37. aider/coders/search_replace.py +757 -0
  38. aider/coders/shell.py +37 -0
  39. aider/coders/single_wholefile_func_coder.py +102 -0
  40. aider/coders/single_wholefile_func_prompts.py +27 -0
  41. aider/coders/udiff_coder.py +429 -0
  42. aider/coders/udiff_prompts.py +117 -0
  43. aider/coders/udiff_simple.py +14 -0
  44. aider/coders/udiff_simple_prompts.py +25 -0
  45. aider/coders/wholefile_coder.py +144 -0
  46. aider/coders/wholefile_func_coder.py +134 -0
  47. aider/coders/wholefile_func_prompts.py +27 -0
  48. aider/coders/wholefile_prompts.py +70 -0
  49. aider/commands.py +1946 -0
  50. aider/copypaste.py +72 -0
  51. aider/deprecated.py +126 -0
  52. aider/diffs.py +128 -0
  53. aider/dump.py +29 -0
  54. aider/editor.py +147 -0
  55. aider/exceptions.py +107 -0
  56. aider/format_settings.py +26 -0
  57. aider/gui.py +545 -0
  58. aider/help.py +163 -0
  59. aider/help_pats.py +19 -0
  60. aider/history.py +178 -0
  61. aider/io.py +1257 -0
  62. aider/linter.py +304 -0
  63. aider/llm.py +47 -0
  64. aider/main.py +1297 -0
  65. aider/mcp/__init__.py +94 -0
  66. aider/mcp/server.py +119 -0
  67. aider/mdstream.py +243 -0
  68. aider/models.py +1344 -0
  69. aider/onboarding.py +428 -0
  70. aider/openrouter.py +129 -0
  71. aider/prompts.py +56 -0
  72. aider/queries/tree-sitter-language-pack/README.md +7 -0
  73. aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  74. aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  75. aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  76. aider/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  77. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. aider/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  91. aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  92. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  93. aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  94. aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  95. aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  96. aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  97. aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  98. aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  99. aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  100. aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  101. aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  102. aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  103. aider/queries/tree-sitter-languages/README.md +23 -0
  104. aider/queries/tree-sitter-languages/c-tags.scm +9 -0
  105. aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  106. aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  107. aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
  108. aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  109. aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  110. aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
  111. aider/queries/tree-sitter-languages/go-tags.scm +30 -0
  112. aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  113. aider/queries/tree-sitter-languages/java-tags.scm +20 -0
  114. aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  115. aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  116. aider/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  117. aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  118. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  119. aider/queries/tree-sitter-languages/php-tags.scm +26 -0
  120. aider/queries/tree-sitter-languages/python-tags.scm +12 -0
  121. aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
  122. aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  123. aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
  124. aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
  125. aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  126. aider/reasoning_tags.py +82 -0
  127. aider/repo.py +621 -0
  128. aider/repomap.py +988 -0
  129. aider/report.py +200 -0
  130. aider/resources/__init__.py +3 -0
  131. aider/resources/model-metadata.json +699 -0
  132. aider/resources/model-settings.yml +2046 -0
  133. aider/run_cmd.py +132 -0
  134. aider/scrape.py +284 -0
  135. aider/sendchat.py +61 -0
  136. aider/special.py +203 -0
  137. aider/tools/__init__.py +26 -0
  138. aider/tools/command.py +58 -0
  139. aider/tools/command_interactive.py +53 -0
  140. aider/tools/delete_block.py +120 -0
  141. aider/tools/delete_line.py +112 -0
  142. aider/tools/delete_lines.py +137 -0
  143. aider/tools/extract_lines.py +276 -0
  144. aider/tools/grep.py +171 -0
  145. aider/tools/indent_lines.py +155 -0
  146. aider/tools/insert_block.py +211 -0
  147. aider/tools/list_changes.py +51 -0
  148. aider/tools/ls.py +49 -0
  149. aider/tools/make_editable.py +46 -0
  150. aider/tools/make_readonly.py +29 -0
  151. aider/tools/remove.py +48 -0
  152. aider/tools/replace_all.py +77 -0
  153. aider/tools/replace_line.py +125 -0
  154. aider/tools/replace_lines.py +160 -0
  155. aider/tools/replace_text.py +125 -0
  156. aider/tools/show_numbered_context.py +101 -0
  157. aider/tools/tool_utils.py +313 -0
  158. aider/tools/undo_change.py +60 -0
  159. aider/tools/view.py +13 -0
  160. aider/tools/view_files_at_glob.py +65 -0
  161. aider/tools/view_files_matching.py +103 -0
  162. aider/tools/view_files_with_symbol.py +121 -0
  163. aider/urls.py +17 -0
  164. aider/utils.py +454 -0
  165. aider/versioncheck.py +113 -0
  166. aider/voice.py +187 -0
  167. aider/waiting.py +221 -0
  168. aider/watch.py +318 -0
  169. aider/watch_prompts.py +12 -0
  170. aider/website/Gemfile +8 -0
  171. aider/website/_includes/blame.md +162 -0
  172. aider/website/_includes/get-started.md +22 -0
  173. aider/website/_includes/help-tip.md +5 -0
  174. aider/website/_includes/help.md +24 -0
  175. aider/website/_includes/install.md +5 -0
  176. aider/website/_includes/keys.md +4 -0
  177. aider/website/_includes/model-warnings.md +67 -0
  178. aider/website/_includes/multi-line.md +22 -0
  179. aider/website/_includes/python-m-aider.md +5 -0
  180. aider/website/_includes/recording.css +228 -0
  181. aider/website/_includes/recording.md +34 -0
  182. aider/website/_includes/replit-pipx.md +9 -0
  183. aider/website/_includes/works-best.md +1 -0
  184. aider/website/_sass/custom/custom.scss +103 -0
  185. aider/website/docs/config/adv-model-settings.md +2260 -0
  186. aider/website/docs/config/aider_conf.md +548 -0
  187. aider/website/docs/config/api-keys.md +90 -0
  188. aider/website/docs/config/dotenv.md +493 -0
  189. aider/website/docs/config/editor.md +127 -0
  190. aider/website/docs/config/mcp.md +95 -0
  191. aider/website/docs/config/model-aliases.md +104 -0
  192. aider/website/docs/config/options.md +890 -0
  193. aider/website/docs/config/reasoning.md +210 -0
  194. aider/website/docs/config.md +44 -0
  195. aider/website/docs/faq.md +384 -0
  196. aider/website/docs/git.md +76 -0
  197. aider/website/docs/index.md +47 -0
  198. aider/website/docs/install/codespaces.md +39 -0
  199. aider/website/docs/install/docker.md +57 -0
  200. aider/website/docs/install/optional.md +100 -0
  201. aider/website/docs/install/replit.md +8 -0
  202. aider/website/docs/install.md +115 -0
  203. aider/website/docs/languages.md +264 -0
  204. aider/website/docs/legal/contributor-agreement.md +111 -0
  205. aider/website/docs/legal/privacy.md +104 -0
  206. aider/website/docs/llms/anthropic.md +77 -0
  207. aider/website/docs/llms/azure.md +48 -0
  208. aider/website/docs/llms/bedrock.md +132 -0
  209. aider/website/docs/llms/cohere.md +34 -0
  210. aider/website/docs/llms/deepseek.md +32 -0
  211. aider/website/docs/llms/gemini.md +49 -0
  212. aider/website/docs/llms/github.md +111 -0
  213. aider/website/docs/llms/groq.md +36 -0
  214. aider/website/docs/llms/lm-studio.md +39 -0
  215. aider/website/docs/llms/ollama.md +75 -0
  216. aider/website/docs/llms/openai-compat.md +39 -0
  217. aider/website/docs/llms/openai.md +58 -0
  218. aider/website/docs/llms/openrouter.md +78 -0
  219. aider/website/docs/llms/other.md +111 -0
  220. aider/website/docs/llms/vertex.md +50 -0
  221. aider/website/docs/llms/warnings.md +10 -0
  222. aider/website/docs/llms/xai.md +53 -0
  223. aider/website/docs/llms.md +54 -0
  224. aider/website/docs/more/analytics.md +127 -0
  225. aider/website/docs/more/edit-formats.md +116 -0
  226. aider/website/docs/more/infinite-output.md +159 -0
  227. aider/website/docs/more-info.md +8 -0
  228. aider/website/docs/recordings/auto-accept-architect.md +31 -0
  229. aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  230. aider/website/docs/recordings/index.md +21 -0
  231. aider/website/docs/recordings/model-accepts-settings.md +69 -0
  232. aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  233. aider/website/docs/repomap.md +112 -0
  234. aider/website/docs/scripting.md +100 -0
  235. aider/website/docs/troubleshooting/aider-not-found.md +24 -0
  236. aider/website/docs/troubleshooting/edit-errors.md +76 -0
  237. aider/website/docs/troubleshooting/imports.md +62 -0
  238. aider/website/docs/troubleshooting/models-and-keys.md +54 -0
  239. aider/website/docs/troubleshooting/support.md +79 -0
  240. aider/website/docs/troubleshooting/token-limits.md +96 -0
  241. aider/website/docs/troubleshooting/warnings.md +12 -0
  242. aider/website/docs/troubleshooting.md +11 -0
  243. aider/website/docs/usage/browser.md +57 -0
  244. aider/website/docs/usage/caching.md +49 -0
  245. aider/website/docs/usage/commands.md +133 -0
  246. aider/website/docs/usage/conventions.md +119 -0
  247. aider/website/docs/usage/copypaste.md +121 -0
  248. aider/website/docs/usage/images-urls.md +48 -0
  249. aider/website/docs/usage/lint-test.md +118 -0
  250. aider/website/docs/usage/modes.md +211 -0
  251. aider/website/docs/usage/not-code.md +179 -0
  252. aider/website/docs/usage/notifications.md +87 -0
  253. aider/website/docs/usage/tips.md +79 -0
  254. aider/website/docs/usage/tutorials.md +30 -0
  255. aider/website/docs/usage/voice.md +121 -0
  256. aider/website/docs/usage/watch.md +294 -0
  257. aider/website/docs/usage.md +102 -0
  258. aider/website/share/index.md +101 -0
  259. aider_ce-0.87.2.dev9.dist-info/METADATA +543 -0
  260. aider_ce-0.87.2.dev9.dist-info/RECORD +264 -0
  261. aider_ce-0.87.2.dev9.dist-info/WHEEL +5 -0
  262. aider_ce-0.87.2.dev9.dist-info/entry_points.txt +3 -0
  263. aider_ce-0.87.2.dev9.dist-info/licenses/LICENSE.txt +202 -0
  264. aider_ce-0.87.2.dev9.dist-info/top_level.txt +1 -0
@@ -0,0 +1,211 @@
1
+ import re
2
+ import traceback
3
+
4
+ from .tool_utils import (
5
+ ToolError,
6
+ apply_change,
7
+ find_pattern_indices,
8
+ format_tool_result,
9
+ generate_unified_diff_snippet,
10
+ handle_tool_error,
11
+ select_occurrence_index,
12
+ validate_file_for_edit,
13
+ )
14
+
15
+
16
+ def _execute_insert_block(
17
+ coder,
18
+ file_path,
19
+ content,
20
+ after_pattern=None,
21
+ before_pattern=None,
22
+ occurrence=1,
23
+ change_id=None,
24
+ dry_run=False,
25
+ position=None,
26
+ auto_indent=True,
27
+ use_regex=False,
28
+ ):
29
+ """
30
+ Insert a block of text after or before a specified pattern using utility functions.
31
+
32
+ Args:
33
+ coder: The coder instance
34
+ file_path: Path to the file to modify
35
+ content: The content to insert
36
+ after_pattern: Pattern to insert after (mutually exclusive with before_pattern and position)
37
+ before_pattern: Pattern to insert before (mutually exclusive with after_pattern and position)
38
+ occurrence: Which occurrence of the pattern to use (1-based, or -1 for last)
39
+ change_id: Optional ID for tracking changes
40
+ dry_run: If True, only simulate the change
41
+ position: Special position like "start_of_file" or "end_of_file"
42
+ auto_indent: If True, automatically adjust indentation of inserted content
43
+ use_regex: If True, treat patterns as regular expressions
44
+ """
45
+ tool_name = "InsertBlock"
46
+ try:
47
+ # 1. Validate parameters
48
+ if sum(x is not None for x in [after_pattern, before_pattern, position]) != 1:
49
+ raise ToolError(
50
+ "Must specify exactly one of: after_pattern, before_pattern, or position"
51
+ )
52
+
53
+ # 2. Validate file and get content
54
+ abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
55
+ lines = original_content.splitlines()
56
+
57
+ # Handle empty files
58
+ if not lines:
59
+ lines = [""]
60
+
61
+ # 3. Determine insertion point
62
+ insertion_line_idx = 0
63
+ pattern_type = ""
64
+ pattern_desc = ""
65
+ occurrence_str = ""
66
+
67
+ if position:
68
+ # Handle special positions
69
+ if position == "start_of_file":
70
+ insertion_line_idx = 0
71
+ pattern_type = "at start of"
72
+ elif position == "end_of_file":
73
+ insertion_line_idx = len(lines)
74
+ pattern_type = "at end of"
75
+ else:
76
+ raise ToolError(
77
+ f"Invalid position: '{position}'. Valid values are 'start_of_file' or"
78
+ " 'end_of_file'"
79
+ )
80
+ else:
81
+ # Handle pattern-based insertion
82
+ pattern = after_pattern if after_pattern else before_pattern
83
+ pattern_type = "after" if after_pattern else "before"
84
+ pattern_desc = f"Pattern '{pattern}'"
85
+
86
+ # Find pattern matches
87
+ pattern_line_indices = find_pattern_indices(lines, pattern, use_regex=use_regex)
88
+
89
+ # Select the target occurrence
90
+ target_line_idx = select_occurrence_index(
91
+ pattern_line_indices, occurrence, pattern_desc
92
+ )
93
+
94
+ # Determine insertion point
95
+ insertion_line_idx = target_line_idx
96
+ if pattern_type == "after":
97
+ insertion_line_idx += 1 # Insert on the line *after* the matched line
98
+
99
+ # Format occurrence info for output
100
+ num_occurrences = len(pattern_line_indices)
101
+ occurrence_str = f"occurrence {occurrence} of " if num_occurrences > 1 else ""
102
+
103
+ # 4. Handle indentation if requested
104
+ content_lines = content.splitlines()
105
+
106
+ if auto_indent and content_lines:
107
+ # Determine base indentation level
108
+ base_indent = ""
109
+ if insertion_line_idx > 0 and lines:
110
+ # Use indentation from the line before insertion point
111
+ reference_line_idx = min(insertion_line_idx - 1, len(lines) - 1)
112
+ reference_line = lines[reference_line_idx]
113
+ base_indent = re.match(r"^(\s*)", reference_line).group(1)
114
+
115
+ # Apply indentation to content lines, preserving relative indentation
116
+ if content_lines:
117
+ # Find minimum indentation in content to preserve relative indentation
118
+ content_indents = [
119
+ len(re.match(r"^(\s*)", line).group(1))
120
+ for line in content_lines
121
+ if line.strip()
122
+ ]
123
+ min_content_indent = min(content_indents) if content_indents else 0
124
+
125
+ # Apply base indentation while preserving relative indentation
126
+ indented_content_lines = []
127
+ for line in content_lines:
128
+ if not line.strip(): # Empty or whitespace-only line
129
+ indented_content_lines.append("")
130
+ else:
131
+ # Remove existing indentation and add new base indentation
132
+ stripped_line = (
133
+ line[min_content_indent:] if min_content_indent <= len(line) else line
134
+ )
135
+ indented_content_lines.append(base_indent + stripped_line)
136
+
137
+ content_lines = indented_content_lines
138
+
139
+ # 5. Prepare the insertion
140
+ new_lines = lines[:insertion_line_idx] + content_lines + lines[insertion_line_idx:]
141
+ new_content = "\n".join(new_lines)
142
+
143
+ if original_content == new_content:
144
+ coder.io.tool_warning("No changes made: insertion would not change file")
145
+ return "Warning: No changes made (insertion would not change file)"
146
+
147
+ # 6. Generate diff for feedback
148
+ diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
149
+
150
+ # 7. Handle dry run
151
+ if dry_run:
152
+ if position:
153
+ dry_run_message = f"Dry run: Would insert block {pattern_type} {file_path}."
154
+ else:
155
+ dry_run_message = (
156
+ f"Dry run: Would insert block {pattern_type} {occurrence_str}pattern"
157
+ f" '{pattern}' in {file_path} at line {insertion_line_idx + 1}."
158
+ )
159
+ return format_tool_result(
160
+ coder,
161
+ tool_name,
162
+ "",
163
+ dry_run=True,
164
+ dry_run_message=dry_run_message,
165
+ diff_snippet=diff_snippet,
166
+ )
167
+
168
+ # 8. Apply Change (Not dry run)
169
+ metadata = {
170
+ "insertion_line_idx": insertion_line_idx,
171
+ "after_pattern": after_pattern,
172
+ "before_pattern": before_pattern,
173
+ "position": position,
174
+ "occurrence": occurrence,
175
+ "content": content,
176
+ "auto_indent": auto_indent,
177
+ "use_regex": use_regex,
178
+ }
179
+ final_change_id = apply_change(
180
+ coder,
181
+ abs_path,
182
+ rel_path,
183
+ original_content,
184
+ new_content,
185
+ "insertblock",
186
+ metadata,
187
+ change_id,
188
+ )
189
+
190
+ # 9. Format and return result
191
+ if position:
192
+ success_message = f"Inserted block {pattern_type} {file_path}"
193
+ else:
194
+ success_message = (
195
+ f"Inserted block {pattern_type} {occurrence_str}pattern in {file_path} at line"
196
+ f" {insertion_line_idx + 1}"
197
+ )
198
+
199
+ return format_tool_result(
200
+ coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
201
+ )
202
+
203
+ except ToolError as e:
204
+ # Handle errors raised by utility functions (expected errors)
205
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
206
+
207
+ except Exception as e:
208
+ coder.io.tool_error(
209
+ f"Error in InsertBlock: {str(e)}\n{traceback.format_exc()}"
210
+ ) # Add traceback
211
+ return f"Error: {str(e)}"
@@ -0,0 +1,51 @@
1
+ import traceback
2
+ from datetime import datetime
3
+
4
+
5
+ def _execute_list_changes(coder, file_path=None, limit=10):
6
+ """
7
+ List recent changes made to files.
8
+
9
+ Parameters:
10
+ - coder: The Coder instance
11
+ - file_path: Optional path to filter changes by file
12
+ - limit: Maximum number of changes to list
13
+
14
+ Returns a formatted list of changes.
15
+ """
16
+ try:
17
+ # If file_path is specified, get the absolute path
18
+ rel_file_path = None
19
+ if file_path:
20
+ abs_path = coder.abs_root_path(file_path)
21
+ rel_file_path = coder.get_rel_fname(abs_path)
22
+
23
+ # Get the list of changes
24
+ changes = coder.change_tracker.list_changes(rel_file_path, limit)
25
+
26
+ if not changes:
27
+ if file_path:
28
+ return f"No changes found for file '{file_path}'"
29
+ else:
30
+ return "No changes have been made yet"
31
+
32
+ # Format the changes into a readable list
33
+ result = "Recent changes:\n"
34
+ for i, change in enumerate(changes):
35
+ change_time = datetime.fromtimestamp(change["timestamp"]).strftime("%H:%M:%S")
36
+ change_type = change["type"]
37
+ file_path = change["file_path"]
38
+ change_id = change["id"]
39
+
40
+ result += (
41
+ f"{i + 1}. [{change_id}] {change_time} - {change_type.upper()} on {file_path}\n"
42
+ )
43
+
44
+ coder.io.tool_output(result) # Also print to console for user
45
+ return result
46
+
47
+ except Exception as e:
48
+ coder.io.tool_error(
49
+ f"Error in ListChanges: {str(e)}\n{traceback.format_exc()}"
50
+ ) # Add traceback
51
+ return f"Error: {str(e)}"
aider/tools/ls.py ADDED
@@ -0,0 +1,49 @@
1
+ import os
2
+
3
+
4
+ def execute_ls(coder, dir_path):
5
+ """
6
+ List files in directory and optionally add some to context.
7
+
8
+ This provides information about the structure of the codebase,
9
+ similar to how a developer would explore directories.
10
+ """
11
+ try:
12
+ # Make the path relative to root if it's absolute
13
+ if dir_path.startswith("/"):
14
+ rel_dir = os.path.relpath(dir_path, coder.root)
15
+ else:
16
+ rel_dir = dir_path
17
+
18
+ # Get absolute path
19
+ abs_dir = coder.abs_root_path(rel_dir)
20
+
21
+ # Check if path exists
22
+ if not os.path.exists(abs_dir):
23
+ coder.io.tool_output(f"⚠️ Directory '{dir_path}' not found")
24
+ return "Directory not found"
25
+
26
+ # Get directory contents
27
+ contents = []
28
+ try:
29
+ with os.scandir(abs_dir) as entries:
30
+ for entry in entries:
31
+ if entry.is_file() and not entry.name.startswith("."):
32
+ rel_path = os.path.join(rel_dir, entry.name)
33
+ contents.append(rel_path)
34
+ except NotADirectoryError:
35
+ # If it's a file, just return the file
36
+ contents = [rel_dir]
37
+
38
+ if contents:
39
+ coder.io.tool_output(f"📋 Listed {len(contents)} file(s) in '{dir_path}'")
40
+ if len(contents) > 10:
41
+ return f"Found {len(contents)} files: {', '.join(contents[:10])}..."
42
+ else:
43
+ return f"Found {len(contents)} files: {', '.join(contents)}"
44
+ else:
45
+ coder.io.tool_output(f"📋 No files found in '{dir_path}'")
46
+ return "No files found in directory"
47
+ except Exception as e:
48
+ coder.io.tool_error(f"Error in ls: {str(e)}")
49
+ return f"Error: {str(e)}"
@@ -0,0 +1,46 @@
1
+ import os
2
+
3
+
4
+ # Keep the underscore prefix as this function is primarily for internal coder use
5
+ def _execute_make_editable(coder, file_path):
6
+ """
7
+ Convert a read-only file to an editable file.
8
+
9
+ This allows the LLM to upgrade a file from read-only to editable
10
+ when it determines it needs to make changes to that file.
11
+ """
12
+ try:
13
+ # Get absolute path
14
+ abs_path = coder.abs_root_path(file_path)
15
+
16
+ # Check if file is already editable
17
+ if abs_path in coder.abs_fnames:
18
+ coder.io.tool_output(f"📝 File '{file_path}' is already editable")
19
+ return "File is already editable"
20
+
21
+ # Check if file exists on disk
22
+ if not os.path.isfile(abs_path):
23
+ coder.io.tool_output(f"⚠️ File '{file_path}' not found")
24
+ return "Error: File not found"
25
+
26
+ # File exists, is not editable, might be read-only or not in context yet
27
+ was_read_only = False
28
+ if abs_path in coder.abs_read_only_fnames:
29
+ coder.abs_read_only_fnames.remove(abs_path)
30
+ was_read_only = True
31
+
32
+ # Add to editable files
33
+ coder.abs_fnames.add(abs_path)
34
+
35
+ if was_read_only:
36
+ coder.io.tool_output(f"📝 Moved '{file_path}' from read-only to editable")
37
+ return "File is now editable (moved from read-only)"
38
+ else:
39
+ # File was not previously in context at all
40
+ coder.io.tool_output(f"📝 Added '{file_path}' directly to editable context")
41
+ # Track if added during exploration? Maybe not needed for direct MakeEditable.
42
+ # coder.files_added_in_exploration.add(rel_path) # Consider if needed
43
+ return "File is now editable (added directly)"
44
+ except Exception as e:
45
+ coder.io.tool_error(f"Error in MakeEditable for '{file_path}': {str(e)}")
46
+ return f"Error: {str(e)}"
@@ -0,0 +1,29 @@
1
+ def _execute_make_readonly(coder, file_path):
2
+ """
3
+ Convert an editable file to a read-only file.
4
+
5
+ This allows the LLM to downgrade a file from editable to read-only
6
+ when it determines it no longer needs to make changes to that file.
7
+ """
8
+ try:
9
+ # Get absolute path
10
+ abs_path = coder.abs_root_path(file_path)
11
+
12
+ # Check if file is in editable context
13
+ if abs_path not in coder.abs_fnames:
14
+ if abs_path in coder.abs_read_only_fnames:
15
+ coder.io.tool_output(f"📚 File '{file_path}' is already read-only")
16
+ return "File is already read-only"
17
+ else:
18
+ coder.io.tool_output(f"⚠️ File '{file_path}' not in context")
19
+ return "File not in context"
20
+
21
+ # Move from editable to read-only
22
+ coder.abs_fnames.remove(abs_path)
23
+ coder.abs_read_only_fnames.add(abs_path)
24
+
25
+ coder.io.tool_output(f"📚 Made '{file_path}' read-only")
26
+ return "File is now read-only"
27
+ except Exception as e:
28
+ coder.io.tool_error(f"Error making file read-only: {str(e)}")
29
+ return f"Error: {str(e)}"
aider/tools/remove.py ADDED
@@ -0,0 +1,48 @@
1
+ import time
2
+
3
+
4
+ def _execute_remove(coder, file_path):
5
+ """
6
+ Explicitly remove a file from context.
7
+
8
+ This allows the LLM to clean up its context when files are no
9
+ longer needed, keeping the context focused and efficient.
10
+ """
11
+ try:
12
+ # Get absolute path
13
+ abs_path = coder.abs_root_path(file_path)
14
+ rel_path = coder.get_rel_fname(abs_path)
15
+
16
+ # Check if file is in context (either editable or read-only)
17
+ removed = False
18
+ if abs_path in coder.abs_fnames:
19
+ # Don't remove if it's the last editable file and there are no read-only files
20
+ if len(coder.abs_fnames) <= 1 and not coder.abs_read_only_fnames:
21
+ coder.io.tool_output(
22
+ f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
23
+ )
24
+ return "Cannot remove - last file in context"
25
+ coder.abs_fnames.remove(abs_path)
26
+ removed = True
27
+ elif abs_path in coder.abs_read_only_fnames:
28
+ # Don't remove if it's the last read-only file and there are no editable files
29
+ if len(coder.abs_read_only_fnames) <= 1 and not coder.abs_fnames:
30
+ coder.io.tool_output(
31
+ f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
32
+ )
33
+ return "Cannot remove - last file in context"
34
+ coder.abs_read_only_fnames.remove(abs_path)
35
+ removed = True
36
+
37
+ if not removed:
38
+ coder.io.tool_output(f"⚠️ File '{file_path}' not in context")
39
+ return "File not in context"
40
+
41
+ # Track in recently removed
42
+ coder.recently_removed[rel_path] = {"removed_at": time.time()}
43
+
44
+ coder.io.tool_output(f"🗑️ Explicitly removed '{file_path}' from context")
45
+ return "Removed file from context"
46
+ except Exception as e:
47
+ coder.io.tool_error(f"Error removing file: {str(e)}")
48
+ return f"Error: {str(e)}"
@@ -0,0 +1,77 @@
1
+ from .tool_utils import (
2
+ ToolError,
3
+ apply_change,
4
+ format_tool_result,
5
+ generate_unified_diff_snippet,
6
+ handle_tool_error,
7
+ validate_file_for_edit,
8
+ )
9
+
10
+
11
+ def _execute_replace_all(coder, file_path, find_text, replace_text, change_id=None, dry_run=False):
12
+ """
13
+ Replace all occurrences of text in a file using utility functions.
14
+ """
15
+ # Get absolute file path
16
+ abs_path = coder.abs_root_path(file_path)
17
+ rel_path = coder.get_rel_fname(abs_path)
18
+ tool_name = "ReplaceAll"
19
+ try:
20
+ # 1. Validate file and get content
21
+ abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
22
+
23
+ # 2. Count occurrences
24
+ count = original_content.count(find_text)
25
+ if count == 0:
26
+ coder.io.tool_warning(f"Text '{find_text}' not found in file '{file_path}'")
27
+ return "Warning: Text not found in file"
28
+
29
+ # 3. Perform the replacement
30
+ new_content = original_content.replace(find_text, replace_text)
31
+
32
+ if original_content == new_content:
33
+ coder.io.tool_warning("No changes made: replacement text is identical to original")
34
+ return "Warning: No changes made (replacement identical to original)"
35
+
36
+ # 4. Generate diff for feedback
37
+ diff_examples = generate_unified_diff_snippet(original_content, new_content, rel_path)
38
+
39
+ # 5. Handle dry run
40
+ if dry_run:
41
+ dry_run_message = (
42
+ f"Dry run: Would replace {count} occurrences of '{find_text}' in {file_path}."
43
+ )
44
+ return format_tool_result(
45
+ coder,
46
+ tool_name,
47
+ "",
48
+ dry_run=True,
49
+ dry_run_message=dry_run_message,
50
+ diff_snippet=diff_examples,
51
+ )
52
+
53
+ # 6. Apply Change (Not dry run)
54
+ metadata = {"find_text": find_text, "replace_text": replace_text, "occurrences": count}
55
+ final_change_id = apply_change(
56
+ coder,
57
+ abs_path,
58
+ rel_path,
59
+ original_content,
60
+ new_content,
61
+ "replaceall",
62
+ metadata,
63
+ change_id,
64
+ )
65
+
66
+ # 7. Format and return result
67
+ success_message = f"Replaced {count} occurrences in {file_path}"
68
+ return format_tool_result(
69
+ coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_examples
70
+ )
71
+
72
+ except ToolError as e:
73
+ # Handle errors raised by utility functions
74
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
75
+ except Exception as e:
76
+ # Handle unexpected errors
77
+ return handle_tool_error(coder, tool_name, e)
@@ -0,0 +1,125 @@
1
+ import os
2
+ import traceback
3
+
4
+
5
+ def _execute_replace_line(
6
+ coder, file_path, line_number, new_content, change_id=None, dry_run=False
7
+ ):
8
+ """
9
+ Replace a specific line identified by line number.
10
+ Useful for fixing errors identified by error messages or linters.
11
+
12
+ Parameters:
13
+ - coder: The Coder instance
14
+ - file_path: Path to the file to modify
15
+ - line_number: The line number to replace (1-based)
16
+ - new_content: New content for the line
17
+ - change_id: Optional ID for tracking the change
18
+ - dry_run: If True, simulate the change without modifying the file
19
+
20
+ Returns a result message.
21
+ """
22
+ try:
23
+ # Get absolute file path
24
+ abs_path = coder.abs_root_path(file_path)
25
+ rel_path = coder.get_rel_fname(abs_path)
26
+
27
+ # Check if file exists
28
+ if not os.path.isfile(abs_path):
29
+ coder.io.tool_error(f"File '{file_path}' not found")
30
+ return "Error: File not found"
31
+
32
+ # Check if file is in editable context
33
+ if abs_path not in coder.abs_fnames:
34
+ if abs_path in coder.abs_read_only_fnames:
35
+ coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
36
+ return "Error: File is read-only. Use MakeEditable first."
37
+ else:
38
+ coder.io.tool_error(f"File '{file_path}' not in context")
39
+ return "Error: File not in context"
40
+
41
+ # Reread file content immediately before modification
42
+ file_content = coder.io.read_text(abs_path)
43
+ if file_content is None:
44
+ coder.io.tool_error(f"Could not read file '{file_path}' before ReplaceLine operation.")
45
+ return f"Error: Could not read file '{file_path}'"
46
+
47
+ # Split into lines
48
+ lines = file_content.splitlines()
49
+
50
+ # Validate line number
51
+ if not isinstance(line_number, int):
52
+ try:
53
+ line_number = int(line_number)
54
+ except ValueError:
55
+ coder.io.tool_error(f"Line number must be an integer, got '{line_number}'")
56
+ coder.io.tool_error(
57
+ f"Invalid line_number value: '{line_number}'. Must be an integer."
58
+ )
59
+ return f"Error: Invalid line_number value '{line_number}'"
60
+
61
+ # Convert 1-based line number to 0-based index
62
+ idx = line_number - 1
63
+
64
+ if idx < 0 or idx >= len(lines):
65
+ coder.io.tool_error(
66
+ f"Line number {line_number} is out of range for file '{file_path}' (has"
67
+ f" {len(lines)} lines)."
68
+ )
69
+ return f"Error: Line number {line_number} out of range"
70
+
71
+ # Store original content for change tracking
72
+ original_content = file_content
73
+ original_line = lines[idx]
74
+
75
+ # Replace the line
76
+ lines[idx] = new_content
77
+
78
+ # Join lines back into a string
79
+ new_content_full = "\n".join(lines)
80
+
81
+ if original_content == new_content_full:
82
+ coder.io.tool_warning("No changes made: new line content is identical to original")
83
+ return "Warning: No changes made (new content identical to original)"
84
+
85
+ # Create a readable diff for the line replacement
86
+ diff = f"Line {line_number}:\n- {original_line}\n+ {new_content}"
87
+
88
+ # Handle dry run
89
+ if dry_run:
90
+ coder.io.tool_output(f"Dry run: Would replace line {line_number} in {file_path}")
91
+ return f"Dry run: Would replace line {line_number}. Diff:\n{diff}"
92
+
93
+ # --- Apply Change (Not dry run) ---
94
+ coder.io.write_text(abs_path, new_content_full)
95
+
96
+ # Track the change
97
+ try:
98
+ metadata = {
99
+ "line_number": line_number,
100
+ "original_line": original_line,
101
+ "new_line": new_content,
102
+ }
103
+ change_id = coder.change_tracker.track_change(
104
+ file_path=rel_path,
105
+ change_type="replaceline",
106
+ original_content=original_content,
107
+ new_content=new_content_full,
108
+ metadata=metadata,
109
+ change_id=change_id,
110
+ )
111
+ except Exception as track_e:
112
+ coder.io.tool_error(f"Error tracking change for ReplaceLine: {track_e}")
113
+ change_id = "TRACKING_FAILED"
114
+
115
+ coder.aider_edited_files.add(rel_path)
116
+
117
+ # Improve feedback
118
+ coder.io.tool_output(
119
+ f"✅ Replaced line {line_number} in {file_path} (change_id: {change_id})"
120
+ )
121
+ return f"Successfully replaced line {line_number} (change_id: {change_id}). Diff:\n{diff}"
122
+
123
+ except Exception as e:
124
+ coder.io.tool_error(f"Error in ReplaceLine: {str(e)}\n{traceback.format_exc()}")
125
+ return f"Error: {str(e)}"