Repository navigation
fix(scanner): resolve Go imports of multi-file packages - #199
Merged
Merged
Conversation
A Go import names a package, and a package is every non-test .go file in its directory. The resolver dropped any import that resolved to more than one file "to avoid inflating hub counts", so an import of a package with two or more files produced no edge at all: --deps listed the package's files as standalone and --importers answered "No files import X" with Coverage: complete (#191). On this repository 21 files import package watch and --importers watch/events.go reported zero. Keep the file edges. The text views collapse them back to package granularity so output stays bounded: --deps draws "main ───▶ watch/ (11 files)" and ranks hubs as "scanner/ (67←, 17 files)", --importers labels the count "(package codemap/watch: Go imports name packages, not files)" and lists hub imports per package. JSON keeps the per-file edges. For a Go file whose package has other files and no cross-package importers, say what is not modeled instead of a bare zero (#138): "No cross-package importers of X. Same-package references are not modeled (N other files in package P)." The note is only printed when there is a package with siblings, so non-Go files never get it. Hub counts for single-file packages are unchanged (config/config.go 40, analysis/contracts.go 30). codemap --deps . text: 7,672 → 15,603 bytes; the growth is the 192 previously dropped edges rendered at package granularity. Tests: testdata/go-multi-file-package reproduces the issue with exact importer lists and a never-imported package as the negative case; TestGoGraphMatchesGoList checks every in-module edge from `go list -json ./...` is present in the graph, on the fixture and on this repository. Closes #191 Closes #138 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159tcanCgA3jxi2E7DdRVWJ
JordanCoin
added a commit
that referenced
this pull request
Oct 7, 2026
Resolves the one conflict in mcp/main.go handleGetImporters: the #140 not-indexed check runs first inside the empty-importers branch (an unindexed file has no Go package to be a sibling of), and an indexed file with zero importers falls through to #199's Go package-granularity wording. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159tcanCgA3jxi2E7DdRVWJ
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 does this PR do?
Fixes #191: in a Go module, an import of a package with two or more non-test
.gofiles produced no edge, so--depslisted the package's files as standalone and--importersansweredNo files import XwithCoverage: complete. Also fixes #138 part 1: a Go file with no cross-package importers now says what the graph does not model instead of printing a bare zero, and the Go note is never printed for non-Go files.Type of change
Problem
scanner/filegraph.goresolves a local Go import to the files of the imported package viafileIndex.goPkgs, then dropped the result whenever it had more than one file:The comment said this was "to avoid inflating hub counts". The effect was that every importer of a multi-file package vanished from the graph. Single-file packages (
config/,analysis/) resolved, which is why hub detection looked plausible on this repository whilepackage watch(21 cross-package importers),package scanner(67) andpackage cmdhad zero.Root cause
A Go import names a package, and in codemap's model a package is every non-test
.gofile in a directory. The resolver's package lookup was correct; thelen(resolved) > 1guard after it threw the answer away whenever it was right for more than one file.Fix
scanner/filegraph.go: keep the fan-out. One import of a package becomes one edge per non-test file of that package (_test.gofiles are never targets). Hub status for a Go file now follows from its package's importers.FileGraph.GoPackage(path)reports the package a Go file belongs to and its non-test files; newscanner.CollapseGoPackageFiles(paths)collapses the files of one Go package todir/ (N files)for text output.ImportersReportgainspackageandpackage_siblings(JSON, omitempty).--importerstext labels the count with its unit and, for an empty answer on a file with package siblings, states what is not modeled.render/depgraph.go: the--depstext view draws Go package imports once at package granularity and ranks hubs per package with distinct importer counts.--json --depsis unchanged in shape and still validates.get_importerstext carries the same label and the same empty-answer sentence;cmd/hooks.gocollapses theImports N hub(s)list the same way. A stale comment incollide.gothat described the dropped edges is corrected; collide's package weights were already computed from raw imports and are unchanged.Before / after on the fixture (
testdata/go-multi-file-package, the repro from #191)Before:
After:
Before / after on this repository
codemap --deps .text, both binaries run on the same tree:config/config (40←), analysis/contracts (30←), internal/buildinfo/version (6←), find/find (2←)79 depsscanner/ (67←, 17 files), internal/projectpath/ (42←, 2 files), config/config (40←), analysis/contracts (30←), watch/ (21←, 11 files), limits/ (20←, 2 files)271 depsThat is 2.03x. Go edges are already rendered at package granularity in the text view (without collapsing, the same output was 94,894 bytes); the remaining growth is the 192 previously dropped edges, one line per importing file (e.g.
blast_radius_test ───▶ scanner/ (17 files)), which is the fix. Unchanged:config/config.go40 importers,analysis/contracts.go30,codemap .hub stars (5 before and after).codemap --json --deps .andcodemap --json --importers config/config.govalidate.New and changed printed lines (docs/OUTPUT-STANDARD.md)
Imported by 21 files (package codemap/watch: Go imports name packages, not files) - changes have wide impact!(also the non-hub formImported by 1 file(s) (package …))go.mod+ directory; reproduced bycodemap --importers <file>.events.go.No cross-package importers of multi/a_test.go. Same-package references are not modeled (2 other files in package example.com/demo/multi).No files import X.because nothing is unmodeled.main ───▶ multi/ (2 files), single/singleandHUBS: scanner/ (67←, 17 files), …in--deps--json --depskeeps per-file edges for tools.Imports 39 hub(s): internal/projectpath/ (2 files), …in--importersand the hooksget_importerstext:21 files import 'watch/events.go' (package codemap/watch: Go imports name packages, not files):and the same empty-answer sentence as (2). Same tests, same reasons.Tests
testdata/go-multi-file-package/: the Go: imports of multi-file packages are dropped from --deps and --importers #191 repro (single,single2,multi,named,app) plusorphan(never imported) andmulti/a_test.go.go build ./... && go vet ./...pass in it.scanner.TestGoMultiFilePackageImportsResolveasserts the exact importer list for every file:[app/main.go]for all six package files,nilfororphan/orphan.go,app/main.goandmulti/a_test.go; exactImports[app/main.go];GoPackageattribution. Fails on main (Importers["multi/a.go"] = [], want [app/main.go]).scanner.TestGoGraphMatchesGoList: runsgo list -json ./...and asserts that for every in-module import edge the compiler reports, every non-test file of the imported package is imported by a file of the importing package in codemap's graph. Runs on the fixture (refuses a vacuous pass: exactly 4 package edges) and on this repository (skipped in-short, or whengois not on PATH). Fails on main withgo list: package example.com/demo/app imports example.com/demo/multi, but no file of example.com/demo/app imports multi/a.go in codemap's graph.scanner.TestCollapseGoPackageFiles,render.TestDepgraphRendersGoPackageImportsAtPackageGranularity(exact edge, HUBS and summary lines),TestImportersCLIReportsGoPackageGranularity(exact printed lines on the fixture),TestImportersCommandListsMultiFilePackageImporter(end to end through the binary; fails on main).TestBuildFileGraphGoPackageTargets,TestBuildFileGraphImportResolutionBoundariesandTestRenderImportersReportExplainsEmptyResult, which encoded the old behaviour.gofmt -l .,go vet ./...,staticcheck ./...clean.go test ./...green except the three tests that fail in a root container on main too (TestRunSetupCreatesConfigAndHooks,TestSelectReportsInaccessiblePrimarySetup,TestListProjectsPreservesPerChildScanErrors).watch.TestLineDelta/TestEventDetectionare timing tests (fixed sleeps aroundos.WriteFile) that occasionally read the file between truncate and write under full-suite load; they pass 6/6 in isolation and this PR does not touchwatch/.Checklist
go build && ./codemap .codemap collide --helpalready describes the package granularity this output now matches)Closes #191
Closes #138
🤖 Generated with Claude Code
https://claude.ai/code/session_0159tcanCgA3jxi2E7DdRVWJ
Generated by Claude Code