Skip to content

fix(ruby): scope-resolution namespaced class/module definitions — F62 (#1933)#1972

Merged
magyargergo merged 7 commits into
abhigyanpatwari:mainfrom
prajapatisparsh:fix/ruby-namespaced-class
Jun 2, 2026
Merged

fix(ruby): scope-resolution namespaced class/module definitions — F62 (#1933)#1972
magyargergo merged 7 commits into
abhigyanpatwari:mainfrom
prajapatisparsh:fix/ruby-namespaced-class

Conversation

@prajapatisparsh

Copy link
Copy Markdown
Contributor

Closes F62 in #1933.

Adds scope_resolution patterns to ruby/query.ts for namespaced class/module definitions (class Foo::Bar, module Baz::Qux).

F63 done via #1940, F64/F65 done via #1937.

Changes:

  • 2 query patterns in ruby/query.ts
  • 5 tests in ruby-namespaced.test.ts
  • 1 fixture
  • Ruby benchmark fingerprint updated

129 existing Ruby tests pass, 5 new tests pass.

@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

@prajapatisparsh is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
10909 10897 0 12 654s

✅ All 10897 tests passed

12 test(s) skipped — expand for details
  • COBOL pipeline benchmark > scales with file count
  • C# pipeline benchmark > scales with file count — namespaces spread across the solution
  • C# pipeline benchmark > scales with file count — all types in one (global) namespace bucket
  • C# pipeline benchmark > scales with file count — all types in one (named) namespace bucket
  • Go pipeline benchmark > scales with file count (workers enabled)
  • Go pipeline benchmark — worker pool (issue Worker idle timeout kills long Go scope extraction and surfaces as Napi::Error during analyze #1848) > does not quarantine the large generated Go file on sub-batch idle timeout
  • Go structural interface detection benchmark > scales linearly with interface × struct count
  • PHP pipeline benchmark > scales with file count (workers enabled)
  • Ruby pipeline benchmark > scales with file count (workers enabled)
  • Rust pipeline benchmark > scales with file count (workers enabled)
  • run.cjs direct-exec entrypoint (fix(cli): steer docs, skills, and hooks through a CLI-neutral project-local runner (#1939) #1945) > resolves a .cmd shim via the Windows shell branch, passing args and exit code
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 80.54% 37895/47050 79.84% 📈 +0.7 🟢 ████████████████░░░░
Branches 69.13% 24119/34887 68.5% 📈 +0.6 🟢 █████████████░░░░░░░
Functions 85.68% 3937/4595 84.94% 📈 +0.7 🟢 █████████████████░░░
Lines 84.12% 34092/40527 83.36% 📈 +0.8 🟢 ████████████████░░░░

📋 View full run · Generated by CI

@magyargergo magyargergo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1972 Tri-Review — fix(ruby): namespaced class/module definitions (F62)

Methods & engines. GitNexus risk / test-CI lanes + Compound-Engineering personas (correctness, adversarial, testing, performance) — all Claude — plus Codex (gpt-5.5, xhigh) as the intended independent engine. Codex's sandbox blocked local execution, so it acted as a second static reader (not an independent executor): by inspection it confirmed the committed golden has 80 entries (not 81), that the golden test auto-globs ruby-* fixtures, and that the new tests are capture-only. The two findings below were reproduced end-to-end by the coordinator (full pipeline run + BASE comparison + a bare-vs-namespaced control) — the strongest evidence here. Two of the three "methods" are the same engine (Claude), so treat cross-persona agreement as consistent, not independently confirmed.

What's solid (validated)

  • Not a regression. Correctness lane + the coordinator's BASE comparison confirm the change is additive: graph output for namespaced declarations is byte-identical to BASE.
  • Heritage intact. class Foo::Bar < Base still emits its inheritance reference (adversarial-verified) — namespacing doesn't break heritage capture.
  • Bench rebaseline legitimate. The performance lane ran the scope-capture bench; the new ruby fingerprint matches the live measurement and scaling stays ~1.03–1.09 (budget 1.5). The tests / benchmarks CI job passes. The two new patterns are O(1)-per-node matching — no O(n²) risk.
  • No collision regression. A suspected class Bar / class Foo::Bar tail-constant collision was probed and found pre-existing & non-regressive.

Findings (2 — see inline)

[P1 · CI blocker · reproduced] Captures golden not regenerated.
The new fixture ruby-namespaced/namespaced.rb is auto-globbed by test/unit/scope-resolution/ruby/ruby-captures-golden.test.ts, but test/fixtures/ruby-captures-golden/expected-captures.json was not regenerated (80 entries, needs 81). Reproduced locally (vitest … ruby-captures-golden → FAIL, expected {…(81)} to deeply equal {…(80)}, exit 1) and confirmed live in CI: tests / ubuntu / coverage is failing, with CI Gate failing downstream. Fix: UPDATE_GOLDEN=1 npx vitest run test/unit/scope-resolution/ruby/ruby-captures-golden.test.ts and commit the regenerated golden.

[P2 · incomplete fix / misleading tests · reproduced] Namespaced declarations still produce no graph node.
With the PR applied, class Foo::Bar / module Baz::Qux still produce no Class/Trait node; their methods are keyed to the full scope (Foo::Bar.bar_method) and the resulting HAS_METHOD edges dangle to a non-existent owner (Class:…:Foo::Bar → MISSING). Reproduced on both resolver legs, with a control where a bare class Plain in the same file does get a node. This is pre-existing (byte-identical at BASE), so it is not a merge regression — but it means the PR's headline F62 goal (modeling namespaced class/module definitions) is reached only at the capture layer, not the graph layer. The 5 new tests assert only emitRubyScopeCaptures output and never run the pipeline, so they pass while this gap stays invisible. Suggested: add a runPipelineFromRepo test asserting the node exists and HAS_METHOD resolves; if true node modeling is intended, reconcile the def name (tail Bar) with the method-owner scope key (full Foo::Bar).

Lower-priority / coverage gaps

  • Orphan fixture. ruby-namespaced.test.ts uses inline source strings and does not load namespaced.rb; the fixture is consumed only by the golden test's ruby-* glob (the test it currently breaks). Either wire it into a pipeline test or it adds no coverage.
  • Untested combo: class Foo::Bar < Base (namespaced + heritage) has no test.
  • Worker-mode parity for namespaced declarations couldn't be verified in the worktree (parse-worker not built).

CI / merge state

Blocking: tests / ubuntu / coverage fail (the golden above) → CI Gate fail. Vercel fail is a separate deploy-auth gate (not code). All quality checks (lint / typecheck / format), tests / benchmarks, platform-sensitive, tree-sitter ABI, and packaged-install smoke jobs pass. Verdict: not mergeable until the captures golden is regenerated (P1). P2 is a worthwhile follow-up but not a merge blocker on its own.

Automated multi-tool digest (GitNexus swarm + Compound-Engineering personas + Codex). Both findings reproduced by the coordinator; verify before acting.

"scaling_budget": 1.5,
"_rebaselined": "#1956 synth-widening: + ruby-qualified-base fixture; synth now reduces a scope_resolution superclass (class C < Mod::Super) to its trailing constant (matching the #1940 legacy leg), at parity. Linear (~1.03). (Earlier #1956: heritage-bearing scale source.)"
"_rebaselined": "#1956 synth-widening: + ruby-qualified-base fixture; synth now reduces a scope_resolution superclass (class C < Mod::Super) to its trailing constant (matching the #1940 legacy leg), at parity. Linear (~1.03). (Earlier #1956: heritage-bearing scale source.)",
"_note": "F62: + scope_resolution class/module declaration captures — fixture count 78→81, fingerprint drift expected."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1 · CI blocker] The captures golden wasn't regenerated — a separate artifact from the bench baseline. [reproduced]

This _note correctly flags bench-fingerprint drift, but the scope-captures golden — test/fixtures/ruby-captures-golden/expected-captures.json (not in this diff) — is a different file and was not regenerated. ruby-captures-golden.test.ts globs every ruby-* fixture, so it now collects ruby-namespaced/namespaced.rb and fails: expected {…(81)} to deeply equal {…(80)} (exit 1). Confirmed failing in CI (tests / ubuntu / coverage → fail; CI Gate fails downstream).

Fix: UPDATE_GOLDEN=1 npx vitest run test/unit/scope-resolution/ruby/ruby-captures-golden.test.ts and commit the updated expected-captures.json.

const classDecls = matches.filter((m) => m['@declaration.class']);
expect(classDecls.length).toBe(1);
expect(classDecls[0]['@declaration.name'].text).toBe('Bar');
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2 · incomplete fix] These assertions verify capture output only — the graph node is never created. [reproduced]

All 5 tests assert emitRubyScopeCaptures output (@declaration.name === 'Bar') but never run the pipeline. Running the full pipeline on this PR's own fixture (runPipelineFromRepo, both resolver legs) shows class Foo::Bar / module Baz::Qux produce no Class/Trait node, and their HAS_METHOD edges dangle to a missing owner (Class:namespaced.rb:Foo::Bar → MISSING); a bare class Plain in the same file does get a node. So the capture lands, but the namespaced declaration still isn't modeled as a graph node.

This is pre-existing (byte-identical at BASE), so not a regression — but the tests pass while the F62 goal isn't met end-to-end. Consider a runPipelineFromRepo assertion that the Class/Trait node exists and HAS_METHOD resolves; the root cause is the def keyed on tail Bar vs the method-owner scope keyed on full Foo::Bar.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

✨ PR Autofix

Found fixable formatting / unused-import issues across 17 changed lines. Comment /autofix on this PR to apply them, or run npm run lint:fix && npm run format locally.

{"schema":"gitnexus.pr-autofix/v2","state":"fixes-available","pr_number":1972,"changed_lines":17,"head_sha":"7cbd723a35b8ba7c310c54139a454dffa85f2f79","run_id":"26811104511","apply_command":"/autofix"}

Comment thread gitnexus/test/integration/resolvers/ruby-namespaced.test.ts Fixed
@prajapatisparsh

Copy link
Copy Markdown
Contributor Author

@magyargergo P1 golden regenerated. P2 is a pre-existing downstream gap — extractScope doesn't model scope_resolution name nodes. Our query captures the tail constant correctly (Bar from Foo::Bar), but the graph builder doesn't create a Class node from it. This is byte-identical to BASE, not a regression from this PR. The fix is a scope-extractor change, not a query change. OK to defer to follow-up in another pr or right here ?

@magyargergo

Copy link
Copy Markdown
Collaborator

Thanks @prajapatisparsh — and your read on both is right.

P1: Confirmed fixed. I re-ran the captures golden on your latest head and it's green (10/10), and the regenerated entry matches the digest the test actually computes — so it's a clean regen, not a hand-edit. CI is re-running now and tests / ubuntu / coverage should go green this time.

P2: Agreed on all counts. The query is doing the right thing (tail constant Bar from Foo::Bar), it's byte-identical to BASE, and it's not a regression from this PR — the gap is purely the graph builder not turning that capture into a Class/Trait node.

I'd lean towards deferring it to a follow-up rather than bolting it onto this PR, for two reasons:

  1. The fix lives in shared scope-extractor code, not the Ruby query — and qualified declaration names aren't a Ruby-only thing (C++, C#, Java, PHP, Rust all use scoped names in their queries too). So it's probably best done generically and checked for parity across languages, which is really its own change.
  2. The extractor already reads a @declaration.qualified_name capture (scope-extractor.ts:558) that no language emits yet — that looks like the intended lever for exactly this. Worth wiring up properly in a dedicated PR.

This PR is small and green, and it's graph-neutral (main already carries the same dangling edges for namespaced Ruby), so I'm happy to merge it as-is. Just two small things first, so we don't leave a "looks done" trap for the next person:

  • Open a quick tracking issue for the scope-extractor gap (or note it under Ruby: parsing-layer coverage gaps (4 findings) #1933) and link it here, so it doesn't get lost.
  • Drop a one-line note in ruby-namespaced.test.ts (or the PR body) that the tests are capture-level and the graph-node modeling is a tracked follow-up.

Then it's good to go — nice clean change otherwise. 👍

@magyargergo

Copy link
Copy Markdown
Collaborator

I'll tackle with the follow-up; :)

@magyargergo

Copy link
Copy Markdown
Collaborator

I'll tackle with the follow-up; :)

here #1975

@magyargergo magyargergo merged commit 6643afb into abhigyanpatwari:main Jun 2, 2026
29 of 30 checks passed
@magyargergo magyargergo linked an issue Jun 5, 2026 that may be closed by this pull request
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ruby: parsing-layer coverage gaps (4 findings)

3 participants