diff --git a/CHANGELOG.md b/CHANGELOG.md index 15afc94..0280167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - 2026-06-27 + +### Added +- **`--analysis-level {1,2}`** (reintroduced): 1 is symbol table + Jedi call graph, 2 adds the PyCG call graph. +- **Coupling-aware PyCG sharding** (`--pycg-shard`) so level 2 scales to large apps. Shards are chosen by Jedi module coupling (strongly-connected-component condensation, so import cycles never split, plus Louvain community detection) instead of a flat file count, so few call edges are severed between shards. PyCG runs on each shard inside a symlink mini-project that bounds it to that shard's files. Ray-parallel. +- **Iterative decomposition of runaway shards.** PyCG's fixpoint can diverge on heavy metaclass / mixin code. A shard that hits the wall-clock timeout is re-sharded at half the budget and re-run, down to a floor (10 files). The residue that still diverges, or an atomic cycle that will not split, falls back to Jedi-only coverage. On the Odoo benchmark (1028 modules) this recovered 22210 PyCG edges versus 17149 for the best uniform ceiling, losing only 20 of 1028 files instead of a whole shard. +- **New flags**: `--pycg-shard-strategy {jedi,package}` (default `jedi`), `--pycg-shard-ceiling` (default 100, the starting per-shard budget), `--pycg-shard-timeout` (default 120, per-shard wall clock), `--pycg-max-iter` (default 50, caps PyCG fixpoint passes so a divergent shard returns a partial graph instead of hanging). + +### Changed +- **BREAKING: CodeQL is replaced by PyCG as the level 2 call graph backend.** `--codeql/--no-codeql` is removed in favor of `--analysis-level`. Edge `provenance` literal `codeql` becomes `pycg`. New dependency: `pycg` (Apache 2.0). The `codeanalyzer.semantic_analysis.codeql` package is removed. + +### Fixed +- The shard planner keys its module graph by file path. `PyModule.module_name` is only the file stem (every `__init__.py`, `models.py`, ...), so keying by name collided and silently dropped files from shards. +- PyCG no longer follows imports into an in-tree dependency venv (e.g. `.codeanalyzer/`) during the whole-project level 2 run: it runs in a symlink mini-project whose root holds project source only, so dependencies resolve outside the bound and stay ghost nodes. +- `_uv_bin` uses only the vendored `uv` from the `uv` PyPI dependency and no longer falls back to a `uv` on `PATH`, so the analyzer always uses the pinned binary. Returns `None` only if the package is missing, in which case callers fall back to pip. + ## [0.2.1] - 2026-06-22 ### Added diff --git a/codeanalyzer/core.py b/codeanalyzer/core.py index 151446e..dad43c9 100644 --- a/codeanalyzer/core.py +++ b/codeanalyzer/core.py @@ -238,15 +238,16 @@ def _get_base_interpreter() -> Path: @staticmethod def _uv_bin() -> Optional[str]: - """Path to a uv binary: the one bundled with the ``uv`` PyPI package (a - dependency, so normally always present -- including inside a Docker image), - else a uv on PATH, else ``None`` (callers fall back to pip).""" + """Path to the uv binary bundled with the ``uv`` PyPI package (a declared + dependency, so always present in our install -- including inside a Docker + image). We deliberately ignore any uv on PATH so the analyzer always uses + the pinned, vendored uv. Returns ``None`` only if the package is somehow + missing (callers fall back to pip).""" try: from uv import find_uv_bin - return str(find_uv_bin()) except Exception: - return shutil.which("uv") + return None def _install_into_venv(self, venv_python: Path, args: List[str]) -> None: """Install packages into the target venv, preferring uv for speed (parallel