codebase_index 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/CODE_OF_CONDUCT.md +83 -0
- data/CONTRIBUTING.md +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +481 -0
- data/exe/codebase-console-mcp +22 -0
- data/exe/codebase-index-mcp +61 -0
- data/exe/codebase-index-mcp-http +64 -0
- data/exe/codebase-index-mcp-start +58 -0
- data/lib/codebase_index/ast/call_site_extractor.rb +106 -0
- data/lib/codebase_index/ast/method_extractor.rb +76 -0
- data/lib/codebase_index/ast/node.rb +88 -0
- data/lib/codebase_index/ast/parser.rb +653 -0
- data/lib/codebase_index/ast.rb +6 -0
- data/lib/codebase_index/builder.rb +137 -0
- data/lib/codebase_index/chunking/chunk.rb +84 -0
- data/lib/codebase_index/chunking/semantic_chunker.rb +290 -0
- data/lib/codebase_index/console/adapters/cache_adapter.rb +58 -0
- data/lib/codebase_index/console/adapters/good_job_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +66 -0
- data/lib/codebase_index/console/audit_logger.rb +75 -0
- data/lib/codebase_index/console/bridge.rb +170 -0
- data/lib/codebase_index/console/confirmation.rb +90 -0
- data/lib/codebase_index/console/connection_manager.rb +173 -0
- data/lib/codebase_index/console/console_response_renderer.rb +78 -0
- data/lib/codebase_index/console/model_validator.rb +81 -0
- data/lib/codebase_index/console/safe_context.rb +82 -0
- data/lib/codebase_index/console/server.rb +557 -0
- data/lib/codebase_index/console/sql_validator.rb +172 -0
- data/lib/codebase_index/console/tools/tier1.rb +118 -0
- data/lib/codebase_index/console/tools/tier2.rb +117 -0
- data/lib/codebase_index/console/tools/tier3.rb +110 -0
- data/lib/codebase_index/console/tools/tier4.rb +79 -0
- data/lib/codebase_index/coordination/pipeline_lock.rb +109 -0
- data/lib/codebase_index/cost_model/embedding_cost.rb +88 -0
- data/lib/codebase_index/cost_model/estimator.rb +128 -0
- data/lib/codebase_index/cost_model/provider_pricing.rb +67 -0
- data/lib/codebase_index/cost_model/storage_cost.rb +52 -0
- data/lib/codebase_index/cost_model.rb +22 -0
- data/lib/codebase_index/db/migrations/001_create_units.rb +38 -0
- data/lib/codebase_index/db/migrations/002_create_edges.rb +35 -0
- data/lib/codebase_index/db/migrations/003_create_embeddings.rb +37 -0
- data/lib/codebase_index/db/migrations/004_create_snapshots.rb +45 -0
- data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +40 -0
- data/lib/codebase_index/db/migrator.rb +71 -0
- data/lib/codebase_index/db/schema_version.rb +73 -0
- data/lib/codebase_index/dependency_graph.rb +227 -0
- data/lib/codebase_index/embedding/indexer.rb +130 -0
- data/lib/codebase_index/embedding/openai.rb +105 -0
- data/lib/codebase_index/embedding/provider.rb +135 -0
- data/lib/codebase_index/embedding/text_preparer.rb +112 -0
- data/lib/codebase_index/evaluation/baseline_runner.rb +115 -0
- data/lib/codebase_index/evaluation/evaluator.rb +146 -0
- data/lib/codebase_index/evaluation/metrics.rb +79 -0
- data/lib/codebase_index/evaluation/query_set.rb +148 -0
- data/lib/codebase_index/evaluation/report_generator.rb +90 -0
- data/lib/codebase_index/extracted_unit.rb +145 -0
- data/lib/codebase_index/extractor.rb +956 -0
- data/lib/codebase_index/extractors/action_cable_extractor.rb +228 -0
- data/lib/codebase_index/extractors/ast_source_extraction.rb +46 -0
- data/lib/codebase_index/extractors/behavioral_profile.rb +309 -0
- data/lib/codebase_index/extractors/caching_extractor.rb +261 -0
- data/lib/codebase_index/extractors/callback_analyzer.rb +232 -0
- data/lib/codebase_index/extractors/concern_extractor.rb +253 -0
- data/lib/codebase_index/extractors/configuration_extractor.rb +219 -0
- data/lib/codebase_index/extractors/controller_extractor.rb +494 -0
- data/lib/codebase_index/extractors/database_view_extractor.rb +278 -0
- data/lib/codebase_index/extractors/decorator_extractor.rb +260 -0
- data/lib/codebase_index/extractors/engine_extractor.rb +204 -0
- data/lib/codebase_index/extractors/event_extractor.rb +211 -0
- data/lib/codebase_index/extractors/factory_extractor.rb +289 -0
- data/lib/codebase_index/extractors/graphql_extractor.rb +917 -0
- data/lib/codebase_index/extractors/i18n_extractor.rb +117 -0
- data/lib/codebase_index/extractors/job_extractor.rb +369 -0
- data/lib/codebase_index/extractors/lib_extractor.rb +249 -0
- data/lib/codebase_index/extractors/mailer_extractor.rb +339 -0
- data/lib/codebase_index/extractors/manager_extractor.rb +202 -0
- data/lib/codebase_index/extractors/middleware_extractor.rb +133 -0
- data/lib/codebase_index/extractors/migration_extractor.rb +469 -0
- data/lib/codebase_index/extractors/model_extractor.rb +960 -0
- data/lib/codebase_index/extractors/phlex_extractor.rb +252 -0
- data/lib/codebase_index/extractors/policy_extractor.rb +214 -0
- data/lib/codebase_index/extractors/poro_extractor.rb +246 -0
- data/lib/codebase_index/extractors/pundit_extractor.rb +223 -0
- data/lib/codebase_index/extractors/rails_source_extractor.rb +473 -0
- data/lib/codebase_index/extractors/rake_task_extractor.rb +343 -0
- data/lib/codebase_index/extractors/route_extractor.rb +181 -0
- data/lib/codebase_index/extractors/scheduled_job_extractor.rb +331 -0
- data/lib/codebase_index/extractors/serializer_extractor.rb +334 -0
- data/lib/codebase_index/extractors/service_extractor.rb +254 -0
- data/lib/codebase_index/extractors/shared_dependency_scanner.rb +91 -0
- data/lib/codebase_index/extractors/shared_utility_methods.rb +99 -0
- data/lib/codebase_index/extractors/state_machine_extractor.rb +398 -0
- data/lib/codebase_index/extractors/test_mapping_extractor.rb +225 -0
- data/lib/codebase_index/extractors/validator_extractor.rb +225 -0
- data/lib/codebase_index/extractors/view_component_extractor.rb +310 -0
- data/lib/codebase_index/extractors/view_template_extractor.rb +261 -0
- data/lib/codebase_index/feedback/gap_detector.rb +89 -0
- data/lib/codebase_index/feedback/store.rb +119 -0
- data/lib/codebase_index/flow_analysis/operation_extractor.rb +209 -0
- data/lib/codebase_index/flow_analysis/response_code_mapper.rb +154 -0
- data/lib/codebase_index/flow_assembler.rb +290 -0
- data/lib/codebase_index/flow_document.rb +191 -0
- data/lib/codebase_index/flow_precomputer.rb +102 -0
- data/lib/codebase_index/formatting/base.rb +40 -0
- data/lib/codebase_index/formatting/claude_adapter.rb +98 -0
- data/lib/codebase_index/formatting/generic_adapter.rb +56 -0
- data/lib/codebase_index/formatting/gpt_adapter.rb +64 -0
- data/lib/codebase_index/formatting/human_adapter.rb +78 -0
- data/lib/codebase_index/graph_analyzer.rb +374 -0
- data/lib/codebase_index/mcp/index_reader.rb +394 -0
- data/lib/codebase_index/mcp/renderers/claude_renderer.rb +81 -0
- data/lib/codebase_index/mcp/renderers/json_renderer.rb +17 -0
- data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +352 -0
- data/lib/codebase_index/mcp/renderers/plain_renderer.rb +240 -0
- data/lib/codebase_index/mcp/server.rb +935 -0
- data/lib/codebase_index/mcp/tool_response_renderer.rb +62 -0
- data/lib/codebase_index/model_name_cache.rb +51 -0
- data/lib/codebase_index/notion/client.rb +217 -0
- data/lib/codebase_index/notion/exporter.rb +219 -0
- data/lib/codebase_index/notion/mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/column_mapper.rb +65 -0
- data/lib/codebase_index/notion/mappers/migration_mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/model_mapper.rb +164 -0
- data/lib/codebase_index/notion/rate_limiter.rb +68 -0
- data/lib/codebase_index/observability/health_check.rb +81 -0
- data/lib/codebase_index/observability/instrumentation.rb +34 -0
- data/lib/codebase_index/observability/structured_logger.rb +75 -0
- data/lib/codebase_index/operator/error_escalator.rb +81 -0
- data/lib/codebase_index/operator/pipeline_guard.rb +99 -0
- data/lib/codebase_index/operator/status_reporter.rb +80 -0
- data/lib/codebase_index/railtie.rb +26 -0
- data/lib/codebase_index/resilience/circuit_breaker.rb +99 -0
- data/lib/codebase_index/resilience/index_validator.rb +185 -0
- data/lib/codebase_index/resilience/retryable_provider.rb +108 -0
- data/lib/codebase_index/retrieval/context_assembler.rb +249 -0
- data/lib/codebase_index/retrieval/query_classifier.rb +131 -0
- data/lib/codebase_index/retrieval/ranker.rb +273 -0
- data/lib/codebase_index/retrieval/search_executor.rb +327 -0
- data/lib/codebase_index/retriever.rb +160 -0
- data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +190 -0
- data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +78 -0
- data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +18 -0
- data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +275 -0
- data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +143 -0
- data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +139 -0
- data/lib/codebase_index/ruby_analyzer.rb +87 -0
- data/lib/codebase_index/session_tracer/file_store.rb +111 -0
- data/lib/codebase_index/session_tracer/middleware.rb +143 -0
- data/lib/codebase_index/session_tracer/redis_store.rb +112 -0
- data/lib/codebase_index/session_tracer/session_flow_assembler.rb +263 -0
- data/lib/codebase_index/session_tracer/session_flow_document.rb +223 -0
- data/lib/codebase_index/session_tracer/solid_cache_store.rb +145 -0
- data/lib/codebase_index/session_tracer/store.rb +67 -0
- data/lib/codebase_index/storage/graph_store.rb +120 -0
- data/lib/codebase_index/storage/metadata_store.rb +169 -0
- data/lib/codebase_index/storage/pgvector.rb +163 -0
- data/lib/codebase_index/storage/qdrant.rb +172 -0
- data/lib/codebase_index/storage/vector_store.rb +156 -0
- data/lib/codebase_index/temporal/snapshot_store.rb +341 -0
- data/lib/codebase_index/version.rb +5 -0
- data/lib/codebase_index.rb +223 -0
- data/lib/generators/codebase_index/install_generator.rb +32 -0
- data/lib/generators/codebase_index/pgvector_generator.rb +37 -0
- data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +15 -0
- data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +43 -0
- data/lib/tasks/codebase_index.rake +583 -0
- data/lib/tasks/codebase_index_evaluation.rake +115 -0
- metadata +252 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'shared_utility_methods'
|
|
4
|
+
require_relative 'shared_dependency_scanner'
|
|
5
|
+
|
|
6
|
+
module CodebaseIndex
|
|
7
|
+
module Extractors
|
|
8
|
+
# ManagerExtractor handles SimpleDelegator subclass extraction.
|
|
9
|
+
#
|
|
10
|
+
# Manager/delegator objects wrap a model and provide a richer interface
|
|
11
|
+
# for specific contexts (e.g., OrderManager wrapping Order with
|
|
12
|
+
# checkout-specific methods). They live in `app/managers/`.
|
|
13
|
+
#
|
|
14
|
+
# We extract:
|
|
15
|
+
# - Wrapped model (via SimpleDelegator superclass or initializer)
|
|
16
|
+
# - Public methods (the manager's added interface)
|
|
17
|
+
# - Delegation chain (what gets delegated vs overridden)
|
|
18
|
+
# - Dependencies (what models/services they reference)
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# extractor = ManagerExtractor.new
|
|
22
|
+
# units = extractor.extract_all
|
|
23
|
+
# order_mgr = units.find { |u| u.identifier == "OrderManager" }
|
|
24
|
+
#
|
|
25
|
+
class ManagerExtractor
|
|
26
|
+
include SharedUtilityMethods
|
|
27
|
+
include SharedDependencyScanner
|
|
28
|
+
|
|
29
|
+
# Directories to scan for manager/delegator objects
|
|
30
|
+
MANAGER_DIRECTORIES = %w[
|
|
31
|
+
app/managers
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@directories = MANAGER_DIRECTORIES.map { |d| Rails.root.join(d) }
|
|
36
|
+
.select(&:directory?)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Extract all manager/delegator objects
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<ExtractedUnit>] List of manager units
|
|
42
|
+
def extract_all
|
|
43
|
+
@directories.flat_map do |dir|
|
|
44
|
+
Dir[dir.join('**/*.rb')].filter_map do |file|
|
|
45
|
+
extract_manager_file(file)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Extract a single manager file
|
|
51
|
+
#
|
|
52
|
+
# @param file_path [String] Path to the manager file
|
|
53
|
+
# @return [ExtractedUnit, nil] The extracted unit or nil if not a manager
|
|
54
|
+
def extract_manager_file(file_path)
|
|
55
|
+
source = File.read(file_path)
|
|
56
|
+
class_name = extract_class_name(file_path, source)
|
|
57
|
+
|
|
58
|
+
return nil unless class_name
|
|
59
|
+
return nil unless manager_file?(source)
|
|
60
|
+
|
|
61
|
+
unit = ExtractedUnit.new(
|
|
62
|
+
type: :manager,
|
|
63
|
+
identifier: class_name,
|
|
64
|
+
file_path: file_path
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
unit.namespace = extract_namespace(class_name)
|
|
68
|
+
unit.source_code = annotate_source(source, class_name)
|
|
69
|
+
unit.metadata = extract_metadata(source, class_name)
|
|
70
|
+
unit.dependencies = extract_dependencies(source, class_name)
|
|
71
|
+
|
|
72
|
+
unit
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
Rails.logger.error("Failed to extract manager #{file_path}: #{e.message}")
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
81
|
+
# Class Discovery
|
|
82
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
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
|
+
def manager_file?(source)
|
|
95
|
+
source.match?(/< SimpleDelegator/) ||
|
|
96
|
+
source.match?(/< DelegateClass\(/) ||
|
|
97
|
+
source.match?(/include Delegator/)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
101
|
+
# Source Annotation
|
|
102
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def annotate_source(source, class_name)
|
|
105
|
+
wrapped = detect_wrapped_model(source, class_name)
|
|
106
|
+
|
|
107
|
+
<<~ANNOTATION
|
|
108
|
+
# ╔═══════════════════════════════════════════════════════════════════════╗
|
|
109
|
+
# ║ Manager: #{class_name.ljust(60)}║
|
|
110
|
+
# ║ Wraps: #{(wrapped || 'unknown').ljust(61)}║
|
|
111
|
+
# ╚═══════════════════════════════════════════════════════════════════════╝
|
|
112
|
+
|
|
113
|
+
#{source}
|
|
114
|
+
ANNOTATION
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
118
|
+
# Metadata Extraction
|
|
119
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
def extract_metadata(source, class_name)
|
|
122
|
+
{
|
|
123
|
+
wrapped_model: detect_wrapped_model(source, class_name),
|
|
124
|
+
delegation_type: detect_delegation_type(source),
|
|
125
|
+
public_methods: extract_public_methods(source),
|
|
126
|
+
class_methods: extract_class_methods(source),
|
|
127
|
+
initialize_params: extract_initialize_params(source),
|
|
128
|
+
delegated_methods: extract_delegated_methods(source),
|
|
129
|
+
overridden_methods: extract_overridden_methods(source),
|
|
130
|
+
custom_errors: extract_custom_errors(source),
|
|
131
|
+
loc: source.lines.count { |l| l.strip.length.positive? && !l.strip.start_with?('#') },
|
|
132
|
+
method_count: source.scan(/def\s+(?:self\.)?\w+/).size
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def detect_wrapped_model(source, class_name)
|
|
137
|
+
# DelegateClass(ModelName) pattern
|
|
138
|
+
return ::Regexp.last_match(1) if source =~ /< DelegateClass\((\w+)\)/
|
|
139
|
+
|
|
140
|
+
# super(model) in initialize
|
|
141
|
+
return ::Regexp.last_match(1).capitalize if source =~ /super\((\w+)\)/
|
|
142
|
+
|
|
143
|
+
# @model = model; super(model) — look for param name
|
|
144
|
+
if source =~ /def\s+initialize\s*\((\w+)/
|
|
145
|
+
param = ::Regexp.last_match(1)
|
|
146
|
+
return param.capitalize unless %w[args options params attributes].include?(param)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Infer from class name: OrderManager -> Order
|
|
150
|
+
stripped = class_name.split('::').last
|
|
151
|
+
inferred = stripped.sub(/Manager\z/, '')
|
|
152
|
+
# Return nil if no suffix was removed (not a FooManager pattern)
|
|
153
|
+
return nil if inferred == stripped || inferred.empty?
|
|
154
|
+
|
|
155
|
+
inferred
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def detect_delegation_type(source)
|
|
159
|
+
return :delegate_class if source.match?(/< DelegateClass\(/)
|
|
160
|
+
return :simple_delegator if source.match?(/< SimpleDelegator/)
|
|
161
|
+
|
|
162
|
+
:unknown
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def extract_delegated_methods(source)
|
|
166
|
+
methods = []
|
|
167
|
+
|
|
168
|
+
# delegate :foo, :bar, to: :something
|
|
169
|
+
source.scan(/delegate\s+(.+?)(?:,\s*to:)/) do |match|
|
|
170
|
+
match[0].scan(/:(\w+)/).flatten.each { |m| methods << m }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
methods
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def extract_overridden_methods(source)
|
|
177
|
+
# Methods that call super — these override delegated behavior
|
|
178
|
+
source.scan(/def\s+(\w+[?!=]?).*?\n.*?super/m).flatten
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def extract_custom_errors(source)
|
|
182
|
+
source.scan(/class\s+(\w+(?:Error|Exception))\s*</).flatten
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
186
|
+
# Dependency Extraction
|
|
187
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
def extract_dependencies(source, class_name)
|
|
190
|
+
deps = []
|
|
191
|
+
|
|
192
|
+
# Wrapped model dependency (specific :via)
|
|
193
|
+
wrapped = detect_wrapped_model(source, class_name)
|
|
194
|
+
deps << { type: :model, target: wrapped, via: :delegation } if wrapped
|
|
195
|
+
|
|
196
|
+
deps.concat(scan_common_dependencies(source))
|
|
197
|
+
|
|
198
|
+
deps.uniq { |d| [d[:type], d[:target]] }
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Extractors
|
|
5
|
+
# MiddlewareExtractor handles Rack middleware stack extraction via runtime introspection.
|
|
6
|
+
#
|
|
7
|
+
# Reads the middleware stack from `Rails.application.middleware` and produces
|
|
8
|
+
# a single ExtractedUnit representing the full ordered stack. Each middleware
|
|
9
|
+
# entry includes the class name, arguments, and insertion point.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# extractor = MiddlewareExtractor.new
|
|
13
|
+
# units = extractor.extract_all
|
|
14
|
+
# stack = units.find { |u| u.identifier == "MiddlewareStack" }
|
|
15
|
+
#
|
|
16
|
+
class MiddlewareExtractor
|
|
17
|
+
def initialize
|
|
18
|
+
# No directories to scan — this is runtime introspection
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Extract the middleware stack as a single ExtractedUnit
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<ExtractedUnit>] Array containing one unit for the stack
|
|
24
|
+
def extract_all
|
|
25
|
+
return [] unless middleware_available?
|
|
26
|
+
|
|
27
|
+
stack = Rails.application.middleware
|
|
28
|
+
entries = extract_middleware_entries(stack)
|
|
29
|
+
|
|
30
|
+
return [] if entries.empty?
|
|
31
|
+
|
|
32
|
+
unit = ExtractedUnit.new(
|
|
33
|
+
type: :middleware,
|
|
34
|
+
identifier: 'MiddlewareStack',
|
|
35
|
+
file_path: nil
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
unit.namespace = nil
|
|
39
|
+
unit.source_code = build_stack_source(entries)
|
|
40
|
+
unit.metadata = build_stack_metadata(entries)
|
|
41
|
+
unit.dependencies = []
|
|
42
|
+
|
|
43
|
+
[unit]
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
Rails.logger.error("Failed to extract middleware stack: #{e.message}")
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Check if Rails middleware stack is available.
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def middleware_available?
|
|
55
|
+
defined?(Rails) &&
|
|
56
|
+
Rails.respond_to?(:application) &&
|
|
57
|
+
Rails.application.respond_to?(:middleware)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Extract individual middleware entries from the stack.
|
|
61
|
+
#
|
|
62
|
+
# @param stack [ActionDispatch::MiddlewareStack] The middleware stack
|
|
63
|
+
# @return [Array<Hash>] List of middleware entry hashes
|
|
64
|
+
def extract_middleware_entries(stack)
|
|
65
|
+
entries = []
|
|
66
|
+
position = 0
|
|
67
|
+
|
|
68
|
+
stack.each do |middleware|
|
|
69
|
+
entry = extract_single_middleware(middleware, position)
|
|
70
|
+
entries << entry if entry
|
|
71
|
+
position += 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
entries
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Extract info from a single middleware entry.
|
|
78
|
+
#
|
|
79
|
+
# @param middleware [ActionDispatch::MiddlewareStack::Middleware] A middleware entry
|
|
80
|
+
# @param position [Integer] Position in the stack
|
|
81
|
+
# @return [Hash, nil]
|
|
82
|
+
def extract_single_middleware(middleware, position)
|
|
83
|
+
name = if middleware.respond_to?(:name)
|
|
84
|
+
middleware.name
|
|
85
|
+
elsif middleware.respond_to?(:klass)
|
|
86
|
+
middleware.klass.to_s
|
|
87
|
+
else
|
|
88
|
+
middleware.to_s
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
args = if middleware.respond_to?(:args)
|
|
92
|
+
middleware.args.map(&:to_s)
|
|
93
|
+
else
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
{ name: name, args: args, position: position }
|
|
98
|
+
rescue StandardError
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Build a human-readable source representation of the middleware stack.
|
|
103
|
+
#
|
|
104
|
+
# @param entries [Array<Hash>] Middleware entries
|
|
105
|
+
# @return [String]
|
|
106
|
+
def build_stack_source(entries)
|
|
107
|
+
lines = []
|
|
108
|
+
lines << '# Rack Middleware Stack'
|
|
109
|
+
lines << "# #{entries.size} middleware(s)"
|
|
110
|
+
lines << '#'
|
|
111
|
+
|
|
112
|
+
entries.each do |entry|
|
|
113
|
+
args_str = entry[:args].any? ? " (#{entry[:args].join(', ')})" : ''
|
|
114
|
+
lines << "# [#{entry[:position]}] #{entry[:name]}#{args_str}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
lines.join("\n")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Build metadata hash for the middleware stack.
|
|
121
|
+
#
|
|
122
|
+
# @param entries [Array<Hash>] Middleware entries
|
|
123
|
+
# @return [Hash]
|
|
124
|
+
def build_stack_metadata(entries)
|
|
125
|
+
{
|
|
126
|
+
middleware_count: entries.size,
|
|
127
|
+
middleware_list: entries.map { |e| e[:name] },
|
|
128
|
+
middleware_details: entries
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|