feat(kernel): R7b Ruby walker — ruby module, tree-sitter-ruby 0.23.1 bump, ref-flag wire slot, ruby default-routed (#1379)

Third R7b port, checklist-first recipe (docs/design/ruby-kernel-port-checklist.md).

Grammar bump first, validated standalone (the rust pattern): tree-sitter-ruby
^0.20.1 (tree-sitter-wasms, 2024-02) → v0.23.1 — crate pinned =0.23.1, wasm
built from tag 71bd32f's checked-in parser.c/scanner.c (both sha-matched
against the crates.io tarball; content bump, ABI stays 14). Old-vs-new
full-init dumps: sinatra/jekyll byte-identical; rails = exactly the one
classified hunk (the `recv&.!=` safe-nav operator misparse fix,
`table_name.!` → `table_name.!=`, precision-positive).

Walker (python.rs chassis + the six ruby divergences) preserves bug-for-bug:
the importTypes:['call'] funnel (class-body DSL — attr_accessor, has_many,
define_method incl. its block, sinatra route blocks — emits NOTHING at
non-body scope), hook-handled module multiply-capture (nested modules re-scan
their subtree per level after popping — `this.hooked` fn-refs from class AND
module AND file), the sibling-scan visibility trio (bare `private` invisible;
`private :sym`/`private def` poison all later defs; the inner def stays
public), bare-call statements (do…end body_statement emits, brace-block
block_body doesn't), `.new` instantiates with last-`::`-segment names,
constant-receiver references refs, require/require_relative path refs
(posix-normalized, `.rb`-suffixed, `Kernel.require` and interpolated-path
quirks included), `=begin` docstring marker survival, and the reverse-order
value-ref DFS.

Wire v2: the hook's mixin `implements` refs carry `filePath: ctx.filePath` —
the ONE extraction-ref denormalized field (php's trait-use refs share the
shape). RefRow's first pad byte becomes a flags slot (REF_FLAG_FILE_PATH);
decode re-attaches its own filePath parameter; KERNEL_ABI_VERSION 1→2 on both
sides (mismatched dist/.node pairs degrade to wasm, as designed).

Gates: sweeps 0-diff sinatra 147/147, jekyll 164/164, rails 3452/3452 (3,763
files, 0 deferrals — ruby error incidence 0.00%, any deferral = walker bug);
full-init dumps byte-identical ×3 (7.2k/9.4k/375.6k lines); kernel-ruby-parity
suite (torture + CRLF + wire-flag pin + defer) + ruby grammar-parity row;
full suite 2,613 green ×2 under CODEGRAPH_KERNEL_EXPECT=1 (one unrelated
mcp-initialize timing flake under parallel load, passes solo 3/3 ×3).
DEFAULT_ROUTED += ruby (12 langs).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-20 15:44:13 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 286e9ccc2d
commit 1909931238
19 changed files with 2335 additions and 16 deletions
+15 -3
View File
@@ -57,14 +57,18 @@
//! ref row (40 bytes):
//! 0 u32 fromNode row index (NONE → use fromNodeIdStr)
//! 4 u8 ReferenceKind (EDGE_KINDS index, or 200 = function_ref)
//! 5 [3] pad
//! 5 u8 flags — bit 0: ref carries the extracting file's path (v2; the
//! ruby/php visitNode hooks set `filePath: ctx.filePath` on their
//! mixin/trait `implements` refs — decode re-attaches the decode
//! call's own filePath, which is byte-identical)
//! 6 [2] pad
//! 8 u32 line (1-based)
//! 12 u32 column (0-based)
//! 16 str referenceName
//! 24 str candidates (NUL-joined list)
//! 32 str fromNodeIdStr
pub const KERNEL_ABI_VERSION: u8 = 1;
pub const KERNEL_ABI_VERSION: u8 = 2;
pub const NONE: u32 = 0xFFFF_FFFF;
pub const META_SIZE: usize = 36;
@@ -117,6 +121,9 @@ pub const EDGE_KINDS: [&str; 12] = [
/// ReferenceKind code for the internal-only `function_ref` (#756).
pub const FUNCTION_REF_CODE: u8 = 200;
/// Ref-row flag bit 0: the ref carries `filePath` = the extracted file.
pub const REF_FLAG_FILE_PATH: u8 = 1;
pub fn node_kind_index(kind: &str) -> Option<u8> {
NODE_KINDS.iter().position(|k| *k == kind).map(|i| i as u8)
}
@@ -303,10 +310,15 @@ impl Tables {
}
pub fn push_ref(&mut self, r: &RefRow) {
self.push_ref_flagged(r, 0);
}
pub fn push_ref_flagged(&mut self, r: &RefRow, flags: u8) {
let buf = &mut self.refs;
buf.extend_from_slice(&r.from_idx.to_le_bytes());
buf.push(r.kind);
buf.extend_from_slice(&[0u8; 3]); // pad
buf.push(flags);
buf.extend_from_slice(&[0u8; 2]); // pad
buf.extend_from_slice(&r.line.to_le_bytes());
buf.extend_from_slice(&r.column.to_le_bytes());
push_str_ref(buf, r.reference_name);
+7 -2
View File
@@ -15,8 +15,10 @@ use tree_sitter::Language;
/// Languages this kernel binary can extract (reported by contractInfo;
/// TS-side routing policy decides what actually routes).
pub const LANGUAGES: [&str; 11] =
["typescript", "tsx", "javascript", "jsx", "java", "python", "go", "c", "cpp", "rust", "csharp"];
pub const LANGUAGES: [&str; 12] = [
"typescript", "tsx", "javascript", "jsx", "java", "python", "go", "c", "cpp", "rust",
"csharp", "ruby",
];
pub fn grammar_for(language: &str) -> Option<Language> {
match language {
@@ -36,6 +38,9 @@ pub fn grammar_for(language: &str) -> Option<Language> {
// R7b: 0.23.5, table-identical to the vendored ABI-15 wasm (#717;
// verified against the crates.io tarball — csharp checklist header).
"csharp" => Some(tree_sitter_c_sharp::LANGUAGE.into()),
// R7b: v0.23.1, sha-matched with the vendored wasm (grammars.ts).
// Content bump only — the tag's parser.c is still ABI 14.
"ruby" => Some(tree_sitter_ruby::LANGUAGE.into()),
_ => None,
}
}
+2
View File
@@ -25,6 +25,7 @@ mod ids;
mod go;
mod java;
mod langs;
mod ruby;
mod rustlang;
mod textutil;
mod python;
@@ -215,6 +216,7 @@ pub fn extract_file(file_path: String, content: String, language: String) -> Res
"c" | "cpp" => ccpp::extract(&file_path, &content, &language).map_err(Error::from_reason)?,
"rust" => rustlang::extract(&file_path, &content).map_err(Error::from_reason)?,
"csharp" => csharp::extract(&file_path, &content).map_err(Error::from_reason)?,
"ruby" => ruby::extract(&file_path, &content).map_err(Error::from_reason)?,
_ => tsjs::extract(&file_path, &content, &language).map_err(Error::from_reason)?,
};
Ok(ExtractBuffers {
File diff suppressed because it is too large Load Diff