Version: 4.4.2 (Homebrew) · macOS · ast-grep installed
Summary
In languages where files inside a module do not import each other by path — Swift is the clearest case, but the same applies to C#, Java within a package, and Kotlin — the dependency graph comes back essentially empty, yet the JSON still declares the analysis authoritative and complete.
Repro
Any single-module Swift app (~290 source files in my test):
Actual
"coverage": {"status": "complete", "sources": [{"name": "ast-grep", "status": "authoritative"}], "issues": []}
Two edges found across the whole project. blast-radius on a 50-file change in the same project reports:
files_with_dependents=0
impacted_outside_diff=0/0
dependency_context_outside_diff=0/0
Expected
Either real edges, or an honest degraded status.
Why this is the worst of the bugs
Parsing is fine — 1068 functions were extracted correctly. The edge model is what doesn't apply: every import in that project resolves to a framework (SwiftUI, Foundation, …), never to another file in the project. So the graph is structurally empty, not wrong-by-accident.
The damage comes from pairing that with status: complete. A consumer — especially an automated one feeding an LLM — reads "complete" plus "0 dependents" and concludes the change is isolated. In my test the changed set included a type with 40 referencing files and another with 33. Reported impact: zero, with full confidence.
A tool that says "I found nothing" is useful. A tool that says "I checked everything and there is nothing" when it cannot check is worse than not running.
Suggested fix
- When a language's import graph yields ~0 internal edges across a large file count, set
coverage.status = "degraded" with an issue explaining that the language needs symbol-reference resolution rather than import resolution.
- Longer term, resolve edges for these languages by type name → declaring file.
ast-grep already extracts the declarations; the missing step is matching references to them. A plain ranking of type name → count of other files referencing it took 1.8s on the same repo and produced exactly the signal blast-radius reported as zero.
Related false positives
The two edges it does find look like filename-against-directory-name matches:
VendorSDK ───▶ src/Integrations/VendorSDK
OtherSDK ───▶ src/Integrations/OtherSDK
Version: 4.4.2 (Homebrew) · macOS ·
ast-grepinstalledSummary
In languages where files inside a module do not import each other by path — Swift is the clearest case, but the same applies to C#, Java within a package, and Kotlin — the dependency graph comes back essentially empty, yet the JSON still declares the analysis authoritative and complete.
Repro
Any single-module Swift app (~290 source files in my test):
Actual
Two edges found across the whole project.
blast-radiuson a 50-file change in the same project reports:Expected
Either real edges, or an honest
degradedstatus.Why this is the worst of the bugs
Parsing is fine — 1068 functions were extracted correctly. The edge model is what doesn't apply: every import in that project resolves to a framework (
SwiftUI,Foundation, …), never to another file in the project. So the graph is structurally empty, not wrong-by-accident.The damage comes from pairing that with
status: complete. A consumer — especially an automated one feeding an LLM — reads "complete" plus "0 dependents" and concludes the change is isolated. In my test the changed set included a type with 40 referencing files and another with 33. Reported impact: zero, with full confidence.A tool that says "I found nothing" is useful. A tool that says "I checked everything and there is nothing" when it cannot check is worse than not running.
Suggested fix
coverage.status = "degraded"with an issue explaining that the language needs symbol-reference resolution rather than import resolution.ast-grepalready extracts the declarations; the missing step is matching references to them. A plain ranking oftype name → count of other files referencing ittook 1.8s on the same repo and produced exactly the signalblast-radiusreported as zero.Related false positives
The two edges it does find look like filename-against-directory-name matches: