codebase_index 0.2.1 → 0.3.1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +95 -300
  4. data/exe/codebase-index-mcp +3 -31
  5. data/exe/codebase-index-mcp-http +3 -31
  6. data/lib/codebase_index/ast/method_extractor.rb +3 -8
  7. data/lib/codebase_index/ast/node.rb +28 -0
  8. data/lib/codebase_index/ast/parser.rb +53 -92
  9. data/lib/codebase_index/builder.rb +67 -4
  10. data/lib/codebase_index/cache/cache_middleware.rb +199 -0
  11. data/lib/codebase_index/cache/cache_store.rb +264 -0
  12. data/lib/codebase_index/cache/redis_cache_store.rb +116 -0
  13. data/lib/codebase_index/cache/solid_cache_store.rb +111 -0
  14. data/lib/codebase_index/chunking/semantic_chunker.rb +29 -24
  15. data/lib/codebase_index/console/adapters/good_job_adapter.rb +7 -40
  16. data/lib/codebase_index/console/adapters/job_adapter.rb +68 -0
  17. data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +7 -40
  18. data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +7 -40
  19. data/lib/codebase_index/console/bridge.rb +7 -0
  20. data/lib/codebase_index/console/console_response_renderer.rb +3 -7
  21. data/lib/codebase_index/console/embedded_executor.rb +2 -1
  22. data/lib/codebase_index/console/server.rb +1 -4
  23. data/lib/codebase_index/dependency_graph.rb +28 -19
  24. data/lib/codebase_index/embedding/indexer.rb +18 -8
  25. data/lib/codebase_index/embedding/openai.rb +27 -6
  26. data/lib/codebase_index/embedding/provider.rb +29 -2
  27. data/lib/codebase_index/evaluation/evaluator.rb +5 -12
  28. data/lib/codebase_index/extractor.rb +40 -44
  29. data/lib/codebase_index/extractors/action_cable_extractor.rb +9 -36
  30. data/lib/codebase_index/extractors/callback_analyzer.rb +22 -8
  31. data/lib/codebase_index/extractors/controller_extractor.rb +3 -93
  32. data/lib/codebase_index/extractors/decorator_extractor.rb +7 -14
  33. data/lib/codebase_index/extractors/engine_extractor.rb +20 -1
  34. data/lib/codebase_index/extractors/graphql_extractor.rb +4 -29
  35. data/lib/codebase_index/extractors/job_extractor.rb +11 -6
  36. data/lib/codebase_index/extractors/lib_extractor.rb +0 -31
  37. data/lib/codebase_index/extractors/mailer_extractor.rb +15 -85
  38. data/lib/codebase_index/extractors/manager_extractor.rb +1 -15
  39. data/lib/codebase_index/extractors/model_extractor.rb +20 -53
  40. data/lib/codebase_index/extractors/phlex_extractor.rb +8 -8
  41. data/lib/codebase_index/extractors/policy_extractor.rb +1 -24
  42. data/lib/codebase_index/extractors/poro_extractor.rb +0 -17
  43. data/lib/codebase_index/extractors/serializer_extractor.rb +12 -7
  44. data/lib/codebase_index/extractors/service_extractor.rb +1 -38
  45. data/lib/codebase_index/extractors/shared_utility_methods.rb +183 -1
  46. data/lib/codebase_index/extractors/validator_extractor.rb +3 -17
  47. data/lib/codebase_index/extractors/view_component_extractor.rb +10 -9
  48. data/lib/codebase_index/filename_utils.rb +32 -0
  49. data/lib/codebase_index/flow_analysis/operation_extractor.rb +1 -4
  50. data/lib/codebase_index/formatting/base.rb +0 -10
  51. data/lib/codebase_index/graph_analyzer.rb +1 -1
  52. data/lib/codebase_index/mcp/bootstrapper.rb +58 -0
  53. data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +35 -34
  54. data/lib/codebase_index/mcp/renderers/plain_renderer.rb +29 -29
  55. data/lib/codebase_index/mcp/server.rb +59 -68
  56. data/lib/codebase_index/mcp/tool_response_renderer.rb +23 -0
  57. data/lib/codebase_index/notion/client.rb +2 -2
  58. data/lib/codebase_index/notion/mapper.rb +1 -0
  59. data/lib/codebase_index/notion/mappers/column_mapper.rb +3 -11
  60. data/lib/codebase_index/notion/mappers/model_mapper.rb +20 -23
  61. data/lib/codebase_index/notion/mappers/shared.rb +22 -0
  62. data/lib/codebase_index/observability/health_check.rb +0 -2
  63. data/lib/codebase_index/observability/structured_logger.rb +12 -30
  64. data/lib/codebase_index/operator/pipeline_guard.rb +0 -7
  65. data/lib/codebase_index/resilience/index_validator.rb +3 -21
  66. data/lib/codebase_index/retrieval/context_assembler.rb +19 -7
  67. data/lib/codebase_index/retrieval/query_classifier.rb +14 -12
  68. data/lib/codebase_index/retrieval/ranker.rb +6 -2
  69. data/lib/codebase_index/retrieval/search_executor.rb +8 -19
  70. data/lib/codebase_index/retriever.rb +1 -9
  71. data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +5 -25
  72. data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +6 -7
  73. data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +58 -53
  74. data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +11 -7
  75. data/lib/codebase_index/session_tracer/file_store.rb +1 -8
  76. data/lib/codebase_index/session_tracer/redis_store.rb +1 -7
  77. data/lib/codebase_index/session_tracer/session_flow_assembler.rb +4 -13
  78. data/lib/codebase_index/session_tracer/solid_cache_store.rb +1 -7
  79. data/lib/codebase_index/session_tracer/store.rb +14 -0
  80. data/lib/codebase_index/storage/metadata_store.rb +37 -10
  81. data/lib/codebase_index/storage/pgvector.rb +37 -5
  82. data/lib/codebase_index/storage/qdrant.rb +39 -6
  83. data/lib/codebase_index/storage/vector_store.rb +11 -0
  84. data/lib/codebase_index/temporal/snapshot_store.rb +14 -10
  85. data/lib/codebase_index/token_utils.rb +19 -0
  86. data/lib/codebase_index/version.rb +1 -1
  87. data/lib/codebase_index.rb +25 -6
  88. data/lib/tasks/codebase_index.rake +2 -2
  89. metadata +11 -2
@@ -71,13 +71,18 @@ module CodebaseIndex
71
71
 
72
72
  private
73
73
 
74
+ # Locate the source file for a mailer class.
75
+ #
76
+ # Convention path first, then introspection via {#resolve_source_location}
77
+ # which filters out vendor/node_modules paths.
78
+ #
79
+ # @param mailer [Class]
80
+ # @return [String]
74
81
  def source_file_for(mailer)
75
- if mailer.instance_methods(false).any?
76
- method = mailer.instance_methods(false).first
77
- mailer.instance_method(method).source_location&.first
78
- end || Rails.root.join("app/mailers/#{mailer.name.underscore}.rb").to_s
79
- rescue StandardError
80
- Rails.root.join("app/mailers/#{mailer.name.underscore}.rb").to_s
82
+ convention_path = Rails.root.join("app/mailers/#{mailer.name.underscore}.rb").to_s
83
+ return convention_path if File.exist?(convention_path)
84
+
85
+ resolve_source_location(mailer, app_root: Rails.root.to_s, fallback: convention_path)
81
86
  end
82
87
 
83
88
  # ──────────────────────────────────────────────────────────────────────
@@ -139,19 +144,10 @@ module CodebaseIndex
139
144
  end
140
145
 
141
146
  def extract_defaults(mailer)
