28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
|
# File 'lib/rails_mcp_code_search/tools/search_tool.rb', line 28
def self.call(query:, server_context:, limit: nil, file_pattern: nil)
runtime = runtime_for(server_context:)
limit = (limit || 10).clamp(1, 50)
if Chunk.count == 0
worker_state = runtime.worker.state
if worker_state == :indexing
return error_response(error: "indexing_in_progress", message: "Index is still building. Try again in a moment.", recoverable: true, suggested_action: "status")
else
return error_response(error: "index_empty", message: "No files indexed yet. Call reindex first.", recoverable: true, suggested_action: "reindex")
end
end
trigger_smart_reindex(runtime)
query_vector = runtime.embedding_adapter.embed([ query ]).first
fetch_limit = file_pattern ? limit * 5 : limit
raw_results = Chunk.nearest_neighbors(:embedding, query_vector, distance: "cosine").first(fetch_limit)
filtered_out = 0
if file_pattern
before_count = raw_results.size
raw_results = raw_results.select { File.fnmatch?(file_pattern, _1.file_path, File::FNM_PATHNAME) }
filtered_out = before_count - raw_results.size
end
results = dedup_overlapping(raw_results)
results = results.first(limit)
runtime.worker.enqueue_hit_counts(results.map(&:id))
runtime.worker.increment_search_count
stale = runtime.worker.state == :indexing
text_response({
results: results.map { format_result(_1) },
metadata: {
query:,
limit:,
count: results.size,
has_more: raw_results.size > limit,
index_state: runtime.worker.state.to_s,
index_completeness: runtime.worker.state == :idle ? 1.0 : runtime.worker.progress,
results_may_be_stale: stale,
total_indexed_chunks: Chunk.count,
filtered_out_count: filtered_out
}
})
rescue => e
error_response(error: "search_error", message: e.message, recoverable: true)
end
|