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
+5
View File
@@ -301,6 +301,11 @@ const VENDORED_WASM_LANGS: ReadonlySet<GrammarLanguage> = new Set([
// parser.c/scanner.c sha-matched against the crates.io tarball. Replaces the
// 2023-era tree-sitter-wasms build (ABI 14 → 15).
'rust',
// R7b (Ruby kernel port prep): tree-sitter-ruby v0.23.1 (71bd32f),
// parser.c/scanner.c sha-matched against the crates.io tarball. Replaces the
// ^0.20.1 tree-sitter-wasms build. Content bump only — the tag's checked-in
// parser.c is still ABI 14 (predates the ABI-15 generator).
'ruby',
]);
/** Absolute path of a language's grammar WASM (vendored or tree-sitter-wasms). */
+9 -3
View File
@@ -30,6 +30,7 @@ import {
NONE,
PROVENANCES,
REF,
REF_FLAG_FILE_PATH,
REF_ROW_SIZE,
VISIBILITIES,
} from './layout';
@@ -148,9 +149,13 @@ export function decodeExtractBuffers(
const row = buffers.refs.subarray(i * REF_ROW_SIZE, (i + 1) * REF_ROW_SIZE);
const fromIdx = row.readUInt32LE(REF.fromIdx);
const kindByte = row.readUInt8(REF.kind);
// No filePath/language here: the wasm extractors emit refs WITHOUT the
// denormalized fields (the store fills them via `ref.filePath ?? filePath`),
// and the kernel must match the extractFromSource seam exactly.
// No filePath/language on ordinary refs: the wasm extractors emit them
// WITHOUT the denormalized fields (the store fills `ref.filePath ??
// filePath`), and the kernel must match the extractFromSource seam
// exactly. The ONE exception is flagged (REF_FLAG_FILE_PATH): the
// ruby/php visitNode hooks set `filePath: ctx.filePath` on their
// mixin/trait `implements` refs — re-attach the decode call's own
// filePath, which is that exact value.
const ref: UnresolvedReference = {
fromNodeId: fromIdx === NONE ? str(arena, row, REF.fromIdStr)! : idByRow[fromIdx]!,
referenceName: str(arena, row, REF.referenceName)!,
@@ -161,6 +166,7 @@ export function decodeExtractBuffers(
line: row.readUInt32LE(REF.line),
column: row.readUInt32LE(REF.column),
};
if ((row.readUInt8(REF.flags) & REF_FLAG_FILE_PATH) !== 0) ref.filePath = filePath;
const candidates = strList(arena, row, REF.candidates);
if (candidates !== undefined) ref.candidates = candidates;
unresolvedReferences[i] = ref;
+5
View File
@@ -59,6 +59,11 @@ const DEFAULT_ROUTED: ReadonlySet<Language> = new Set<Language>([
// Deferral 0.053.3% — both-branches-kept `#if` damage that errors on BOTH
// arms (the preParse blanking hoist keeps the kernel's input identical).
'csharp',
// R7b (2026-07-20): parity swept 0-diff on sinatra/jekyll/rails (3,763
// files byte-parity, 0 deferrals — ruby error incidence is 0.00%) +
// full-init dump-diffs byte-identical ×3. Any deferral on a ruby sweep is
// a walker-bug signal, not grammar reality.
'ruby',
]);
/**
+11 -1
View File
@@ -17,7 +17,7 @@
* path instead of mis-decoding.
*/
export const KERNEL_ABI_VERSION = 1;
export const KERNEL_ABI_VERSION = 2;
/** Sentinel for "absent" in u32 slots and string-ref offsets. */
export const NONE = 0xffffffff;
@@ -77,6 +77,7 @@ export const EDGE = {
export const REF = {
fromIdx: 0, // u32 (NONE → fromIdStr)
kind: 4, // u8 — EDGE_KINDS index, or FUNCTION_REF_CODE
flags: 5, // u8 — REF_FLAGS bits (v2)
line: 8, // u32
column: 12, // u32
referenceName: 16, // str
@@ -87,6 +88,15 @@ export const REF = {
/** ReferenceKind wire code for the internal-only `function_ref` (#756). */
export const FUNCTION_REF_CODE = 200;
/**
* Ref-row flag bits (v2). FILE_PATH: the ref carries `filePath` = the
* extracted file — the ruby/php visitNode hooks set `filePath: ctx.filePath`
* on their mixin/trait `implements` refs (unlike every other extraction ref,
* which the store denormalizes); decode re-attaches its own filePath
* parameter, which is byte-identical.
*/
export const REF_FLAG_FILE_PATH = 1;
/** Node bool-flag bit pairs: bit(2n) = present, bit(2n+1) = value. */
export const FLAG = {
isExported: 0,
Binary file not shown.