142
- defaults = {}
143
-
144
- begin
145
- mailer_defaults = mailer.default
146
- defaults[:from] = mailer_defaults[:from] if mailer_defaults[:from]
147
- defaults[:reply_to] = mailer_defaults[:reply_to] if mailer_defaults[:reply_to]
148
- defaults[:cc] = mailer_defaults[:cc] if mailer_defaults[:cc]
149
- defaults[:bcc] = mailer_defaults[:bcc] if mailer_defaults[:bcc]
150
- rescue StandardError
151
- # Defaults not accessible
152
- end
153
-
154
- defaults
147
+ mailer_defaults = mailer.default
148
+ mailer_defaults.slice(:from, :reply_to, :cc, :bcc).compact
149
+ rescue StandardError
150
+ {}
155
151
  end
156
152
 
157
153
  def extract_callbacks(mailer)
@@ -172,72 +168,6 @@ module CodebaseIndex
172
168
  []
173
169
  end
174
170
 
175
- # Extract :only/:except action lists and :if/:unless conditions from a callback.
176
- #
177
- # Modern Rails (4.2+) stores conditions in @if/@unless ivar arrays.
178
- # ActionFilter objects hold action Sets; other conditions are procs/symbols.
179
- #
180
- # @param callback [ActiveSupport::Callbacks::Callback]
181
- # @return [Array(Array<String>, Array<String>, Array<String>, Array<String>)]
182
- # [only_actions, except_actions, if_labels, unless_labels]
183
- def extract_callback_conditions(callback)
184
- if_conditions = callback.instance_variable_get(:@if) || []
185
- unless_conditions = callback.instance_variable_get(:@unless) || []
186
-
187
- only = []
188
- except = []
189
- if_labels = []
190
- unless_labels = []
191
-
192
- if_conditions.each do |cond|
193
- actions = extract_action_filter_actions(cond)
194
- if actions
195
- only.concat(actions)
196
- else
197
- if_labels << condition_label(cond)
198
- end
199
- end
200
-
201
- unless_conditions.each do |cond|
202
- actions = extract_action_filter_actions(cond)
203
- if actions
204
- except.concat(actions)
205
- else
206
- unless_labels << condition_label(cond)
207
- end
208
- end
209
-
210
- [only, except, if_labels, unless_labels]
211
- end
212
-
213
- # Extract action names from an ActionFilter-like condition object.
214
- # Duck-types on the @actions ivar being a Set, avoiding dependence
215
- # on private class names across Rails versions.
216
- #
217
- # @param condition [Object] A condition from the callback's @if/@unless array
218
- # @return [Array<String>, nil] Action names, or nil if not an ActionFilter
219
- def extract_action_filter_actions(condition)
220
- return nil unless condition.instance_variable_defined?(:@actions)
221
-
222
- actions = condition.instance_variable_get(:@actions)
223
- return nil unless actions.is_a?(Set)
224
-
225
- actions.to_a
226
- end
227
-
228
- # Human-readable label for a non-ActionFilter condition.
229
- #
230
- # @param condition [Object] A proc, symbol, or other condition
231
- # @return [String]
232
- def condition_label(condition)
233
- case condition
234
- when Symbol then ":#{condition}"
235
- when Proc then 'Proc'
236
- when String then condition
237
- else condition.class.name
238
- end
239
- end
240
-
241
171
  def extract_layout(mailer, source)
242
172
  # From class definition
