siada-cli 0.0.1__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 (215) hide show
  1. agent_config.yaml +34 -0
  2. siada/__init__.py +13 -0
  3. siada/agent_hub/__init__.py +10 -0
  4. siada/agent_hub/coder/bug_fix_agent.py +398 -0
  5. siada/agent_hub/coder/code_gen_agent.py +235 -0
  6. siada/agent_hub/coder/fe_gen_agent.py +37 -0
  7. siada/agent_hub/coder/issue_review_agent.py +68 -0
  8. siada/agent_hub/coder/prompt/__init__.py +0 -0
  9. siada/agent_hub/coder/prompt/base/__init__.py +0 -0
  10. siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
  11. siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
  12. siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
  13. siada/agent_hub/coder/prompt/base/rules.py +40 -0
  14. siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
  15. siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
  16. siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
  17. siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
  18. siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
  19. siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
  20. siada/agent_hub/coder/tracing/__init__.py +22 -0
  21. siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
  22. siada/agent_hub/hooks/__init__.py +0 -0
  23. siada/agent_hub/hooks/io_hooks.py +30 -0
  24. siada/agent_hub/siada_agent.py +214 -0
  25. siada/config/__init__.py +0 -0
  26. siada/config/config_loader.py +67 -0
  27. siada/entrypoint/__init__.py +3 -0
  28. siada/entrypoint/args_parser/__init__.py +0 -0
  29. siada/entrypoint/args_parser/args.py +195 -0
  30. siada/entrypoint/interaction/__init__.py +0 -0
  31. siada/entrypoint/interaction/base_turn.py +107 -0
  32. siada/entrypoint/interaction/config.py +30 -0
  33. siada/entrypoint/interaction/controller.py +122 -0
  34. siada/entrypoint/interaction/nointeractive_controller.py +27 -0
  35. siada/entrypoint/interaction/turn/interface.py +116 -0
  36. siada/entrypoint/interaction/turn/models.py +34 -0
  37. siada/entrypoint/interaction/turn/run_turn.py +641 -0
  38. siada/entrypoint/interaction/turn_factory.py +35 -0
  39. siada/entrypoint/interaction/turn_models.py +28 -0
  40. siada/entrypoint/siadahub.py +413 -0
  41. siada/foundation/__init__.py +0 -0
  42. siada/foundation/code_agent_context.py +48 -0
  43. siada/foundation/config.py +59 -0
  44. siada/foundation/constants.py +2 -0
  45. siada/foundation/context.py +76 -0
  46. siada/foundation/logging.py +341 -0
  47. siada/foundation/tools/__init__.py +0 -0
  48. siada/foundation/tools/get_git_diff.py +105 -0
  49. siada/io/__init__.py +0 -0
  50. siada/io/banner.py +148 -0
  51. siada/io/color_settings.py +207 -0
  52. siada/io/color_utils.py +16 -0
  53. siada/io/components/__init__.py +0 -0
  54. siada/io/components/mdstream.py +204 -0
  55. siada/io/console_printer.py +68 -0
  56. siada/io/io.py +661 -0
  57. siada/io/key_bindings.py +129 -0
  58. siada/io/notification_command.py +40 -0
  59. siada/io/test_banner.py +64 -0
  60. siada/models/__init__.py +0 -0
  61. siada/models/agent.py +47 -0
  62. siada/models/coder/__init__.py +0 -0
  63. siada/models/converter.py +27 -0
  64. siada/models/model_base_config.py +121 -0
  65. siada/models/model_run_config.py +153 -0
  66. siada/provider/__init__.py +0 -0
  67. siada/provider/client_factory.py +112 -0
  68. siada/provider/llm_client.py +11 -0
  69. siada/provider/openrouter/__init__.py +0 -0
  70. siada/provider/openrouter/coverter.py +17 -0
  71. siada/provider/openrouter/openroute_provider.py +33 -0
  72. siada/provider/provider_factory.py +53 -0
  73. siada/queries/tree-sitter-language-pack/README.md +7 -0
  74. siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  91. siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  92. siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  93. siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  94. siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  95. siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  96. siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  97. siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  98. siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  99. siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  100. siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  101. siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  102. siada/queries/tree-sitter-languages/README.md +23 -0
  103. siada/queries/tree-sitter-languages/c-tags.scm +9 -0
  104. siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  105. siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  106. siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
  107. siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  108. siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  109. siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
  110. siada/queries/tree-sitter-languages/go-tags.scm +30 -0
  111. siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  112. siada/queries/tree-sitter-languages/java-tags.scm +20 -0
  113. siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  114. siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  115. siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  116. siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  117. siada/queries/tree-sitter-languages/php-tags.scm +26 -0
  118. siada/queries/tree-sitter-languages/python-tags.scm +12 -0
  119. siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
  120. siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  121. siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
  122. siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
  123. siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  124. siada/services/__init__.py +30 -0
  125. siada/services/at_file_recommendation_design_document.md +1213 -0
  126. siada/services/code_context_manager.py +136 -0
  127. siada/services/enhanced_fix_result_check.py +546 -0
  128. siada/services/execution_trace_collector.py +207 -0
  129. siada/services/file_recommendation/__init__.py +136 -0
  130. siada/services/file_recommendation/core/__init__.py +17 -0
  131. siada/services/file_recommendation/core/completion_engine.py +351 -0
  132. siada/services/file_recommendation/core/config.py +62 -0
  133. siada/services/file_recommendation/core/file_discovery.py +353 -0
  134. siada/services/file_recommendation/core/suggestion.py +110 -0
  135. siada/services/file_recommendation/utils/__init__.py +22 -0
  136. siada/services/file_recommendation/utils/path_utils.py +138 -0
  137. siada/services/file_recommendation/utils/text_utils.py +253 -0
  138. siada/services/fix_result_check.py +377 -0
  139. siada/services/handle_at_command/README.md +247 -0
  140. siada/services/handle_at_command/__init__.py +115 -0
  141. siada/services/handle_at_command/exceptions.py +46 -0
  142. siada/services/handle_at_command/models.py +92 -0
  143. siada/services/handle_at_command/parser.py +189 -0
  144. siada/services/handle_at_command/processor.py +321 -0
  145. siada/services/handle_at_command/resolver.py +318 -0
  146. siada/services/handle_at_command/utils.py +345 -0
  147. siada/services/model_info_service.py +103 -0
  148. siada/services/siada_runner.py +170 -0
  149. siada/session/__init__.py +29 -0
  150. siada/session/session_manager.py +66 -0
  151. siada/session/session_models.py +43 -0
  152. siada/support/__init__.py +0 -0
  153. siada/support/completer.py +204 -0
  154. siada/support/editor.py +146 -0
  155. siada/support/envprocessor.py +54 -0
  156. siada/support/repo.py +51 -0
  157. siada/support/slash_commands.py +410 -0
  158. siada/support/spinner.py +320 -0
  159. siada/tools/__init__.py +0 -0
  160. siada/tools/ast/README.md +345 -0
  161. siada/tools/ast/ast_tool.py +341 -0
  162. siada/tools/ast/models.py +11 -0
  163. siada/tools/coder/__init__.py +0 -0
  164. siada/tools/coder/ask_followup_question.py +70 -0
  165. siada/tools/coder/cmd_runner.py +132 -0
  166. siada/tools/coder/file_operator.py +284 -0
  167. siada/tools/coder/file_search/README.md +223 -0
  168. siada/tools/coder/file_search/__init__.py +10 -0
  169. siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
  170. siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
  171. siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
  172. siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
  173. siada/tools/coder/file_search/bin/rg.exe +0 -0
  174. siada/tools/coder/file_search/search.py +437 -0
  175. siada/tools/coder/file_search/setup_package.py +150 -0
  176. siada/tools/coder/file_search/test_search.py +142 -0
  177. siada/tools/coder/files.py +147 -0
  178. siada/tools/coder/fix_attempt_completion.py +58 -0
  179. siada/tools/coder/issue_review_completion.py +29 -0
  180. siada/tools/coder/observation/__init__.py +0 -0
  181. siada/tools/coder/observation/error.py +22 -0
  182. siada/tools/coder/observation/file_observation.py +216 -0
  183. siada/tools/coder/observation/observation.py +71 -0
  184. siada/tools/coder/repo_map/__init__.py +157 -0
  185. siada/tools/coder/repo_map/dump.py +29 -0
  186. siada/tools/coder/repo_map/io.py +226 -0
  187. siada/tools/coder/repo_map/repo_map.py +881 -0
  188. siada/tools/coder/repo_map/special.py +203 -0
  189. siada/tools/coder/repo_map/token_counter.py +154 -0
  190. siada/tools/coder/repo_map/waiting.py +221 -0
  191. siada/tools/coder/reproduce_completion.py +39 -0
  192. siada/tools/coder/run_cmd.py +60 -0
  193. siada/tools/coder/test_completion.py +25 -0
  194. siada/tools/coder/tool_docs.py +44 -0
  195. siada/tools/compression_tool.py +200 -0
  196. siada/tools/read_many_files/README.md +220 -0
  197. siada/tools/read_many_files/__init__.py +25 -0
  198. siada/tools/read_many_files/file_processor.py +486 -0
  199. siada/tools/read_many_files/filters.py +326 -0
  200. siada/tools/read_many_files/formatters.py +204 -0
  201. siada/tools/read_many_files/models.py +189 -0
  202. siada/tools/read_many_files_tool.py +233 -0
  203. siada/tools/tool_call_format/__init__.py +55 -0
  204. siada/tools/tool_call_format/file_to_language.py +231 -0
  205. siada/tools/tool_call_format/formatter_factory.py +80 -0
  206. siada/tools/tool_call_format/formatters.py +282 -0
  207. siada/tools/tool_call_format/tool_call_formatter.py +54 -0
  208. siada/tools/web/__init__.py +0 -0
  209. siada/tools/web/web_crawl.py +208 -0
  210. siada/utils.py +194 -0
  211. siada_cli-0.0.1.dist-info/LICENSE +201 -0
  212. siada_cli-0.0.1.dist-info/METADATA +457 -0
  213. siada_cli-0.0.1.dist-info/RECORD +215 -0
  214. siada_cli-0.0.1.dist-info/WHEEL +4 -0
  215. siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,284 @@
1
+ import base64
2
+ import mimetypes
3
+ from pathlib import Path
4
+
5
+ from agents import RunContextWrapper, function_tool
6
+ from openhands_aci.editor import OHEditor, ToolResult, ToolError
7
+ from openhands_aci.utils.diff import get_diff
8
+
9
+ from siada.foundation.logging import logger
10
+
11
+ from binaryornot.check import is_binary
12
+
13
+
14
+ from siada.tools.coder.files import read_lines
15
+ from siada.tools.coder.observation.file_observation import FileReadObservation, FileEditObservation
16
+ from siada.tools.coder.observation.observation import FunctionCallResult, FileEditSource
17
+ from siada.tools.coder.observation.error import ErrorObservation
18
+ from siada.tools.coder.observation.observation import FileReadSource
19
+ from siada.tools.coder.tool_docs import EDIT_DOCS
20
+ from siada.foundation.code_agent_context import CodeAgentContext
21
+
22
+
23
+ @function_tool(
24
+ name_override="read_file", description_override="Read the file."
25
+ )
26
+ async def read(
27
+ context: RunContextWrapper[CodeAgentContext],
28
+ path: str,
29
+ start: int = 0,
30
+ end: int = -1,
31
+ impl_source: FileReadSource = FileReadSource.DEFAULT,
32
+ view_range: list[int] | None = None
33
+ ) -> FunctionCallResult:
34
+ """
35
+ Read file content with support for various file types including text, images, PDFs, and videos.
36
+
37
+ This function handles different file types and returns appropriate observations:
38
+ - Text files: Returns content as string with optional line range selection
39
+ - Image files: Returns base64-encoded data URI
40
+ - PDF files: Returns base64-encoded data URI
41
+ - Video files: Returns base64-encoded data URI
42
+ - Binary files: Returns error observation
43
+
44
+ Args:
45
+ context: The run context wrapper containing agent context and root directory
46
+ path: File path to read (relative to working directory or absolute)
47
+ start: Starting line number for text files (0-based, default: 0)
48
+ end: Ending line number for text files (-1 means end of file, default: -1)
49
+ impl_source: Implementation source for reading (DEFAULT or OH_ACI)
50
+ view_range: Optional line range for OH_ACI implementation [start, end]
51
+
52
+ Returns:
53
+ Observation: FileReadObservation with file content or ErrorObservation on failure
54
+
55
+ Raises:
56
+ No exceptions are raised directly - all errors are captured and returned as ErrorObservation
57
+ """
58
+
59
+ # Cannot read binary files
60
+ if is_binary(path):
61
+ return ErrorObservation('ERROR_BINARY_FILE')
62
+
63
+ # Get the working directory from context and initialize file editor
64
+ working_dir = context.context.root_dir
65
+ file_editor = OHEditor(workspace_root=working_dir)
66
+
67
+ # Use OH_ACI implementation if specified
68
+ if impl_source == FileReadSource.OH_ACI:
69
+ result_str, _ = _execute_file_editor(
70
+ file_editor,
71
+ command='view',
72
+ path=path,
73
+ view_range=view_range,
74
+ )
75
+
76
+ return FileReadObservation(
77
+ content=result_str,
78
+ path=path,
79
+ impl_source=FileReadSource.OH_ACI,
80
+ )
81
+
82
+ # NOTE: the client code is running inside the sandbox,
83
+ # so there's no need to check permission
84
+ # Resolve the file path (convert relative to absolute if needed)
85
+ filepath = _resolve_path(path, working_dir)
86
+ try:
87
+ # Handle image files - return as base64-encoded data URI
88
+ if filepath.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
89
+ with open(filepath, 'rb') as file: # noqa: ASYNC101
90
+ image_data = file.read()
91
+ encoded_image = base64.b64encode(image_data).decode('utf-8')
92
+ mime_type, _ = mimetypes.guess_type(filepath)
93
+ if mime_type is None:
94
+ mime_type = 'image/png' # default to PNG if mime type cannot be determined
95
+ encoded_image = f'data:{mime_type};base64,{encoded_image}'
96
+
97
+ return FileReadObservation(path=filepath, content=encoded_image)
98
+ # Handle PDF files - return as base64-encoded data URI
99
+ elif filepath.lower().endswith('.pdf'):
100
+ with open(filepath, 'rb') as file: # noqa: ASYNC101
101
+ pdf_data = file.read()
102
+ encoded_pdf = base64.b64encode(pdf_data).decode('utf-8')
103
+ encoded_pdf = f'data:application/pdf;base64,{encoded_pdf}'
104
+ return FileReadObservation(path=filepath, content=encoded_pdf)
105
+ # Handle video files - return as base64-encoded data URI
106
+ elif filepath.lower().endswith(('.mp4', '.webm', '.ogg')):
107
+ with open(filepath, 'rb') as file: # noqa: ASYNC101
108
+ video_data = file.read()
109
+ encoded_video = base64.b64encode(video_data).decode('utf-8')
110
+ mime_type, _ = mimetypes.guess_type(filepath)
111
+ if mime_type is None:
112
+ mime_type = 'video/mp4' # default to MP4 if MIME type cannot be determined
113
+ encoded_video = f'data:{mime_type};base64,{encoded_video}'
114
+
115
+ return FileReadObservation(path=filepath, content=encoded_video)
116
+
117
+ # Handle text files - read with UTF-8 encoding and apply line range if specified
118
+ with open(filepath, 'r', encoding='utf-8') as file: # noqa: ASYNC101
119
+ lines = read_lines(file.readlines(), start, end)
120
+ except FileNotFoundError:
121
+ # File does not exist at the specified path
122
+ return ErrorObservation(
123
+ f'File not found: {filepath}. Your current working directory is {working_dir}.'
124
+ )
125
+ except UnicodeDecodeError:
126
+ # File contains non-UTF-8 content that cannot be decoded
127
+ return ErrorObservation(f'File could not be decoded as utf-8: {filepath}.')
128
+ except IsADirectoryError:
129
+ # Path points to a directory instead of a file
130
+ return ErrorObservation(
131
+ f'Path is a directory: {filepath}. You can only read files'
132
+ )
133
+
134
+ # Join the lines back into a single string and return the observation
135
+ code_view = ''.join(lines)
136
+ return FileReadObservation(path=filepath, content=code_view)
137
+
138
+ @function_tool(
139
+ name_override="edit_file", description_override=EDIT_DOCS
140
+ )
141
+ async def edit(
142
+ context: RunContextWrapper[CodeAgentContext],
143
+ command: str,
144
+ path: str,
145
+ file_text: str | None = None,
146
+ old_str: str | None = None,
147
+ new_str: str | None = None,
148
+ insert_line: int | None = None,
149
+ view_range: list[int] | None = None
150
+ ) -> FunctionCallResult:
151
+ return _edit_file(
152
+ context=context,
153
+ command=command,
154
+ path=path,
155
+ file_text=file_text,
156
+ old_str=old_str,
157
+ new_str=new_str,
158
+ insert_line=insert_line,
159
+ view_range=view_range
160
+ )
161
+
162
+ def _edit_file(
163
+ context: RunContextWrapper[CodeAgentContext],
164
+ command: str,
165
+ path: str,
166
+ file_text: str | None = None,
167
+ old_str: str | None = None,
168
+ new_str: str | None = None,
169
+ insert_line: int | None = None,
170
+ view_range: list[int] | None = None
171
+ ) -> FunctionCallResult:
172
+ file_editor = OHEditor(workspace_root=context.context.root_dir)
173
+ result_str, (old_content, new_content) = _execute_file_editor(
174
+ file_editor,
175
+ command=command,
176
+ path=path,
177
+ file_text=file_text,
178
+ old_str=old_str,
179
+ new_str=new_str,
180
+ insert_line=insert_line,
181
+ view_range=view_range,
182
+ enable_linting=False,
183
+ )
184
+
185
+ return FileEditObservation(
186
+ content=result_str,
187
+ path=path,
188
+ old_content=old_str,
189
+ new_content=new_str,
190
+ impl_source=FileEditSource.OH_ACI,
191
+ diff=get_diff(
192
+ old_contents=old_content or '',
193
+ new_contents=new_content or '',
194
+ filepath=path,
195
+ ),
196
+ command=command,
197
+ )
198
+
199
+
200
+ def _execute_file_editor(
201
+ editor: OHEditor,
202
+ command: str,
203
+ path: str,
204
+ file_text: str | None = None,
205
+ view_range: list[int] | None = None,
206
+ old_str: str | None = None,
207
+ new_str: str | None = None,
208
+ insert_line: int | str | None = None,
209
+ enable_linting: bool = False,
210
+ ) -> tuple[str, tuple[str | None, str | None]]:
211
+ """Execute file editor command and handle exceptions.
212
+
213
+ Args:
214
+ editor: The OHEditor instance
215
+ command: Editor command to execute
216
+ path: File path
217
+ file_text: Optional file text content
218
+ view_range: Optional view range tuple (start, end)
219
+ old_str: Optional string to replace
220
+ new_str: Optional replacement string
221
+ insert_line: Optional line number for insertion (can be int or str)
222
+ enable_linting: Whether to enable linting
223
+
224
+ Returns:
225
+ tuple: A tuple containing the output string and a tuple of old and new file content
226
+ """
227
+ result: ToolResult | None = None
228
+
229
+ # Convert insert_line from string to int if needed
230
+ if insert_line is not None and isinstance(insert_line, str):
231
+ try:
232
+ insert_line = int(insert_line)
233
+ except ValueError:
234
+ return (
235
+ f"ERROR:\nInvalid insert_line value: '{insert_line}'. Expected an integer.",
236
+ (None, None),
237
+ )
238
+
239
+ try:
240
+ result = editor(
241
+ command=command,
242
+ path=path,
243
+ file_text=file_text,
244
+ view_range=view_range,
245
+ old_str=old_str,
246
+ new_str=new_str,
247
+ insert_line=insert_line,
248
+ enable_linting=enable_linting,
249
+ )
250
+ except ToolError as e:
251
+ result = ToolResult(error=e.message)
252
+ except TypeError as e:
253
+ # Handle unexpected arguments or type errors
254
+ return f'ERROR:\n{str(e)}', (None, None)
255
+
256
+ if result.error:
257
+ return f'ERROR:\n{result.error}', (None, None)
258
+
259
+ if not result.output:
260
+ logger.warning(f'No output from file_editor for {path}')
261
+ return '', (None, None)
262
+
263
+ return result.output, (result.old_content, result.new_content)
264
+
265
+ def _resolve_path(path: str, working_dir: str) -> str:
266
+ """
267
+ Resolve a file path to an absolute path.
268
+
269
+ If the provided path is relative, it will be resolved relative to the working directory.
270
+ If the path is already absolute, it will be returned as-is.
271
+
272
+ Args:
273
+ path: The file path to resolve (can be relative or absolute)
274
+ working_dir: The working directory to use as base for relative paths
275
+
276
+ Returns:
277
+ str: The absolute file path
278
+ """
279
+ filepath = Path(path)
280
+ if not filepath.is_absolute():
281
+ # Convert relative path to absolute by joining with working directory
282
+ return str(Path(working_dir) / filepath)
283
+ # Return absolute path as-is
284
+ return str(filepath)
@@ -0,0 +1,223 @@
1
+ # File Search - High-Performance File Search Tool
2
+
3
+ A Python file search module based on ripgrep, providing fast and accurate code search functionality.
4
+
5
+ ## Features
6
+
7
+ - **High-Performance Search**: Uses ripgrep tool, several times faster than traditional regex search
8
+ - **Context Information**: Provides context around matching lines
9
+ - **File Filtering**: Supports glob pattern filtering for specific file types
10
+ - **Cross-Platform Support**: Supports Windows, macOS, Linux
11
+ - **Package-Friendly**: Supports both development environment and packaged distribution
12
+ - **Auto Fallback**: Prioritizes built-in binary files, can fallback to system installation
13
+
14
+ ## Quick Start
15
+
16
+ ### Basic Usage
17
+
18
+ ```python
19
+ from file_search import regex_search_files
20
+
21
+ # Search for TODO comments
22
+ results = regex_search_files(
23
+ cwd="/path/to/project",
24
+ directory_path="/path/to/search",
25
+ regex=r"TODO:",
26
+ file_pattern="*.py"
27
+ )
28
+ print(results)
29
+ ```
30
+
31
+ ### Class Interface
32
+
33
+ ```python
34
+ from file_search import RipgrepSearcher
35
+
36
+ searcher = RipgrepSearcher()
37
+ results = searcher.search_in_files(
38
+ directory_path="./siada",
39
+ regex=r"def\s+\w+\(",
40
+ file_pattern="*.py",
41
+ cwd="/path/to/project"
42
+ )
43
+ ```
44
+
45
+ ## Installation and Configuration
46
+
47
+ ### Automatic Configuration (Recommended)
48
+
49
+ The tool automatically searches for ripgrep binary files in the following priority order:
50
+
51
+ 1. `RIPGREP_BINARY_PATH` environment variable
52
+ 2. Built-in binary files (development/packaging environment)
53
+ 3. ripgrep in system PATH
54
+
55
+ ### Manual Configuration
56
+
57
+ To specify a specific ripgrep path:
58
+
59
+ ```bash
60
+ export RIPGREP_BINARY_PATH="/path/to/rg"
61
+ ```
62
+
63
+ ### Binary Files
64
+
65
+ Built-in support for binary files on the following platforms:
66
+ - Windows: `rg.exe`
67
+ - macOS Intel: `rg-macos-x64`
68
+ - macOS Apple Silicon: `rg-macos-arm64`
69
+ - Linux x64: `rg-linux-x64`
70
+ - Linux ARM64: `rg-linux-arm64`
71
+
72
+ ## Usage Examples
73
+
74
+ ### Search Code Comments
75
+ ```python
76
+ # Search for TODO/FIXME comments
77
+ regex_search_files(".", ".", r"TODO:|FIXME:|HACK:", "*.py")
78
+ ```
79
+
80
+ ### Search Functions and Classes
81
+ ```python
82
+ # Search for function definitions
83
+ regex_search_files(".", ".", r"def\s+\w+\(", "*.py")
84
+
85
+ # Search for class definitions
86
+ regex_search_files(".", ".", r"class\s+\w+", "*.py")
87
+ ```
88
+
89
+ ### Search Import Statements
90
+ ```python
91
+ regex_search_files(".", ".", r"^import\s+|^from\s+\w+\s+import", "*.py")
92
+ ```
93
+
94
+ ### Multi-File Type Search
95
+ ```python
96
+ regex_search_files(".", ".", r"console\.log", "*.{js,ts}")
97
+ ```
98
+
99
+ ## Output Format
100
+
101
+ ```
102
+ Found 2 results.
103
+
104
+ src/main.py
105
+ │----
106
+ │def process_data(data):
107
+ │ # TODO: Add error handling
108
+ │ return data
109
+ │----
110
+
111
+ src/utils.py
112
+ │----
113
+ │class Helper:
114
+ │ def __init__(self):
115
+ │ # TODO: Initialize properly
116
+ │ pass
117
+ │----
118
+ ```
119
+
120
+ ## Parameter Description
121
+
122
+ - **cwd**: Current working directory, used to calculate relative paths
123
+ - **directory_path**: Directory path to search
124
+ - **regex**: Regular expression pattern (Rust syntax)
125
+ - **file_pattern**: File pattern filter (e.g., "*.py", "*.js", "*")
126
+
127
+ ## Regular Expression Syntax
128
+
129
+ Uses Rust regular expression syntax with main features:
130
+ - Unicode support
131
+ - Multi-line mode by default
132
+ - Supports lookahead and lookbehind assertions
133
+ - Detailed syntax: https://docs.rs/regex/latest/regex/#syntax
134
+
135
+ ## Performance Characteristics
136
+
137
+ - **Result Limit**: Returns maximum 300 results
138
+ - **Output Limit**: Prevents excessive memory usage
139
+ - **Context Control**: Provides 1 line of context before and after each match
140
+
141
+ ## Package Distribution
142
+
143
+ ### Package Configuration
144
+
145
+ Add to `pyproject.toml`:
146
+
147
+ ```toml
148
+ [tool.setuptools.package-data]
149
+ "src.tools.coder.file_search" = ["bin/*", "README.md"]
150
+
151
+ [tool.setuptools.packages.find]
152
+ where = ["src"]
153
+ include = ["src.tools.coder.file_search*"]
154
+ ```
155
+
156
+ ### Verify Packaging
157
+
158
+ ```bash
159
+ # Run packaging check
160
+ python siada/tools/coder/file_search/setup_package.py
161
+
162
+ # Test functionality
163
+ python siada/tools/coder/file_search/test_search.py
164
+ ```
165
+
166
+ ## Troubleshooting
167
+
168
+ ### Common Issues
169
+
170
+ **Cannot find ripgrep binary file**
171
+ - Set environment variable: `export RIPGREP_BINARY_PATH="/path/to/rg"`
172
+ - Install system ripgrep: `brew install ripgrep` (macOS) or `apt install ripgrep` (Ubuntu)
173
+
174
+ **Permission errors**
175
+ - Unix systems: `chmod +x file_search/bin/rg-*`
176
+
177
+ **No search results**
178
+ - Check search path and regular expression syntax
179
+ - Confirm file pattern matches target files
180
+
181
+ ### Debugging
182
+
183
+ ```python
184
+ from file_search.search import RipgrepSearcher
185
+
186
+ try:
187
+ searcher = RipgrepSearcher()
188
+ print(f"Ripgrep binary: {searcher.rg_path}")
189
+ except RuntimeError as e:
190
+ print(f"Error: {e}")
191
+ ```
192
+
193
+ ## Docker Environment
194
+
195
+ ```dockerfile
196
+ # Install system ripgrep
197
+ RUN apt-get update && apt-get install -y ripgrep
198
+
199
+ # Or set environment variable
200
+ ENV RIPGREP_BINARY_PATH=/usr/bin/rg
201
+ ```
202
+
203
+ ## Testing
204
+
205
+ Run test cases:
206
+
207
+ ```bash
208
+ python siada/tools/coder/file_search/test_search.py
209
+ ```
210
+
211
+ Tests include:
212
+ - Binary file detection
213
+ - Basic search functionality
214
+ - File type filtering
215
+ - Error handling mechanisms
216
+
217
+ ## Technical Details
218
+
219
+ - Based on ripgrep high-performance search engine
220
+ - Supports automatic adaptation for development and packaging environments
221
+ - Automatic permission repair mechanism
222
+ - Multi-path search strategy
223
+ - Graceful error handling and fallback mechanisms
@@ -0,0 +1,10 @@
1
+ """
2
+ File Search - 高性能文件搜索工具
3
+
4
+ 基于 ripgrep 的 Python 文件搜索模块,提供快速、准确的代码搜索功能。
5
+ """
6
+
7
+ from .search import RipgrepSearcher, SearchResult, regex_search_files
8
+
9
+ __all__ = ['RipgrepSearcher', 'SearchResult', 'regex_search_files']
10
+ __version__ = '1.0.0'
Binary file