Repository navigation
feat(graph): read C/C++, PHP, C#, Dart, components, Lua, GDScript imports, Swift at module level, and measure the edges (refs #980) - #1030
Merged
Conversation
…cript imports (refs #980) The import graph read 8 language families; a blast-radius query on a file in any other answered 'not tracked' (#979). It now reads 15. One resolver per family in src/graph/resolvers, zero dependencies, precision first: an import that cannot be resolved to exactly one file of the repository yields no edge (an external package, a system header, a commented-out import, an ambiguous name). C/C++/ObjC includes through the compiler's own search order; PHP use through composer psr-4 (every matching prefix, every directory) then declared classes; C# using linked to the files declaring the types the file names, not to the whole namespace; Dart relative and package: imports; Vue, Svelte and Astro scripts through the JS/TS resolver; Lua require; GDScript preload/extends. Laravel's Str.php lists 164 importers, the same files grep finds. Swift names modules, not files, so it draws no file edge: --impact on a Swift file keeps 'not tracked' and lists the files in other SwiftPM targets that import its module. The ranker is still handed the original eight families (#934): buildRankingGraph reads families 'legacy', so retrieval does not move with a better blast-radius graph. Adding the new files to the repo's own corpus did move one hard task by a BM25 margin of 0.012; folding resolvers/index.js into builder.js restored 63/90. The #979 tests now use Swift for the file-level assertions.
…-only code stays out of the package (refs #980) The seven resolvers added in a9dadaf live in src/graph/import-resolvers.js instead of a directory of nine files, and the Swift module view is src/graph/swift-modules.js. Behaviour is unchanged: the same fixtures, the same 45 mutants killed (one more fixture closes a survivor: an alias of a type the file declares itself must not be an edge to itself). moduleEdges and swiftFiles' companion enumeration existed only for the accuracy benchmark; the benchmark now builds that list from the product's own moduleImpact (scripts/lib/import-accuracy.mjs), so the published package carries only what a user surface calls. Retrieval gate, measured against develop in a clean worktree: hard 63/90 (70.0%, the floor, 0 tasks of headroom), mined 66.7%, easy 90.0%, all identical to develop, with no task changing between hit and miss. 27 of 166 task ranks moved by a place or two, which is what an added indexed file does to the self-scored corpus; two earlier layouts of the same code flipped h089 and then h081. That is footprint, not design, and the gate has no headroom to absorb it.
…efs #980) A development run of the component reference on vitepress found that `import {\n a,\n b\n} from '../x'` gave no edge: the matcher shared with plain JS/TS is line-based. For Vue, Svelte and Astro scripts the matcher now takes a list across lines, which is what prettier-formatted component code writes. Plain .js/.ts files keep the one-line reading, and so does the ranker's graph: it is built from the same matcher, so widening it moves retrieval (#934) and is its own change, with its own gate review. A test pins both halves (components read, plain TypeScript and the ranking graph unchanged) and two mutants guard them. KNOWN_LIMITATIONS says so, and names the two other specifiers neither family finds: an extension-less one that resolves to a .d.ts, and ./x.js written for an x.ts. Retrieval gate on this tree: hard 63/90 (70.0%), mined 68.3%, easy 90.0%, no hard task changes between hit and miss against develop; one mined task moves from rank 6 to 5, which is the footprint of an indexed file, not a ranking change.
…harness before the first run (refs #980) How the import graph's file-to-file edges are scored against what each language's own tooling reports, written down and committed before the scorer has been run on any evaluation repository: - references: clang -E -H on three configurations (C/C++/ObjC); the framework parser + ts.preProcessFile + ts.resolveModuleName (Vue, Svelte, Astro); PHP's tokenizer + Composer's own ClassLoader; swiftc -emit-imported-modules + swift package describe (Swift, module level). - C#, Dart, Lua, GDScript are listed unmeasured, with the reason: no reference that could be run here. Their resolvers are covered by fixtures. - evaluation repositories pinned to the commits the xrepo manifest pins; development repositories (spdlog, vitepress, guzzle, fixtures) kept out. - eight predictions (P1-P8) with thresholds, and the cluster bootstrap of the call-graph benchmark (2000 resamples, seed 980, 95%); a cell under 100 edges is marked and not quoted. - known biases stated up front: PHP precision is a lower bound (literal require/include is not read by the reference), component recall is bounded by the shared JS resolver (.d.ts targets, ./x.js for x.ts, multi-line lists in plain JS/TS). The harness (scripts/run-import-accuracy.mjs) records the protocol's sha256 and refuses --save for a partial run, an edited protocol or a dirty scored tree. 25 mutants of the arithmetic, the page renderer, the adapters and the runner's guards are killed by test/integration/import-accuracy.test.js. Development runs on spdlog, vitepress and guzzle changed three things in the references and removed one dead step; they are listed in the protocol.
…e second run (refs #980) The first registered run reported how many edges were missed, not why. Reading the component group's misses showed most of them share one cause (388 of svelte's 390 are one target reached by a package naming itself), so the scorer now counts, for every missed and every extra edge, how the import reaches its file: written .js for a .ts file, a #name import, a JSON module, a bare specifier that resolves inside the repository, a relative path to a .d.ts, a file importing itself, else the extension of the file reached. Nothing that decides an edge or a figure changed. The deviation log in the protocol records the first run's headline figures, which the second run must reproduce before anything is published. Six mutants of the new code are killed by the harness test; the page renderer gains the two tables.
…ive component is not its own importer (refs #980) Both found by reading the misses of the first registered run of the component reference, both in the new code, neither a tuned rule: - Astro accepts blank lines before the `---` fence; the pattern required the fence at the first character, so a file that starts with a newline lost its frontmatter (and with it every import). - A Svelte or Astro component that renders itself (svelte:self, a recursive component) imports its own file. The C, C# and other new resolvers never draw a file's edge to itself; the component path did. Tests pin both (and that a fence further down the file is not frontmatter); two mutants are killed. Retrieval gate on this tree: hard 63/90 (70.0%), mined 68.3%, easy 90.0%, as before.
…e statement that PHP, C/C++ and components are unread (refs #980) The registered run, from the committed protocol and a clean tree, over nine pinned repositories (the report is the third run; the protocol's deviation log keeps the first run's figures and says what changed): C / C++ / Objective-C precision 99.7% recall 99.7% (clang -E -H) PHP precision 100.0% recall 100.0% (Composer's autoloader) Swift, module level precision 100.0% recall 99.3% (swiftc + SwiftPM) Vue / Svelte / Astro precision 100.0% recall 79.1% (TypeScript resolution) Seven of the eight registered predictions held. P4 (component recall >= 90%) was missed, and the page says so: every remaining miss is in the resolver all .js and .ts files share (a package importing itself by name through its exports field, ./x.js written for x.ts, JSON modules, #name imports), which is deliberately unchanged because the ranker's graph is built from it (#934). C#, Dart, Lua and GDScript have fixtures and no figure: no reference could be run. The page's tables are generated from the saved report and pinned to it by a test, as are the figures quoted in KNOWN_LIMITATIONS and the prose. cli.md still said the import graph could not read PHP, C#, C/C++, Dart, Lua, GDScript and the component languages, which stopped being true with a9dadaf; it now lists what is read and what Swift gets.
…l spelling (refs #980) Graph keys are lowercased. Dart's package discovery walked up from a KEY to look for pubspec.yaml, so on a case-sensitive filesystem it looked in directories that do not exist whenever a path had a capital letter, and every `package:` import of a package of the same repository resolved to nothing. macOS is case-insensitive, so the tests passed there; CI on Linux caught it. It now starts from the file's real path, as the PHP, C# and GDScript resolvers do. A new test builds all four resolvers that read project files (pubspec.yaml, composer.json, a .csproj, project.godot) with capitals in the directory names and runs them with fs behaving as on a case-sensitive filesystem, so the mistake is visible on macOS too; a mutant that goes back to the key is killed.
…out, and log the two Linux fixes (refs #980) `swiftc -emit-imported-modules -o /dev/stdout` fails on Linux with "No such device or address" when the child's stdout is a socket, as it is under Node's spawnSync, so every file was reported unreadable and the test failed in CI. It now writes the module list to a temporary file. The Swift group was run again on macOS without --save and matches the saved report exactly (figures, counts, samples, diagnostics), so the report stands. The protocol's deviation log records this and the Dart fix before it.
5 tasks
This was referenced Oct 26, 2026
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.
What this does
The import graph (
--impact,get_impact, the blast radius inplan,doctor) now reads C/C++/Objective-C, PHP, C#, Dart, Vue/Svelte/Astro, Lua and GDScript (fifteen families, up from eight), and gives a module-level answer for Swift. It then measures how far those edges agree with what each language's own tooling resolves, and publishes the result, including the prediction that was missed.Refs #980. This does not close it: C#, Dart, Lua and GDScript have fixtures and no accuracy figure (no reference could be run), and the git co-change reference is not done. Closing it is your call.
Shipped (
src/, in the npm package: +29 KB packed, +2 files)src/graph/import-resolvers.js: the seven resolvers. Precision first: an import that cannot be resolved to exactly one repository file (an external package, a system header, an ambiguous name, a path built at run time) yields no edge. One file on purpose; see "The retrieval gate" below.src/graph/swift-modules.js: Swift is module level (Package.swifttargets).--impacton a Swift file names its target and lists the files in other targets that import it, labelled module level. No file-to-file edge is drawn.builder.js: the families,$libfor SvelteKit, import lists that span lines in component scripts only. The ranker is still handed only the original eight families (graph: follow the detected source roots, not a hard-coded dir list #934/retrieval: calibrate the graph boost for large import graphs #935).language-support.js,impact.js: the "not tracked" answer (graph: --impact and get_impact say 'no importers' for PHP, C#, Dart, Swift, Lua, GDScript and C/C++ - the import graph reads 8 of 38 languages #979) now applies to what is really unread.scripts/run-import-accuracy.mjs,scripts/import-accuracy/,scripts/lib/import-accuracy*.mjs), the protocol, the report, the guide page.What it measured
Nine pinned repositories, each exported at its pinned commit, against
clang -E -H(host, Linux, Windows), TypeScript's own module resolution behind the three framework parsers, Composer's own autoloader, andswiftc+ SwiftPM. Over the importing files both sides read:The protocol and eight predictions were committed (
975d281) before the scorer ran on any evaluation repository. Seven held. P4 (component recall ≥ 90%) was missed, at 79.1%, and the page says so.Why: every remaining component miss is in the resolver that all
.js/.tsfiles share, not in the component reader. 390 of the 406 are in svelte, 389 of them files inside thesveltepackage importing"svelte", which TypeScript resolves through the package's ownexports. The rest:./x.jswritten forx.ts(12, astro), JSON modules (3), a#nameimport (1), and one pair of files whose names differ only in case (the evaluation ran on a case-insensitive filesystem). I did not change that resolver: the ranker's graph is built from it, so it needs its own gate review.Also: C/C++ and PHP precision are lower bounds (an include behind a switch the reference did not run; a literal
requirethe reference does not read). Laravel'sStr.phphas 167 direct importers, every PHP source file that namesIlluminate\Support\Str(the four othergrephits are Blade templates).Things you should know
import {\n a,\n b\n} from './x'gives no edge in a plain.ts/.jsfile. Prettier-wrapped imports are common, so--impacton JS/TS files under-reports importers. A development run on vitepress found it; I read such lists in component scripts only (wrappedflag, pinned by a test and two mutants) and left the shared matcher as it was, because the ranker's graph is built from it and widening it moves retrieval. It deserves its own issue with its own gate review; I have not filed one.---fence lost its frontmatter; a recursive component was its own importer); I fixed both (91aec34) and ran a third time, which is the saved report. Against the first run: C, PHP, Swift are bit-for-bit unchanged; components went 99.7% → 100.0% precision (1,539 → 1,535 edges), 79.0% → 79.1% recall. P4 is missed either way.validate:retrievalhard is 63/90 = 70.0% exactly, with 0 tasks of headroom; thehardcorpus scores this repository against itself, so adding indexed source moves it. The first layout of this code (nine files) flipped h089; a single file flipped h081 (via a benchmark-onlymoduleEdgesthat matched the query "edges"); the final layout flips none: hard 63/90, mined 68.3%, easy 90.0%, against develop's 63/90, 66.7%, 90.0% (one mined task moves from rank 6 to 5). 27 of 166 task ranks shift by a place or two. That is footprint, not design, and I chose the layout (one module; benchmark-only code moved out of the package) partly with the gate in mind. A gate that a refactor can flip deserves your attention.pubspec.yamlin directories spelled as the graph's lowercased keys spell them, so on a case-sensitive filesystem everypackage:import of a package in the same repo resolved to nothing whenever a path had a capital letter (f502dd2). A new test runs the four resolvers that read project files (Dart, PHP, C#, GDScript) withfsbehaving case-sensitively, so macOS now catches it too. (2) A benchmark bug: the Swift reference wroteswiftc's output to/dev/stdout, which Linux refuses when stdout is a socket (6725460); the Swift group re-run on macOS reproduces the saved report exactly. Both are in the protocol's deviation log.main.swiftas the entry point; the component reference keeps.d.tstargets; the C reference lost a stub-and-retry step that a mutation test showed never changed a result. All listed in the protocol.Not in this PR
./x.js→x.ts, JSON,#imports,.d.ts, import lists across lines). Each is a real gap for plain JS/TS too and would move the ranker's graph.Test plan
test/integration/import-resolvers.test.js: 29 pass; 50/50 mutants of the resolvers and the builder wiring are killedtest/integration/import-accuracy.test.js: 22 pass with the reference tools (20 pass, 1 skip without them); 32/32 mutants of the arithmetic, the page renderer, the adapters and the--saveguards are killed; the page, the saved report and every figure quoted in prose and inKNOWN_LIMITATIONS.mdare pinned to each othertest/integration/graph-unsupported-languages.test.js: 35 pass (the "not tracked" answer now applies to Swift)validate:retrievalPASS (hard 63/90 at the 70.0% floor, see above);validate:xrepoPASS (116/208, no task changed)check:metrics,check:version-meta,validate:llms,check:bundle:repro, VitePress build (no dead links)mainexports the API, no fingerprinting; nothing underscripts/,benchmarks/,test/ordocs-vp/is in the tarball🤖 Generated with Claude Code