243
173
  return ::Regexp.last_match(1) if source =~ /layout\s+['":](\w+)/
@@ -53,7 +53,7 @@ module CodebaseIndex
53
53
  # @return [ExtractedUnit, nil] The extracted unit or nil if not a manager
54
54
  def extract_manager_file(file_path)
55
55
  source = File.read(file_path)
56
- class_name = extract_class_name(file_path, source)
56
+ class_name = extract_class_name(file_path, source, 'managers')
57
57
 
58
58
  return nil unless class_name
59
59
  return nil unless manager_file?(source)
@@ -81,16 +81,6 @@ module CodebaseIndex
81
81
  # Class Discovery
82
82
  # ──────────────────────────────────────────────────────────────────────
83
83
 
84
- def extract_class_name(file_path, source)
85
- return ::Regexp.last_match(1) if source =~ /^\s*class\s+([\w:]+)/
86
-
87
- file_path
88
- .sub("#{Rails.root}/", '')
89
- .sub(%r{^app/managers/}, '')
90
- .sub('.rb', '')
91
- .camelize
92
- end
93
-
94
84
  def manager_file?(source)
95
85
  source.match?(/< SimpleDelegator/) ||
96
86
  source.match?(/< DelegateClass\(/) ||
@@ -178,10 +168,6 @@ module CodebaseIndex
178
168
  source.scan(/def\s+(\w+[?!=]?).*?\n.*?super/m).flatten
179
169
  end
180
170
 
181
- def extract_custom_errors(source)
182
- source.scan(/class\s+(\w+(?:Error|Exception))\s*</).flatten
183
- end
184
-
185
171
  # ──────────────────────────────────────────────────────────────────────
186
172
  # Dependency Extraction
187
173
  # ──────────────────────────────────────────────────────────────────────
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'digest'
4
4
  require_relative '../ast/parser'
5
+ require_relative 'shared_utility_methods'
5
6
  require_relative 'shared_dependency_scanner'
6
7
  require_relative 'callback_analyzer'
7
8
 
@@ -23,15 +24,17 @@ module CodebaseIndex
23
24
  # user_unit = units.find { |u| u.identifier == "User" }
24
25
  #
25
26
  class ModelExtractor
27
+ include SharedUtilityMethods
26
28
  include SharedDependencyScanner
27
29
 
28
- AR_INTERNAL_METHOD_PATTERNS = [
29
- /\A_/, # _run_save_callbacks, _validators, etc.
30
- /\Aautosave_associated_records_for_/, # autosave_associated_records_for_comments
31
- /\Avalidate_associated_records_for_/, # validate_associated_records_for_comments
32
- /\Aafter_(?:add|remove)_for_/, # collection callbacks
33
- /\Abefore_(?:add|remove)_for_/ # collection callbacks
34
- ].freeze
30
+ # Single combined regex for filtering AR-generated internal methods.
31
+ # Replaces 5 separate patterns with one alternation for O(1) matching.
32
+ AR_INTERNAL_METHOD_PATTERN = /\A(?:
33
+ _ | # _run_save_callbacks, _validators, etc.
34
+ autosave_associated_records_for_ | # autosave_associated_records_for_comments
35
+ validate_associated_records_for_ | # validate_associated_records_for_comments
36
+ (?:after|before)_(?:add|remove)_for_ # collection callbacks
37
+ )/x
35
38
 
36
39
  def initialize
37
40
  @concern_cache = {}
@@ -84,38 +87,14 @@ module CodebaseIndex
84
87
 
85
88
  # Find the source file for a model, handling STI and namespacing.
86
89
  #
87
- # Falls back to convention-based path when reflection points outside
88
- # the app (e.g., ActiveRecord::Core#initialize for models that don't
89
- # override initialize).
90
+ # Uses convention path first (most reliable for models), then falls
91
+ # back to introspection via {#resolve_source_location} which filters
92
+ # out vendor/node_modules paths.
90
93
  def source_file_for(model)
91
- app_root = Rails.root.to_s
92
94
  convention_path = Rails.root.join("app/models/#{model.name.underscore}.rb").to_s
93
-
94
- # Tier 1: Instance methods defined directly on this model
95
- model.instance_methods(false).each do |method_name|
96
- loc = model.instance_method(method_name).source_location&.first
97
- return loc if loc&.start_with?(app_root)
98
- end
99
-
100
- # Tier 2: Class/singleton methods (catches models with only scopes)
101
- model.methods(false).each do |method_name|
102
- loc = model.method(method_name).source_location&.first
103
- return loc if loc&.start_with?(app_root)
104
- end
105
-
106
- # Tier 3: Convention path if file exists
107
95
  return convention_path if File.exist?(convention_path)
108
96
 
109
- # Tier 4: const_source_location (Ruby 3.0+)
110
- if Object.respond_to?(:const_source_location)
111
- loc = Object.const_source_location(model.name)&.first
112
- return loc if loc&.start_with?(app_root)
113
- end
114
-
115
- # Tier 5: Always return convention path — never a gem path
116
- convention_path
117
- rescue StandardError
118
- convention_path
97
+ resolve_source_location(model, app_root: Rails.root.to_s, fallback: convention_path)
119
98
  end
120
99
 
121
100
  # Detect Rails-generated HABTM join models (e.g., Product::HABTM_Categories)
@@ -241,7 +220,7 @@ module CodebaseIndex
241
220
  # Skip obvious non-app modules (from gems/stdlib)
242
221
  if Object.respond_to?(:const_source_location)
243
222
  loc = Object.const_source_location(mod.name)
244
- next false if loc && !loc.first&.start_with?(app_root)
223
+ next false if loc && !app_source?(loc.first, app_root)
245
224
  end
246
225
 
247
226
  # Include if it's in app/models/concerns or app/controllers/concerns
@@ -258,16 +237,18 @@ module CodebaseIndex
258
237
  # @param mod [Module] The module to check
259
238
  # @return [Boolean] true if the module is defined within Rails.root
260
239
  def defined_in_app?(mod)
240
+ app_root = Rails.root.to_s
241
+
261
242
  # Fast path: const_source_location is cheaper than iterating methods
262
243
  if mod.respond_to?(:const_source_location) || Object.respond_to?(:const_source_location)
263
244
  loc = Object.const_source_location(mod.name)
264
- return loc.first.start_with?(Rails.root.to_s) if loc
245
+ return app_source?(loc.first, app_root) if loc
265
246
  end
266
247
 
267
248
  # Slow path: check instance method source locations
268
249
  mod.instance_methods(false).any? do |method|
269
250
  loc = mod.instance_method(method).source_location&.first
270
- loc&.start_with?(Rails.root.to_s)
251
+ app_source?(loc, app_root)
271
252
  end
272
253
  rescue StandardError
273
254
  false
@@ -836,19 +817,6 @@ module CodebaseIndex
836
817
  # Condition & Filter Helpers
837
818
  # ──────────────────────────────────────────────────────────────────────
838
819
 
839
- # Human-readable label for a condition (Symbol, Proc, String, etc.)
840
- #
841
- # @param condition [Object] A proc, symbol, or other condition
842
- # @return [String]
843
- def condition_label(condition)
844
- case condition
845
- when Symbol then ":#{condition}"
846
- when Proc then 'Proc'
847
- when String then condition
848
- else condition.class.name
849
- end
850
- end
851
-
852
820
  # Build conditions hash from validator options, converting Procs to labels
853
821
  #
854
822
  # @param validator [ActiveModel::Validator]
@@ -906,8 +874,7 @@ module CodebaseIndex
906
874
  # @return [Array<Symbol>]
907
875
  def filter_instance_methods(methods)
908
876
  methods.reject do |method_name|
909
- name = method_name.to_s
910
- AR_INTERNAL_METHOD_PATTERNS.any? { |pattern| pattern.match?(name) }
877
+ AR_INTERNAL_METHOD_PATTERN.match?(method_name.to_s)
911
878
  end
912
879
  end
913
880
 
@@ -96,8 +96,14 @@ module CodebaseIndex
96
96
  defined?(ViewComponent::Base) && klass < ViewComponent::Base
97
97
  end
98
98
 
99
+ # Locate the source file for a Phlex component class.
100
+ #
101
+ # Convention paths first, then introspection via {#resolve_source_location}
102
+ # which filters out vendor/node_modules paths.
103
+ #
104
+ # @param component [Class]
105
+ # @return [String, nil]
99
106
  def source_file_for(component)
100
- # Try common locations
101
107
  possible_paths = [
102
108
  Rails.root.join("app/views/components/#{component.name.underscore}.rb"),
103
109
  Rails.root.join("app/components/#{component.name.underscore}.rb"),
@@ -107,13 +113,7 @@ module CodebaseIndex
107
113
  found = possible_paths.find { |p| File.exist?(p) }
108
114
  return found.to_s if found
109
115
 
110
- # Try to get from method source location
111
- if component.instance_methods(false).any?
112
- method = component.instance_methods(false).first
113
- component.instance_method(method).source_location&.first
114
- end
115
- rescue StandardError
116
- nil
116
+ resolve_source_location(component, app_root: Rails.root.to_s, fallback: nil)
117
117
  end
118
118
 
119
119
  def read_source(file_path)
@@ -56,7 +56,7 @@ module CodebaseIndex
56
56
  # @return [ExtractedUnit, nil] The extracted unit or nil if not a policy
57
57
  def extract_policy_file(file_path)
58
58
  source = File.read(file_path)
59
- class_name = extract_class_name(file_path, source)
59
+ class_name = extract_class_name(file_path, source, 'policies')
60
60
 
61
61
  return nil unless class_name
62
62
  return nil if skip_file?(source)
@@ -80,25 +80,6 @@ module CodebaseIndex
80
80
 
81
81
  private
82
82
 
83
- # ──────────────────────────────────────────────────────────────────────
84
- # Class Discovery
85
- # ──────────────────────────────────────────────────────────────────────
86
-
87
- def extract_class_name(file_path, source)
88
- return ::Regexp.last_match(1) if source =~ /^\s*class\s+([\w:]+)/
89
-
90
- file_path
91
- .sub("#{Rails.root}/", '')
92
- .sub(%r{^app/policies/}, '')
93
- .sub('.rb', '')
94
- .camelize
95
- end
96
-
97
- def skip_file?(source)
98
- # Skip module-only files (concerns, base modules)
99
- source.match?(/^\s*module\s+\w+\s*$/) && !source.match?(/^\s*class\s+/)
100
- end
101
-
102
83
  # ──────────────────────────────────────────────────────────────────────
103
84
  # Source Annotation
104
85
  # ──────────────────────────────────────────────────────────────────────
@@ -189,10 +170,6 @@ module CodebaseIndex
189
170
  source.match?(/attr_reader\s+:user\s*,\s*:record/)
190
171
  end
191
172
 
192
- def extract_custom_errors(source)
193
- source.scan(/class\s+(\w+(?:Error|Exception))\s*</).flatten
194
- end
195
-
196
173
  # ──────────────────────────────────────────────────────────────────────
197
174
  # Dependency Extraction
198
175
  # ──────────────────────────────────────────────────────────────────────
@@ -212,23 +212,6 @@ module CodebaseIndex
212
212
  }
213
213
  end
214
214
 
215
- # Extract the parent class name from a class definition.
216
- #
217
- # @param source [String] Ruby source code
218
- # @return [String, nil] Parent class name or nil
219
- def extract_parent_class(source)
220
- match = source.match(/^\s*class\s+[\w:]+\s*<\s*([\w:]+)/)
221
- match ? match[1] : nil
222
- end
223
-
224
- # Count non-blank, non-comment lines of code.
225
- #
226
- # @param source [String] Ruby source code
227
- # @return [Integer] LOC count
228
- def count_loc(source)
229
- source.lines.count { |l| l.strip.length.positive? && !l.strip.start_with?('#') }
230
- end
231
-
232
215
  # ──────────────────────────────────────────────────────────────────────
233
216
  # Dependency Extraction
234
217
  # ──────────────────────────────────────────────────────────────────────
@@ -171,13 +171,18 @@ module CodebaseIndex
171
171
  source.match?(/view\s+:/) # Blueprinter views
172
172
  end
173
173
 
174
+ # Locate the source file for a serializer class.
175
+ #
176
+ # Convention path first, then introspection via {#resolve_source_location}
177
+ # which filters out vendor/node_modules paths.
178
+ #
179
+ # @param klass [Class]
180
+ # @return [String, nil]
174
181
  def source_file_for(klass)
175
- methods = klass.instance_methods(false)
176
- if methods.any?
177
- klass.instance_method(methods.first).source_location&.first
178
- end || Rails.root.join("app/serializers/#{klass.name.underscore}.rb").to_s
179
- rescue StandardError
180
- nil
182
+ convention_path = Rails.root.join("app/serializers/#{klass.name.underscore}.rb").to_s
183
+ return convention_path if File.exist?(convention_path)
184
+
185
+ resolve_source_location(klass, app_root: Rails.root.to_s, fallback: convention_path)
181
186
  end
182
187
 
183
188
  # ──────────────────────────────────────────────────────────────────────
@@ -309,7 +314,7 @@ module CodebaseIndex
309
314
 
310
315
  def extract_views(source)
311
316
  # Blueprinter views: `view :extended do`
312
- source.scan(/view\s+:(\w+)/).flatten.map { |v| v }
317
+ source.scan(/view\s+:(\w+)/).flatten
313
318
  end
314
319
 
315
320
  # ──────────────────────────────────────────────────────────────────────
@@ -57,7 +57,7 @@ module CodebaseIndex
57
57
  # @return [ExtractedUnit, nil] The extracted unit or nil if not a service
58
58
  def extract_service_file(file_path)
59
59
  source = File.read(file_path)
60
- class_name = extract_class_name(file_path, source)
60
+ class_name = extract_class_name(file_path, source, '(?:services|interactors|operations|commands|use_cases)')
61
61
 
62
62
  return nil unless class_name
63
63
  return nil if skip_file?(source)
@@ -81,29 +81,6 @@ module CodebaseIndex
81
81
 
82
82
  private
83
83
 
84
- # ──────────────────────────────────────────────────────────────────────
85
- # Class Discovery
86
- # ──────────────────────────────────────────────────────────────────────
87
-
88
- def extract_class_name(file_path, source)
89
- # Try to extract from source first (handles nested modules)
90
- return ::Regexp.last_match(1) if source =~ /^\s*class\s+([\w:]+)/
91
-
92
- # Fall back to convention
93
- relative_path = file_path.sub("#{Rails.root}/", '')
94
-
95
- # app/services/payments/stripe_service.rb -> Payments::StripeService
96
- relative_path
97
- .sub(%r{^app/(services|interactors|operations|commands|use_cases)/}, '')
98
- .sub('.rb', '')
99
- .camelize
100
- end
101
-
102
- def skip_file?(source)
103
- # Skip module-only files (concerns, base modules)
104
- source.match?(/^\s*module\s+\w+\s*$/) && !source.match?(/^\s*class\s+/)
105
- end
106
-
107
84
  # ──────────────────────────────────────────────────────────────────────
108
85
  # Source Annotation
109
86
  # ──────────────────────────────────────────────────────────────────────
@@ -123,16 +100,6 @@ module CodebaseIndex
123
100
  annotation + source
124
101
  end
125
102
 
126
- def detect_entry_points(source)
127
- points = []
128
- points << 'call' if source.match?(/def (self\.)?call\b/)
129
- points << 'perform' if source.match?(/def (self\.)?perform\b/)
130
- points << 'execute' if source.match?(/def (self\.)?execute\b/)
131
- points << 'run' if source.match?(/def (self\.)?run\b/)
132
- points << 'process' if source.match?(/def (self\.)?process\b/)
133
- points.empty? ? ['unknown'] : points
134
- end
135
-
136
103
  # ──────────────────────────────────────────────────────────────────────
137
104
  # Metadata Extraction
138
105
  # ──────────────────────────────────────────────────────────────────────
@@ -188,10 +155,6 @@ module CodebaseIndex
188
155
  deps.uniq
189
156
  end
190
157
 
191
- def extract_custom_errors(source)
192
- source.scan(/class\s+(\w+(?:Error|Exception))\s*</).flatten
193
- end
194
-
195
158
  def extract_rescue_handlers(source)
196
159
  source.scan(/rescue\s+([\w:]+)/).flatten.uniq
197
160
  end