ace-search 0.24.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/nav/protocols/wfi-sources/ace-search.yml +19 -0
  3. data/.ace-defaults/search/config.yml +45 -0
  4. data/.ace-defaults/search/presets/code.yml +22 -0
  5. data/CHANGELOG.md +404 -0
  6. data/LICENSE +21 -0
  7. data/README.md +42 -0
  8. data/Rakefile +14 -0
  9. data/exe/ace-search +14 -0
  10. data/handbook/agents/research.ag.md +214 -0
  11. data/handbook/agents/search.ag.md +331 -0
  12. data/handbook/skills/as-search-feature-research/SKILL.md +29 -0
  13. data/handbook/skills/as-search-research/SKILL.md +37 -0
  14. data/handbook/skills/as-search-run/SKILL.md +47 -0
  15. data/handbook/workflow-instructions/search/feature-research.wf.md +274 -0
  16. data/handbook/workflow-instructions/search/research.wf.md +211 -0
  17. data/handbook/workflow-instructions/search/run.wf.md +289 -0
  18. data/lib/ace/search/atoms/debug_logger.rb +61 -0
  19. data/lib/ace/search/atoms/fd_executor.rb +168 -0
  20. data/lib/ace/search/atoms/pattern_analyzer.rb +176 -0
  21. data/lib/ace/search/atoms/result_parser.rb +111 -0
  22. data/lib/ace/search/atoms/ripgrep_executor.rb +160 -0
  23. data/lib/ace/search/atoms/search_path_resolver.rb +79 -0
  24. data/lib/ace/search/atoms/tool_checker.rb +69 -0
  25. data/lib/ace/search/cli/commands/search.rb +240 -0
  26. data/lib/ace/search/cli.rb +34 -0
  27. data/lib/ace/search/models/search_options.rb +66 -0
  28. data/lib/ace/search/models/search_preset.rb +34 -0
  29. data/lib/ace/search/models/search_result.rb +109 -0
  30. data/lib/ace/search/molecules/dwim_analyzer.rb +52 -0
  31. data/lib/ace/search/molecules/fzf_integrator.rb +71 -0
  32. data/lib/ace/search/molecules/preset_manager.rb +98 -0
  33. data/lib/ace/search/molecules/search_option_builder.rb +113 -0
  34. data/lib/ace/search/molecules/time_filter.rb +60 -0
  35. data/lib/ace/search/organisms/result_aggregator.rb +73 -0
  36. data/lib/ace/search/organisms/result_formatter.rb +103 -0
  37. data/lib/ace/search/organisms/unified_searcher.rb +165 -0
  38. data/lib/ace/search/version.rb +7 -0
  39. data/lib/ace/search.rb +87 -0
  40. metadata +181 -0
