Repository navigation
fix(call-graph): TS/JS spread qualifier, namespace-import aliases, package self-reference (#3787 #3788 #3789) - #4261
Merged
Conversation
_call_qualifier read the first dot of `...` as a member-access separator with an empty identifier before it, so `...parseCaseValue(x)` got qualifier `<expr>` and resolved as an untyped receiver (methods only), missing the same-file function. A spread now ends the receiver walk: `...f()` is a bare call and `...ns.f()` is a call on `ns`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
…3788) `import * as processors from "./json-schema-processors.js"` binds an alias that is not the file's name, and the resolver's import step matched a qualifier only against imported file stems and directories. A JS/TS file now records its namespace aliases (import * as, import = require, const = require) as file_data.namespace_imports (persisted and rehydrated for delta scans); the import pass resolves each to a file, and `ns.f()` reaches that module, or what a barrel module re-exports, through the import step. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
…ckage.json (#3789) `from "zod/v4"` inside zod was a bare npm specifier, so a package's own tests and examples drew no edge to its source and every `z.x()` call through that import went unresolved. The nearest package.json's `name` and `exports` map (or `main`/`module`, or the subpath itself when there is no `exports`) now resolve a self-reference to the scanned file the manifest declares. A specifier naming another package, an undeclared subpath, or a target that is not a scanned file still resolves to nothing. Workspace siblings are not handled. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
Recall 57.1 -> 60.6, resolution recall 73.7 -> 78.4, confident judged 1520 -> 1614, strict precision 96.6 -> 96.8; confident precision stays 99.9. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
…3789) The tree-sitter reference treated every bare specifier as external, so the 188 zod edges the engine now draws for `zod/v3` / `zod/v4` self-imports read as false positives (precision 77.0%). It now resolves a specifier naming the file's own package through the nearest package.json exports, independently of the engine. zod: 859 resolvable imports, 817 engine edges (was 666 / 629), precision and recall 100.0%. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
fp-ts TaskEither.getApplicativeTaskValidation now links to Either.getApplicativeValidation (fan_out 0 -> 1, fan_in 3 -> 4, pagerank ripple) via the spread/namespace fixes. Zero-dep leg also moves two tiny pagerank ripples (racket fun.c, apollo WAITLIST.agc). Full leg: this environment's own full-precision bless drifts from the committed one on origin/main itself, so only the delta between a bless of main and a bless of this branch was applied. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
…anifest keys (#3789) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
… fun.c, apollo WAITLIST.agc) Matches the zero-dependency leg; blessed with crucible_check --update in the gate's own venvs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
Golden masters taken from main; re-blessed in the next commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
…ank ripples) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
Contributor
…L empty-except) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three TS/JS call-graph and import-graph fixes, measured against the TypeScript compiler reference (zod, the only TS reference repo in
tests/import_graph_corpus.json).#3787: a spread
...f()is not a receiver_call_qualifierread the first dot of...as a member-access separator with no identifier before it, so...parseCaseValue(x)got qualifier<expr>and resolved as an untyped receiver (methods only), missing the same-file function....f()is a bare call (''),...ns.f()is a call onns.test_calls_out_qualifiers_3329.py::test_spread_operator_is_not_a_receiver(fails without the fix).#3788:
ns.f()when the alias differs from the file nameimport * as,import x = require,const x = require) asfile_data.namespace_imports(new nullable column with schema heal, rehydrated for delta scans; an alias bound to two different modules is dropped). The import pass resolves each to a file (NetworkRiskSensor.namespace_aliases), andns.f()resolves at theimportstep to that module or what a barrel (index.*, 2 hops) re-exports.explain_call.pypasses the aliases too.tests/core_engine/test_namespace_imports_3788.py(13 tests: capture, resolution, barrel, negatives, persist/rehydrate).#3789: a package importing itself by name
_resolve_path_mirrortreatedzod/v4as an external package.core/package_self_reference.py. The nearestpackage.jsonnamemust equal the specifier's package, thenexports(subpaths,*patterns, conditions in declaration order,nullhides; with noexports:main/module/the subpath) names the target, which must be a scanned file (.jsmaps to.ts). Another package, an undeclared subpath, or a nameless/nested manifest resolves to nothing. Reads viasource_text.read_source, bounded (1 MB, depth 6, 24 targets, per-directory cache). Needs the scan root (set by galaxyscope). Workspace siblings are not handled.tests/core_engine/test_package_self_reference_3789.py(18 tests).Measurements (zod, vs tsc 6.0.2)
Confident precision does not drop; ambiguous precision stays 32.1. #3789 adds nothing to the call graph by itself: the 133
z.x()calls need the self-import edge AND a re-export chain (see follow-ups). Its evidence is the import graph:The 188 new engine edges all read as false positives because the tree-sitter reference treated every bare specifier as external. Samples are real (
import { z } from "zod/v3"insrc/v3/benchmarks/*.tstosrc/v3/index.ts, declared by package.jsonexports). The reference now implements Node self-reference independently (_self_referenceinimport_graph_accuracy.py). javascript (express) was not measured: the corpus repo is not fetched here.Baselines
call_graph_resolution_typescript_baseline.json: recall 57.1 to 60.6, resolution recall 73.7 to 78.4, judged 1520 to 1614, strict precision 96.6 to 96.8.import_graph_accuracy_baseline.json, typescript only: edges 629 to 817, imports 666 to 859 (hand-edited:--regenerateneeds every language fetched).Golden drift (both legs re-blessed with
crucible_check --update, scope audited withbless_scope.py)TaskEither.getApplicativeTaskValidationnow links toEither.getApplicativeValidation(fan_out 0 to 1, fan_in 3 to 4, PageRank 4e-05 to 5.6e-05). TS, fixed by [TS/JS] A spread call...f()records qualifier<expr>, so a bare call resolves as an untyped receiver and finds nothing #3787/Call resolver (TS/JS):ns.f()misses when the namespace import's alias differs from the file name (import * as processors from "./json-schema-processors.js") #3788.fun.c(_apply) and apolloWAITLIST.agc(TASKOVER): global PageRank renormalisation after the new edge. No other field moved. 0 files newly parsed/excluded.~/venvs/galaxy_venvdirectly drifts ~2,900 leaves on origin/main itself (that venv differs from the gate's); bless viacrucible_check.py --updateinstead. Also, a bless under heavy load can time out bootstrap.rtl.css and fake drift.Gates
pr_gates.py: 7/7 pass (lint, audits, secrets, xray, gauntlet, golden, full suite) on the tree merged with main.Follow-ups (not filed)
z.x()recall needs re-export tracking:v4/index -> classic/index -> external -> schemas;external.tsis not barrel-named, so alias scope stops there. Needs per-fileexport * fromcapture.explain_call.pybuilds its sensor without a scan root, so self-reference aliases do not reproduce there.str.trim(),map.get()) links confidently to a same-named repo method viafile/import#3756 (same resolver): the untyped-receiver fallback is the_ladder(bucket.methods, ...)call in_resolve_one, after the new alias rule; nothing here changes it.Closes #3787
Closes #3788
Closes #3789
🤖 Generated with Claude Code
https://claude.ai/code/session_013sNMtAg6s9dyyjNgnjRHi8