rubydex 0.2.8 → 0.3.0
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/README.md +69 -3
- data/THIRD_PARTY_LICENSES.html +168 -1381
- data/exe/rdx +136 -34
- data/ext/rubydex/declaration.c +1 -1
- data/ext/rubydex/definition.c +32 -4
- data/ext/rubydex/extconf.rb +0 -8
- data/ext/rubydex/graph.c +29 -18
- data/ext/rubydex/query.c +105 -0
- data/ext/rubydex/query.h +8 -0
- data/ext/rubydex/reference.c +60 -0
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +12 -0
- data/ext/rubydex/utils.h +5 -0
- data/lib/rubydex/mcp_server/protocol.rb +156 -0
- data/lib/rubydex/mcp_server/tools/base_tool.rb +109 -0
- data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +30 -0
- data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +46 -0
- data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +68 -0
- data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +46 -0
- data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +55 -0
- data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +55 -0
- data/lib/rubydex/mcp_server.rb +219 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +26 -4
- data/rust/Cargo.lock +5 -552
- data/rust/Cargo.toml +0 -1
- data/rust/rubydex/Cargo.toml +1 -0
- data/rust/rubydex/benches/graph_memory.rs +22 -4
- data/rust/rubydex/src/compile_assertions.rs +15 -0
- data/rust/rubydex/src/config.rs +54 -34
- data/rust/rubydex/src/diagnostic.rs +1 -1
- data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
- data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
- data/rust/rubydex/src/listing.rs +159 -102
- data/rust/rubydex/src/main.rs +3 -125
- data/rust/rubydex/src/model/declaration.rs +0 -11
- data/rust/rubydex/src/model/definitions.rs +27 -26
- data/rust/rubydex/src/model/document.rs +43 -7
- data/rust/rubydex/src/model/graph.rs +47 -35
- data/rust/rubydex/src/model/id.rs +55 -0
- data/rust/rubydex/src/model/ids.rs +21 -9
- data/rust/rubydex/src/model/name.rs +35 -7
- data/rust/rubydex/src/model/references.rs +16 -13
- data/rust/rubydex/src/operation/applier.rs +0 -2
- data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
- data/rust/rubydex/src/query/cypher/schema.rs +790 -0
- data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
- data/rust/rubydex/src/query/cypher/tests.rs +228 -0
- data/rust/rubydex/src/query/cypher.rs +57 -0
- data/rust/rubydex/src/query.rs +2 -0
- data/rust/rubydex/src/resolution.rs +248 -227
- data/rust/rubydex/src/resolution_tests.rs +263 -65
- data/rust/rubydex-sys/src/declaration_api.rs +6 -3
- data/rust/rubydex-sys/src/definition_api.rs +27 -7
- data/rust/rubydex-sys/src/graph_api.rs +175 -27
- data/rust/rubydex-sys/src/reference_api.rs +58 -12
- metadata +17 -10
- data/exe/rubydex_mcp +0 -17
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/rust/rubydex-mcp/Cargo.toml +0 -34
- data/rust/rubydex-mcp/src/main.rs +0 -48
- data/rust/rubydex-mcp/src/server.rs +0 -1148
- data/rust/rubydex-mcp/src/tools.rs +0 -49
- data/rust/rubydex-mcp/tests/mcp.rs +0 -302
|
@@ -15,47 +15,51 @@ use rubydex::model::ids::{DeclarationId, NameId, UriId, declaration_id_from_look
|
|
|
15
15
|
use rubydex::model::keywords;
|
|
16
16
|
use rubydex::model::name::NameRef;
|
|
17
17
|
use rubydex::model::visibility::Visibility;
|
|
18
|
+
use rubydex::query::cypher::{self, OutputFormat};
|
|
18
19
|
use rubydex::query::{CompletionCandidate, CompletionContext, CompletionReceiver};
|
|
19
20
|
use rubydex::resolution::Resolver;
|
|
20
21
|
use rubydex::{indexing, integrity, listing, query};
|
|
21
22
|
use std::ffi::CString;
|
|
22
23
|
use std::path::{Path, PathBuf};
|
|
23
|
-
use std::{mem, ptr};
|
|
24
|
+
use std::{mem, ptr, sync::RwLock};
|
|
24
25
|
|
|
25
26
|
pub type GraphPointer = *mut c_void;
|
|
26
27
|
|
|
27
|
-
/// Creates a new graph
|
|
28
|
+
/// Creates a new graph wrapped in a `Box<RwLock<Graph>>` for thread-safe shared access across Ractors.
|
|
28
29
|
#[unsafe(no_mangle)]
|
|
29
30
|
pub extern "C" fn rdx_graph_new() -> GraphPointer {
|
|
30
|
-
Box::into_raw(Box::new(Graph::new())) as GraphPointer
|
|
31
|
+
Box::into_raw(Box::new(RwLock::new(Graph::new()))) as GraphPointer
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/// Frees a Graph through its pointer
|
|
34
35
|
#[unsafe(no_mangle)]
|
|
35
36
|
pub extern "C" fn rdx_graph_free(pointer: GraphPointer) {
|
|
36
37
|
unsafe {
|
|
37
|
-
let _ = Box::from_raw(pointer.cast::<Graph
|
|
38
|
+
let _ = Box::from_raw(pointer.cast::<RwLock<Graph>>());
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/// Runs `action` against the graph referenced by `pointer` under a read lock.
|
|
43
|
+
///
|
|
44
|
+
/// # Panics
|
|
45
|
+
///
|
|
46
|
+
/// Panics if the `RwLock` is poisoned (a writer panicked while holding the lock).
|
|
41
47
|
pub fn with_graph<F, T>(pointer: GraphPointer, action: F) -> T
|
|
42
48
|
where
|
|
43
49
|
F: FnOnce(&Graph) -> T,
|
|
44
50
|
{
|
|
45
|
-
let
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
result
|
|
51
|
+
let rwlock = unsafe { &*pointer.cast::<RwLock<Graph>>() };
|
|
52
|
+
let guard = rwlock.read().unwrap();
|
|
53
|
+
action(&guard)
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
fn with_mut_graph<F, T>(pointer: GraphPointer, action: F) -> T
|
|
52
57
|
where
|
|
53
58
|
F: FnOnce(&mut Graph) -> T,
|
|
54
59
|
{
|
|
55
|
-
let
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
result
|
|
60
|
+
let rwlock = unsafe { &*pointer.cast::<RwLock<Graph>>() };
|
|
61
|
+
let mut guard = rwlock.write().unwrap();
|
|
62
|
+
action(&mut guard)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/// Searches the graph using exact substring matching, returning every declaration whose name matches any of the
|
|
@@ -169,37 +173,41 @@ pub unsafe extern "C" fn rdx_graph_resolve_constant(
|
|
|
169
173
|
})
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
/// Adds
|
|
176
|
+
/// Adds glob patterns to exclude from file discovery during indexing.
|
|
173
177
|
///
|
|
174
178
|
/// # Panics
|
|
175
179
|
///
|
|
176
|
-
/// Will panic if the given array of C string
|
|
180
|
+
/// Will panic if the given array of C string patterns cannot be converted to a `Vec<String>`.
|
|
177
181
|
///
|
|
178
182
|
/// # Safety
|
|
179
183
|
///
|
|
180
184
|
/// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
|
|
181
|
-
/// - `
|
|
185
|
+
/// - `patterns` must be an array of `count` valid, null-terminated UTF-8 strings.
|
|
182
186
|
#[unsafe(no_mangle)]
|
|
183
|
-
pub unsafe extern "C" fn
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
pub unsafe extern "C" fn rdx_graph_exclude_patterns(
|
|
188
|
+
pointer: GraphPointer,
|
|
189
|
+
patterns: *const *const c_char,
|
|
190
|
+
count: usize,
|
|
191
|
+
) {
|
|
192
|
+
let patterns: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(patterns, count).unwrap() };
|
|
193
|
+
let entries: Vec<Box<str>> = patterns.into_iter().map(String::into_boxed_str).collect();
|
|
194
|
+
with_mut_graph(pointer, |graph| graph.exclude_patterns(entries));
|
|
187
195
|
}
|
|
188
196
|
|
|
189
|
-
/// Returns the currently excluded
|
|
190
|
-
///
|
|
197
|
+
/// Returns the currently excluded glob patterns as an array of C strings. Writes the count to `out_count`. Returns NULL
|
|
198
|
+
/// if no patterns are excluded. Caller must free with `free_c_string_array`.
|
|
191
199
|
///
|
|
192
200
|
/// # Safety
|
|
193
201
|
///
|
|
194
202
|
/// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
|
|
195
203
|
/// - `out_count` must be a valid, writable pointer.
|
|
196
204
|
#[unsafe(no_mangle)]
|
|
197
|
-
pub unsafe extern "C" fn
|
|
205
|
+
pub unsafe extern "C" fn rdx_graph_excluded_patterns(
|
|
198
206
|
pointer: GraphPointer,
|
|
199
207
|
out_count: *mut usize,
|
|
200
208
|
) -> *const *const c_char {
|
|
201
209
|
with_graph(pointer, |graph| {
|
|
202
|
-
let excluded = graph.
|
|
210
|
+
let excluded = graph.excluded_patterns();
|
|
203
211
|
|
|
204
212
|
if excluded.is_empty() {
|
|
205
213
|
unsafe { *out_count = 0 };
|
|
@@ -213,7 +221,7 @@ pub unsafe extern "C" fn rdx_graph_excluded_paths(
|
|
|
213
221
|
// on Windows if a configuration file is using forward slashes. For example:
|
|
214
222
|
//
|
|
215
223
|
// C:\project/vendor/bundle
|
|
216
|
-
let normalized = path.
|
|
224
|
+
let normalized = path.replace(std::path::MAIN_SEPARATOR, "/");
|
|
217
225
|
|
|
218
226
|
CString::new(normalized)
|
|
219
227
|
.ok()
|
|
@@ -313,7 +321,7 @@ pub unsafe extern "C" fn rdx_index_all(
|
|
|
313
321
|
let file_paths: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(file_paths, count).unwrap() };
|
|
314
322
|
|
|
315
323
|
with_mut_graph(pointer, |graph| {
|
|
316
|
-
let (file_paths, listing_errors) = listing::collect_file_paths(file_paths, &graph.
|
|
324
|
+
let (file_paths, listing_errors) = listing::collect_file_paths(file_paths, &graph.excluded_patterns());
|
|
317
325
|
let indexing_errors = indexing::index_files(graph, file_paths, indexing::IndexerBackend::RubyIndexer);
|
|
318
326
|
|
|
319
327
|
let all_errors: Vec<String> = listing_errors
|
|
@@ -1058,6 +1066,145 @@ pub unsafe extern "C" fn rdx_keyword_get(name: *const c_char) -> *const CKeyword
|
|
|
1058
1066
|
}
|
|
1059
1067
|
}
|
|
1060
1068
|
|
|
1069
|
+
/// The result of running a Cypher query, carrying either the formatted output or an error message.
|
|
1070
|
+
#[repr(C)]
|
|
1071
|
+
pub struct CQueryResult {
|
|
1072
|
+
/// Non-null on success; null on error. Caller must free with `free_c_string`.
|
|
1073
|
+
pub output: *const c_char,
|
|
1074
|
+
/// Non-null on error; null on success. Caller must free with `free_c_string`.
|
|
1075
|
+
pub error: *const c_char,
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
impl CQueryResult {
|
|
1079
|
+
fn success(output: &str) -> Self {
|
|
1080
|
+
match CString::new(output) {
|
|
1081
|
+
Ok(c_string) => Self {
|
|
1082
|
+
output: c_string.into_raw().cast_const(),
|
|
1083
|
+
error: ptr::null(),
|
|
1084
|
+
},
|
|
1085
|
+
Err(_) => Self::error("query output contained an interior NUL byte"),
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
fn error(message: &str) -> Self {
|
|
1090
|
+
Self {
|
|
1091
|
+
output: ptr::null(),
|
|
1092
|
+
error: CString::new(message).map_or(ptr::null(), |s| s.into_raw().cast_const()),
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/// The result of parsing a Cypher query into an opaque, reusable parsed-query object.
|
|
1098
|
+
#[repr(C)]
|
|
1099
|
+
pub struct CParseResult {
|
|
1100
|
+
/// Non-null on success: a heap-allocated parsed query. Free with `rdx_cypher_query_free`.
|
|
1101
|
+
pub query: *mut c_void,
|
|
1102
|
+
/// Non-null on error; null on success. Caller must free with `free_c_string`.
|
|
1103
|
+
pub error: *const c_char,
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/// Parses a Cypher query string into an opaque parsed-query object, without needing a graph.
|
|
1107
|
+
///
|
|
1108
|
+
/// On success, `query` is a heap-allocated parsed query that can be executed against a graph with
|
|
1109
|
+
/// `rdx_query_run` and must eventually be freed with `rdx_cypher_query_free`. On failure, `error`
|
|
1110
|
+
/// holds the message.
|
|
1111
|
+
///
|
|
1112
|
+
/// # Safety
|
|
1113
|
+
///
|
|
1114
|
+
/// - `query` must be a valid, null-terminated UTF-8 string.
|
|
1115
|
+
#[unsafe(no_mangle)]
|
|
1116
|
+
pub unsafe extern "C" fn rdx_cypher_parse(query: *const c_char) -> CParseResult {
|
|
1117
|
+
let Ok(query_str) = (unsafe { utils::convert_char_ptr_to_string(query) }) else {
|
|
1118
|
+
return CParseResult {
|
|
1119
|
+
query: ptr::null_mut(),
|
|
1120
|
+
error: CString::new("query is not valid UTF-8").map_or(ptr::null(), |s| s.into_raw().cast_const()),
|
|
1121
|
+
};
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
match cypher::parse(&query_str) {
|
|
1125
|
+
Ok(parsed) => CParseResult {
|
|
1126
|
+
query: Box::into_raw(Box::new(parsed)).cast::<c_void>(),
|
|
1127
|
+
error: ptr::null(),
|
|
1128
|
+
},
|
|
1129
|
+
Err(error) => CParseResult {
|
|
1130
|
+
query: ptr::null_mut(),
|
|
1131
|
+
error: CString::new(error.to_string()).map_or(ptr::null(), |s| s.into_raw().cast_const()),
|
|
1132
|
+
},
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/// Frees a parsed query previously returned by `rdx_cypher_parse`.
|
|
1137
|
+
///
|
|
1138
|
+
/// # Safety
|
|
1139
|
+
///
|
|
1140
|
+
/// - `query` must be a pointer returned by `rdx_cypher_parse`, or null. It must not be used after.
|
|
1141
|
+
#[unsafe(no_mangle)]
|
|
1142
|
+
pub unsafe extern "C" fn rdx_cypher_query_free(query: *mut c_void) {
|
|
1143
|
+
if query.is_null() {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
let _ = unsafe { Box::from_raw(query.cast::<cypher::Query>()) };
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/// Executes a previously parsed query (from `rdx_cypher_parse`) against the graph and returns the
|
|
1150
|
+
/// formatted output or an error message. `format` must be `"table"` or `"json"`.
|
|
1151
|
+
///
|
|
1152
|
+
/// # Safety
|
|
1153
|
+
///
|
|
1154
|
+
/// - `query` must be a valid pointer returned by `rdx_cypher_parse`.
|
|
1155
|
+
/// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
|
|
1156
|
+
/// - `format` must be a valid, null-terminated UTF-8 string.
|
|
1157
|
+
#[unsafe(no_mangle)]
|
|
1158
|
+
pub unsafe extern "C" fn rdx_query_run(
|
|
1159
|
+
query: *const c_void,
|
|
1160
|
+
pointer: GraphPointer,
|
|
1161
|
+
format: *const c_char,
|
|
1162
|
+
) -> CQueryResult {
|
|
1163
|
+
if query.is_null() {
|
|
1164
|
+
return CQueryResult::error("query is null");
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
let Ok(format_str) = (unsafe { utils::convert_char_ptr_to_string(format) }) else {
|
|
1168
|
+
return CQueryResult::error("format is not valid UTF-8");
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
let output_format = match format_str.as_str() {
|
|
1172
|
+
"table" => OutputFormat::Table,
|
|
1173
|
+
"json" => OutputFormat::Json,
|
|
1174
|
+
other => {
|
|
1175
|
+
return CQueryResult::error(&format!("unknown query format `{other}` (expected `table` or `json`)"));
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
let parsed = unsafe { &*query.cast::<cypher::Query>() };
|
|
1180
|
+
|
|
1181
|
+
with_graph(pointer, |graph| {
|
|
1182
|
+
match cypher::run_parsed(graph, parsed, output_format) {
|
|
1183
|
+
Ok(output) => CQueryResult::success(&output),
|
|
1184
|
+
Err(error) => CQueryResult::error(&error.to_string()),
|
|
1185
|
+
}
|
|
1186
|
+
})
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/// Returns a description of the queryable Cypher schema (node labels, relationship types, and
|
|
1190
|
+
/// properties) in the given format (`"table"` or `"json"`). The schema is static and requires no
|
|
1191
|
+
/// graph. Caller must free the returned pointer with `free_c_string`.
|
|
1192
|
+
///
|
|
1193
|
+
/// # Safety
|
|
1194
|
+
///
|
|
1195
|
+
/// - `format` must be a valid, null-terminated UTF-8 string.
|
|
1196
|
+
#[unsafe(no_mangle)]
|
|
1197
|
+
pub unsafe extern "C" fn rdx_cypher_schema(format: *const c_char) -> *const c_char {
|
|
1198
|
+
let format_str = unsafe { utils::convert_char_ptr_to_string(format) }.unwrap_or_else(|_| "table".to_string());
|
|
1199
|
+
let output_format = if format_str == "json" {
|
|
1200
|
+
OutputFormat::Json
|
|
1201
|
+
} else {
|
|
1202
|
+
OutputFormat::Table
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
CString::new(cypher::schema(output_format)).map_or(ptr::null(), |s| s.into_raw().cast_const())
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1061
1208
|
#[repr(u8)]
|
|
1062
1209
|
#[derive(Debug, Clone, Copy)]
|
|
1063
1210
|
pub enum CVisibility {
|
|
@@ -1166,7 +1313,7 @@ mod tests {
|
|
|
1166
1313
|
.ref_count()
|
|
1167
1314
|
);
|
|
1168
1315
|
|
|
1169
|
-
let graph_ptr = Box::into_raw(Box::new(graph)) as GraphPointer;
|
|
1316
|
+
let graph_ptr = Box::into_raw(Box::new(RwLock::new(graph))) as GraphPointer;
|
|
1170
1317
|
|
|
1171
1318
|
// Build the nesting array: ["Foo"] since BAR is inside class Foo
|
|
1172
1319
|
let nesting_strings = [CString::new("Foo").unwrap()];
|
|
@@ -1182,7 +1329,8 @@ mod tests {
|
|
|
1182
1329
|
assert_eq!((*decl).id(), *DeclarationId::from("Foo::BAR"));
|
|
1183
1330
|
};
|
|
1184
1331
|
|
|
1185
|
-
let graph = unsafe { Box::from_raw(graph_ptr.cast::<Graph
|
|
1332
|
+
let graph = unsafe { Box::from_raw(graph_ptr.cast::<RwLock<Graph>>()) };
|
|
1333
|
+
let graph = graph.read().unwrap();
|
|
1186
1334
|
|
|
1187
1335
|
assert_eq!(
|
|
1188
1336
|
1,
|
|
@@ -120,7 +120,7 @@ pub unsafe extern "C" fn rdx_method_references_iter_free(iter: *mut MethodRefere
|
|
|
120
120
|
unsafe { MethodReferencesIter::free(iter) }
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/// Returns the UTF-8 name string for a constant reference id.
|
|
123
|
+
/// Returns the UTF-8 name string for a constant reference id, or NULL if the reference cannot be found.
|
|
124
124
|
/// Caller must free with `free_c_string`.
|
|
125
125
|
///
|
|
126
126
|
/// # Safety
|
|
@@ -129,12 +129,14 @@ pub unsafe extern "C" fn rdx_method_references_iter_free(iter: *mut MethodRefere
|
|
|
129
129
|
///
|
|
130
130
|
/// # Panics
|
|
131
131
|
///
|
|
132
|
-
/// This function will panic if the reference cannot be found.
|
|
132
|
+
/// This function will panic if the reference's name cannot be found.
|
|
133
133
|
#[unsafe(no_mangle)]
|
|
134
134
|
pub unsafe extern "C" fn rdx_constant_reference_name(pointer: GraphPointer, reference_id: u64) -> *const c_char {
|
|
135
135
|
with_graph(pointer, |graph| {
|
|
136
136
|
let ref_id = ConstantReferenceId::new(reference_id);
|
|
137
|
-
let reference = graph.constant_references().get(&ref_id)
|
|
137
|
+
let Some(reference) = graph.constant_references().get(&ref_id) else {
|
|
138
|
+
return ptr::null();
|
|
139
|
+
};
|
|
138
140
|
let name = graph.names().get(reference.name_id()).expect("Name ID should exist");
|
|
139
141
|
|
|
140
142
|
let name_string = graph
|
|
@@ -146,7 +148,7 @@ pub unsafe extern "C" fn rdx_constant_reference_name(pointer: GraphPointer, refe
|
|
|
146
148
|
})
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
/// Returns the UTF-8 name string for a method reference id.
|
|
151
|
+
/// Returns the UTF-8 name string for a method reference id, or NULL if the reference cannot be found.
|
|
150
152
|
/// Caller must free with `free_c_string`.
|
|
151
153
|
///
|
|
152
154
|
/// # Safety
|
|
@@ -155,12 +157,14 @@ pub unsafe extern "C" fn rdx_constant_reference_name(pointer: GraphPointer, refe
|
|
|
155
157
|
///
|
|
156
158
|
/// # Panics
|
|
157
159
|
///
|
|
158
|
-
/// This function will panic if the reference cannot be found.
|
|
160
|
+
/// This function will panic if the reference's name cannot be found.
|
|
159
161
|
#[unsafe(no_mangle)]
|
|
160
162
|
pub unsafe extern "C" fn rdx_method_reference_name(pointer: GraphPointer, reference_id: u64) -> *const c_char {
|
|
161
163
|
with_graph(pointer, |graph| {
|
|
162
164
|
let ref_id = MethodReferenceId::new(reference_id);
|
|
163
|
-
let reference = graph.method_references().get(&ref_id)
|
|
165
|
+
let Some(reference) = graph.method_references().get(&ref_id) else {
|
|
166
|
+
return ptr::null();
|
|
167
|
+
};
|
|
164
168
|
let name = graph
|
|
165
169
|
.strings()
|
|
166
170
|
.get(reference.str())
|
|
@@ -170,7 +174,7 @@ pub unsafe extern "C" fn rdx_method_reference_name(pointer: GraphPointer, refere
|
|
|
170
174
|
})
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
/// Returns a newly allocated `Location` for the given constant reference id.
|
|
177
|
+
/// Returns a newly allocated `Location` for the given constant reference id, or NULL if the reference cannot be found.
|
|
174
178
|
/// Caller must free the returned pointer with `rdx_location_free`.
|
|
175
179
|
///
|
|
176
180
|
/// # Safety
|
|
@@ -180,12 +184,14 @@ pub unsafe extern "C" fn rdx_method_reference_name(pointer: GraphPointer, refere
|
|
|
180
184
|
///
|
|
181
185
|
/// # Panics
|
|
182
186
|
///
|
|
183
|
-
/// This function will panic if
|
|
187
|
+
/// This function will panic if the reference's document cannot be found.
|
|
184
188
|
#[unsafe(no_mangle)]
|
|
185
189
|
pub unsafe extern "C" fn rdx_constant_reference_location(pointer: GraphPointer, reference_id: u64) -> *mut Location {
|
|
186
190
|
with_graph(pointer, |graph| {
|
|
187
191
|
let ref_id = ConstantReferenceId::new(reference_id);
|
|
188
|
-
let reference = graph.constant_references().get(&ref_id)
|
|
192
|
+
let Some(reference) = graph.constant_references().get(&ref_id) else {
|
|
193
|
+
return ptr::null_mut();
|
|
194
|
+
};
|
|
189
195
|
let document = graph
|
|
190
196
|
.documents()
|
|
191
197
|
.get(&reference.uri_id())
|
|
@@ -226,6 +232,25 @@ pub unsafe extern "C" fn rdx_resolved_constant_reference_declaration(
|
|
|
226
232
|
})
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
/// Returns a pointer to the URI ID of the document a constant reference belongs
|
|
236
|
+
/// to, or NULL if the reference cannot be found. Caller must free the returned
|
|
237
|
+
/// pointer with `free_u64`.
|
|
238
|
+
///
|
|
239
|
+
/// # Safety
|
|
240
|
+
/// - `pointer` must be a valid pointer previously returned by `rdx_graph_new`.
|
|
241
|
+
/// - `reference_id` must be a valid reference id.
|
|
242
|
+
#[unsafe(no_mangle)]
|
|
243
|
+
pub unsafe extern "C" fn rdx_constant_reference_document(pointer: GraphPointer, reference_id: u64) -> *const u64 {
|
|
244
|
+
with_graph(pointer, |graph| {
|
|
245
|
+
let ref_id = ConstantReferenceId::new(reference_id);
|
|
246
|
+
if let Some(reference) = graph.constant_references().get(&ref_id) {
|
|
247
|
+
Box::into_raw(Box::new(*reference.uri_id())).cast_const()
|
|
248
|
+
} else {
|
|
249
|
+
ptr::null()
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
229
254
|
/// Returns the declaration of the resolved receiver for the given method reference. Returns NULL when the method
|
|
230
255
|
/// reference has no tracked receiver or when the receiver could not be resolved. Caller must free with
|
|
231
256
|
/// `free_c_declaration`.
|
|
@@ -263,7 +288,7 @@ pub unsafe extern "C" fn rdx_method_reference_receiver_declaration(
|
|
|
263
288
|
})
|
|
264
289
|
}
|
|
265
290
|
|
|
266
|
-
/// Returns a newly allocated `Location` for the given method reference id.
|
|
291
|
+
/// Returns a newly allocated `Location` for the given method reference id, or NULL if the reference cannot be found.
|
|
267
292
|
/// Caller must free the returned pointer with `rdx_location_free`.
|
|
268
293
|
///
|
|
269
294
|
/// # Safety
|
|
@@ -273,12 +298,14 @@ pub unsafe extern "C" fn rdx_method_reference_receiver_declaration(
|
|
|
273
298
|
///
|
|
274
299
|
/// # Panics
|
|
275
300
|
///
|
|
276
|
-
/// This function will panic if
|
|
301
|
+
/// This function will panic if the reference's document cannot be found.
|
|
277
302
|
#[unsafe(no_mangle)]
|
|
278
303
|
pub unsafe extern "C" fn rdx_method_reference_location(pointer: GraphPointer, reference_id: u64) -> *mut Location {
|
|
279
304
|
with_graph(pointer, |graph| {
|
|
280
305
|
let ref_id = MethodReferenceId::new(reference_id);
|
|
281
|
-
let reference = graph.method_references().get(&ref_id)
|
|
306
|
+
let Some(reference) = graph.method_references().get(&ref_id) else {
|
|
307
|
+
return ptr::null_mut();
|
|
308
|
+
};
|
|
282
309
|
let document = graph
|
|
283
310
|
.documents()
|
|
284
311
|
.get(&reference.uri_id())
|
|
@@ -288,6 +315,25 @@ pub unsafe extern "C" fn rdx_method_reference_location(pointer: GraphPointer, re
|
|
|
288
315
|
})
|
|
289
316
|
}
|
|
290
317
|
|
|
318
|
+
/// Returns a pointer to the URI ID of the document a method reference belongs
|
|
319
|
+
/// to, or NULL if the reference cannot be found. Caller must free the returned
|
|
320
|
+
/// pointer with `free_u64`.
|
|
321
|
+
///
|
|
322
|
+
/// # Safety
|
|
323
|
+
/// - `pointer` must be a valid pointer previously returned by `rdx_graph_new`.
|
|
324
|
+
/// - `reference_id` must be a valid reference id.
|
|
325
|
+
#[unsafe(no_mangle)]
|
|
326
|
+
pub unsafe extern "C" fn rdx_method_reference_document(pointer: GraphPointer, reference_id: u64) -> *const u64 {
|
|
327
|
+
with_graph(pointer, |graph| {
|
|
328
|
+
let ref_id = MethodReferenceId::new(reference_id);
|
|
329
|
+
if let Some(reference) = graph.method_references().get(&ref_id) {
|
|
330
|
+
Box::into_raw(Box::new(*reference.uri_id())).cast_const()
|
|
331
|
+
} else {
|
|
332
|
+
ptr::null()
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
291
337
|
/// Frees a `CConstantReference` previously returned by an FFI function.
|
|
292
338
|
///
|
|
293
339
|
/// # Safety
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubydex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A high-performance static analysis suite for Ruby, built in Rust with
|
|
14
14
|
Ruby APIs
|
|
@@ -16,7 +16,6 @@ email:
|
|
|
16
16
|
- ruby@shopify.com
|
|
17
17
|
executables:
|
|
18
18
|
- rdx
|
|
19
|
-
- rubydex_mcp
|
|
20
19
|
extensions:
|
|
21
20
|
- ext/rubydex/extconf.rb
|
|
22
21
|
extra_rdoc_files: []
|
|
@@ -25,7 +24,6 @@ files:
|
|
|
25
24
|
- README.md
|
|
26
25
|
- THIRD_PARTY_LICENSES.html
|
|
27
26
|
- exe/rdx
|
|
28
|
-
- exe/rubydex_mcp
|
|
29
27
|
- ext/rubydex/declaration.c
|
|
30
28
|
- ext/rubydex/declaration.h
|
|
31
29
|
- ext/rubydex/definition.c
|
|
@@ -40,6 +38,8 @@ files:
|
|
|
40
38
|
- ext/rubydex/handle.h
|
|
41
39
|
- ext/rubydex/location.c
|
|
42
40
|
- ext/rubydex/location.h
|
|
41
|
+
- ext/rubydex/query.c
|
|
42
|
+
- ext/rubydex/query.h
|
|
43
43
|
- ext/rubydex/reference.c
|
|
44
44
|
- ext/rubydex/reference.h
|
|
45
45
|
- ext/rubydex/rubydex.c
|
|
@@ -48,7 +48,6 @@ files:
|
|
|
48
48
|
- ext/rubydex/utils.c
|
|
49
49
|
- ext/rubydex/utils.h
|
|
50
50
|
- lib/rubydex.rb
|
|
51
|
-
- lib/rubydex/bin/rubydex_mcp.exe
|
|
52
51
|
- lib/rubydex/comment.rb
|
|
53
52
|
- lib/rubydex/declaration.rb
|
|
54
53
|
- lib/rubydex/diagnostic.rb
|
|
@@ -58,6 +57,15 @@ files:
|
|
|
58
57
|
- lib/rubydex/keyword.rb
|
|
59
58
|
- lib/rubydex/keyword_parameter.rb
|
|
60
59
|
- lib/rubydex/location.rb
|
|
60
|
+
- lib/rubydex/mcp_server.rb
|
|
61
|
+
- lib/rubydex/mcp_server/protocol.rb
|
|
62
|
+
- lib/rubydex/mcp_server/tools/base_tool.rb
|
|
63
|
+
- lib/rubydex/mcp_server/tools/codebase_stats_tool.rb
|
|
64
|
+
- lib/rubydex/mcp_server/tools/find_constant_references_tool.rb
|
|
65
|
+
- lib/rubydex/mcp_server/tools/get_declaration_tool.rb
|
|
66
|
+
- lib/rubydex/mcp_server/tools/get_descendants_tool.rb
|
|
67
|
+
- lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb
|
|
68
|
+
- lib/rubydex/mcp_server/tools/search_declarations_tool.rb
|
|
61
69
|
- lib/rubydex/mixin.rb
|
|
62
70
|
- lib/rubydex/reference.rb
|
|
63
71
|
- lib/rubydex/signature.rb
|
|
@@ -68,11 +76,6 @@ files:
|
|
|
68
76
|
- rust/about.toml
|
|
69
77
|
- rust/about_templates/about.hbs
|
|
70
78
|
- rust/about_templates/mingw_licenses.hbs
|
|
71
|
-
- rust/rubydex-mcp/Cargo.toml
|
|
72
|
-
- rust/rubydex-mcp/src/main.rs
|
|
73
|
-
- rust/rubydex-mcp/src/server.rs
|
|
74
|
-
- rust/rubydex-mcp/src/tools.rs
|
|
75
|
-
- rust/rubydex-mcp/tests/mcp.rs
|
|
76
79
|
- rust/rubydex-sys/Cargo.toml
|
|
77
80
|
- rust/rubydex-sys/build.rs
|
|
78
81
|
- rust/rubydex-sys/cbindgen.toml
|
|
@@ -127,6 +130,10 @@ files:
|
|
|
127
130
|
- rust/rubydex/src/operation/ruby_builder.rs
|
|
128
131
|
- rust/rubydex/src/position.rs
|
|
129
132
|
- rust/rubydex/src/query.rs
|
|
133
|
+
- rust/rubydex/src/query/cypher.rs
|
|
134
|
+
- rust/rubydex/src/query/cypher/schema.rs
|
|
135
|
+
- rust/rubydex/src/query/cypher/schema_info.rs
|
|
136
|
+
- rust/rubydex/src/query/cypher/tests.rs
|
|
130
137
|
- rust/rubydex/src/resolution.rs
|
|
131
138
|
- rust/rubydex/src/resolution_tests.rs
|
|
132
139
|
- rust/rubydex/src/stats.rs
|
data/exe/rubydex_mcp
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "rbconfig"
|
|
5
|
-
|
|
6
|
-
host_os = RbConfig::CONFIG.fetch("host_os")
|
|
7
|
-
executable = host_os.match?(/mswin|mingw|cygwin/) ? "rubydex_mcp.exe" : "rubydex_mcp"
|
|
8
|
-
binary = File.expand_path("../lib/rubydex/bin/#{executable}", __dir__)
|
|
9
|
-
|
|
10
|
-
unless File.executable?(binary)
|
|
11
|
-
abort(<<~MESSAGE.chomp)
|
|
12
|
-
rubydex_mcp is not available at #{binary}.
|
|
13
|
-
Install a precompiled rubydex gem, or reinstall rubydex with Cargo available so the MCP executable can be built locally.
|
|
14
|
-
MESSAGE
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
exec(binary, *ARGV)
|
|
Binary file
|
data/rust/rubydex-mcp/Cargo.toml
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "rubydex-mcp"
|
|
3
|
-
version = "0.2.6"
|
|
4
|
-
edition = "2024"
|
|
5
|
-
rust-version = "1.89.0"
|
|
6
|
-
license = "MIT"
|
|
7
|
-
description = "MCP server exposing Rubydex semantic Ruby code intelligence tools."
|
|
8
|
-
homepage = "https://github.com/Shopify/rubydex"
|
|
9
|
-
repository = "https://github.com/Shopify/rubydex"
|
|
10
|
-
readme = "README.md"
|
|
11
|
-
keywords = ["ruby", "mcp", "static-analysis"]
|
|
12
|
-
categories = ["development-tools"]
|
|
13
|
-
|
|
14
|
-
[[bin]]
|
|
15
|
-
name = "rubydex_mcp"
|
|
16
|
-
path = "src/main.rs"
|
|
17
|
-
|
|
18
|
-
[dependencies]
|
|
19
|
-
rubydex = { version = "0.2.6", path = "../rubydex" }
|
|
20
|
-
clap = { version = "4.5.16", features = ["derive"] }
|
|
21
|
-
rmcp = { version = "1.4", features = ["server", "macros", "transport-io", "schemars"] }
|
|
22
|
-
tokio = { version = "1", features = ["macros", "rt", "io-std"] }
|
|
23
|
-
serde = { version = "1", features = ["derive"] }
|
|
24
|
-
serde_json = "1"
|
|
25
|
-
schemars = "1"
|
|
26
|
-
url = "2"
|
|
27
|
-
|
|
28
|
-
[dev-dependencies]
|
|
29
|
-
rubydex = { version = "0.2.6", path = "../rubydex", features = ["test_utils"] }
|
|
30
|
-
assert_cmd = "2.0"
|
|
31
|
-
serde_json = "1"
|
|
32
|
-
|
|
33
|
-
[lints]
|
|
34
|
-
workspace = true
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
use clap::Parser;
|
|
2
|
-
|
|
3
|
-
mod server;
|
|
4
|
-
mod tools;
|
|
5
|
-
|
|
6
|
-
#[derive(Parser, Debug)]
|
|
7
|
-
#[command(
|
|
8
|
-
name = "rubydex_mcp",
|
|
9
|
-
about = "Rubydex MCP server for AI-assisted Ruby code intelligence",
|
|
10
|
-
version
|
|
11
|
-
)]
|
|
12
|
-
struct Args {
|
|
13
|
-
#[arg(value_name = "PATH", default_value = ".")]
|
|
14
|
-
path: String,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fn main() {
|
|
18
|
-
let args = Args::parse();
|
|
19
|
-
|
|
20
|
-
let root = match std::fs::canonicalize(&args.path) {
|
|
21
|
-
Ok(p) => p
|
|
22
|
-
.into_os_string()
|
|
23
|
-
.into_string()
|
|
24
|
-
.expect("Project path is not valid UTF-8"),
|
|
25
|
-
Err(e) => {
|
|
26
|
-
eprintln!("Warning: failed to canonicalize '{}': {e}", args.path);
|
|
27
|
-
args.path
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Create the server and start indexing in the background.
|
|
32
|
-
let server = server::RubydexServer::new(root.clone());
|
|
33
|
-
server.spawn_indexer(root);
|
|
34
|
-
|
|
35
|
-
// Serve MCP over stdio immediately while indexing runs.
|
|
36
|
-
// We need to do this because Claude Code's default MCP server timeout is 30 seconds,
|
|
37
|
-
// And in big codebases it's possible to exceed that and Claude Code would just consider
|
|
38
|
-
// the server fail to connect.
|
|
39
|
-
let rt = tokio::runtime::Builder::new_current_thread()
|
|
40
|
-
.enable_all()
|
|
41
|
-
.build()
|
|
42
|
-
.expect("Failed to build tokio runtime");
|
|
43
|
-
|
|
44
|
-
if let Err(e) = rt.block_on(server.serve()) {
|
|
45
|
-
eprintln!("MCP server error: {e}");
|
|
46
|
-
std::process::exit(1);
|
|
47
|
-
}
|
|
48
|
-
}
|