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.
Files changed (171) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/CODE_OF_CONDUCT.md +83 -0
  4. data/CONTRIBUTING.md +65 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +481 -0
  7. data/exe/codebase-console-mcp +22 -0
  8. data/exe/codebase-index-mcp +61 -0
  9. data/exe/codebase-index-mcp-http +64 -0
  10. data/exe/codebase-index-mcp-start +58 -0
  11. data/lib/codebase_index/ast/call_site_extractor.rb +106 -0
  12. data/lib/codebase_index/ast/method_extractor.rb +76 -0
  13. data/lib/codebase_index/ast/node.rb +88 -0
  14. data/lib/codebase_index/ast/parser.rb +653 -0
  15. data/lib/codebase_index/ast.rb +6 -0
  16. data/lib/codebase_index/builder.rb +137 -0
  17. data/lib/codebase_index/chunking/chunk.rb +84 -0
  18. data/lib/codebase_index/chunking/semantic_chunker.rb +290 -0
  19. data/lib/codebase_index/console/adapters/cache_adapter.rb +58 -0
  20. data/lib/codebase_index/console/adapters/good_job_adapter.rb +66 -0
  21. data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +66 -0
  22. data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +66 -0
  23. data/lib/codebase_index/console/audit_logger.rb +75 -0
  24. data/lib/codebase_index/console/bridge.rb +170 -0
  25. data/lib/codebase_index/console/confirmation.rb +90 -0
  26. data/lib/codebase_index/console/connection_manager.rb +173 -0
  27. data/lib/codebase_index/console/console_response_renderer.rb +78 -0
  28. data/lib/codebase_index/console/model_validator.rb +81 -0
  29. data/lib/codebase_index/console/safe_context.rb +82 -0
  30. data/lib/codebase_index/console/server.rb +557 -0
  31. data/lib/codebase_index/console/sql_validator.rb +172 -0
  32. data/lib/codebase_index/console/tools/tier1.rb +118 -0
  33. data/lib/codebase_index/console/tools/tier2.rb +117 -0
  34. data/lib/codebase_index/console/tools/tier3.rb +110 -0
  35. data/lib/codebase_index/console/tools/tier4.rb +79 -0
  36. data/lib/codebase_index/coordination/pipeline_lock.rb +109 -0
  37. data/lib/codebase_index/cost_model/embedding_cost.rb +88 -0
  38. data/lib/codebase_index/cost_model/estimator.rb +128 -0
  39. data/lib/codebase_index/cost_model/provider_pricing.rb +67 -0
  40. data/lib/codebase_index/cost_model/storage_cost.rb +52 -0
  41. data/lib/codebase_index/cost_model.rb +22 -0
  42. data/lib/codebase_index/db/migrations/001_create_units.rb +38 -0
  43. data/lib/codebase_index/db/migrations/002_create_edges.rb +35 -0
  44. data/lib/codebase_index/db/migrations/003_create_embeddings.rb +37 -0
  45. data/lib/codebase_index/db/migrations/004_create_snapshots.rb +45 -0
  46. data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +40 -0
  47. data/lib/codebase_index/db/migrator.rb +71 -0
  48. data/lib/codebase_index/db/schema_version.rb +73 -0
  49. data/lib/codebase_index/dependency_graph.rb +227 -0
  50. data/lib/codebase_index/embedding/indexer.rb +130 -0
  51. data/lib/codebase_index/embedding/openai.rb +105 -0
  52. data/lib/codebase_index/embedding/provider.rb +135 -0
  53. data/lib/codebase_index/embedding/text_preparer.rb +112 -0
  54. data/lib/codebase_index/evaluation/baseline_runner.rb +115 -0
  55. data/lib/codebase_index/evaluation/evaluator.rb +146 -0
  56. data/lib/codebase_index/evaluation/metrics.rb +79 -0
  57. data/lib/codebase_index/evaluation/query_set.rb +148 -0
  58. data/lib/codebase_index/evaluation/report_generator.rb +90 -0
  59. data/lib/codebase_index/extracted_unit.rb +145 -0
  60. data/lib/codebase_index/extractor.rb +956 -0
  61. data/lib/codebase_index/extractors/action_cable_extractor.rb +228 -0
  62. data/lib/codebase_index/extractors/ast_source_extraction.rb +46 -0
  63. data/lib/codebase_index/extractors/behavioral_profile.rb +309 -0
  64. data/lib/codebase_index/extractors/caching_extractor.rb +261 -0
  65. data/lib/codebase_index/extractors/callback_analyzer.rb +232 -0
  66. data/lib/codebase_index/extractors/concern_extractor.rb +253 -0
  67. data/lib/codebase_index/extractors/configuration_extractor.rb +219 -0
  68. data/lib/codebase_index/extractors/controller_extractor.rb +494 -0
  69. data/lib/codebase_index/extractors/database_view_extractor.rb +278 -0
  70. data/lib/codebase_index/extractors/decorator_extractor.rb +260 -0
  71. data/lib/codebase_index/extractors/engine_extractor.rb +204 -0
  72. data/lib/codebase_index/extractors/event_extractor.rb +211 -0
  73. data/lib/codebase_index/extractors/factory_extractor.rb +289 -0
  74. data/lib/codebase_index/extractors/graphql_extractor.rb +917 -0
  75. data/lib/codebase_index/extractors/i18n_extractor.rb +117 -0
  76. data/lib/codebase_index/extractors/job_extractor.rb +369 -0
  77. data/lib/codebase_index/extractors/lib_extractor.rb +249 -0
  78. data/lib/codebase_index/extractors/mailer_extractor.rb +339 -0
  79. data/lib/codebase_index/extractors/manager_extractor.rb +202 -0
  80. data/lib/codebase_index/extractors/middleware_extractor.rb +133 -0
  81. data/lib/codebase_index/extractors/migration_extractor.rb +469 -0
  82. data/lib/codebase_index/extractors/model_extractor.rb +960 -0
  83. data/lib/codebase_index/extractors/phlex_extractor.rb +252 -0
  84. data/lib/codebase_index/extractors/policy_extractor.rb +214 -0
  85. data/lib/codebase_index/extractors/poro_extractor.rb +246 -0
  86. data/lib/codebase_index/extractors/pundit_extractor.rb +223 -0
  87. data/lib/codebase_index/extractors/rails_source_extractor.rb +473 -0
  88. data/lib/codebase_index/extractors/rake_task_extractor.rb +343 -0
  89. data/lib/codebase_index/extractors/route_extractor.rb +181 -0
  90. data/lib/codebase_index/extractors/scheduled_job_extractor.rb +331 -0
  91. data/lib/codebase_index/extractors/serializer_extractor.rb +334 -0
  92. data/lib/codebase_index/extractors/service_extractor.rb +254 -0
  93. data/lib/codebase_index/extractors/shared_dependency_scanner.rb +91 -0
  94. data/lib/codebase_index/extractors/shared_utility_methods.rb +99 -0
  95. data/lib/codebase_index/extractors/state_machine_extractor.rb +398 -0
  96. data/lib/codebase_index/extractors/test_mapping_extractor.rb +225 -0
  97. data/lib/codebase_index/extractors/validator_extractor.rb +225 -0
  98. data/lib/codebase_index/extractors/view_component_extractor.rb +310 -0
  99. data/lib/codebase_index/extractors/view_template_extractor.rb +261 -0
  100. data/lib/codebase_index/feedback/gap_detector.rb +89 -0
  101. data/lib/codebase_index/feedback/store.rb +119 -0
  102. data/lib/codebase_index/flow_analysis/operation_extractor.rb +209 -0
  103. data/lib/codebase_index/flow_analysis/response_code_mapper.rb +154 -0
  104. data/lib/codebase_index/flow_assembler.rb +290 -0
  105. data/lib/codebase_index/flow_document.rb +191 -0
  106. data/lib/codebase_index/flow_precomputer.rb +102 -0
  107. data/lib/codebase_index/formatting/base.rb +40 -0
  108. data/lib/codebase_index/formatting/claude_adapter.rb +98 -0
  109. data/lib/codebase_index/formatting/generic_adapter.rb +56 -0
  110. data/lib/codebase_index/formatting/gpt_adapter.rb +64 -0
  111. data/lib/codebase_index/formatting/human_adapter.rb +78 -0
  112. data/lib/codebase_index/graph_analyzer.rb +374 -0
  113. data/lib/codebase_index/mcp/index_reader.rb +394 -0
  114. data/lib/codebase_index/mcp/renderers/claude_renderer.rb +81 -0
  115. data/lib/codebase_index/mcp/renderers/json_renderer.rb +17 -0
  116. data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +352 -0
  117. data/lib/codebase_index/mcp/renderers/plain_renderer.rb +240 -0
  118. data/lib/codebase_index/mcp/server.rb +935 -0
  119. data/lib/codebase_index/mcp/tool_response_renderer.rb +62 -0
  120. data/lib/codebase_index/model_name_cache.rb +51 -0
  121. data/lib/codebase_index/notion/client.rb +217 -0
  122. data/lib/codebase_index/notion/exporter.rb +219 -0
  123. data/lib/codebase_index/notion/mapper.rb +39 -0
  124. data/lib/codebase_index/notion/mappers/column_mapper.rb +65 -0
  125. data/lib/codebase_index/notion/mappers/migration_mapper.rb +39 -0
  126. data/lib/codebase_index/notion/mappers/model_mapper.rb +164 -0
  127. data/lib/codebase_index/notion/rate_limiter.rb +68 -0
  128. data/lib/codebase_index/observability/health_check.rb +81 -0
  129. data/lib/codebase_index/observability/instrumentation.rb +34 -0
  130. data/lib/codebase_index/observability/structured_logger.rb +75 -0
  131. data/lib/codebase_index/operator/error_escalator.rb +81 -0
  132. data/lib/codebase_index/operator/pipeline_guard.rb +99 -0
  133. data/lib/codebase_index/operator/status_reporter.rb +80 -0
  134. data/lib/codebase_index/railtie.rb +26 -0
  135. data/lib/codebase_index/resilience/circuit_breaker.rb +99 -0
  136. data/lib/codebase_index/resilience/index_validator.rb +185 -0
  137. data/lib/codebase_index/resilience/retryable_provider.rb +108 -0
  138. data/lib/codebase_index/retrieval/context_assembler.rb +249 -0
  139. data/lib/codebase_index/retrieval/query_classifier.rb +131 -0
  140. data/lib/codebase_index/retrieval/ranker.rb +273 -0
  141. data/lib/codebase_index/retrieval/search_executor.rb +327 -0
  142. data/lib/codebase_index/retriever.rb +160 -0
  143. data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +190 -0
  144. data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +78 -0
  145. data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +18 -0
  146. data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +275 -0
  147. data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +143 -0
  148. data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +139 -0
  149. data/lib/codebase_index/ruby_analyzer.rb +87 -0
  150. data/lib/codebase_index/session_tracer/file_store.rb +111 -0
  151. data/lib/codebase_index/session_tracer/middleware.rb +143 -0
  152. data/lib/codebase_index/session_tracer/redis_store.rb +112 -0
  153. data/lib/codebase_index/session_tracer/session_flow_assembler.rb +263 -0
  154. data/lib/codebase_index/session_tracer/session_flow_document.rb +223 -0
  155. data/lib/codebase_index/session_tracer/solid_cache_store.rb +145 -0
  156. data/lib/codebase_index/session_tracer/store.rb +67 -0
  157. data/lib/codebase_index/storage/graph_store.rb +120 -0
  158. data/lib/codebase_index/storage/metadata_store.rb +169 -0
  159. data/lib/codebase_index/storage/pgvector.rb +163 -0
  160. data/lib/codebase_index/storage/qdrant.rb +172 -0
  161. data/lib/codebase_index/storage/vector_store.rb +156 -0
  162. data/lib/codebase_index/temporal/snapshot_store.rb +341 -0
  163. data/lib/codebase_index/version.rb +5 -0
  164. data/lib/codebase_index.rb +223 -0
  165. data/lib/generators/codebase_index/install_generator.rb +32 -0
  166. data/lib/generators/codebase_index/pgvector_generator.rb +37 -0
  167. data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +15 -0
  168. data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +43 -0
  169. data/lib/tasks/codebase_index.rake +583 -0
  170. data/lib/tasks/codebase_index_evaluation.rake +115 -0
  171. 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