feat(ingestion): Link object literal methods to exported bindings#1718
Conversation
|
@luyua9 is attempting to deploy a commit to the NexusCore Team on Vercel. A member of the Team first needs to authorize it. |
|
Claude finished @magyargergo's task in 6m 44s —— View job PR #1718 Production-Readiness ReviewReviewer: Adversarial production-readiness review — ingestion, AST parsing, graph relationships, scope-resolution migration safety. Task List
Review BarThe bar is: every item in §§ 2.1–2.7 of Current PR StateOne commit ( Branch hygiene: clean feature/fix PR. Understanding of the ChangeThe PR adds a new helper Architecture / Migration Ownership FindingTypeScript and JavaScript are both migrated to scope-resolution (RFC #909). Both providers implement However: the scope-resolution path is explicitly documented as additive to, not a replacement of, the legacy DAG. The structural edges (DEFINES, HAS_METHOD, HAS_PROPERTY) are exclusively built by the legacy DAG. The scope-resolution path produces Files Inspected
CI / Checks StatusOf 9 workflow runs, 8 are FindingsFinding 1 — BLOCKER: Caller/Impact Resolution Remains Broken (Lane 3)Severity: CRITICAL // parsing-processor.ts:793
symbolTable.add(file.path, nodeName, nodeId, nodeLabel, {
...
ownerId: enclosingClassId ?? undefined, // ← object literal ownerId is NOT set
...
});
// parse-worker.ts:2321
...(enclosingClassId ? { ownerId: enclosingClassId } : {}), // ← same gapWhen an object literal method is parsed, Production risk: Issue #1358's core symptom — Recommended fix: Also set Blocks merge: YES. Finding 2 — BLOCKER: Boundary Guard Is Ineffective for Local Object Literals (Lane 6)Severity: HIGH while (current) {
if (current.type === 'object') sawObjectLiteral = true;
if (sawObjectLiteral && current.type === 'variable_declarator') {
// ← returns here for the local case
const nameNode = current.childForFieldName?.('name');
...
return { ownerId: ..., ownerName: ... };
}
// ← boundary check never reached for local case
if (current !== node && (FUNCTION_NODE_TYPES... || CLASS_CONTAINER_TYPES...)) {
return null;
}
current = current.parent;
}For a local object literal like: export function processAll() {
const handler = { async run(x: string) { return fetch(x); } };
}The AST parent walk from The same false attribution fires for:
Production risk: Incorrect Recommended fix: After finding the Blocks merge: YES. Finding 3 — BLOCKER: Tests Do Not Cover the Issue's Production Symptom (Lane 5)Severity: HIGH The new test asserts:
Missing assertions:
Per Blocks merge: YES (directly tied to Finding 1 — the CALLS gap cannot be verified without an end-to-end test). Finding 4 — MEDIUM:
|
|
@luyua9 can you please look into claude's findings? 🙏 |
|
@luyua9 any update? |
…tion (PR abhigyanpatwari#1718 review) Addresses adversarial production-readiness review on PR abhigyanpatwari#1718 / issue abhigyanpatwari#1358: - F1 (caller resolution) — setting `ownerId` on object-literal method symbols alone is not sufficient; the scope-resolution receiver-bound resolver only consults class-like or type-annotated bindings, so lowercase value receivers (`export const fooService = {...}; fooService.getUser(...)`) never reach the owner-indexed lookup. Adds a Case 5 value-receiver bridge in receiver-bound-calls.ts that resolves the receiver name as a Const/Variable binding, translates its def to the canonical graph node id, and emits the CALLS edge via the owner-indexed method registry. - F2 (boundary guard) — rewrites findObjectLiteralBindingInfo as an explicit two-phase AST walk: Phase A tracks object-literal depth (returns null for nested literals and pre-declarator function/class boundaries — IIFE patterns); Phase B walks the declarator's ancestors and rejects function, class, and block-statement containers (if / for / while / try / catch / switch / etc.) before reaching program/export_statement. Prevents false HAS_METHOD edges for locally-scoped or block-scoped object literals. - F4 — drops the dead `ownerName` field from ObjectLiteralBindingInfo. Constraint: TS/JS are scope-resolution migrated per RFC abhigyanpatwari#909; the legacy Call-Resolution DAG (call-processor.ts) is intentionally left untouched. Tests: - test/integration/ast-helpers-object-literal-binding.test.ts (13 cases) — pins helper semantics: happy paths, function/arrow/class-ctor boundaries, nested literals, block scope (if / for-of / try), IIFE, assignment expressions without declarator. - test/integration/object-literal-owner-resolution.test.ts (9 cases) — drives the full pipeline against an on-disk fixture: sequential CALLS edge emission (issue abhigyanpatwari#1358 proof), worker-mode parity, negative local binding, and nested-literal attribution boundary. Full sweep: 2958/2958 integration + 6056/6056 unit tests pass.
|
Addressed Claude's adversarial production-readiness review in Findings closureF1 — Caller / impact resolution (BLOCKER → fixed). The review's recommended fix (setting Fix is in two parts:
Implemented in the scope-resolution path only — the legacy DAG ( F2 — Boundary guard (BLOCKER → fixed).
F3 — Test coverage (BLOCKER → fixed). Two new integration test files:
All assertions use exact F4 — Dead code (fixed). F5 — F6 — CI auth gate. This commit pushes from a fork, so the same fork-PR authorization gate still applies. Maintainer can authorize the workflow runs to verify on CI. Local verification
Files changed |
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 9450 tests passed 1 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
|
Claude finished @magyargergo's task in 13m 4s —— View job PR #1718 Adversarial Production-Readiness ReviewReviewer: Adversarial production-readiness review — ingestion, AST parsing, graph relationships, scope-resolution, worker parity, false-positive suppression. Task List
Review Bar for This PR
Problem Being SolvedIssue #1358: Current PR StateTwo feature commits: Merge Status and MergeabilityCI: all green. Verified: main CI run (26211101572) — typecheck Repository History Considered
Branch Hygiene AssessmentAcceptable. Four merge-from-main commits ( Understanding of the ChangeStructural fix (legacy DAG path —
Scope-resolution fix (
FindingsFinding 1 — NOTED: Scope-Resolution Type Label Mismatch (Latent, Non-Blocking)Severity: LOW Evidence (verified):
In
The lookup succeeds via the simple-key fallback, and the owner graph ID resolves correctly. Verified by the integration test passing end-to-end. This is accidental correctness — if two bindings share the same simple name in the same file, or if the simple-key fallback were removed, this could silently break. However it works correctly under current codebase conventions. Recommended follow-up: Consider either making Blocks merge: NO. Integration tests prove correctness on the actual path. Finding 2 — NOTED: Case 5 Hardcodes
|
…wner resolution Multi-agent code review on the prior commit surfaced 7 actionable findings, all walked through and applied here. None change observable behavior for issue abhigyanpatwari#1358's fix; all harden correctness, predicate stability, and test signal. - abhigyanpatwari#1 (P1 / 3-reviewer corroboration): Case 5 in receiver-bound-calls.ts no longer hand-builds graph.addRelationship + a dedup key. New tryEmitEdgeWithExplicitTargetId in edges.ts takes a pre-resolved target id (the canonical Method nodeId from the parser) and reuses every invariant of tryEmitEdge: dedup-key format, collapse-flag honoring, caller-id resolution, rel-id shape, mapReferenceKindToEdgeType for read/write ACCESSES. This also lands the adversarial reviewer's "F2" follow-up (hardcoded type: 'CALLS' for non-call sites) for free. - abhigyanpatwari#2 (P2 cross-reviewer): findValueBindingInScope's predicate inverted from denylist ("not class-like and not callable") to explicit allowlist matching reconcileOwnership's registration set: Const | Variable | Property | Static. Extracted as isOwnableValueLabel so future NodeLabel additions require an explicit opt-in. - abhigyanpatwari#6 (P2): walkScopeChain<T>() extracted; both findClassBindingInScope and findValueBindingInScope now route through it. Local scope.bindings are exhausted BEFORE lookupBindingsAt (imported/augmented) at every scope level — preserves JavaScript lexical scoping where a local const shadows an imported binding of the same name. Behavior was already correct in findClassBindingInScope but was implicit; now it is the walker's explicit, documented contract. - abhigyanpatwari#7 (P2): scope-walker duplication closed. findClassBindingInScope and findValueBindingInScope reduce to thin wrappers over walkScopeChain with their respective predicate. findClassBindingInScope keeps its qualifiedNames + dotted-name fallback tail. - abhigyanpatwari#3 (P2): parse-worker.ts hoists `const ownerId = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId` once before the symbol push, dropping the duplicated coalesce + `as string` cast. Matches the cast-free pattern at parsing-processor.ts:793. HAS_METHOD emit site reuses the same hoisted local. - abhigyanpatwari#4 (P2): object-literal-owner-resolution.test.ts Test A's CALLS-edge assertion no longer matches by name alone. .toEqual now pins the canonical target id (Method:src/service.ts:getUser#1 via generateId), confidence (0.85), and reason ('import-resolved'). A regression that emits the edge at confidence=0, with the wrong reason, or against a phantom Method node now fails the test. - abhigyanpatwari#5 (P2): worker-parity test adds a CI tripwire — when CI=1 and dist/parse-worker.js is missing, throw at module top with a clear message. Locally, skipIf(!hasDistWorker) keeps the fast-iteration experience; CI cannot pass with U3 (worker-path ownerId) unverified. Verification: tsc --noEmit clean. Targeted regression sweep on ast-helpers-object-literal-binding (13), object-literal-owner-resolution (9), has-method (60), cross-file-binding (40) — 122/122 pass. Full unit sweep: 6056/6056. Integration suite: 1 pre-existing Windows-flake in worker-pool.test.ts (passes 28/28 in isolation) unrelated to this diff.
…G (PR abhigyanpatwari#1718 review F1) Eliminates the architectural fragility surfaced by PR abhigyanpatwari#1718's adversarial review Finding 1. Previously, normalizeNodeLabel('const') returned 'Variable' while the legacy DAG parse phase emits 'Const' graph nodes (via @definition.const capture for lexical_declaration). PR abhigyanpatwari#1718's Case 5 value-receiver bridge resolved correctly only because resolveDefGraphId happened to fall back to simpleKey after the qualified-key miss — accidental correctness. After this change, scope-resolution defs for `const x = ...` declarations report def.type === 'Const', matching the graph node label. resolveDefGraphId's qualified-key path now hits on the first try; the simple-key fallback is no longer load-bearing for value receivers and can be tightened in future without silently breaking Case 5. Audit completeness verification: - Grep `\bVariable\b` across src/core/ingestion/scope-resolution/ surfaced two consumer sites that already accept both labels: reconcile-ownership.ts:101+168 (`def.type === 'Variable' || def.type === 'Const' || ...`) and walkers.ts:207 isOwnableValueLabel (`Const | Variable | Property | Static`). No language hook in src/core/ingestion/languages/ branches on `def.type === 'Variable'` for what's actually a const declaration. - Sentinel stress test (the full unit + integration suite run with the renamed label in place): 6137/6137 unit tests pass; 2967/2967 integration tests pass. One pre-existing Windows-only flake on worker-pool.test.ts when run alongside the full integration suite (passes 28/28 in isolation, unrelated to scope-extractor — same flake observed before this diff). The variable mapping (`'variable' → 'Variable'`) is preserved for `var` declarations, matching the legacy DAG's `@definition.variable` capture for variable_declaration. The split now mirrors the parse-phase capture distinction exactly. Per plan docs/plans/2026-05-21-002-feat-pr1718-followups-class-instance-and-label-normalization-plan.md U4 + U5. T1 (class-instance singleton resolution from issue abhigyanpatwari#1358's second sub-case) is deferred to a standalone pre-plan investigation, not shipped here.
… singleton sub-cases Closes the remaining sub-cases of issue abhigyanpatwari#1358 surfaced by PR abhigyanpatwari#1718's adversarial review (Finding 4, NOTED): the class-instance singleton (`export const fooService = new FooService();`) and the factory-pattern singleton (`export const fooService = makeFooService();`). Pre-plan investigation (per docs/plans/2026-05-21-002 § "Pre-Plan Investigation Task (T1)") confirmed Outcome A for both patterns — they already resolve end-to-end through scope-resolution's `@type-binding.constructor` capture (languages/typescript/query.ts:489-511) + `propagateImportedReturnTypes` chain-follow (scope-resolution/passes/imported-return-types.ts:114) + receiver-bound Case 4 simple typeBinding lookup (receiver-bound-calls.ts:625). The mechanism was wired correctly before this session; the regression-net wasn't. This test pins the behavior: - Pattern 1: `caller → FooService.getUser` CALLS edge with confidence 0.85 and reason 'import-resolved' - Pattern 2: same edge shape via factory chain-follow (the `@type-binding.alias` capture for `const u = find()` style) Both assertions use exact `.toEqual([{...}])` shape pinning so a future regression that targets a phantom Method node, emits at lower confidence, or drops the cross-file import-resolved reason fails loudly. Verification: 5/5 pass, 127/127 in targeted regression sweep including object-literal-owner-resolution.test.ts, ast-helpers-object-literal- binding.test.ts, has-method.test.ts, and cross-file-binding.test.ts. No production code change. The class methods get a class-qualified node id (`Method:src/service.ts:FooService.getUser#1`) distinguishing them from same-name methods on other classes — distinct from the bare-name node id shape PR abhigyanpatwari#1718's object-literal case uses.
…age for TS/JS (issue abhigyanpatwari#1358) Closes the remaining sub-cases of issue abhigyanpatwari#1358 surfaced by PR abhigyanpatwari#1718's adversarial review (Finding 4). PR abhigyanpatwari#1718 fixed object-literal-shorthand singletons (`export const fooService = { getUser() {} }`); this commit adds parallel coverage for the two other singleton shapes that resolve through the existing scope-resolution chain: // Pattern 1 — class-instance singleton export class FooService { getUser(id) { ... } } export const fooService = new FooService(); // Pattern 2 — factory-pattern singleton export class FooService { getUser(id) { ... } } export function makeFooService() { return new FooService(); } export const fooService = makeFooService(); Pre-plan investigation (per local plan docs/plans/2026-05-21-002 § "Pre-Plan Investigation Task (T1)") confirmed Outcome A — both patterns already resolve end-to-end through: - `@type-binding.constructor` capture (languages/{typescript,javascript}/ query.ts) seeds `fooService → FooService` at parse time - `propagateImportedReturnTypes` (scope-resolution/passes/ imported-return-types.ts:114) mirrors the typeBinding cross-file - Receiver-bound Case 4 simple typeBinding lookup (scope-resolution/passes/receiver-bound-calls.ts:625) MRO-walks FooService and emits the CALLS edge to getUser Tests added per language × pattern (5 each, 10 total): - node existence (Class, Method, Function, Const, plus Function for the factory pattern's `makeFooService`) - HAS_METHOD edge from class to method (class-instance variant) - CALLS edge from caller to `getUser` with `targetFilePath: 'src/service.{ts,js}'`, `reason: 'import-resolved'`, `confidence: 0.85` — exact `.toEqual([{...}])` shape pinning so a regression that emits at lower confidence or drops the cross-file reason fails loudly Fixtures placed under the existing `test/fixtures/lang-resolution/` convention. Tests appended to `test/integration/resolvers/{typescript,javascript}.test.ts`, matching the in-file pattern of every other resolver scenario. Also supersedes and removes the standalone `test/integration/class-instance-and-factory-singleton-resolution.test.ts` introduced earlier in this PR session (`0df91b77`) — the proper home for language-resolver scenarios is the per-language resolver test file alongside similar fixtures (`javascript-self-this-resolution`, `javascript-cross-file`, `typescript-tsconfig-paths`, etc.). One canonical location for the scenario, not two. Verification: 10/10 new singleton tests pass; 297/297 full TS+JS resolver suite pass (no regression in any existing resolver test).
…arity (CI run 26223603426) The class-instance and factory-pattern singleton CALLS-edge resolution tests added in c8e573b rely on scope-resolution-only mechanisms (`@type-binding.constructor` capture + `propagateImportedReturnTypes` mirror + receiver-bound Case 4). The `scope-parity / typescript parity` and `scope-parity / javascript parity` CI jobs run with `REGISTRY_PRIMARY_TYPESCRIPT=0` / `REGISTRY_PRIMARY_JAVASCRIPT=0` and exercise the legacy DAG path, which has no cross-file constructor-derived typeBinding propagation. Verified by job 77202610819 (TS parity) and 77202610869 (JS parity) failing with: × resolves caller.fooService.getUser() to FooService.getUser via constructor-inferred typeBinding × resolves caller.fooService.getUser() through the factory chain to FooService.getUser Note: my local Windows shell-prefix env-var invocation did not propagate the flag into vitest workers correctly (the cpp parity gate's 47-skipped behavior masked the issue when I ran an ad-hoc comparison), so the empirical "both modes pass" finding I posted earlier was wrong. CI is the source of truth. Changes: - test/integration/resolvers/helpers.ts: add `typescript` and `javascript` entries to `LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES` for the 2 CALLS-edge resolution tests in each language. Node-existence and HAS_METHOD assertions are NOT excluded — those pass under legacy DAG (parser-level emission is intact). - test/integration/resolvers/typescript.test.ts: drop the `it` import from vitest; replace with `const it = createResolverParityIt('typescript');` shadow (matches the c/cpp/csharp/go pattern at the top of those files). - test/integration/resolvers/javascript.test.ts: same shadow with `createResolverParityIt('javascript')`. Verification: - Default mode (registry-primary): 297/297 TS+JS resolver tests pass. - Legacy DAG mode: the 4 listed singleton CALLS-edge tests will skip; all other singleton assertions (node existence + HAS_METHOD edge) continue to run and pass under both modes.
Summary
Fixes #1358.
TypeScript/JavaScript object literal shorthand methods such as:
were parsed as top-level
Methodnodes with onlyDEFINESedges from the file. The exported object binding (fooService) had no edge to those methods, so graph/impact queries could not discoverfooService.getUser.This PR adds a narrow AST helper for object-literal method owners and uses it in both parsing paths to emit
HAS_METHODfrom the exported object binding to shorthand object methods.Validation
npx vitest run test/unit/parsing-worker-fallback.test.tsgit diff --check