codebase_index 0.2.1 → 0.3.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +95 -300
- data/exe/codebase-index-mcp +3 -31
- data/exe/codebase-index-mcp-http +3 -31
- data/lib/codebase_index/ast/method_extractor.rb +3 -8
- data/lib/codebase_index/ast/node.rb +28 -0
- data/lib/codebase_index/ast/parser.rb +53 -92
- data/lib/codebase_index/builder.rb +67 -4
- data/lib/codebase_index/cache/cache_middleware.rb +199 -0
- data/lib/codebase_index/cache/cache_store.rb +264 -0
- data/lib/codebase_index/cache/redis_cache_store.rb +116 -0
- data/lib/codebase_index/cache/solid_cache_store.rb +111 -0
- data/lib/codebase_index/chunking/semantic_chunker.rb +29 -24
- data/lib/codebase_index/console/adapters/good_job_adapter.rb +7 -40
- data/lib/codebase_index/console/adapters/job_adapter.rb +68 -0
- data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +7 -40
- data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +7 -40
- data/lib/codebase_index/console/bridge.rb +7 -0
- data/lib/codebase_index/console/console_response_renderer.rb +3 -7
- data/lib/codebase_index/console/embedded_executor.rb +2 -1
- data/lib/codebase_index/console/server.rb +1 -4
- data/lib/codebase_index/dependency_graph.rb +28 -19
- data/lib/codebase_index/embedding/indexer.rb +18 -8
- data/lib/codebase_index/embedding/openai.rb +27 -6
- data/lib/codebase_index/embedding/provider.rb +29 -2
- data/lib/codebase_index/evaluation/evaluator.rb +5 -12
- data/lib/codebase_index/extractor.rb +40 -44
- data/lib/codebase_index/extractors/action_cable_extractor.rb +9 -36
- data/lib/codebase_index/extractors/callback_analyzer.rb +22 -8
- data/lib/codebase_index/extractors/controller_extractor.rb +3 -93
- data/lib/codebase_index/extractors/decorator_extractor.rb +7 -14
- data/lib/codebase_index/extractors/engine_extractor.rb +20 -1
- data/lib/codebase_index/extractors/graphql_extractor.rb +4 -29
- data/lib/codebase_index/extractors/job_extractor.rb +11 -6
- data/lib/codebase_index/extractors/lib_extractor.rb +0 -31
- data/lib/codebase_index/extractors/mailer_extractor.rb +15 -85
- data/lib/codebase_index/extractors/manager_extractor.rb +1 -15
- data/lib/codebase_index/extractors/model_extractor.rb +20 -53
- data/lib/codebase_index/extractors/phlex_extractor.rb +8 -8
- data/lib/codebase_index/extractors/policy_extractor.rb +1 -24
- data/lib/codebase_index/extractors/poro_extractor.rb +0 -17
- data/lib/codebase_index/extractors/serializer_extractor.rb +12 -7
- data/lib/codebase_index/extractors/service_extractor.rb +1 -38
- data/lib/codebase_index/extractors/shared_utility_methods.rb +183 -1
- data/lib/codebase_index/extractors/validator_extractor.rb +3 -17
- data/lib/codebase_index/extractors/view_component_extractor.rb +10 -9
- data/lib/codebase_index/filename_utils.rb +32 -0
- data/lib/codebase_index/flow_analysis/operation_extractor.rb +1 -4
- data/lib/codebase_index/formatting/base.rb +0 -10
- data/lib/codebase_index/graph_analyzer.rb +1 -1
- data/lib/codebase_index/mcp/bootstrapper.rb +58 -0
- data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +35 -34
- data/lib/codebase_index/mcp/renderers/plain_renderer.rb +29 -29
- data/lib/codebase_index/mcp/server.rb +59 -68
- data/lib/codebase_index/mcp/tool_response_renderer.rb +23 -0
- data/lib/codebase_index/notion/client.rb +2 -2
- data/lib/codebase_index/notion/mapper.rb +1 -0
- data/lib/codebase_index/notion/mappers/column_mapper.rb +3 -11
- data/lib/codebase_index/notion/mappers/model_mapper.rb +20 -23
- data/lib/codebase_index/notion/mappers/shared.rb +22 -0
- data/lib/codebase_index/observability/health_check.rb +0 -2
- data/lib/codebase_index/observability/structured_logger.rb +12 -30
- data/lib/codebase_index/operator/pipeline_guard.rb +0 -7
- data/lib/codebase_index/resilience/index_validator.rb +3 -21
- data/lib/codebase_index/retrieval/context_assembler.rb +19 -7
- data/lib/codebase_index/retrieval/query_classifier.rb +14 -12
- data/lib/codebase_index/retrieval/ranker.rb +6 -2
- data/lib/codebase_index/retrieval/search_executor.rb +8 -19
- data/lib/codebase_index/retriever.rb +1 -9
- data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +5 -25
- data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +6 -7
- data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +58 -53
- data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +11 -7
- data/lib/codebase_index/session_tracer/file_store.rb +1 -8
- data/lib/codebase_index/session_tracer/redis_store.rb +1 -7
- data/lib/codebase_index/session_tracer/session_flow_assembler.rb +4 -13
- data/lib/codebase_index/session_tracer/solid_cache_store.rb +1 -7
- data/lib/codebase_index/session_tracer/store.rb +14 -0
- data/lib/codebase_index/storage/metadata_store.rb +37 -10
- data/lib/codebase_index/storage/pgvector.rb +37 -5
- data/lib/codebase_index/storage/qdrant.rb +39 -6
- data/lib/codebase_index/storage/vector_store.rb +11 -0
- data/lib/codebase_index/temporal/snapshot_store.rb +14 -10
- data/lib/codebase_index/token_utils.rb +19 -0
- data/lib/codebase_index/version.rb +1 -1
- data/lib/codebase_index.rb +25 -6
- data/lib/tasks/codebase_index.rake +2 -2
- metadata +11 -2
|
@@ -58,17 +58,17 @@ module CodebaseIndex
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def render_search(data, **)
|
|
61
|
-
query = data
|
|
62
|
-
count = data
|
|
63
|
-
results = data
|
|
61
|
+
query = fetch_key(data, :query)
|
|
62
|
+
count = fetch_key(data, :result_count, 0)
|
|
63
|
+
results = fetch_key(data, :results, [])
|
|
64
64
|
|
|
65
65
|
lines = []
|
|
66
66
|
lines << "Search: \"#{query}\" (#{count} results)"
|
|
67
67
|
lines << DIVIDER
|
|
68
68
|
|
|
69
69
|
results.each do |r|
|
|
70
|
-
ident = r
|
|
71
|
-
type = r
|
|
70
|
+
ident = fetch_key(r, :identifier)
|
|
71
|
+
type = fetch_key(r, :type)
|
|
72
72
|
lines << " #{ident} (#{type})"
|
|
73
73
|
end
|
|
74
74
|
|
|
@@ -84,7 +84,7 @@ module CodebaseIndex
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def render_structure(data, **)
|
|
87
|
-
manifest = data
|
|
87
|
+
manifest = fetch_key(data, :manifest, {})
|
|
88
88
|
lines = []
|
|
89
89
|
lines << 'Codebase Structure'
|
|
90
90
|
lines << DIVIDER
|
|
@@ -100,7 +100,7 @@ module CodebaseIndex
|
|
|
100
100
|
counts.sort_by { |_k, v| -v }.each { |type, count| lines << " #{type}: #{count}" }
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
summary = data
|
|
103
|
+
summary = fetch_key(data, :summary)
|
|
104
104
|
if summary
|
|
105
105
|
lines << ''
|
|
106
106
|
lines << DIVIDER
|
|
@@ -115,14 +115,14 @@ module CodebaseIndex
|
|
|
115
115
|
lines << 'Graph Analysis'
|
|
116
116
|
lines << DIVIDER
|
|
117
117
|
|
|
118
|
-
stats = data
|
|
118
|
+
stats = fetch_key(data, :stats)
|
|
119
119
|
if stats.is_a?(Hash)
|
|
120
120
|
stats.each { |k, v| lines << " #{k}: #{v}" }
|
|
121
121
|
lines << ''
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
%w[orphans dead_ends hubs cycles bridges].each do |section|
|
|
125
|
-
items = data
|
|
125
|
+
items = fetch_key(data, section)
|
|
126
126
|
next unless items.is_a?(Array) && items.any?
|
|
127
127
|
|
|
128
128
|
lines << "#{section.tr('_', ' ').upcase}:"
|
|
@@ -144,14 +144,14 @@ module CodebaseIndex
|
|
|
144
144
|
|
|
145
145
|
def render_pagerank(data, **)
|
|
146
146
|
lines = []
|
|
147
|
-
lines << "PageRank Scores (#{data
|
|
147
|
+
lines << "PageRank Scores (#{fetch_key(data, :total_nodes)} nodes)"
|
|
148
148
|
lines << DIVIDER
|
|
149
149
|
|
|
150
|
-
results = data
|
|
150
|
+
results = fetch_key(data, :results, [])
|
|
151
151
|
results.each_with_index do |r, i|
|
|
152
|
-
ident = r
|
|
153
|
-
type = r
|
|
154
|
-
score = r
|
|
152
|
+
ident = fetch_key(r, :identifier)
|
|
153
|
+
type = fetch_key(r, :type)
|
|
154
|
+
score = fetch_key(r, :score)
|
|
155
155
|
lines << " #{i + 1}. #{ident} (#{type}) - #{score}"
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -159,17 +159,17 @@ module CodebaseIndex
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def render_framework(data, **)
|
|
162
|
-
keyword = data
|
|
163
|
-
count = data
|
|
164
|
-
results = data
|
|
162
|
+
keyword = fetch_key(data, :keyword)
|
|
163
|
+
count = fetch_key(data, :result_count, 0)
|
|
164
|
+
results = fetch_key(data, :results, [])
|
|
165
165
|
|
|
166
166
|
lines = []
|
|
167
167
|
lines << "Framework: \"#{keyword}\" (#{count} results)"
|
|
168
168
|
lines << DIVIDER
|
|
169
169
|
|
|
170
170
|
results.each do |r|
|
|
171
|
-
ident = r
|
|
172
|
-
type = r
|
|
171
|
+
ident = fetch_key(r, :identifier)
|
|
172
|
+
type = fetch_key(r, :type)
|
|
173
173
|
lines << " #{ident} (#{type})"
|
|
174
174
|
end
|
|
175
175
|
|
|
@@ -177,17 +177,17 @@ module CodebaseIndex
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
def render_recent_changes(data, **)
|
|
180
|
-
count = data
|
|
181
|
-
results = data
|
|
180
|
+
count = fetch_key(data, :result_count, 0)
|
|
181
|
+
results = fetch_key(data, :results, [])
|
|
182
182
|
|
|
183
183
|
lines = []
|
|
184
184
|
lines << "Recent Changes (#{count} units)"
|
|
185
185
|
lines << DIVIDER
|
|
186
186
|
|
|
187
187
|
results.each do |r|
|
|
188
|
-
ident = r
|
|
189
|
-
type = r
|
|
190
|
-
modified = r
|
|
188
|
+
ident = fetch_key(r, :identifier)
|
|
189
|
+
type = fetch_key(r, :type)
|
|
190
|
+
modified = fetch_key(r, :last_modified) || '-'
|
|
191
191
|
lines << " #{ident} (#{type}) - #{modified}"
|
|
192
192
|
end
|
|
193
193
|
|
|
@@ -210,10 +210,10 @@ module CodebaseIndex
|
|
|
210
210
|
private
|
|
211
211
|
|
|
212
212
|
def render_plain_traversal(label, data)
|
|
213
|
-
root = data
|
|
213
|
+
root = fetch_key(data, :root)
|
|
214
214
|
found = data[:found] || data['found']
|
|
215
|
-
nodes = data
|
|
216
|
-
message = data
|
|
215
|
+
nodes = fetch_key(data, :nodes, {})
|
|
216
|
+
message = fetch_key(data, :message)
|
|
217
217
|
|
|
218
218
|
lines = []
|
|
219
219
|
lines << "#{label} of #{root}"
|
|
@@ -225,8 +225,8 @@ module CodebaseIndex
|
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
nodes.each do |id, info|
|
|
228
|
-
depth = info
|
|
229
|
-
deps = info
|
|
228
|
+
depth = fetch_key(info, :depth) || 0
|
|
229
|
+
deps = fetch_key(info, :deps, [])
|
|
230
230
|
indent = ' ' * (depth + 1)
|
|
231
231
|
lines << "#{indent}#{id}"
|
|
232
232
|
deps.each { |d| lines << "#{indent} -> #{d}" }
|
|
@@ -48,8 +48,16 @@ module CodebaseIndex
|
|
|
48
48
|
|
|
49
49
|
define_lookup_tool(server, reader, respond, renderer)
|
|
50
50
|
define_search_tool(server, reader, respond, renderer)
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
define_traversal_tool(server, reader, respond, renderer,
|
|
52
|
+
name: 'dependencies',
|
|
53
|
+
description: 'Traverse forward dependencies of a unit (what it depends on). Returns a BFS tree with depth.',
|
|
54
|
+
reader_method: :traverse_dependencies,
|
|
55
|
+
render_key: :dependencies)
|
|
56
|
+
define_traversal_tool(server, reader, respond, renderer,
|
|
57
|
+
name: 'dependents',
|
|
58
|
+
description: 'Traverse reverse dependencies of a unit (what depends on it). Returns a BFS tree with depth.',
|
|
59
|
+
reader_method: :traverse_dependents,
|
|
60
|
+
render_key: :dependents)
|
|
53
61
|
define_structure_tool(server, reader, respond, renderer)
|
|
54
62
|
define_graph_analysis_tool(server, reader, respond, renderer)
|
|
55
63
|
define_pagerank_tool(server, reader, respond, renderer)
|
|
@@ -89,7 +97,38 @@ module CodebaseIndex
|
|
|
89
97
|
end
|
|
90
98
|
end
|
|
91
99
|
|
|
100
|
+
# Coerce a value to an Array. Wraps a single String in an Array;
|
|
101
|
+
# leaves existing Arrays and nil unchanged.
|
|
102
|
+
#
|
|
103
|
+
# @param value [String, Array, nil] The input value
|
|
104
|
+
# @return [Array, nil]
|
|
105
|
+
def coerce_array(value)
|
|
106
|
+
value.is_a?(String) ? [value] : value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Apply offset+limit pagination to a single section key within a container hash.
|
|
110
|
+
# Adds `_total`, `_truncated`, and `_offset` metadata keys when truncating.
|
|
111
|
+
#
|
|
112
|
+
# @param container [Hash] The hash to mutate
|
|
113
|
+
# @param key [String] The section key whose value is an Array
|
|
114
|
+
# @param limit [Integer, nil] Max items to retain; nil means no limit
|
|
115
|
+
# @param offset [Integer] Items to skip from the front
|
|
116
|
+
# @return [void]
|
|
117
|
+
def paginate_section(container, key, limit, offset)
|
|
118
|
+
original = container[key]
|
|
119
|
+
return unless original.is_a?(Array)
|
|
120
|
+
|
|
121
|
+
sliced = offset.positive? ? original.drop(offset) : original
|
|
122
|
+
container[key] = limit ? truncate_section(sliced, limit) : sliced
|
|
123
|
+
if original.size > offset + (limit || original.size)
|
|
124
|
+
container["#{key}_total"] = original.size
|
|
125
|
+
container["#{key}_truncated"] = true
|
|
126
|
+
end
|
|
127
|
+
container["#{key}_offset"] = offset if offset.positive?
|
|
128
|
+
end
|
|
129
|
+
|
|
92
130
|
def define_lookup_tool(server, reader, respond, renderer)
|
|
131
|
+
coerce = method(:coerce_array)
|
|
93
132
|
server.define_tool(
|
|
94
133
|
name: 'lookup',
|
|
95
134
|
description: 'Look up a code unit by its exact identifier. Returns full source code, metadata, ' \
|
|
@@ -108,7 +147,7 @@ module CodebaseIndex
|
|
|
108
147
|
required: ['identifier']
|
|
109
148
|
}
|
|
110
149
|
) do |identifier:, server_context:, include_source: nil, sections: nil|
|
|
111
|
-
sections =
|
|
150
|
+
sections = coerce.call(sections)
|
|
112
151
|
unit = reader.find_unit(identifier)
|
|
113
152
|
if unit
|
|
114
153
|
always_include = %w[type identifier file_path namespace]
|
|
@@ -126,6 +165,7 @@ module CodebaseIndex
|
|
|
126
165
|
end
|
|
127
166
|
|
|
128
167
|
def define_search_tool(server, reader, respond, renderer)
|
|
168
|
+
coerce = method(:coerce_array)
|
|
129
169
|
server.define_tool(
|
|
130
170
|
name: 'search',
|
|
131
171
|
description: 'Search code units by pattern. Matches against identifiers by default; can also search source_code and metadata fields.',
|
|
@@ -145,8 +185,8 @@ module CodebaseIndex
|
|
|
145
185
|
required: ['query']
|
|
146
186
|
}
|
|
147
187
|
) do |query:, server_context:, types: nil, fields: nil, limit: nil|
|
|
148
|
-
types =
|
|
149
|
-
fields =
|
|
188
|
+
types = coerce.call(types)
|
|
189
|
+
fields = coerce.call(fields)
|
|
150
190
|
results = reader.search(
|
|
151
191
|
query,
|
|
152
192
|
types: types,
|
|
@@ -161,10 +201,11 @@ module CodebaseIndex
|
|
|
161
201
|
end
|
|
162
202
|
end
|
|
163
203
|
|
|
164
|
-
def
|
|
204
|
+
def define_traversal_tool(server, reader, respond, renderer, name:, description:, reader_method:, render_key:)
|
|
205
|
+
coerce = method(:coerce_array)
|
|
165
206
|
server.define_tool(
|
|
166
|
-
name:
|
|
167
|
-
description:
|
|
207
|
+
name: name,
|
|
208
|
+
description: description,
|
|
168
209
|
input_schema: {
|
|
169
210
|
properties: {
|
|
170
211
|
identifier: { type: 'string', description: 'Unit identifier to start from' },
|
|
@@ -177,47 +218,13 @@ module CodebaseIndex
|
|
|
177
218
|
required: ['identifier']
|
|
178
219
|
}
|
|
179
220
|
) do |identifier:, server_context:, depth: nil, types: nil|
|
|
180
|
-
types =
|
|
181
|
-
result = reader.
|
|
182
|
-
identifier,
|
|
183
|
-
depth: depth || 2,
|
|
184
|
-
types: types
|
|
185
|
-
)
|
|
221
|
+
types = coerce.call(types)
|
|
222
|
+
result = reader.send(reader_method, identifier, depth: depth || 2, types: types)
|
|
186
223
|
if result[:found] == false
|
|
187
224
|
result[:message] =
|
|
188
225
|
"Identifier '#{identifier}' not found in the index. Use 'search' to find valid identifiers."
|
|
189
226
|
end
|
|
190
|
-
respond.call(renderer.render(
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def define_dependents_tool(server, reader, respond, renderer)
|
|
195
|
-
server.define_tool(
|
|
196
|
-
name: 'dependents',
|
|
197
|
-
description: 'Traverse reverse dependencies of a unit (what depends on it). Returns a BFS tree with depth.',
|
|
198
|
-
input_schema: {
|
|
199
|
-
properties: {
|
|
200
|
-
identifier: { type: 'string', description: 'Unit identifier to start from' },
|
|
201
|
-
depth: { type: 'integer', description: 'Maximum traversal depth (default: 2)' },
|
|
202
|
-
types: {
|
|
203
|
-
type: 'array', items: { type: 'string' },
|
|
204
|
-
description: 'Filter to these types'
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
required: ['identifier']
|
|
208
|
-
}
|
|
209
|
-
) do |identifier:, server_context:, depth: nil, types: nil|
|
|
210
|
-
types = [types] if types.is_a?(String)
|
|
211
|
-
result = reader.traverse_dependents(
|
|
212
|
-
identifier,
|
|
213
|
-
depth: depth || 2,
|
|
214
|
-
types: types
|
|
215
|
-
)
|
|
216
|
-
if result[:found] == false
|
|
217
|
-
result[:message] =
|
|
218
|
-
"Identifier '#{identifier}' not found in the index. Use 'search' to find valid identifiers."
|
|
219
|
-
end
|
|
220
|
-
respond.call(renderer.render(:dependents, result))
|
|
227
|
+
respond.call(renderer.render(render_key, result))
|
|
221
228
|
end
|
|
222
229
|
end
|
|
223
230
|
|
|
@@ -241,7 +248,7 @@ module CodebaseIndex
|
|
|
241
248
|
end
|
|
242
249
|
|
|
243
250
|
def define_graph_analysis_tool(server, reader, respond, renderer)
|
|
244
|
-
|
|
251
|
+
paginate = method(:paginate_section)
|
|
245
252
|
server.define_tool(
|
|
246
253
|
name: 'graph_analysis',
|
|
247
254
|
description: 'Get structural analysis of the dependency graph: orphans, dead ends, hubs, cycles, and bridges.',
|
|
@@ -265,16 +272,7 @@ module CodebaseIndex
|
|
|
265
272
|
if limit || effective_offset.positive?
|
|
266
273
|
truncated = data.dup
|
|
267
274
|
%w[orphans dead_ends hubs cycles bridges].each do |key|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
original = truncated[key]
|
|
271
|
-
sliced = effective_offset.positive? ? original.drop(effective_offset) : original
|
|
272
|
-
truncated[key] = limit ? truncate.call(sliced, limit) : sliced
|
|
273
|
-
if original.size > effective_offset + (limit || original.size)
|
|
274
|
-
truncated["#{key}_total"] = original.size
|
|
275
|
-
truncated["#{key}_truncated"] = true
|
|
276
|
-
end
|
|
277
|
-
truncated["#{key}_offset"] = effective_offset if effective_offset.positive?
|
|
275
|
+
paginate.call(truncated, key, limit, effective_offset)
|
|
278
276
|
end
|
|
279
277
|
truncated
|
|
280
278
|
else
|
|
@@ -282,16 +280,7 @@ module CodebaseIndex
|
|
|
282
280
|
end
|
|
283
281
|
else
|
|
284
282
|
single = { section => data[section], 'stats' => data['stats'] }
|
|
285
|
-
|
|
286
|
-
original = data[section]
|
|
287
|
-
sliced = effective_offset.positive? ? original.drop(effective_offset) : original
|
|
288
|
-
single[section] = limit ? truncate.call(sliced, limit) : sliced
|
|
289
|
-
if original.size > effective_offset + (limit || original.size)
|
|
290
|
-
single["#{section}_total"] = original.size
|
|
291
|
-
single["#{section}_truncated"] = true
|
|
292
|
-
end
|
|
293
|
-
single["#{section}_offset"] = effective_offset if effective_offset.positive?
|
|
294
|
-
end
|
|
283
|
+
paginate.call(single, section, limit, effective_offset) if limit || effective_offset.positive?
|
|
295
284
|
single
|
|
296
285
|
end
|
|
297
286
|
|
|
@@ -300,6 +289,7 @@ module CodebaseIndex
|
|
|
300
289
|
end
|
|
301
290
|
|
|
302
291
|
def define_pagerank_tool(server, reader, respond, renderer)
|
|
292
|
+
coerce = method(:coerce_array)
|
|
303
293
|
server.define_tool(
|
|
304
294
|
name: 'pagerank',
|
|
305
295
|
description: 'Get PageRank importance scores for code units. Higher scores indicate more structurally important nodes.',
|
|
@@ -313,7 +303,7 @@ module CodebaseIndex
|
|
|
313
303
|
}
|
|
314
304
|
}
|
|
315
305
|
) do |server_context:, limit: nil, types: nil|
|
|
316
|
-
types =
|
|
306
|
+
types = coerce.call(types)
|
|
317
307
|
scores = reader.dependency_graph.pagerank
|
|
318
308
|
graph_data = reader.raw_graph_data
|
|
319
309
|
nodes = graph_data['nodes'] || {}
|
|
@@ -362,6 +352,7 @@ module CodebaseIndex
|
|
|
362
352
|
end
|
|
363
353
|
|
|
364
354
|
def define_recent_changes_tool(server, reader, respond, renderer)
|
|
355
|
+
coerce = method(:coerce_array)
|
|
365
356
|
server.define_tool(
|
|
366
357
|
name: 'recent_changes',
|
|
367
358
|
description: 'List recently modified code units sorted by git last_modified timestamp. ' \
|
|
@@ -376,7 +367,7 @@ module CodebaseIndex
|
|
|
376
367
|
}
|
|
377
368
|
}
|
|
378
369
|
) do |server_context:, limit: nil, types: nil|
|
|
379
|
-
types =
|
|
370
|
+
types = coerce.call(types)
|
|
380
371
|
results = reader.recent_changes(limit: limit || 10, types: types)
|
|
381
372
|
respond.call(renderer.render(:recent_changes, {
|
|
382
373
|
result_count: results.size,
|
|
@@ -57,6 +57,29 @@ module CodebaseIndex
|
|
|
57
57
|
def render_default(data)
|
|
58
58
|
raise NotImplementedError, "#{self.class}#render_default must be implemented"
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Fetch a value from a hash by symbol or string key, falling back to a default.
|
|
64
|
+
#
|
|
65
|
+
# Handles data hashes that may use either symbol or string keys (e.g., data
|
|
66
|
+
# assembled from JSON parsing vs. direct Hash literals).
|
|
67
|
+
#
|
|
68
|
+
# @param data [Hash] The source hash
|
|
69
|
+
# @param key [Symbol, String] The key to look up
|
|
70
|
+
# @param default [Object] Value to return when key is absent (default: nil)
|
|
71
|
+
# @return [Object]
|
|
72
|
+
def fetch_key(data, key, default = nil)
|
|
73
|
+
sym_key = key.to_sym
|
|
74
|
+
str_key = key.to_s
|
|
75
|
+
if data.key?(sym_key)
|
|
76
|
+
data[sym_key]
|
|
77
|
+
elsif data.key?(str_key)
|
|
78
|
+
data[str_key]
|
|
79
|
+
else
|
|
80
|
+
default
|
|
81
|
+
end
|
|
82
|
+
end
|
|
60
83
|
end
|
|
61
84
|
end
|
|
62
85
|
end
|
|
@@ -129,7 +129,7 @@ module CodebaseIndex
|
|
|
129
129
|
retries = 0
|
|
130
130
|
|
|
131
131
|
loop do
|
|
132
|
-
response = execute_with_retry(method, path, body
|
|
132
|
+
response = execute_with_retry(method, path, body)
|
|
133
133
|
|
|
134
134
|
return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
|
|
135
135
|
|
|
@@ -148,7 +148,7 @@ module CodebaseIndex
|
|
|
148
148
|
#
|
|
149
149
|
# @return [Net::HTTPResponse]
|
|
150
150
|
# @raise [CodebaseIndex::Error] on persistent network failures
|
|
151
|
-
def execute_with_retry(method, path, body
|
|
151
|
+
def execute_with_retry(method, path, body)
|
|
152
152
|
attempts = 0
|
|
153
153
|
begin
|
|
154
154
|
@rate_limiter.throttle { execute_http(method, path, body) }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'shared'
|
|
4
|
+
|
|
3
5
|
module CodebaseIndex
|
|
4
6
|
module Notion
|
|
5
7
|
module Mappers
|
|
@@ -13,7 +15,7 @@ module CodebaseIndex
|
|
|
13
15
|
# properties = mapper.map(column, model_identifier: "User", validations: [...], parent_page_id: "page-123")
|
|
14
16
|
#
|
|
15
17
|
class ColumnMapper
|
|
16
|
-
|
|
18
|
+
include Shared
|
|
17
19
|
|
|
18
20
|
# Map a single column to Notion Columns page properties.
|
|
19
21
|
#
|
|
@@ -49,16 +51,6 @@ module CodebaseIndex
|
|
|
49
51
|
|
|
50
52
|
matched.map { |v| v['type'] }.join(', ')
|
|
51
53
|
end
|
|
52
|
-
|
|
53
|
-
# Build a Notion rich_text property.
|
|
54
|
-
#
|
|
55
|
-
# @param text [String]
|
|
56
|
-
# @return [Hash]
|
|
57
|
-
def rich_text_property(text)
|
|
58
|
-
content = text.to_s
|
|
59
|
-
content = "#{content[0...1997]}..." if content.length > MAX_RICH_TEXT_LENGTH
|
|
60
|
-
{ rich_text: [{ text: { content: content } }] }
|
|
61
|
-
end
|
|
62
54
|
end
|
|
63
55
|
end
|
|
64
56
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'shared'
|
|
4
|
+
|
|
3
5
|
module CodebaseIndex
|
|
4
6
|
module Notion
|
|
5
7
|
module Mappers
|
|
@@ -14,7 +16,7 @@ module CodebaseIndex
|
|
|
14
16
|
# client.create_page(database_id: db_id, properties: properties)
|
|
15
17
|
#
|
|
16
18
|
class ModelMapper
|
|
17
|
-
|
|
19
|
+
include Shared
|
|
18
20
|
|
|
19
21
|
# Map a model unit to Notion Data Models page properties.
|
|
20
22
|
#
|
|
@@ -83,9 +85,7 @@ module CodebaseIndex
|
|
|
83
85
|
|
|
84
86
|
# @return [String]
|
|
85
87
|
def format_associations(associations)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
associations.map { |a| format_single_association(a) }.join("\n")
|
|
88
|
+
format_list(associations) { |items| items.map { |a| format_single_association(a) }.join("\n") }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# @return [String]
|
|
@@ -99,18 +99,16 @@ module CodebaseIndex
|
|
|
99
99
|
|
|
100
100
|
# @return [String]
|
|
101
101
|
def format_validations(validations)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
102
|
+
format_list(validations) do |items|
|
|
103
|
+
items.group_by { |v| v['attribute'] }.map do |attr, vals|
|
|
104
|
+
"#{attr}: #{vals.map { |v| v['type'] }.join(', ')}"
|
|
105
|
+
end.join("\n")
|
|
106
|
+
end
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# @return [String]
|
|
110
110
|
def format_callbacks(callbacks)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
callbacks.map { |callback| format_single_callback(callback) }.join("\n")
|
|
111
|
+
format_list(callbacks) { |items| items.map { |callback| format_single_callback(callback) }.join("\n") }
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
# @return [String]
|
|
@@ -135,16 +133,12 @@ module CodebaseIndex
|
|
|
135
133
|
|
|
136
134
|
# @return [String]
|
|
137
135
|
def format_scopes(scopes)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
scopes.map { |s| s['name'] }.join(', ')
|
|
136
|
+
format_list(scopes) { |items| items.map { |s| s['name'] }.join(', ') }
|
|
141
137
|
end
|
|
142
138
|
|
|
143
139
|
# @return [String]
|
|
144
140
|
def format_dependencies(dependencies)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
dependencies.map { |dep| "#{dep['target']} (via #{dep['via']})" }.join(', ')
|
|
141
|
+
format_list(dependencies) { |items| items.map { |dep| "#{dep['target']} (via #{dep['via']})" }.join(', ') }
|
|
148
142
|
end
|
|
149
143
|
|
|
150
144
|
# @return [Hash]
|
|
@@ -152,11 +146,14 @@ module CodebaseIndex
|
|
|
152
146
|
{ title: [{ text: { content: text } }] }
|
|
153
147
|
end
|
|
154
148
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
# Return 'None' for nil/empty lists; otherwise yield items to a formatting block.
|
|
150
|
+
#
|
|
151
|
+
# @param items [Array, nil]
|
|
152
|
+
# @return [String]
|
|
153
|
+
def format_list(items)
|
|
154
|
+
return 'None' if items.nil? || items.empty?
|
|
155
|
+
|
|
156
|
+
yield items
|
|
160
157
|
end
|
|
161
158
|
end
|
|
162
159
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Notion
|
|
5
|
+
module Mappers
|
|
6
|
+
# Shared helpers for Notion mapper classes.
|
|
7
|
+
module Shared
|
|
8
|
+
MAX_RICH_TEXT_LENGTH = 2000
|
|
9
|
+
|
|
10
|
+
# Build a Notion rich_text property, truncating to API limits.
|
|
11
|
+
#
|
|
12
|
+
# @param text [String]
|
|
13
|
+
# @return [Hash]
|
|
14
|
+
def rich_text_property(text)
|
|
15
|
+
content = text.to_s
|
|
16
|
+
content = "#{content[0...1997]}..." if content.length > MAX_RICH_TEXT_LENGTH
|
|
17
|
+
{ rich_text: [{ text: { content: content } }] }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -22,36 +22,18 @@ module CodebaseIndex
|
|
|
22
22
|
@output = output
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def warn(event, **data)
|
|
38
|
-
write_entry('warn', event, data)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Log at error level.
|
|
42
|
-
#
|
|
43
|
-
# @param event [String] Event name
|
|
44
|
-
# @param data [Hash] Additional structured data
|
|
45
|
-
def error(event, **data)
|
|
46
|
-
write_entry('error', event, data)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Log at debug level.
|
|
50
|
-
#
|
|
51
|
-
# @param event [String] Event name
|
|
52
|
-
# @param data [Hash] Additional structured data
|
|
53
|
-
def debug(event, **data)
|
|
54
|
-
write_entry('debug', event, data)
|
|
25
|
+
# @!method info(event, **data)
|
|
26
|
+
# Log at info level.
|
|
27
|
+
# @param event [String] Event name
|
|
28
|
+
# @param data [Hash] Additional structured data
|
|
29
|
+
# @!method warn(event, **data)
|
|
30
|
+
# Log at warn level.
|
|
31
|
+
# @!method error(event, **data)
|
|
32
|
+
# Log at error level.
|
|
33
|
+
# @!method debug(event, **data)
|
|
34
|
+
# Log at debug level.
|
|
35
|
+
%w[info warn error debug].each do |level|
|
|
36
|
+
define_method(level) { |event, **data| write_entry(level, event, data) }
|
|
55
37
|
end
|
|
56
38
|
|
|
57
39
|
private
|