Phase 2 — Static analysis¶
The second phase identifies the Kotlin files affected by the bump — both directly (they import a symbol from the bumped library) and transitively (they import a directly-affected file) — and tags every impacted file with its source set.
Inputs¶
phase1/before/,phase1/after/— the shadow copies from Phase 1.phase1/manifest.json— the resolved coordinates and bump category.
Outputs¶
What happens¶
- Parse every
.ktundersrc/with Tree-sitter (tree-sitter-kotlin). - Build a symbol index: file → declared symbols, file → imported symbols, file → source set.
- Resolve the bumped Maven coordinate to a set of Kotlin package roots via the
MAVEN_TO_KOTLINmapping table (e.g.io.ktor:*→io.ktor.*). - Mark every file whose imports intersect those roots as
impact_level = 2(direct). - BFS over the symbol graph from each direct file. Record the parent at every step in
propagated_from. Mark each newly reached file asimpact_level = 1(transitive). - Tag every impacted file with its source set based on its path under
src/<sourceSet>Main/kotlin/. - Scan the impacted files for
expect/actualdeclarations. Pairs are surfaced as review targets — not compatibility proofs. - Compute per-file metrics:
rloc(real lines of code),mcc(a McCabe-like heuristic). These feed Phase 5.
Output sample¶
A single FileImpact entry inside phase2/impact_graph.json#impacted_files:
{
"file_path": "shared/src/commonMain/kotlin/com/example/api/PokedexClient.kt",
"relation": "direct",
"distance": 0,
"imports_from_dependency": ["io.ktor.client.HttpClient"],
"propagated_from": [],
"metrics": { "rloc": 84, "functions": 9, "mcc": 12 },
"declarations": ["PokedexClient"],
"source_set": "common"
}
Detected pairs travel separately under impact_graph.json#expect_actual_pairs:
{
"expect_fqcn": "com.example.HttpEngineFactory",
"expect_file": "shared/src/commonMain/kotlin/com/example/HttpEngineFactory.kt",
"actual_files": [
"shared/src/androidMain/kotlin/com/example/HttpEngineFactory.android.kt",
"shared/src/iosMain/kotlin/com/example/HttpEngineFactory.ios.kt"
]
}
Per-source-set counts are not pre-computed in the model — the renderer groups impacted_files by source_set at report time. The PR-comment sunburst preview is built from the same grouping.
Edge cases¶
plugin_or_toolchainbumps — AGP, KSP, the Gradle wrapper. Zero static impact by design (no Kotlin imports). See L2.- Compose Multiplatform —
org.jetbrains.compose.*coordinates are not yet mapped toandroidx.compose.*package roots. See L7. - Non-convention source-set layout — files outside
src/<sourceSet>Main/kotlin/are taggedunknown. See L3. - Dependency injection — bindings expressed only through Koin DSL, Hilt annotations or reflection are not in the graph. See L8.
Contracts¶
Next phase¶
Phase 3 reads the same shadow copies and builds the APKs for the dynamic analysis. It does not read Phase 2 output — the two phases run in parallel in CI.