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,58 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Self-healing wrapper for the CodebaseIndex MCP server.
3
- # Ensures Ruby dependencies are installed, validates the index directory,
4
- # and starts the stdio MCP server.
5
- #
6
- # Usage (direct):
7
- # codebase-index-mcp-start /path/to/index_dir
8
- #
9
- # Usage (.mcp.json):
10
- # {
11
- # "command": "${HOME}/work/codebase_index/exe/codebase-index-mcp-start",
12
- # "args": ["${HOME}/my-rails-app/tmp/codebase_index"]
13
- # }
14
- #
15
- # All diagnostic output goes to stderr to keep stdio clean for MCP protocol.
16
-
17
- set -euo pipefail
18
-
19
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20
- GEM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
21
- INDEX_DIR="${1:-${CODEBASE_INDEX_DIR:-}}"
22
-
23
- # --- Validate index directory ---
24
- if [[ -z "$INDEX_DIR" ]]; then
25
- echo "Error: No index directory specified." >&2
26
- echo "Usage: codebase-index-mcp-start /path/to/index_dir" >&2
27
- exit 1
28
- fi
29
-
30
- if [[ ! -d "$INDEX_DIR" ]]; then
31
- echo "Error: Index directory does not exist: $INDEX_DIR" >&2
32
- echo "Run extraction first: bundle exec rake codebase_index:extract" >&2
33
- exit 1
34
- fi
35
-
36
- if [[ ! -f "$INDEX_DIR/manifest.json" ]]; then
37
- echo "Error: No manifest.json in: $INDEX_DIR" >&2
38
- echo "Run extraction first: bundle exec rake codebase_index:extract" >&2
39
- exit 1
40
- fi
41
-
42
- # --- Ensure Ruby dependencies are installed ---
43
- export BUNDLE_GEMFILE="${GEM_DIR}/Gemfile"
44
-
45
- if ! bundle check > /dev/null 2>&1; then
46
- echo "Installing codebase_index dependencies..." >&2
47
- if ! bundle install --quiet >&2 2>&1; then
48
- echo "Error: bundle install failed. Check Ruby version and network." >&2
49
- exit 1
50
- fi
51
- echo "Dependencies installed." >&2
52
- fi
53
-
54
- # --- Pin MCP protocol version for Claude Code compatibility ---
55
- export MCP_PROTOCOL_VERSION="${MCP_PROTOCOL_VERSION:-2024-11-05}"
56
-
57
- # --- Start the MCP server ---
58
- exec bundle exec ruby "${GEM_DIR}/exe/codebase-index-mcp" "$INDEX_DIR"
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
- require_relative 'node'
5
-
6
- module CodebaseIndex
7
- module Ast
8
- # Method names that are too common to be useful in call graphs.
9
- INSIGNIFICANT_METHODS = Set.new(%w[
10
- to_s to_i to_f to_a to_h to_sym to_r to_c to_str to_proc
11
- nil? present? blank? empty? any? none? frozen? is_a? kind_of?
12
- respond_to? respond_to_missing? instance_of? equal?
13
- == != eql? <=> === =~ !~ >= <= > <
14
- ! & | ^ ~ + - * / % **
15
- freeze dup clone inspect hash object_id class
16
- send __send__ method tap then yield_self itself
17
- new allocate
18
- [] []=
19
- length size count
20
- first last
21
- map each select reject flat_map collect detect find_index
22
- merge merge! update
23
- keys values
24
- push pop shift unshift
25
- strip chomp chop downcase upcase
26
- puts print p pp warn raise fail
27
- require require_relative load autoload
28
- attr_reader attr_writer attr_accessor
29
- private protected public
30
- include extend prepend
31
- ]).freeze
32
-
33
- # Extracts call sites from an AST node tree.
34
- #
35
- # Returns method calls found in the tree, ordered by source line number.
36
- # Used by both RubyAnalyzer (call graph building) and FlowAssembler
37
- # (execution flow ordering).
38
- #
39
- # @example Extracting calls from a method body
40
- # parser = Ast::Parser.new
41
- # root = parser.parse(source)
42
- # calls = Ast::CallSiteExtractor.new.extract(root)
43
- # calls.first #=> { receiver: "User", method_name: "find", arguments: ["id"], line: 3, block: false }
44
- #
45
- class CallSiteExtractor
46
- # Extract all call sites from an AST node, ordered by line number.
47
- #
48
- # @param node [Ast::Node] The AST node to search
49
- # @return [Array<Hash>] Call site hashes ordered by line ascending
50
- def extract(node)
51
- calls = []
52
- collect_calls(node, calls)
53
- calls.sort_by { |c| c[:line] }
54
- end
55
-
56
- # Extract only significant call sites, filtering out noise.
57
- #
58
- # @param node [Ast::Node] The AST node to search
59
- # @param known_units [Array<String>] Known unit identifiers for relevance filtering
60
- # @return [Array<Hash>] Filtered call site hashes
61
- def extract_significant(node, known_units: [])
62
- calls = extract(node)
63
- known_set = Set.new(known_units)
64
-
65
- calls.reject do |call|
66
- INSIGNIFICANT_METHODS.include?(call[:method_name]) &&
67
- (known_units.empty? || !known_set.include?(call[:receiver]))
68
- end
69
- end
70
-
71
- private
72
-
73
- def collect_calls(node, calls)
74
- return unless node.is_a?(Ast::Node)
75
-
76
- case node.type
77
- when :send
78
- calls << {
79
- receiver: node.receiver,
80
- method_name: node.method_name,
81
- arguments: node.arguments || [],
82
- line: node.line,
83
- block: false
84
- }
85
- when :block
86
- # The send node in a block gets block: true
87
- send_child = node.children&.first
88
- if send_child.is_a?(Ast::Node) && send_child.type == :send
89
- calls << {
90
- receiver: send_child.receiver,
91
- method_name: send_child.method_name,
92
- arguments: send_child.arguments || [],
93
- line: send_child.line,
94
- block: true
95
- }
96
- end
97
- # Also recurse into block body (children[1])
98
- node.children&.drop(1)&.each { |child| collect_calls(child, calls) }
99
- return # Don't double-recurse into children
100
- end
101
-
102
- (node.children || []).each { |child| collect_calls(child, calls) }
103
- end
104
- end
105
- end
106
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'parser'
4
- require_relative 'node'
5
-
6
- module CodebaseIndex
7
- module Ast
8
- # Extracts method definitions and their source from Ruby source code.
9
- #
10
- # Replaces the fragile ~240 lines of `nesting_delta` / `neutralize_strings_and_comments`
11
- # / `detect_heredoc_start` indentation heuristics in controller and mailer extractors.
12
- #
13
- # @example Extracting a method's source
14
- # extractor = Ast::MethodExtractor.new
15
- # source = extractor.extract_method_source(code, "create")
16
- # # => "def create\n @user = User.find(params[:id])\nend\n"
17
- #
18
- class MethodExtractor
19
- include SourceSpan
20
-
21
- # @param parser [Ast::Parser, nil] Parser instance (creates default if nil)
22
- def initialize(parser: nil)
23
- @parser = parser || Parser.new
24
- end
25
-
26
- # Extract a method definition node by name.
27
- #
28
- # @param source [String] Ruby source code
29
- # @param method_name [String] Method name to find
30
- # @param class_method [Boolean] If true, look for `def self.method_name`
31
- # @return [Ast::Node, nil] The :def or :defs node, or nil if not found
32
- def extract_method(source, method_name, class_method: false)
33
- root = @parser.parse(source)
34
- target_type = class_method ? :defs : :def
35
-
36
- root.find_all(target_type).find do |node|
37
- node.method_name == method_name.to_s
38
- end
39
- end
40
-
41
- # Extract all method definition nodes from source.
42
- #
43
- # @param source [String] Ruby source code
44
- # @return [Array<Ast::Node>] All :def and :defs nodes
45
- def extract_all_methods(source)
46
- root = @parser.parse(source)
47
- root.find_all(:def) + root.find_all(:defs)
48
- end
49
-
50
- # Extract the raw source text of a method, including def...end.
51
- #
52
- # This is the key replacement for `extract_action_source` in the controller
53
- # and mailer extractors. Uses AST line tracking instead of indentation heuristics.
54
- #
55
- # @param source [String] Ruby source code
56
- # @param method_name [String] Method name to find
57
- # @param class_method [Boolean] If true, look for `def self.method_name`
58
- # @return [String, nil] The method source text, or nil if not found
59
- def extract_method_source(source, method_name, class_method: false)
60
- node = extract_method(source, method_name, class_method: class_method)
61
- return nil unless node
62
-
63
- # If the node has a source field populated by the parser, use it
64
- return node.source if node.source
65
-
66
- # Fallback: extract by line range
67
- extract_source_span(source, node.line, node.end_line)
68
- end
69
- end
70
- end
71
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CodebaseIndex
4
- module Ast
5
- # Normalized AST node struct used by all consumers.
6
- #
7
- # Provides a parser-independent representation of Ruby AST nodes.
8
- # Both Prism and the parser gem are normalized to this common structure.
9
- #
10
- # @example Creating a send node
11
- # node = Ast::Node.new(
12
- # type: :send,
13
- # children: [],
14
- # line: 42,
15
- # receiver: "User",
16
- # method_name: "find",
17
- # arguments: ["id"]
18
- # )
19
- #
20
- Node = Struct.new(
21
- :type, # Symbol: :send, :block, :if, :def, :defs, :class, :module, :const, :begin, etc.
22
- :children, # Array<Ast::Node | String | Symbol | Integer | nil>
23
- :line, # Integer: 1-based source line number
24
- :receiver, # String | nil: method call receiver (for :send)
25
- :method_name, # String | nil: method name (for :send, :def, :defs)
26
- :arguments, # Array<String>: argument representations (for :send)
27
- :source, # String | nil: raw source text of this node
28
- :end_line, # Integer | nil: 1-based end line number (when available)
29
- keyword_init: true
30
- ) do
31
- # Find all descendant nodes matching a type.
32
- #
33
- # @param target_type [Symbol] The node type to search for
34
- # @return [Array<Ast::Node>] All matching descendant nodes
35
- def find_all(target_type)
36
- results = []
37
- queue = [self]
38
- while (current = queue.shift)
39
- results << current if current.type == target_type
40
- (current.children || []).each do |child|
41
- queue << child if child.is_a?(Ast::Node)
42
- end
43
- end
44
- results
45
- end
46
-
47
- # Find the first descendant node matching a type (depth-first).
48
- #
49
- # @param target_type [Symbol] The node type to search for
50
- # @return [Ast::Node, nil] The first matching node or nil
51
- def find_first(target_type)
52
- return self if type == target_type
53
-
54
- (children || []).each do |child|
55
- next unless child.is_a?(Ast::Node)
56
-
57
- result = child.find_first(target_type)
58
- return result if result
59
- end
60
- nil
61
- end
62
-
63
- # Return source text representation.
64
- #
65
- # @return [String] The source field if present, otherwise a reconstruction
66
- def to_source
67
- return source if source
68
-
69
- case type
70
- when :send
71
- parts = []
72
- parts << receiver if receiver
73
- parts << method_name if method_name
74
- parts.join('.')
75
- when :const
76
- parts = []
77
- parts << receiver if receiver
78
- parts << method_name if method_name
79
- parts.join('::')
80
- when :def, :defs
81
- "def #{method_name}"
82
- else
83
- type.to_s
84
- end
85
- end
86
- end
87
-
88
- # Mixin for line-range source extraction, shared across Parser, MethodExtractor,
89
- # and ClassAnalyzer.
90
- #
91
- # @example
92
- # include Ast::SourceSpan
93
- # extract_source_span(source, node.line, node.end_line)
94
- #
95
- module SourceSpan
96
- private
97
-
98
- # Extract source lines for a 1-based start/end line range.
99
- #
100
- # @param source [String] Full source text
101
- # @param start_line [Integer, nil] 1-based start line
102
- # @param end_line [Integer, nil] 1-based end line
103
- # @return [String, nil] Extracted lines joined, or nil if out of range
104
- def extract_source_span(source, start_line, end_line)
105
- return nil unless start_line && end_line
106
-
107
- lines = source.lines
108
- start_idx = start_line - 1
109
- end_idx = end_line - 1
110
- return nil if start_idx.negative? || end_idx >= lines.length
111
-
112
- lines[start_idx..end_idx].join
113
- end
114
- end
115
- end
116
- end