Class: RubynCode::Index::CodebaseIndex
- Inherits:
-
Object
- Object
- RubynCode::Index::CodebaseIndex
- Defined in:
- lib/rubyn_code/index/codebase_index.rb
Overview
Rails-aware codebase index built with Prism (Ruby’s built-in parser). Stores classes, modules, methods, associations, and Rails edges in a JSON file for fast session startup. First build scans all .rb files; incremental updates re-index only changed files.
Constant Summary collapse
- INDEX_DIR =
rubocop:disable Metrics/ClassLength – structural summary methods
'.rubyn-code'- INDEX_FILE =
'codebase_index.json'- CHARS_PER_TOKEN =
4
Instance Attribute Summary collapse
-
#edges ⇒ Object
readonly
Returns the value of attribute edges.
-
#index_path ⇒ Object
readonly
Returns the value of attribute index_path.
-
#nodes ⇒ Object
readonly
Returns the value of attribute nodes.
Instance Method Summary collapse
-
#build! ⇒ Object
Build the index from scratch (first session).
-
#impact_analysis(file_path) ⇒ Object
Find all nodes related to a given file (callers, dependents, specs).
-
#initialize(project_root:) ⇒ CodebaseIndex
constructor
A new instance of CodebaseIndex.
-
#load ⇒ Object
Load existing index from disk.
-
#load_or_build! ⇒ Object
Load if exists, otherwise build from scratch.
-
#query(term) ⇒ Object
Query the index for symbols matching a search term.
- #stats ⇒ Object
-
#to_prompt_summary ⇒ Object
Compact summary for system prompt injection (~200-500 tokens).
-
#to_structural_summary(max_tokens: 500) ⇒ Object
Structural map for system prompt: model names with associations, controllers, and service objects.
-
#update! ⇒ Object
Incremental update: re-index only files changed since last build.
Constructor Details
#initialize(project_root:) ⇒ CodebaseIndex
Returns a new instance of CodebaseIndex.
19 20 21 22 23 24 25 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 19 def initialize(project_root:) @project_root = File.(project_root) @index_path = File.join(@project_root, INDEX_DIR, INDEX_FILE) @nodes = [] # { type:, name:, file:, line:, params:, visibility: } @edges = [] # { from:, to:, relationship: } @file_mtimes = {} end |
Instance Attribute Details
#edges ⇒ Object (readonly)
Returns the value of attribute edges.
17 18 19 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 17 def edges @edges end |
#index_path ⇒ Object (readonly)
Returns the value of attribute index_path.
17 18 19 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 17 def index_path @index_path end |
#nodes ⇒ Object (readonly)
Returns the value of attribute nodes.
17 18 19 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 17 def nodes @nodes end |
Instance Method Details
#build! ⇒ Object
Build the index from scratch (first session).
28 29 30 31 32 33 34 35 36 37 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 28 def build! @nodes = [] @edges = [] @file_mtimes = {} ruby_files.each { |file| index_file(file) } extract_rails_edges save! self end |
#impact_analysis(file_path) ⇒ Object
Find all nodes related to a given file (callers, dependents, specs).
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 82 def impact_analysis(file_path) relative = relative_path(file_path) direct = @nodes.select { |n| n['file'] == relative } names = direct.map { |n| n['name'] }.compact = edges_involving(names) { definitions: direct, relationships: , affected_files: .flat_map { |e| find_files_for(e) }.uniq } end |
#load ⇒ Object
Load existing index from disk.
40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 40 def load return nil unless File.exist?(@index_path) data = JSON.parse(File.read(@index_path)) @nodes = data['nodes'] || [] @edges = data['edges'] || [] @file_mtimes = data['file_mtimes'] || {} self rescue StandardError nil end |
#load_or_build! ⇒ Object
Load if exists, otherwise build from scratch.
53 54 55 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 53 def load_or_build! load || build! end |
#query(term) ⇒ Object
Query the index for symbols matching a search term.
73 74 75 76 77 78 79 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 73 def query(term) pattern = term.to_s.downcase @nodes.select do |node| node['name'].to_s.downcase.include?(pattern) || node['file'].to_s.downcase.include?(pattern) end end |
#stats ⇒ Object
121 122 123 124 125 126 127 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 121 def stats { files_indexed: @file_mtimes.size, nodes: @nodes.size, edges: @edges.size } end |
#to_prompt_summary ⇒ Object
Compact summary for system prompt injection (~200-500 tokens).
96 97 98 99 100 101 102 103 104 105 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 96 def to_prompt_summary counts = node_type_counts assoc_count = @edges.count { |e| e['relationship'] == 'association' } lines = ['Codebase Index:'] lines << " Classes: #{counts['class']}, Methods: #{counts['method']}" lines << " Models: #{counts['model']}, Controllers: #{counts['controller']}, Services: #{counts['service']}" lines << " Associations: #{assoc_count}" lines.join("\n") end |
#to_structural_summary(max_tokens: 500) ⇒ Object
Structural map for system prompt: model names with associations, controllers, and service objects. Capped to stay within token budget.
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 109 def to_structural_summary(max_tokens: 500) budget = max_tokens * CHARS_PER_TOKEN lines = ['Codebase Structure:'] append_model_section(lines) append_controller_section(lines) append_service_section(lines) append_stats_section(lines) truncate_to_budget(lines, budget) end |
#update! ⇒ Object
Incremental update: re-index only files changed since last build.
58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 58 def update! changed = detect_changed_files return self if changed.empty? changed.each do |file| remove_nodes_for(file) index_file(file) if File.exist?(file) end extract_rails_edges save! self end |