Class: SqlChatbot::Services::CodeIndexer

Inherits:
Object
  • Object
show all
Defined in:
lib/sql_chatbot/services/code_indexer.rb

Defined Under Namespace

Classes: IndexedFile, RouteInfo

Constant Summary collapse

SUPPORTED_EXTENSIONS =
Set.new(%w[
  .js .ts .jsx .tsx .rb .py .erb .vue .php .java .go .cs .ex .exs .svelte .kt .rs .dart .scala
]).freeze
SKIP_DIRS =
Set.new(%w[
  node_modules .git dist build vendor tmp __pycache__ .next .svelte-kit .nuxt target bin obj deps _build
]).freeze
DEFAULT_MAX_FILES =
2000
MAX_FILE_SIZE =

100KB — skip vendor/minified JS libraries

100_000
CONTEXT_LINES =
10
MAX_SNIPPET_LINES =
50
MAX_RESULTS =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_files: DEFAULT_MAX_FILES) ⇒ CodeIndexer

Returns a new instance of CodeIndexer.



25
26
27
28
29
30
# File 'lib/sql_chatbot/services/code_indexer.rb', line 25

def initialize(max_files: DEFAULT_MAX_FILES)
  @max_files = max_files
  @files = []
  @routes = []
  @file_count = 0
end

Instance Attribute Details

#file_countObject (readonly)

Returns the value of attribute file_count.



23
24
25
# File 'lib/sql_chatbot/services/code_indexer.rb', line 23

def file_count
  @file_count
end

Instance Method Details

#get_route_summaryObject



95
96
97
98
99
100
# File 'lib/sql_chatbot/services/code_indexer.rb', line 95

def get_route_summary
  return "No routes detected." if @routes.empty?

  lines = @routes.map { |r| "#{r.method} #{r.path} -> #{r.file}" }
  "Routes detected:\n#{lines.join("\n")}"
end

#get_routesObject



91
92
93
# File 'lib/sql_chatbot/services/code_indexer.rb', line 91

def get_routes
  @routes.map { |r| { method: r.method, path: r.path, file: r.file } }
end

#index(code_paths) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/sql_chatbot/services/code_indexer.rb', line 32

def index(code_paths)
  @files = []
  @routes = []

  code_paths.each do |code_path|
    break if @files.length >= @max_files
    scan_directory(code_path, code_path)
  end

  if @files.length > @max_files
    @files = @files.first(@max_files)
    warn "CodeIndexer: file cap reached (#{@max_files}). Some files were not indexed."
  end

  @file_count = @files.length
  detect_routes
end

#search(terms) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/sql_chatbot/services/code_indexer.rb', line 50

def search(terms)
  lower_terms = terms.map(&:downcase)
  results = []

  @files.each do |file|
    lower_content = file.content.downcase
    match_count = lower_terms.count { |term| lower_content.include?(term) }
    next if match_count == 0

    lines = file.content.split("\n")
    matched_line_indices = Set.new

    lower_terms.each do |term|
      lines.each_with_index do |line, i|
        matched_line_indices.add(i) if line.downcase.include?(term)
      end
    end

    # Build snippet with context (+-10 lines around each match, max ~50 lines)
    include_lines = Set.new
    matched_line_indices.each do |idx|
      start_line = [0, idx - CONTEXT_LINES].max
      end_line = [lines.length - 1, idx + CONTEXT_LINES].min
      (start_line..end_line).each { |i| include_lines.add(i) }
    end

    sorted_lines = include_lines.to_a.sort.first(MAX_SNIPPET_LINES)
    snippet = sorted_lines.map { |i| lines[i] }.join("\n")

    results << {
      file: file.relative_path,
      content: snippet,
      match_count: match_count
    }
  end

  # Sort by match_count descending, take top 10
  results.sort_by! { |r| -r[:match_count] }
  results.first(MAX_RESULTS)
end