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.
-
#update_file!(path) ⇒ Object
Incremental update for a single known-changed file (e.g. after a write_file/edit_file tool call).
Constructor Details
#initialize(project_root:) ⇒ CodebaseIndex
Returns a new instance of CodebaseIndex.
20 21 22 23 24 25 26 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 20 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.
18 19 20 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 18 def edges @edges end |
#index_path ⇒ Object (readonly)
Returns the value of attribute index_path.
18 19 20 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 18 def index_path @index_path end |
#nodes ⇒ Object (readonly)
Returns the value of attribute nodes.
18 19 20 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 18 def nodes @nodes end |
Instance Method Details
#build! ⇒ Object
Build the index from scratch (first session).
29 30 31 32 33 34 35 36 37 38 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 29 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).
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 98 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.
41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 41 def load return nil unless File.exist?(@index_path) data = JSON.parse(File.read(@index_path)) @nodes = data['nodes'] || [] # uniq drops duplicate edges accumulated by older versions, which # appended tests edges on every update! without dedup. @edges = (data['edges'] || []).uniq @file_mtimes = data['file_mtimes'] || {} self rescue StandardError nil end |
#load_or_build! ⇒ Object
Load if exists, otherwise build from scratch.
56 57 58 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 56 def load_or_build! load || build! end |
#query(term) ⇒ Object
Query the index for symbols matching a search term.
89 90 91 92 93 94 95 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 89 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
137 138 139 140 141 142 143 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 137 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).
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 112 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.
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 125 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.
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 61 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 |
#update_file!(path) ⇒ Object
Incremental update for a single known-changed file (e.g. after a write_file/edit_file tool call). Avoids the full-tree scan in update!.
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/rubyn_code/index/codebase_index.rb', line 77 def update_file!(path) absolute = File.(path, @project_root) return self unless absolute.start_with?("#{@project_root}/") remove_nodes_for(absolute) index_file(absolute) if File.exist?(absolute) extract_rails_edges save! self end |