codebase_index 0.3.2 → 0.4.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codebase_index.rb +3 -243
  3. metadata +28 -223
  4. data/CHANGELOG.md +0 -89
  5. data/CODE_OF_CONDUCT.md +0 -83
  6. data/CONTRIBUTING.md +0 -65
  7. data/LICENSE.txt +0 -21
  8. data/README.md +0 -325
  9. data/exe/codebase-console +0 -59
  10. data/exe/codebase-console-mcp +0 -22
  11. data/exe/codebase-index-mcp +0 -34
  12. data/exe/codebase-index-mcp-http +0 -37
  13. data/exe/codebase-index-mcp-start +0 -58
  14. data/lib/codebase_index/ast/call_site_extractor.rb +0 -106
  15. data/lib/codebase_index/ast/method_extractor.rb +0 -71
  16. data/lib/codebase_index/ast/node.rb +0 -116
  17. data/lib/codebase_index/ast/parser.rb +0 -614
  18. data/lib/codebase_index/ast.rb +0 -6
  19. data/lib/codebase_index/builder.rb +0 -200
  20. data/lib/codebase_index/cache/cache_middleware.rb +0 -199
  21. data/lib/codebase_index/cache/cache_store.rb +0 -264
  22. data/lib/codebase_index/cache/redis_cache_store.rb +0 -116
  23. data/lib/codebase_index/cache/solid_cache_store.rb +0 -111
  24. data/lib/codebase_index/chunking/chunk.rb +0 -84
  25. data/lib/codebase_index/chunking/semantic_chunker.rb +0 -295
  26. data/lib/codebase_index/console/adapters/cache_adapter.rb +0 -58
  27. data/lib/codebase_index/console/adapters/good_job_adapter.rb +0 -33
  28. data/lib/codebase_index/console/adapters/job_adapter.rb +0 -68
  29. data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +0 -33
  30. data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +0 -33
  31. data/lib/codebase_index/console/audit_logger.rb +0 -75
  32. data/lib/codebase_index/console/bridge.rb +0 -177
  33. data/lib/codebase_index/console/confirmation.rb +0 -90
  34. data/lib/codebase_index/console/connection_manager.rb +0 -173
  35. data/lib/codebase_index/console/console_response_renderer.rb +0 -74
  36. data/lib/codebase_index/console/embedded_executor.rb +0 -373
  37. data/lib/codebase_index/console/model_validator.rb +0 -81
  38. data/lib/codebase_index/console/rack_middleware.rb +0 -87
  39. data/lib/codebase_index/console/safe_context.rb +0 -82
  40. data/lib/codebase_index/console/server.rb +0 -612
  41. data/lib/codebase_index/console/sql_validator.rb +0 -172
  42. data/lib/codebase_index/console/tools/tier1.rb +0 -118
  43. data/lib/codebase_index/console/tools/tier2.rb +0 -117
  44. data/lib/codebase_index/console/tools/tier3.rb +0 -110
  45. data/lib/codebase_index/console/tools/tier4.rb +0 -79
  46. data/lib/codebase_index/coordination/pipeline_lock.rb +0 -109
  47. data/lib/codebase_index/cost_model/embedding_cost.rb +0 -88
  48. data/lib/codebase_index/cost_model/estimator.rb +0 -128
  49. data/lib/codebase_index/cost_model/provider_pricing.rb +0 -67
  50. data/lib/codebase_index/cost_model/storage_cost.rb +0 -52
  51. data/lib/codebase_index/cost_model.rb +0 -22
  52. data/lib/codebase_index/db/migrations/001_create_units.rb +0 -38
  53. data/lib/codebase_index/db/migrations/002_create_edges.rb +0 -35
  54. data/lib/codebase_index/db/migrations/003_create_embeddings.rb +0 -37
  55. data/lib/codebase_index/db/migrations/004_create_snapshots.rb +0 -45
  56. data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +0 -40
  57. data/lib/codebase_index/db/migrator.rb +0 -71
  58. data/lib/codebase_index/db/schema_version.rb +0 -73
  59. data/lib/codebase_index/dependency_graph.rb +0 -236
  60. data/lib/codebase_index/embedding/indexer.rb +0 -140
  61. data/lib/codebase_index/embedding/openai.rb +0 -126
  62. data/lib/codebase_index/embedding/provider.rb +0 -162
  63. data/lib/codebase_index/embedding/text_preparer.rb +0 -112
  64. data/lib/codebase_index/evaluation/baseline_runner.rb +0 -115
  65. data/lib/codebase_index/evaluation/evaluator.rb +0 -139
  66. data/lib/codebase_index/evaluation/metrics.rb +0 -79
  67. data/lib/codebase_index/evaluation/query_set.rb +0 -148
  68. data/lib/codebase_index/evaluation/report_generator.rb +0 -90
  69. data/lib/codebase_index/extracted_unit.rb +0 -145
  70. data/lib/codebase_index/extractor.rb +0 -1028
  71. data/lib/codebase_index/extractors/action_cable_extractor.rb +0 -201
  72. data/lib/codebase_index/extractors/ast_source_extraction.rb +0 -46
  73. data/lib/codebase_index/extractors/behavioral_profile.rb +0 -309
  74. data/lib/codebase_index/extractors/caching_extractor.rb +0 -261
  75. data/lib/codebase_index/extractors/callback_analyzer.rb +0 -246
  76. data/lib/codebase_index/extractors/concern_extractor.rb +0 -292
  77. data/lib/codebase_index/extractors/configuration_extractor.rb +0 -219
  78. data/lib/codebase_index/extractors/controller_extractor.rb +0 -404
  79. data/lib/codebase_index/extractors/database_view_extractor.rb +0 -278
  80. data/lib/codebase_index/extractors/decorator_extractor.rb +0 -253
  81. data/lib/codebase_index/extractors/engine_extractor.rb +0 -223
  82. data/lib/codebase_index/extractors/event_extractor.rb +0 -211
  83. data/lib/codebase_index/extractors/factory_extractor.rb +0 -289
  84. data/lib/codebase_index/extractors/graphql_extractor.rb +0 -892
  85. data/lib/codebase_index/extractors/i18n_extractor.rb +0 -117
  86. data/lib/codebase_index/extractors/job_extractor.rb +0 -374
  87. data/lib/codebase_index/extractors/lib_extractor.rb +0 -218
  88. data/lib/codebase_index/extractors/mailer_extractor.rb +0 -269
  89. data/lib/codebase_index/extractors/manager_extractor.rb +0 -188
  90. data/lib/codebase_index/extractors/middleware_extractor.rb +0 -133
  91. data/lib/codebase_index/extractors/migration_extractor.rb +0 -469
  92. data/lib/codebase_index/extractors/model_extractor.rb +0 -988
  93. data/lib/codebase_index/extractors/phlex_extractor.rb +0 -252
  94. data/lib/codebase_index/extractors/policy_extractor.rb +0 -191
  95. data/lib/codebase_index/extractors/poro_extractor.rb +0 -229
  96. data/lib/codebase_index/extractors/pundit_extractor.rb +0 -223
  97. data/lib/codebase_index/extractors/rails_source_extractor.rb +0 -473
  98. data/lib/codebase_index/extractors/rake_task_extractor.rb +0 -343
  99. data/lib/codebase_index/extractors/route_extractor.rb +0 -181
  100. data/lib/codebase_index/extractors/scheduled_job_extractor.rb +0 -331
  101. data/lib/codebase_index/extractors/serializer_extractor.rb +0 -339
  102. data/lib/codebase_index/extractors/service_extractor.rb +0 -217
  103. data/lib/codebase_index/extractors/shared_dependency_scanner.rb +0 -91
  104. data/lib/codebase_index/extractors/shared_utility_methods.rb +0 -281
  105. data/lib/codebase_index/extractors/state_machine_extractor.rb +0 -398
  106. data/lib/codebase_index/extractors/test_mapping_extractor.rb +0 -225
  107. data/lib/codebase_index/extractors/validator_extractor.rb +0 -211
  108. data/lib/codebase_index/extractors/view_component_extractor.rb +0 -311
  109. data/lib/codebase_index/extractors/view_template_extractor.rb +0 -261
  110. data/lib/codebase_index/feedback/gap_detector.rb +0 -89
  111. data/lib/codebase_index/feedback/store.rb +0 -119
  112. data/lib/codebase_index/filename_utils.rb +0 -32
  113. data/lib/codebase_index/flow_analysis/operation_extractor.rb +0 -206
  114. data/lib/codebase_index/flow_analysis/response_code_mapper.rb +0 -154
  115. data/lib/codebase_index/flow_assembler.rb +0 -290
  116. data/lib/codebase_index/flow_document.rb +0 -191
  117. data/lib/codebase_index/flow_precomputer.rb +0 -102
  118. data/lib/codebase_index/formatting/base.rb +0 -30
  119. data/lib/codebase_index/formatting/claude_adapter.rb +0 -98
  120. data/lib/codebase_index/formatting/generic_adapter.rb +0 -56
  121. data/lib/codebase_index/formatting/gpt_adapter.rb +0 -64
  122. data/lib/codebase_index/formatting/human_adapter.rb +0 -78
  123. data/lib/codebase_index/graph_analyzer.rb +0 -374
  124. data/lib/codebase_index/mcp/bootstrapper.rb +0 -96
  125. data/lib/codebase_index/mcp/index_reader.rb +0 -394
  126. data/lib/codebase_index/mcp/renderers/claude_renderer.rb +0 -81
  127. data/lib/codebase_index/mcp/renderers/json_renderer.rb +0 -17
  128. data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +0 -353
  129. data/lib/codebase_index/mcp/renderers/plain_renderer.rb +0 -240
  130. data/lib/codebase_index/mcp/server.rb +0 -961
  131. data/lib/codebase_index/mcp/tool_response_renderer.rb +0 -85
  132. data/lib/codebase_index/model_name_cache.rb +0 -51
  133. data/lib/codebase_index/notion/client.rb +0 -217
  134. data/lib/codebase_index/notion/exporter.rb +0 -219
  135. data/lib/codebase_index/notion/mapper.rb +0 -40
  136. data/lib/codebase_index/notion/mappers/column_mapper.rb +0 -57
  137. data/lib/codebase_index/notion/mappers/migration_mapper.rb +0 -39
  138. data/lib/codebase_index/notion/mappers/model_mapper.rb +0 -161
  139. data/lib/codebase_index/notion/mappers/shared.rb +0 -22
  140. data/lib/codebase_index/notion/rate_limiter.rb +0 -68
  141. data/lib/codebase_index/observability/health_check.rb +0 -79
  142. data/lib/codebase_index/observability/instrumentation.rb +0 -34
  143. data/lib/codebase_index/observability/structured_logger.rb +0 -57
  144. data/lib/codebase_index/operator/error_escalator.rb +0 -81
  145. data/lib/codebase_index/operator/pipeline_guard.rb +0 -92
  146. data/lib/codebase_index/operator/status_reporter.rb +0 -80
  147. data/lib/codebase_index/railtie.rb +0 -38
  148. data/lib/codebase_index/resilience/circuit_breaker.rb +0 -99
  149. data/lib/codebase_index/resilience/index_validator.rb +0 -167
  150. data/lib/codebase_index/resilience/retryable_provider.rb +0 -108
  151. data/lib/codebase_index/retrieval/context_assembler.rb +0 -261
  152. data/lib/codebase_index/retrieval/query_classifier.rb +0 -133
  153. data/lib/codebase_index/retrieval/ranker.rb +0 -277
  154. data/lib/codebase_index/retrieval/search_executor.rb +0 -316
  155. data/lib/codebase_index/retriever.rb +0 -152
  156. data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +0 -170
  157. data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +0 -77
  158. data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +0 -18
  159. data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +0 -280
  160. data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +0 -143
  161. data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +0 -143
  162. data/lib/codebase_index/ruby_analyzer.rb +0 -87
  163. data/lib/codebase_index/session_tracer/file_store.rb +0 -104
  164. data/lib/codebase_index/session_tracer/middleware.rb +0 -143
  165. data/lib/codebase_index/session_tracer/redis_store.rb +0 -106
  166. data/lib/codebase_index/session_tracer/session_flow_assembler.rb +0 -254
  167. data/lib/codebase_index/session_tracer/session_flow_document.rb +0 -223
  168. data/lib/codebase_index/session_tracer/solid_cache_store.rb +0 -139
  169. data/lib/codebase_index/session_tracer/store.rb +0 -81
  170. data/lib/codebase_index/storage/graph_store.rb +0 -120
  171. data/lib/codebase_index/storage/metadata_store.rb +0 -196
  172. data/lib/codebase_index/storage/pgvector.rb +0 -195
  173. data/lib/codebase_index/storage/qdrant.rb +0 -205
  174. data/lib/codebase_index/storage/vector_store.rb +0 -167
  175. data/lib/codebase_index/temporal/json_snapshot_store.rb +0 -245
  176. data/lib/codebase_index/temporal/snapshot_store.rb +0 -345
  177. data/lib/codebase_index/token_utils.rb +0 -19
  178. data/lib/codebase_index/version.rb +0 -5
  179. data/lib/generators/codebase_index/install_generator.rb +0 -32
  180. data/lib/generators/codebase_index/pgvector_generator.rb +0 -37
  181. data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +0 -15
  182. data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +0 -43
  183. data/lib/tasks/codebase_index.rake +0 -597
  184. data/lib/tasks/codebase_index_evaluation.rake +0 -115