@@ -0,0 +1,289 @@
1
+ ---
2
+ doc-type: workflow
3
+ title: Search Workflow
4
+ purpose: search workflow instruction
5
+ ace-docs:
6
+ last-updated: 2026-03-06
7
+ last-checked: 2026-03-21
8
+ ---
9
+
10
+ # Search Workflow
11
+
12
+ ## Purpose
13
+
14
+ Intelligent code and file discovery using the **ace-search** gem - finding files by name, searching for code patterns, and exploring project structure.
15
+
16
+ ## Primary Tool: ace-search
17
+
18
+ You use the **ace-search** command exclusively for all search operations. This unified tool combines file and content searching with intelligent DWIM (Do What I Mean) pattern analysis.
19
+
20
+ ## Search Modes
21
+
22
+ ### Auto Mode (Default - DWIM)
23
+ Let ace-search intelligently detect search type:
24
+ ```bash
25
+ # File glob patterns auto-detected
26
+ ace-search "*.rb"
27
+ ace-search "test_*.md"
28
+
29
+ # Content searches auto-detected
30
+ ace-search "class TaskManager"
31
+ ace-search "def initialize"
32
+
33
+ # Hybrid searches
34
+ ace-search "bin/as-search"
35
+ ace-search "TODO"
36
+ ```
37
+
38
+ ### Explicit Modes
39
+
40
+ **File Search** - Find files by name/pattern:
41
+ ```bash
42
+ ace-search "agent" --file
43
+ ace-search "*.md" --file
44
+ ace-search "*Manager*" --file
45
+ ```
46
+
47
+ **Content Search** - Search within files:
48
+ ```bash
49
+ ace-search "require 'ace/core'" --content
50
+ ace-search "TODO|FIXME" --content
51
+ ace-search "class.*Agent" --content
52
+ ```
53
+
54
+ **Hybrid Mode** - Search both:
55
+ ```bash
56
+ ace-search "TaskManager" --hybrid
57
+ ace-search "config" --hybrid
58
+ ```
59
+
60
+ ## Scope Control
61
+
62
+ ### Search Scope
63
+ Limit search to specific paths:
64
+ ```bash
65
+ # Search specific directory (change directory first)
66
+ cd lib/ && ace-search "pattern"
67
+ cd ace-taskflow/ && ace-search "TODO"
68
+
69
+ # Or use --include to filter paths
70
+ ace-search "pattern" --include "lib/**/*"
71
+ ace-search "TODO" --include "ace-taskflow/**/*"
72
+ ```
73
+
74
+ ### File Pattern Filtering (Glob)
75
+ Filter by file patterns:
76
+ ```bash
77
+ # Search only Ruby files
78
+ ace-search "class" --content --glob "**/*.rb"
79
+
80
+ # Search only markdown in specific paths
81
+ ace-search "TODO" --content --glob "docs/**/*.md"
82
+
83
+ # Multiple patterns
84
+ ace-search "config" --glob "**/*.{yml,yaml,json}"
85
+ ```
86
+
87
+ ### Git Scope
88
+ Limit to git-tracked files:
89
+ ```bash
90
+ # Staged files only
91
+ ace-search "console.log" --staged
92
+
93
+ # Tracked files only
94
+ ace-search "TODO" --tracked
95
+
96
+ # Changed files only
97
+ ace-search "FIXME" --changed
98
+ ```
99
+
100
+ ## Search Modifiers
101
+
102
+ ### Pattern Matching
103
+ ```bash
104
+ # Case-insensitive
105
+ ace-search "todo" --case-insensitive
106
+
107
+ # Whole word matching
108
+ ace-search "test" --whole-word
109
+
110
+ # Multiline patterns
111
+ ace-search "class.*end" --multiline
112
+ ```
113
+
114
+ ### Context Display
115
+ Show surrounding lines:
116
+ ```bash
117
+ # 3 lines before and after
118
+ ace-search "error" --context 3
119
+
120
+ # 2 lines after
121
+ ace-search "warning" --after-context 2
122
+
123
+ # 2 lines before
124
+ ace-search "exception" --before-context 2
125
+ ```
126
+
127
+ ### Output Control
128
+ ```bash
129
+ # Limit results
130
+ ace-search "TODO" --max-results 20
131
+
132
+ # Show only filenames
133
+ ace-search "deprecated" --files-with-matches
134
+
135
+ # Output formats
136
+ ace-search "class" --format json
137
+ ace-search "def" --format yaml
138
+ ```
139
+
140
+ ## Preset Support
141
+
142
+ Use predefined search configurations:
143
+ ```bash
144
+ # Use preset from .ace/search/presets/
145
+ ace-search --preset ruby-classes
146
+
147
+ # List available presets
148
+ ace-search --list-presets
149
+ ```
150
+
151
+ ## Common Workflows
152
+
153
+ ### Finding Implementation
154
+ ```bash
155
+ # 1. Broad discovery
156
+ ace-search "TaskManager"
157
+
158
+ # 2. Narrow by file type
159
+ ace-search "TaskManager" --glob "**/*.rb"
160
+
161
+ # 3. Find class definition
162
+ ace-search "class TaskManager" --content --glob "**/*.rb"
163
+
164
+ # 4. Find usage
165
+ ace-search "TaskManager.new" --content
166
+ ```
167
+
168
+ ### Searching Configuration
169
+ ```bash
170
+ # Find YAML config files
171
+ ace-search "*.yml" --file --search-root .ace
172
+
173
+ # Search within config
174
+ ace-search "model: opus" --content --glob "**/*.yml"
175
+
176
+ # Find environment variables
177
+ ace-search "API_KEY" --content
178
+ ```
179
+
180
+ ### Exploring Code Structure
181
+ ```bash
182
+ # Find all requires
183
+ ace-search "require 'ace" --content --glob "**/*.rb"
184
+
185
+ # Find class definitions
186
+ ace-search "class.*< " --content --glob "**/*.rb"
187
+
188
+ # Find method definitions (in lib/ directory)
189
+ cd lib/ && ace-search "def " --content
190
+ ```
191
+
192
+ ### Debugging and Maintenance
193
+ ```bash
194
+ # Find TODOs and FIXMEs
195
+ ace-search "TODO|FIXME" --content
196
+
197
+ # Find deprecated code
198
+ ace-search "deprecated" --case-insensitive --content
199
+
200
+ # Find error handling
201
+ ace-search "rescue|raise" --content --glob "**/*.rb"
202
+ ```
203
+
204
+ ## Search Strategy
205
+
206
+ ### Progressive Refinement
207
+ 1. **Start broad**: Use auto mode to understand scope
208
+ ```bash
209
+ ace-search "notification"
210
+ ```
211
+
212
+ 2. **Identify patterns**: Look at results to understand structure
213
+ ```bash
214
+ ace-search "notification" --files-with-matches
215
+ ```
216
+
217
+ 3. **Narrow focus**: Add filters based on findings
218
+ ```bash
219
+ ace-search "class.*Notification" --content --glob "**/*.rb" --include "lib/**/*"
220
+ ```
221
+
222
+ 4. **Verify relevance**: Check sample results
223
+ ```bash
224
+ ace-search "Notification.new" --content --max-results 5
225
+ ```
226
+
227
+ ### Efficiency Tips
228
+ - Use `--file` for filename-only searches (faster)
229
+ - Change directories or use `--include` to limit scope
230
+ - Use `--glob` to filter file types
231
+ - Apply `--max-results` for initial exploration
232
+ - Leverage auto mode for intelligent detection
233
+
234
+ ## Response Format
235
+
236
+ ### Success Response
237
+ ```markdown
238
+ ## Search Summary
239
+ Found [N] matches for "[pattern]" across [M] files.
240
+
241
+ ## Key Results
242
+ - path/to/file.rb:42: [relevant match with context]
243
+ - another/file.md:15: [relevant match with context]
244
+
245
+ ## Patterns Observed
246
+ - [Common themes or structures]
247
+ - [Notable file/directory concentrations]
248
+
249
+ ## Suggestions
250
+ - Refine with: ace-search "[pattern]" --glob "**/*.ext" --include "path/**/*"
251
+ - Explore: [specific files or directories of interest]
252
+ ```
253
+
254
+ ### No Results Response
255
+ ```markdown
256
+ ## Search Summary
257
+ No matches found for "[pattern]".
258
+
259
+ ## Suggestions
260
+ - Try alternative terms or patterns
261
+ - Broaden scope: --search-root .
262
+ - Use case-insensitive: --case-insensitive
263
+ - Check different file types: --glob "**/*.ext"
264
+ ```
265
+
266
+ ### Large Result Set Response
267
+ ```markdown
268
+ ## Search Summary
269
+ Found [N] matches (showing first [M] due to limit).
270
+
271
+ ## Top Results
272
+ [Most relevant matches]
273
+
274
+ ## Refinement Suggestions
275
+ To narrow results, try:
276
+ - ace-search "[pattern]" --glob "**/*.rb"
277
+ - cd specific/path/ && ace-search "[pattern]"
278
+ - ace-search "[pattern]" --whole-word
279
+ - ace-search "[pattern]" --include "specific/path/**/*"
280
+ ```
281
+
282
+ ## Important Notes
283
+
284
+ - **Discovery Only**: This workflow searches but does not modify files
285
+ - **DWIM Mode**: Auto mode intelligently detects file vs content searches
286
+ - **Git Integration**: Supports scoping to staged/tracked/changed files
287
+ - **Preset Support**: Can use predefined search configurations
288
+ - **Performance**: Use specific modes and filters for faster searches
289
+ - **Results Limit**: Default max-results prevents overwhelming output
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Search
5
+ module Atoms
6
+ # Centralized debug logging for ace-search
7
+ #
8
+ # NOTE: This logger caches the ENV["DEBUG"] state on first use for performance
9
+ # and is intended for single-threaded, short-lived CLI processes.
10
+ #
11
+ # Usage:
12
+ # DebugLogger.log("message")
13
+ # DebugLogger.section("Title") do
14
+ # DebugLogger.log("detail 1")
15
+ # DebugLogger.log("detail 2")
16
+ # end
17
+ module DebugLogger
18
+ # Check if debug logging is enabled via DEBUG environment variable
19
+ #
20
+ # @return [Boolean] true if DEBUG is set to "1" or "true"
21
+ def self.enabled?
22
+ @enabled ||= begin
23
+ debug_value = ENV["DEBUG"]
24
+ debug_value == "1" || debug_value == "true"
25
+ end
26
+ end
27
+
28
+ # Log a debug message to stderr if debugging is enabled
29
+ #
30
+ # @param message [String] Message to log
31
+ # @param prefix [String] Prefix for the message (default: "DEBUG")
32
+ # @return [void]
33
+ def self.log(message, prefix: "DEBUG")
34
+ return unless enabled?
35
+ warn "#{prefix}: #{message}"
36
+ end
37
+
38
+ # Log a section with title and optional block for grouped output
39
+ #
40
+ # @param title [String] Section title
41
+ # @yield Optional block to execute within the section
42
+ # @return [void]
43
+ def self.section(title)
44
+ return unless enabled?
45
+
46
+ warn "=" * 60
47
+ warn "DEBUG: #{title}"
48
+ yield if block_given?
49
+ warn "=" * 60
50
+ end
51
+
52
+ # Reset the enabled cache (useful for testing)
53
+ #
54
+ # @return [void]
55
+ def self.reset!
56
+ @enabled = nil
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "shellwords"
5
+ require "timeout"
6
+ require_relative "debug_logger"
7
+
8
+ module Ace
9
+ module Search
10
+ module Atoms
11
+ # FdExecutor provides a safe wrapper around fd command
12
+ # This is an atom - pure function for executing fd commands
13
+ module FdExecutor
14
+ module_function
15
+
16
+ # Execute fd with given pattern and options
17
+ # @param pattern [String] Search pattern (can be glob or regex)
18
+ # @param options [Hash] Search options
19
+ # @return [Hash] Command result with success status and output
20
+ def execute(pattern = nil, options = {})
21
+ command = build_command(pattern, options)
22
+ # Read timeout from options (runtime override), then config, then fallback to 120 seconds
23
+ default_timeout = Ace::Search.config["timeout"] || 120
24
+ timeout_seconds = options.fetch(:timeout, default_timeout)
25
+
26
+ # Debug output
27
+ DebugLogger.section("FdExecutor") do
28
+ DebugLogger.log("Command: #{command}")
29
+ DebugLogger.log("Will chdir to: #{options[:search_path] || "(current directory)"}")
30
+ end
31
+
32
+ begin
33
+ # Change to search directory if specified, otherwise use current dir
34
+ # This ensures .gitignore, excludes, and includes are processed correctly
35
+ search_dir = options[:search_path] || "."
36
+
37
+ stdout, stderr, status = Timeout.timeout(timeout_seconds) do
38
+ Open3.capture3(command, chdir: search_dir)
39
+ end
40
+
41
+ {
42
+ success: status.success?,
43
+ stdout: stdout,
44
+ stderr: stderr,
45
+ exit_code: status.exitstatus,
46
+ command: command
47
+ }
48
+ rescue Timeout::Error
49
+ {
50
+ success: false,
51
+ error: "Command timed out after #{timeout_seconds} seconds",
52
+ exit_code: -1
53
+ }
54
+ rescue => e
55
+ {
56
+ success: false,
57
+ error: e.message,
58
+ exit_code: -1
59
+ }
60
+ end
61
+ end
62
+
63
+ # Check if fd is available
64
+ # @return [Boolean] True if fd is installed
65
+ def available?
66
+ stdout, _stderr, status = Open3.capture3("which fd")
67
+ status.success? && !stdout.strip.empty?
68
+ rescue
69
+ false
70
+ end
71
+
72
+ # Build fd command with proper escaping
73
+ # @param pattern [String, nil] Search pattern (nil for all files)
74
+ # @param options [Hash] Search options
75
+ # @return [String] Complete fd command
76
+ def build_command(pattern = nil, options = {})
77
+ args = ["fd"]
78
+
79
+ # Basic options
80
+ args << "--color=never" unless options[:color]
81
+ args << "--absolute-path" if options[:absolute_path]
82
+ args << "--follow" if options[:follow_symlinks]
83
+ args << "--hidden" if options[:include_hidden] || options[:hidden]
84
+ args << "--no-ignore" if options[:no_ignore]
85
+ args << "--no-ignore-vcs" if options[:no_ignore_vcs]
86
+
87
+ # Type filtering
88
+ case options[:fd_type]
89
+ when "f", "file"
90
+ args << "--type=file"
91
+ when "d", "directory"
92
+ args << "--type=directory"
93
+ when "l", "symlink"
94
+ args << "--type=symlink"
95
+ when "s", "socket"
96
+ args << "--type=socket"
97
+ when "p", "pipe"
98
+ args << "--type=pipe"
99
+ end
100
+
101
+ # Extension filtering
102
+ if options[:extension]
103
+ Array(options[:extension]).each { |ext| args << "--extension=#{ext}" }
104
+ end
105
+
106
+ # Size filtering
107
+ args << "--size=#{options[:size]}" if options[:size]
108
+
109
+ # Depth limiting
110
+ args << "--max-depth=#{options[:max_depth]}" if options[:max_depth]
111
+ args << "--min-depth=#{options[:min_depth]}" if options[:min_depth]
112
+
113
+ # Case sensitivity
114
+ args << "--ignore-case" if options[:ignore_case]
115
+ args << "--case-sensitive" if options[:case_sensitive]
116
+
117
+ # Max results
118
+ args << "--max-results=#{options[:max_results]}" if options[:max_results]
119
+
120
+ # Exclude patterns
121
+ if options[:exclude]
122
+ Array(options[:exclude]).each { |pattern| args << "--exclude=#{pattern}" }
123
+ end
124
+
125
+ # Search paths
126
+ # When search_path is set, we use chdir in execute(), so search current dir
127
+ paths = if options[:search_path]
128
+ ["."] # Will chdir to search_path, then search current dir
129
+ elsif options[:paths]
130
+ options[:paths]
131
+ else
132
+ ["."]
133
+ end
134
+
135
+ # Build the complete command
136
+ command_parts = args
137
+
138
+ # Add pattern with appropriate flag
139
+ if pattern
140
+ # Check if pattern looks like a glob
141
+ if pattern.include?("*") || pattern.include?("?") || pattern.include?("[")
142
+ command_parts << "--glob"
143
+ end
144
+ command_parts << Shellwords.escape(pattern)
145
+ end
146
+
147
+ command_parts.concat(paths.map { |p| Shellwords.escape(p) })
148
+
149
+ command_parts.join(" ")
150
+ end
151
+
152
+ # Get fd version
153
+ # @return [String, nil] Version string or nil if not available
154
+ def version
155
+ return nil unless available?
156
+
157
+ stdout, _stderr, status = Open3.capture3("fd --version")
158
+ if status.success?
159
+ version_match = stdout.match(/fd ([\d.]+)/)
160
+ version_match ? version_match[1] : nil
161
+ end
162
+ rescue
163
+ nil
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Search
5
+ module Atoms
6
+ # PatternAnalyzer provides intelligent analysis of search patterns for DWIM mode selection
7
+ # This is an atom - pure function for pattern analysis
8
+ module PatternAnalyzer
9
+ # File glob indicators
10
+ FILE_GLOB_PATTERNS = [
11
+ /^\*+\./, # *.extension
12
+ /\*\*\/\*/, # **/* recursive patterns
13
+ /^\w+\/\*+/, # dir/* patterns
14
+ /\.\w+$/, # .extension endings
15
+ /\/\*+$/, # ending with /*
16
+ /^\w+\*+\w*$/ # word* or *word patterns for filenames
17
+ ].freeze
18
+
19
+ # Content regex indicators
20
+ CONTENT_REGEX_PATTERNS = [
21
+ /[|+?{}()\[\]\\]/, # Regex metacharacters (removed * from here)
22
+ /def\s+\w+/, # Method definitions
23
+ /class\s+\w+/, # Class definitions
24
+ /function\s+\w+/, # Function definitions
25
+ /import\s+/, # Import statements
26
+ /require\s+/, # Require statements
27
+ /\b(TODO|FIXME|BUG|HACK)\b/, # Code annotations
28
+ /^\^/, # Line start anchor
29
+ /\$$/, # Line end anchor
30
+ /\\[bBdDsSwW]/ # Character class escapes
31
+ ].freeze
32
+
33
+ # Literal text indicators
34
+ LITERAL_INDICATORS = [
35
+ /^\w+$/, # Single word
36
+ /^[\w\s]+$/, # Words with spaces
37
+ /^"[^"]*"$/, # Quoted strings
38
+ /^'[^']*'$/ # Single quoted strings
39
+ ].freeze
40
+
41
+ module_function
42
+
43
+ # Analyze a pattern and determine its most likely type
44
+ # @param pattern [String] Pattern to analyze
45
+ # @return [Hash] Analysis result with type and confidence
46
+ def analyze_pattern(pattern)
47
+ return {type: :invalid, confidence: 0.0, reason: "Pattern is nil"} if pattern.nil?
48
+ return {type: :invalid, confidence: 0.0, reason: "Pattern is empty"} if pattern.empty?
49
+
50
+ clean_pattern = pattern.strip
51
+
52
+ # Check for file glob patterns
53
+ if file_glob_pattern?(clean_pattern)
54
+ return {
55
+ type: :file_glob,
56
+ confidence: calculate_file_glob_confidence(clean_pattern),
57
+ reason: "Contains file glob patterns",
58
+ suggested_tool: "fd"
59
+ }
60
+ end
61
+
62
+ # Check for content regex patterns
63
+ if content_regex_pattern?(clean_pattern)
64
+ return {
65
+ type: :content_regex,
66
+ confidence: calculate_content_regex_confidence(clean_pattern),
67
+ reason: "Contains regex metacharacters or code patterns",
68
+ suggested_tool: "rg"
69
+ }
70
+ end
71
+
72
+ # Check for literal patterns
73
+ if literal_pattern?(clean_pattern)
74
+ return {
75
+ type: :literal,
76
+ confidence: calculate_literal_confidence(clean_pattern),
77
+ reason: "Simple literal text pattern",
78
+ suggested_tool: "rg"
79
+ }
80
+ end
81
+
82
+ # Default to hybrid if unclear
83
+ {
84
+ type: :hybrid,
85
+ confidence: 0.5,
86
+ reason: "Pattern could match files or content",
87
+ suggested_tool: "both"
88
+ }
89
+ end
90
+
91
+ # Check if pattern looks like a file glob
92
+ def file_glob_pattern?(pattern)
93
+ FILE_GLOB_PATTERNS.any? { |regex| pattern.match?(regex) }
94
+ end
95
+
96
+ # Check if pattern looks like content regex
97
+ def content_regex_pattern?(pattern)
98
+ CONTENT_REGEX_PATTERNS.any? { |regex| pattern.match?(regex) }
99
+ end
100
+
101
+ # Check if pattern looks like literal text
102
+ def literal_pattern?(pattern)
103
+ LITERAL_INDICATORS.any? { |regex| pattern.match?(regex) }
104
+ end
105
+
106
+ # Suggest search mode based on pattern analysis
107
+ def suggest_search_mode(pattern, flags = {})
108
+ return :files if flags[:files_only] || flags[:name_only]
109
+ return :content if flags[:content_only]
110
+
111
+ analysis = analyze_pattern(pattern)
112
+
113
+ case analysis[:type]
114
+ when :file_glob
115
+ :files
116
+ when :content_regex, :literal
117
+ :content
118
+ when :hybrid
119
+ :both
120
+ else
121
+ :content
122
+ end
123
+ end
124
+
125
+ # Extract file extensions from a pattern if present
126
+ def extract_extensions(pattern)
127
+ extensions = []
128
+
129
+ # Match patterns like *.rb, **/*.js, etc.
130
+ extension_matches = pattern.scan(/\*+\.(\w+)/)
131
+ extensions.concat(extension_matches.flatten)
132
+
133
+ # Match explicit .ext at the end
134
+ if pattern.match?(/\.(\w+)$/)
135
+ extension_match = pattern.match(/\.(\w+)$/)
136
+ extensions << extension_match[1]
137
+ end
138
+
139
+ extensions.uniq
140
+ end
141
+
142
+ def calculate_file_glob_confidence(pattern)
143
+ confidence = 0.5
144
+
145
+ confidence += 0.3 if pattern.include?("*")
146
+ confidence += 0.2 if pattern.match?(/\.\w+$/)
147
+ confidence += 0.1 if pattern.include?("/")
148
+
149
+ [confidence, 1.0].min
150
+ end
151
+
152
+ def calculate_content_regex_confidence(pattern)
153
+ confidence = 0.5
154
+
155
+ metachar_count = pattern.scan(/[|+?{}()\[\]\\]/).length
156
+ confidence += metachar_count * 0.1
157
+
158
+ confidence += 0.2 if pattern.match?(/\b(def|class|function|import|require)\s+/)
159
+ confidence += 0.1 if pattern.match?(/^\^|\$$/)
160
+
161
+ [confidence, 1.0].min
162
+ end
163
+
164
+ def calculate_literal_confidence(pattern)
165
+ confidence = 0.6
166
+
167
+ confidence += 0.2 if pattern.match?(/^\w+$/)
168
+ confidence += 0.1 if pattern.length < 20
169
+ confidence += 0.3 if pattern.match?(/^["'][^"']*["']$/)
170
+
171
+ [confidence, 1.0].min
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end