# Phase 3: Phase Summary

## What Was Built

Phase 3 delivered the complete static analysis pipeline — the engine that turns
a git repository into structured data the LLM can reason about. Given a repo path,
it discovers all tracked source files via `git ls-files`, parses Python files
using the stdlib `ast` module to extract imports, function definitions, and entry
points, constructs a directed import graph with in-degree centrality, groups
related files into logical modules via label propagation clustering, detects test
files by naming convention, parses `coverage.xml` when available, and scores every
file by importance signals so Phase 6 can make smart token-budget decisions about
what to include in the LLM prompt.

## Key Decisions

**`LanguageAdapter` as a structural Protocol (not ABC).** Phase 4 will add
tree-sitter adapters. The Protocol approach lets those adapters simply implement
the right methods without inheriting from a base class — which matters because
tree-sitter parsers are fundamentally different objects. The `AdapterRegistry`
handles dispatch cleanly, and extension requires only registering a new adapter.

**Label propagation over graph partitioning libraries.** The plan considered
community detection algorithms from networkx, but the no-new-dependencies rule
applied — label propagation is O(E × iterations) and converges in 5–15 rounds
for codebases under 1000 files. The directory-proximity weight bonus (0.3 on
same-directory edges) ensures that even files with no imports tend to cluster
with their directory neighbours.

**In-degree only for centrality.** Betweenness centrality would be more precise
but is O(VE) — too slow for large repos. In-degree (O(E)) correlates well with
"this file is a hub" for typical Python codebases where a `constants.py` or
`models.py` gets imported by many others.

**`src/` layout support via suffix matching in absolute import resolution.** The
resolver tries exact matches first, then searches for any repo file ending with
`/{candidate}`. This handles `src/`, `lib/`, or any single-level prefix without
hardcoding the prefix name. It's slightly O(n) per import, acceptable for MVP
file counts.

## Complex Logic Explained

**Relative import level navigation (`_resolve_relative_import`).**
Python's `level` on `ImportFrom` nodes counts dots: `.` = 1, `..` = 2. Level 1
means *current package directory* (no navigation up). Level 2 means *parent
package* (navigate up once). The bug fixed in review was `parts[:-level]` —
for level=1 it went up to parent. The fix: `go_up = level - 1`, then walk up
`go_up` times from `Path(importing_file).parent`.

**Label propagation convergence and small-cluster merging.**
Each file starts as its own label. Each iteration, every file (in random order)
adopts the most-weighted label from its neighbours. Convergence is detected by
`changed = False` per iteration. After convergence, clusters smaller than
`min_module_size` look for their most-connected larger cluster and merge into it
— or keep themselves as a small cluster if they have no connections. This
produces clean module boundaries even when the codebase has isolated utility
files.

**Cluster confidence as internal/total edge ratio.**
`_compute_confidence` counts edges that stay within the cluster ("internal")
vs. edges that cross cluster boundaries ("external"). A cluster where 80% of
its imports are within itself scores 0.8 confidence. A cluster of disconnected
files with no imports scores 0.0 (the `total == 0` case). The score feeds into
Phase 6 to calibrate how strongly to describe a cluster as a "module".

## Connection to Previous Phases

Consumes Phase 1's `config.py` (repo path validation), `constants.py`
(`EntryType`, `InvestigationCategory`, `FILE_PRIORITY_*` scoring constants, and
`DEFAULT_TOKEN_BUDGET`). The `AnalysisResult` produced here is the primary input
for Phase 6 (LLM briefing generation) — Phase 6 reads `scored_files`,
`entry_points`, `modules`, and `test_info` to construct the prompt context.
Phase 2 DB models (`Module`, `FileSnapshot`) are what the orchestrator will
eventually persist results into — that wiring happens in Phase 6, not here.
Phase 5 will write property-based tests against the interface contracts defined
in this phase's plan.