@@ -1,353 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CodebaseIndex
4
- module MCP
5
- module Renderers
6
- # Renders MCP tool responses as pure Markdown.
7
- # Headers, tables, code blocks, and bullet lists — no JSON structural markers.
8
- class MarkdownRenderer < ToolResponseRenderer
9
- # ── lookup ──────────────────────────────────────────────────
10
-
11
- # @param data [Hash] Unit data from IndexReader#find_unit
12
- # @return [String] Markdown-formatted unit
13
- def render_lookup(data, **)
14
- return 'Unit not found' unless data.is_a?(Hash) && data['identifier']
15
-
16
- lines = []
17
- lines << "## #{data['identifier']} (#{data['type']})"
18
- lines << ''
19
- lines << "**File:** `#{data['file_path']}`" if data['file_path']
20
- lines << "**Namespace:** #{data['namespace']}" if data['namespace']
21
- lines << ''
22
-
23
- lines << render_metadata_section(data['metadata']) if data['metadata'].is_a?(Hash) && data['metadata'].any?
24
-
25
- if data['source_code']
26
- lines << '### Source'
27
- lines << ''
28
- lines << '```ruby'
29
- lines << data['source_code'].chomp
30
- lines << '```'
31
- lines << ''
32
- end
33
-
34
- if data['dependencies'].is_a?(Array) && data['dependencies'].any?
35
- lines << '### Dependencies'
36
- lines << ''
37
- data['dependencies'].each { |dep| lines << "- #{dep}" }
38
- lines << ''
39
- end
40
-
41
- if data['dependents'].is_a?(Array) && data['dependents'].any?
42
- lines << '### Dependents'
43
- lines << ''
44
- data['dependents'].each { |dep| lines << "- #{dep}" }
45
- lines << ''
46
- end
47
-
48
- lines.join("\n").rstrip
49
- end
50
-
51
- # ── search ──────────────────────────────────────────────────
52
-
53
- # @param data [Hash] Search results with :query, :result_count, :results
54
- # @return [String] Markdown search results
55
- def render_search(data, **)
56
- query = fetch_key(data, :query)
57
- count = fetch_key(data, :result_count, 0)
58
- results = fetch_key(data, :results, [])
59
-
60
- lines = []
61
- lines << "## Search: \"#{query}\""
62
- lines << ''
63
- lines << "#{count} result#{'s' unless count == 1} found."
64
- lines << ''
65
-
66
- results.each do |r|
67
- ident = fetch_key(r, :identifier)
68
- type = fetch_key(r, :type)
69
- match = fetch_key(r, :match_field)
70
- line = "- **#{ident}** (#{type})"
71
- line += " — matched in #{match}" if match
72
- lines << line
73
- end
74
-
75
- lines.join("\n").rstrip
76
- end
77
-
78
- # ── dependencies / dependents ───────────────────────────────
79
-
80
- # @param data [Hash] Traversal result with :root, :nodes, :found
81
- # @return [String] Markdown dependency tree
82
- def render_dependencies(data, **)
83
- render_traversal('Dependencies', data)
84
- end
85
-
86
- # @param data [Hash] Traversal result with :root, :nodes, :found
87
- # @return [String] Markdown dependents tree
88
- def render_dependents(data, **)
89
- render_traversal('Dependents', data)
90
- end
91
-
92
- # ── structure ───────────────────────────────────────────────
93
-
94
- # @param data [Hash] Structure data with :manifest and optional :summary
95
- # @return [String] Markdown structure overview
96
- def render_structure(data, **)
97
- manifest = fetch_key(data, :manifest, {})
98
- lines = []
99
- lines << '## Codebase Structure'
100
- lines << ''
101
-
102
- %w[rails_version ruby_version git_branch git_sha extracted_at].each do |key|
103
- lines << "- **#{key.tr('_', ' ').capitalize}:** #{manifest[key]}" if manifest[key]
104
- end
105
- lines << "- **Total units:** #{manifest['total_units']}" if manifest['total_units']
106
- lines << ''
107
-
108
- counts = manifest['counts']
109
- if counts.is_a?(Hash) && counts.any?
110
- lines << '| Type | Count |'
111
- lines << '|------|-------|'
112
- counts.sort_by { |_k, v| -v }.each do |type, count|
113
- lines << "| #{type} | #{count} |"
114
- end
115
- lines << ''
116
- end
117
-
118
- summary = fetch_key(data, :summary)
119
- if summary
120
- lines << '### Summary'
121
- lines << ''
122
- lines << summary
123
- end
124
-
125
- lines.join("\n").rstrip
126
- end
127
-
128
- # ── graph_analysis ──────────────────────────────────────────
129
-
130
- # @param data [Hash] Graph analysis with section arrays and stats
131
- # @return [String] Markdown graph analysis
132
- def render_graph_analysis(data, **)
133
- lines = []
134
- lines << '## Graph Analysis'
135
- lines << ''
136
-
137
- stats = fetch_key(data, :stats)
138
- if stats.is_a?(Hash)
139
- stats.each { |k, v| lines << "- **#{k}:** #{v}" }
140
- lines << ''
141
- end
142
-
143
- %w[orphans dead_ends hubs cycles bridges].each do |section|
144
- items = fetch_key(data, section)
145
- next unless items.is_a?(Array) && items.any?
146
-
147
- lines << "### #{section.tr('_', ' ').capitalize}"
148
- lines << ''
149
- items.each do |item|
150
- lines << if item.is_a?(Hash)
151
- "- **#{item['identifier']}** (#{item['type']}) — #{item['dependent_count']} dependents"
152
- else
153
- "- #{item}"
154
- end
155
- end
156
-
157
- total_key = "#{section}_total"
158
- if data[total_key]
159
- lines << ''
160
- lines << "_Showing #{items.size} of #{data[total_key]} (truncated)_"
161
- end
162
- lines << ''
163
- end
164
-
165
- lines.join("\n").rstrip
166
- end
167
-
168
- # ── pagerank ────────────────────────────────────────────────
169
-
170
- # @param data [Hash] PageRank data with :total_nodes and :results
171
- # @return [String] Markdown table of ranked nodes
172
- def render_pagerank(data, **)
173
- lines = []
174
- lines << '## PageRank Scores'
175
- lines << ''
176
- lines << "#{fetch_key(data, :total_nodes)} nodes in graph."
177
- lines << ''
178
- lines << '| Rank | Identifier | Type | Score |'
179
- lines << '|------|-----------|------|-------|'
180
-
181
- results = fetch_key(data, :results, [])
182
- results.each_with_index do |r, i|
183
- lines << "| #{i + 1} | #{fetch_key(r, :identifier)} | #{fetch_key(r, :type)} | #{fetch_key(r, :score)} |"
184
- end
185
-
186
- lines.join("\n").rstrip
187
- end
188
-
189
- # ── framework ───────────────────────────────────────────────
190
-
191
- # @param data [Hash] Framework search results
192
- # @return [String] Markdown framework results
193
- def render_framework(data, **)
194
- keyword = fetch_key(data, :keyword)
195
- count = fetch_key(data, :result_count, 0)
196
- results = fetch_key(data, :results, [])
197
-
198
- lines = []
199
- lines << "## Framework: \"#{keyword}\""
200
- lines << ''
201
- lines << "#{count} result#{'s' unless count == 1} found."
202
- lines << ''
203
-
204
- results.each do |r|
205
- ident = fetch_key(r, :identifier)
206
- type = fetch_key(r, :type)
207
- file = fetch_key(r, :file_path)
208
- line = "- **#{ident}** (#{type})"
209
- line += " — `#{file}`" if file
210
- lines << line
211
- end
212
-
213
- lines.join("\n").rstrip
214
- end
215
-
216
- # ── recent_changes ──────────────────────────────────────────
217
-
218
- # @param data [Hash] Recent changes with :result_count and :results
219
- # @return [String] Markdown table of recent changes
220
- def render_recent_changes(data, **)
221
- count = fetch_key(data, :result_count, 0)
222
- results = fetch_key(data, :results, [])
223
-
224
- lines = []
225
- lines << '## Recent Changes'
226
- lines << ''
227
- lines << "#{count} recently modified unit#{'s' unless count == 1}."
228
- lines << ''
229
- lines << '| Identifier | Type | Last Modified | Author |'
230
- lines << '|-----------|------|---------------|--------|'
231
-
232
- results.each do |r|
233
- ident = fetch_key(r, :identifier)
234
- type = fetch_key(r, :type)
235
- modified = fetch_key(r, :last_modified) || '-'
236
- author = fetch_key(r, :author) || '-'
237
- lines << "| #{ident} | #{type} | #{modified} | #{author} |"
238
- end
239
-
240
- lines.join("\n").rstrip
241
- end
242
-
243
- # ── Default fallback ────────────────────────────────────────
244
-
245
- # @param data [Object] Any data
246
- # @return [String] Markdown-formatted default output
247
- def render_default(data)
248
- case data
249
- when Hash
250
- render_hash_as_markdown(data)
251
- when Array
252
- render_array_as_markdown(data)
253
- else
254
- data.to_s
255
- end
256
- end
257
-
258
- private
259
-
260
- def render_traversal(label, data)
261
- root = fetch_key(data, :root)
262
- found = data[:found] || data['found']
263
- nodes = fetch_key(data, :nodes, {})
264
- message = fetch_key(data, :message)
265
-
266
- lines = []
267
- lines << "## #{label} of #{root}"
268
- lines << ''
269
-
270
- if found == false
271
- lines << (message || "Identifier '#{root}' not found in the index.")
272
- return lines.join("\n").rstrip
273
- end
274
-
275
- nodes.each do |id, info|
276
- depth = fetch_key(info, :depth) || 0
277
- deps = fetch_key(info, :deps, [])
278
- indent = ' ' * depth
279
- lines << "#{indent}- **#{id}**"
280
- deps.each { |d| lines << "#{indent} - #{d}" }
281
- end
282
-
283
- lines.join("\n").rstrip
284
- end
285
-
286
- def render_metadata_section(metadata)
287
- lines = []
288
- lines << '### Metadata'
289
- lines << ''
290
-
291
- metadata.each do |key, value|
292
- case value
293
- when Array
294
- next if value.empty?
295
-
296
- lines << "**#{key}:**"
297
- value.each do |item|
298
- if item.is_a?(Hash)
299
- summary = item.map { |k, v| "#{k}: #{v}" }.join(', ')
300
- lines << " - #{summary}"
301
- else
302
- lines << " - #{item}"
303
- end
304
- end
305
- when Hash
306
- lines << "**#{key}:** #{value.map { |k, v| "#{k}=#{v}" }.join(', ')}"
307
- else
308
- lines << "**#{key}:** #{value}"
309
- end
310
- end
311
- lines << ''
312
- lines.join("\n")
313
- end
314
-
315
- def render_hash_as_markdown(hash)
316
- lines = []
317
- hash.each do |key, value|
318
- case value
319
- when Hash
320
- lines << "**#{key}:**"
321
- value.each { |k, v| lines << " - #{k}: #{v}" }
322
- when Array
323
- lines << "**#{key}:** #{value.size} items"
324
- value.first(10).each do |item|
325
- lines << " - #{item.is_a?(Hash) ? item.values.first(3).join(', ') : item}"
326
- end
327
- else
328
- lines << "**#{key}:** #{value}"
329
- end
330
- end
331
- lines.join("\n")
332
- end
333
-
334
- def render_array_as_markdown(array)
335
- return '_(empty)_' if array.empty?
336
-
337
- if array.first.is_a?(Hash)
338
- keys = array.first.keys.first(5)
339
- lines = []
340
- lines << "| #{keys.join(' | ')} |"
341
- lines << "| #{keys.map { '---' }.join(' | ')} |"
342
- array.each do |row|
343
- lines << "| #{keys.map { |k| row[k] }.join(' | ')} |"
344
- end
345
- lines.join("\n")
346
- else
347
- array.map { |item| "- #{item}" }.join("\n")
348
- end
349
- end
350
- end
351
- end
352
- end
353
- end
@@ -1,240 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CodebaseIndex
4
- module MCP
5
- module Renderers
6
- # Renders MCP tool responses as plain text with === dividers.
7
- # Lightweight fallback format with no markup.
8
- class PlainRenderer < ToolResponseRenderer
9
- DIVIDER = ('=' * 60).freeze
10
-
11
- def render_lookup(data, **)
12
- return 'Unit not found' unless data.is_a?(Hash) && data['identifier']
13
-
14
- lines = []
15
- lines << "#{data['identifier']} (#{data['type']})"
16
- lines << DIVIDER
17
- lines << "File: #{data['file_path']}" if data['file_path']
18
- lines << "Namespace: #{data['namespace']}" if data['namespace']
19
- lines << ''
20
-
21
- if data['metadata'].is_a?(Hash) && data['metadata'].any?
22
- lines << 'Metadata:'
23
- data['metadata'].each do |key, value|
24
- case value
25
- when Array
26
- next if value.empty?
27
-
28
- lines << " #{key}:"
29
- value.each do |item|
30
- lines << " - #{item.is_a?(Hash) ? item.map { |k, v| "#{k}: #{v}" }.join(', ') : item}"
31
- end
32
- when Hash
33
- lines << " #{key}: #{value.map { |k, v| "#{k}=#{v}" }.join(', ')}"
34
- else
35
- lines << " #{key}: #{value}"
36
- end
37
- end
38
- lines << ''
39
- end
40
-
41
- if data['source_code']
42
- lines << 'Source:'
43
- lines << DIVIDER
44
- lines << data['source_code'].chomp
45
- lines << DIVIDER
46
- lines << ''
47
- end
48
-
49
- if data['dependencies'].is_a?(Array) && data['dependencies'].any?
50
- lines << "Dependencies: #{data['dependencies'].join(', ')}"
51
- end
52
-
53
- if data['dependents'].is_a?(Array) && data['dependents'].any?
54
- lines << "Dependents: #{data['dependents'].join(', ')}"
55
- end
56
-
57
- lines.join("\n").rstrip
58
- end
59
-
60
- def render_search(data, **)
61
- query = fetch_key(data, :query)
62
- count = fetch_key(data, :result_count, 0)
63
- results = fetch_key(data, :results, [])
64
-
65
- lines = []
66
- lines << "Search: \"#{query}\" (#{count} results)"
67
- lines << DIVIDER
68
-
69
- results.each do |r|
70
- ident = fetch_key(r, :identifier)
71
- type = fetch_key(r, :type)
72
- lines << " #{ident} (#{type})"
73
- end
74
-
75
- lines.join("\n").rstrip
76
- end
77
-
78
- def render_dependencies(data, **)
79
- render_plain_traversal('Dependencies', data)
80
- end
81
-
82
- def render_dependents(data, **)
83
- render_plain_traversal('Dependents', data)
84
- end
85
-
86
- def render_structure(data, **)
87
- manifest = fetch_key(data, :manifest, {})
88
- lines = []
89
- lines << 'Codebase Structure'
90
- lines << DIVIDER
91
-
92
- %w[rails_version ruby_version git_branch git_sha extracted_at total_units].each do |key|
93
- lines << " #{key}: #{manifest[key]}" if manifest[key]
94
- end
95
-
96
- counts = manifest['counts']
97
- if counts.is_a?(Hash) && counts.any?
98
- lines << ''
99
- lines << 'Unit counts:'
100
- counts.sort_by { |_k, v| -v }.each { |type, count| lines << " #{type}: #{count}" }
101
- end
102
-
103
- summary = fetch_key(data, :summary)
104
- if summary
105
- lines << ''
106
- lines << DIVIDER
107
- lines << summary
108
- end
109
-
110
- lines.join("\n").rstrip
111
- end
112
-
113
- def render_graph_analysis(data, **)
114
- lines = []
115
- lines << 'Graph Analysis'
116
- lines << DIVIDER
117
-
118
- stats = fetch_key(data, :stats)
119
- if stats.is_a?(Hash)
120
- stats.each { |k, v| lines << " #{k}: #{v}" }
121
- lines << ''
122
- end
123
-
124
- %w[orphans dead_ends hubs cycles bridges].each do |section|
125
- items = fetch_key(data, section)
126
- next unless items.is_a?(Array) && items.any?
127
-
128
- lines << "#{section.tr('_', ' ').upcase}:"
129
- items.each do |item|
130
- lines << if item.is_a?(Hash)
131
- " #{item['identifier']} (#{item['type']}) - #{item['dependent_count']} dependents"
132
- else
133
- " #{item}"
134
- end
135
- end
136
-
137
- total_key = "#{section}_total"
138
- lines << " (showing #{items.size} of #{data[total_key]})" if data[total_key]
139
- lines << ''
140
- end
141
-
142
- lines.join("\n").rstrip
143
- end
144
-
145
- def render_pagerank(data, **)
146
- lines = []
147
- lines << "PageRank Scores (#{fetch_key(data, :total_nodes)} nodes)"
148
- lines << DIVIDER
149
-
150
- results = fetch_key(data, :results, [])
151
- results.each_with_index do |r, i|
152
- ident = fetch_key(r, :identifier)
153
- type = fetch_key(r, :type)
154
- score = fetch_key(r, :score)
155
- lines << " #{i + 1}. #{ident} (#{type}) - #{score}"
156
- end
157
-
158
- lines.join("\n").rstrip
159
- end
160
-
161
- def render_framework(data, **)
162
- keyword = fetch_key(data, :keyword)
163
- count = fetch_key(data, :result_count, 0)
164
- results = fetch_key(data, :results, [])
165
-
166
- lines = []
167
- lines << "Framework: \"#{keyword}\" (#{count} results)"
168
- lines << DIVIDER
169
-
170
- results.each do |r|
171
- ident = fetch_key(r, :identifier)
172
- type = fetch_key(r, :type)
173
- lines << " #{ident} (#{type})"
174
- end
175
-
176
- lines.join("\n").rstrip
177
- end
178
-
179
- def render_recent_changes(data, **)
180
- count = fetch_key(data, :result_count, 0)
181
- results = fetch_key(data, :results, [])
182
-
183
- lines = []
184
- lines << "Recent Changes (#{count} units)"
185
- lines << DIVIDER
186
-
187
- results.each do |r|
188
- ident = fetch_key(r, :identifier)
189
- type = fetch_key(r, :type)
190
- modified = fetch_key(r, :last_modified) || '-'
191
- lines << " #{ident} (#{type}) - #{modified}"
192
- end
193
-
194
- lines.join("\n").rstrip
195
- end
196
-
197
- # @param data [Object] Any data
198
- # @return [String] Plain text output
199
- def render_default(data)
200
- case data
201
- when Hash
202
- data.map { |k, v| "#{k}: #{v}" }.join("\n")
203
- when Array
204
- data.map { |item| " #{item}" }.join("\n")
205
- else
206
- data.to_s
207
- end
208
- end
209
-
210
- private
211
-
212
- def render_plain_traversal(label, data)
213
- root = fetch_key(data, :root)
214
- found = data[:found] || data['found']
215
- nodes = fetch_key(data, :nodes, {})
216
- message = fetch_key(data, :message)
217
-
218
- lines = []
219
- lines << "#{label} of #{root}"
220
- lines << DIVIDER
221
-
222
- if found == false
223
- lines << (message || "Identifier '#{root}' not found in the index.")
224
- return lines.join("\n").rstrip
225
- end
226
-
227
- nodes.each do |id, info|
228
- depth = fetch_key(info, :depth) || 0
229
- deps = fetch_key(info, :deps, [])
230
- indent = ' ' * (depth + 1)
231
- lines << "#{indent}#{id}"
232
- deps.each { |d| lines << "#{indent} -> #{d}" }
233
- end
234
-
235
- lines.join("\n").rstrip
236
- end
237
- end
238
- end
239
- end
240
- end