swarm_memory 2.1.5 → 2.1.6

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,194 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module DocumentConverters
6
- # Converts XLSX/XLS spreadsheets to text with image extraction
7
- class XlsxConverter < BaseConverter
8
- class << self
9
- def gem_name
10
- "roo"
11
- end
12
-
13
- def format_name
14
- "XLSX/XLS"
15
- end
16
-
17
- def extensions
18
- [".xlsx", ".xls"]
19
- end
20
-
21
- # XLS files require an additional gem
22
- def xls_gem_available?
23
- gem_available?("roo-xls")
24
- end
25
- end
26
-
27
- # Convert a spreadsheet to text/content
28
- # @param file_path [String] Path to the spreadsheet file
29
- # @return [String, RubyLLM::Content] Converted content or error message
30
- def convert(file_path)
31
- unless self.class.available?
32
- return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
33
- end
34
-
35
- # Check for legacy XLS files
36
- extension = File.extname(file_path).downcase
37
- if extension == ".xls" && !self.class.xls_gem_available?
38
- return error("Legacy .xls files require the 'roo-xls' gem. Install with: gem install roo-xls")
39
- end
40
-
41
- spreadsheet = nil
42
-
43
- begin
44
- require "roo"
45
- require "csv"
46
- require "tmpdir"
47
-
48
- spreadsheet = Roo::Spreadsheet.open(file_path)
49
-
50
- # Extract images from all sheets
51
- image_paths = extract_images(spreadsheet, file_path)
52
-
53
- output = []
54
- output << "Spreadsheet: #{File.basename(file_path)}"
55
- output << "Sheets: #{spreadsheet.sheets.size}"
56
- output << "=" * 60
57
- output << ""
58
-
59
- spreadsheet.sheets.each_with_index do |sheet_name, sheet_idx|
60
- sheet = spreadsheet.sheet(sheet_name)
61
-
62
- output << "Sheet #{sheet_idx + 1}: #{sheet_name}"
63
- output << "-" * 60
64
-
65
- # Add sheet dimensions
66
- first_row = sheet.first_row
67
- last_row = sheet.last_row
68
- first_col = sheet.first_column
69
- last_col = sheet.last_column
70
-
71
- if first_row && last_row && first_col && last_col
72
- row_count = last_row - first_row + 1
73
- col_count = last_col - first_col + 1
74
- output << "Dimensions: #{row_count} rows × #{col_count} columns"
75
- output << ""
76
- else
77
- output << "(Empty sheet)"
78
- output << ""
79
- next
80
- end
81
-
82
- # Extract data rows
83
- sheet.each_row_streaming(pad_cells: true) do |row|
84
- row_values = row.map do |cell|
85
- format_cell_value(cell)
86
- end
87
-
88
- # Skip completely empty rows
89
- next if row_values.all? { |v| v.nil? || v.empty? }
90
-
91
- # Format as CSV with proper escaping
92
- output << CSV.generate_line(row_values).chomp
93
- end
94
-
95
- output << ""
96
- end
97
-
98
- text_content = output.join("\n")
99
-
100
- # If there are images, return Content with attachments
101
- if image_paths.any?
102
- content = RubyLLM::Content.new(text_content)
103
- image_paths.each do |image_path|
104
- content.add_attachment(image_path)
105
- end
106
- content
107
- else
108
- # No images, return just text
109
- text_content
110
- end
111
- rescue ArgumentError => e
112
- error("Failed to open spreadsheet: #{e.message}")
113
- rescue RangeError => e
114
- error("Sheet access error: #{e.message}")
115
- rescue Zip::Error => e
116
- error("Corrupted or invalid XLSX file: #{e.message}")
117
- rescue IOError => e
118
- error("File reading error: #{e.message}")
119
- rescue StandardError => e
120
- error("Failed to parse spreadsheet: #{e.message}")
121
- ensure
122
- # Always clean up resources
123
- spreadsheet.close if spreadsheet&.respond_to?(:close)
124
- end
125
- end
126
-
127
- private
128
-
129
- # Format cell value based on type
130
- # @param cell [Roo::Cell] The cell to format
131
- # @return [String] Formatted cell value
132
- def format_cell_value(cell)
133
- return "" if cell.nil? || cell.empty?
134
-
135
- case cell.type
136
- when :string
137
- cell.value.to_s
138
- when :float, :number
139
- cell.value.to_s
140
- when :date
141
- cell.value.strftime("%Y-%m-%d")
142
- when :datetime
143
- cell.value.strftime("%Y-%m-%d %H:%M:%S")
144
- when :time
145
- hours = (cell.value / 3600).to_i
146
- minutes = ((cell.value % 3600) / 60).to_i
147
- seconds = (cell.value % 60).to_i
148
- format("%02d:%02d:%02d", hours, minutes, seconds)
149
- when :boolean
150
- cell.value ? "TRUE" : "FALSE"
151
- when :formula
152
- # Returns calculated value, not the formula itself
153
- cell.value.to_s
154
- when :link
155
- cell.value.to_s
156
- when :percentage
157
- (cell.value * 100).to_s + "%"
158
- else
159
- cell.value.to_s
160
- end
161
- rescue StandardError
162
- "[ERROR]"
163
- end
164
-
165
- # Extract images from spreadsheet
166
- # @param spreadsheet [Roo::Spreadsheet] The spreadsheet
167
- # @param _xlsx_path [String] Path to the XLSX file (unused but kept for consistency)
168
- # @return [Array<String>] Array of temporary file paths containing extracted images
169
- def extract_images(spreadsheet, _xlsx_path)
170
- image_paths = []
171
-
172
- spreadsheet.sheets.each do |sheet_name|
173
- # Check if the spreadsheet supports image extraction
174
- next unless spreadsheet.respond_to?(:images)
175
-
176
- images = spreadsheet.images(sheet_name)
177
- next unless images && !images.empty?
178
-
179
- images.each do |img_path|
180
- if img_path && File.exist?(img_path)
181
- image_paths << img_path
182
- end
183
- end
184
- end
185
-
186
- image_paths
187
- rescue StandardError
188
- # If image extraction fails, don't fail the entire spreadsheet read
189
- []
190
- end
191
- end
192
- end
193
- end
194
- end
@@ -1,145 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Edit tool for performing exact string replacements in files
6
- #
7
- # Uses exact string matching to find and replace content.
8
- # Requires unique matches and proper Read tool usage beforehand.
9
- # Enforces read-before-edit rule.
10
- class Edit < RubyLLM::Tool
11
- include PathResolver
12
-
13
- # Factory pattern: declare what parameters this tool needs for instantiation
14
- class << self
15
- def creation_requirements
16
- [:agent_name, :directory]
17
- end
18
- end
19
-
20
- description <<~DESC
21
- Performs exact string replacements in files.
22
- You must use your Read tool at least once in the conversation before editing.
23
- This tool will error if you attempt an edit without reading the file.
24
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix.
25
- The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match.
26
- Never include any part of the line number prefix in the old_string or new_string.
27
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
28
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
29
- The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string.
30
- Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
31
-
32
- IMPORTANT - Path Handling:
33
- - Relative paths (e.g., "tmp/file.txt", "src/main.rb") are resolved relative to your agent's working directory
34
- - Absolute paths (e.g., "/tmp/file.txt", "/etc/passwd") are treated as system absolute paths
35
- - When the user says "tmp/file.txt" they mean the tmp directory in your working directory, NOT /tmp
36
- - Only use absolute paths (starting with /) when explicitly referring to system-level paths
37
- DESC
38
-
39
- param :file_path,
40
- type: "string",
41
- desc: "Path to the file. Use relative paths (e.g., 'tmp/file.txt') for files in your working directory, or absolute paths (e.g., '/etc/passwd') for system files.",
42
- required: true
43
-
44
- param :old_string,
45
- type: "string",
46
- desc: "The exact text to replace (must match exactly including whitespace)",
47
- required: true
48
-
49
- param :new_string,
50
- type: "string",
51
- desc: "The text to replace it with (must be different from old_string)",
52
- required: true
53
-
54
- param :replace_all,
55
- type: "boolean",
56
- desc: "Replace all occurrences of old_string (default false)",
57
- required: false
58
-
59
- # Initialize the Edit tool for a specific agent
60
- #
61
- # @param agent_name [Symbol, String] The agent identifier
62
- # @param directory [String] Agent's working directory
63
- def initialize(agent_name:, directory:)
64
- super()
65
- initialize_agent_context(agent_name: agent_name, directory: directory)
66
- end
67
-
68
- # Override name to return simple "Edit" instead of full class path
69
- def name
70
- "Edit"
71
- end
72
-
73
- def execute(file_path:, old_string:, new_string:, replace_all: false)
74
- # Validate inputs
75
- return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
76
- return validation_error("old_string is required") if old_string.nil? || old_string.empty?
77
- return validation_error("new_string is required") if new_string.nil?
78
-
79
- # old_string and new_string must be different
80
- if old_string == new_string
81
- return validation_error("old_string and new_string must be different. They are currently identical.")
82
- end
83
-
84
- # CRITICAL: Resolve path against agent directory
85
- resolved_path = resolve_path(file_path)
86
-
87
- # File must exist (use resolved path)
88
- unless File.exist?(resolved_path)
89
- return validation_error("File does not exist: #{file_path}")
90
- end
91
-
92
- # Enforce read-before-edit (use resolved path)
93
- unless Stores::ReadTracker.file_read?(@agent_name, resolved_path)
94
- return validation_error(
95
- "Cannot edit file without reading it first. " \
96
- "You must use the Read tool on '#{file_path}' before editing it. " \
97
- "This ensures you have the current file contents to match against.",
98
- )
99
- end
100
-
101
- # Read current content (use resolved path)
102
- content = File.read(resolved_path, encoding: "UTF-8")
103
-
104
- # Check if old_string exists in file
105
- unless content.include?(old_string)
106
- return validation_error(<<~ERROR.chomp)
107
- old_string not found in file. Make sure it matches exactly, including all whitespace and indentation.
108
- Do not include line number prefixes from Read tool output.
109
- ERROR
110
- end
111
-
112
- # Count occurrences
113
- occurrences = content.scan(old_string).count
114
-
115
- # If not replace_all and multiple occurrences, error
116
- if !replace_all && occurrences > 1
117
- return validation_error(<<~ERROR.chomp)
118
- Found #{occurrences} occurrences of old_string.
119
- Either provide more surrounding context to make the match unique, or use replace_all: true to replace all occurrences.
120
- ERROR
121
- end
122
-
123
- # Perform replacement
124
- new_content = if replace_all
125
- content.gsub(old_string, new_string)
126
- else
127
- content.sub(old_string, new_string)
128
- end
129
-
130
- # Write back to file (use resolved path)
131
- File.write(resolved_path, new_content, encoding: "UTF-8")
132
-
133
- # Build success message
134
- replaced_count = replace_all ? occurrences : 1
135
- "Successfully replaced #{replaced_count} occurrence(s) in #{file_path}"
136
- rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
137
- error("File contains invalid UTF-8. Cannot edit binary or improperly encoded files.")
138
- rescue Errno::EACCES
139
- error("Permission denied: Cannot read or write file '#{file_path}'")
140
- rescue StandardError => e
141
- error("Unexpected error editing file: #{e.class.name} - #{e.message}")
142
- end
143
- end
144
- end
145
- end
@@ -1,166 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Glob tool for fast file and directory pattern matching
6
- #
7
- # Finds files and directories matching glob patterns, sorted by modification time.
8
- # Works efficiently with any codebase size.
9
- class Glob < RubyLLM::Tool
10
- include PathResolver
11
-
12
- # Factory pattern: declare what parameters this tool needs for instantiation
13
- class << self
14
- def creation_requirements
15
- [:directory]
16
- end
17
- end
18
-
19
- def initialize(directory:)
20
- super()
21
- @directory = File.expand_path(directory)
22
- end
23
-
24
- define_method(:name) { "Glob" }
25
-
26
- description <<~DESC
27
- - Fast file pattern matching tool that works with any directory size
28
-
29
- - Supports glob patterns like "**/*.js" or "src/**/*.ts"
30
-
31
- - Returns matching file paths sorted by modification time
32
-
33
- - Use this tool when you need to find files by name patterns
34
-
35
- - When you are doing an open ended search that may require multiple rounds of
36
- globbing and grepping, use the Agent tool instead
37
-
38
- - You have the capability to call multiple tools in a single response. It is
39
- always better to speculatively perform multiple searches as a batch that are
40
- potentially useful.
41
- DESC
42
-
43
- param :pattern,
44
- type: "string",
45
- desc: "The glob pattern to match files against",
46
- required: true
47
-
48
- param :path,
49
- type: "string",
50
- desc: "The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter \"undefined\" or \"null\" - simply omit it for the default behavior. Must be a valid directory path if provided.",
51
- required: false
52
-
53
- # Backward compatibility alias - use Defaults module for new code
54
- MAX_RESULTS = Defaults::Limits::GLOB_RESULTS
55
-
56
- def execute(pattern:, path: nil)
57
- # Validate inputs
58
- return validation_error("pattern is required") if pattern.nil? || pattern.to_s.strip.empty?
59
-
60
- # Validate path if provided
61
- if path && !path.to_s.strip.empty?
62
- # Check for literal "undefined" or "null" strings
63
- if path.to_s.strip.downcase == "undefined" || path.to_s.strip.downcase == "null"
64
- return validation_error("Invalid path value. Omit the path parameter entirely to use the current working directory.")
65
- end
66
-
67
- unless File.exist?(path)
68
- return validation_error("Path does not exist: #{path}")
69
- end
70
-
71
- unless File.directory?(path)
72
- return validation_error("Path is not a directory: #{path}")
73
- end
74
-
75
- # CRITICAL: Resolve relative paths against agent directory
76
- search_path = resolve_path(path)
77
- else
78
- # CRITICAL: Use agent's directory as default (NOT Dir.pwd)
79
- search_path = @directory
80
- end
81
-
82
- # Execute glob from specified path
83
- begin
84
- # Build full pattern by joining search path with pattern
85
- # If pattern is already absolute, File.join will use it as-is
86
- full_pattern = if pattern.start_with?("/")
87
- # Pattern is absolute, use it directly
88
- pattern
89
- else
90
- # Pattern is relative, join with search path
91
- File.join(search_path, pattern)
92
- end
93
-
94
- matches = Dir.glob(full_pattern, File::FNM_DOTMATCH)
95
-
96
- # Remove . and .. entries (handle both with and without trailing slashes)
97
- matches.reject! do |f|
98
- basename = File.basename(f.chomp("/"))
99
- basename == "." || basename == ".."
100
- end
101
-
102
- # Handle no matches
103
- if matches.empty?
104
- return "No matches found for pattern: #{pattern}"
105
- end
106
-
107
- # Sort by modification time (most recent first)
108
- matches.sort_by! { |f| -File.mtime(f).to_i }
109
-
110
- # Limit results
111
- if matches.count > MAX_RESULTS
112
- matches = matches.take(MAX_RESULTS)
113
- truncated = true
114
- else
115
- truncated = false
116
- end
117
-
118
- # Format output
119
- output = matches.join("\n")
120
-
121
- # Add system reminder if truncated
122
- if truncated
123
- output += <<~REMINDER
124
-
125
- <system-reminder>
126
- Results limited to first #{MAX_RESULTS} matches (sorted by most recently modified).
127
- Consider using a more specific pattern to narrow your search.
128
- </system-reminder>
129
- REMINDER
130
- end
131
-
132
- # Add usage reminder
133
- output += "\n\n" + build_usage_reminder(matches.count, pattern)
134
-
135
- output
136
- rescue Errno::EACCES => e
137
- error("Permission denied: #{e.message}")
138
- rescue StandardError => e
139
- error("Failed to execute glob: #{e.class.name} - #{e.message}")
140
- end
141
- rescue StandardError => e
142
- error("Unexpected error during glob: #{e.class.name} - #{e.message}")
143
- end
144
-
145
- private
146
-
147
- def validation_error(message)
148
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
149
- end
150
-
151
- def error(message)
152
- "Error: #{message}"
153
- end
154
-
155
- def build_usage_reminder(count, pattern)
156
- <<~REMINDER
157
- <system-reminder>
158
- Found #{count} match#{"es" if count != 1} for '#{pattern}' (files and directories).
159
- These paths are sorted by modification time (most recent first).
160
- You can now use the Read tool to examine specific files, or use Grep to search within these files.
161
- </system-reminder>
162
- REMINDER
163
- end
164
- end
165
- end
166
- end