diff --git a/.gitignore b/.gitignore index 05920d3..9d7ace8 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,7 @@ celerybeat.pid # Environments .env .venv +.venv-analysis/ env/ venv/ ENV/ diff --git a/docs/dependencies.md b/docs/dependencies.md index 0fb1d0c..a5705f4 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -13,6 +13,25 @@ uv sync --locked --group automation --python 3.12 The second command selects an automation environment; use a separate UV_PROJECT_ENVIRONMENT when keeping application and tool environments side by side. `code2llm` itself is excluded from the registry catalog when it is the local editable package. +## Analysis environment (pfix / pipreqs) + +The analyzer toolchain (`clickmd` / `code2logic` → `pfix` → `pipreqs`) enters the +locked dependency graph transitively. `pipreqs` 0.5.0 — the newest published +release — declares `Requires-Python >=3.8.1,<3.13`, so analyzer runs must not +use the application `.venv` on Python 3.13 (PLF-036 / issue #26). Until pipreqs +or pfix publishes a 3.13-compatible release, materialize the pinned analysis +environment instead: + +```sh +scripts/analysis-env.sh # create/sync .venv-analysis (Python 3.12, uv.lock) +scripts/analysis-env.sh --check # + uv pip check + pipreqs smoke test +``` + +The application `.venv` is preserved untouched until the dependency-lock +review; `.venv-analysis` is disposable and rebuilds deterministically from +`uv.lock`. Run analyzer entry points with `.venv-analysis/bin/python`. + + ## Daily updates and verification [Dependabot configuration](../.github/dependabot.yml) checks the explicit internal package allowlist daily, including weekends, and groups updates in one PR. It includes transitive dependencies and may widen a manifest constraint when needed. Local or Git sources require separate review. diff --git a/scripts/analysis-env.sh b/scripts/analysis-env.sh new file mode 100755 index 0000000..18c3cbe --- /dev/null +++ b/scripts/analysis-env.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Pinned analysis environment for the analyzer toolchain (pfix -> pipreqs). +# +# pipreqs 0.5.0 (the newest published release) declares +# Requires-Python >=3.8.1,<3.13, so the analyzer chain must not run on the +# application .venv (Python 3.13) until pipreqs or pfix publishes a +# 3.13-compatible release. This script materializes a separate, locked +# Python 3.12 environment (PLF-036 / issue #26). +# +# Usage: +# scripts/analysis-env.sh # create/sync .venv-analysis +# scripts/analysis-env.sh --check # sync + uv pip check + smoke test +# +# The application .venv is never touched. Remove .venv-analysis only with +# `uv python dir` knowledge; it is disposable and rebuilds deterministically +# from uv.lock. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ANALYSIS_PYTHON="${ANALYSIS_PYTHON:-3.12}" +ANALYSIS_ENV="${ANALYSIS_ENV:-$ROOT/.venv-analysis}" + +cd "$ROOT" +UV_PROJECT_ENVIRONMENT="$ANALYSIS_ENV" uv sync --locked --python "$ANALYSIS_PYTHON" --all-extras + +if [[ "${1:-}" == "--check" ]]; then + UV_PROJECT_ENVIRONMENT="$ANALYSIS_ENV" uv pip check --python "$ANALYSIS_ENV/bin/python" + # Analyzer smoke test: pipreqs resolves imports for a small sample project. + "$ANALYSIS_ENV/bin/python" - <<'PY' +import tempfile +from pathlib import Path + +from pipreqs import pipreqs + +with tempfile.TemporaryDirectory() as tmp: + sample = Path(tmp) + (sample / "sample.py").write_text( + "import json\nimport pathlib\nimport yaml\n\nprint(json, pathlib, yaml)\n", + encoding="utf-8", + ) + imports = pipreqs.get_all_imports(str(sample)) + # pipreqs filters stdlib/local imports: only third-party names remain. + assert "yaml" in imports, f"third-party import not resolved: {imports}" + assert "json" not in imports, f"stdlib import should be filtered: {imports}" + print(f"analysis-env smoke OK (pipreqs resolved {len(imports)} imports)") +PY +fi diff --git a/tests/test_analysis_env.py b/tests/test_analysis_env.py new file mode 100644 index 0000000..7f8a508 --- /dev/null +++ b/tests/test_analysis_env.py @@ -0,0 +1,46 @@ +"""Guardrails for the pinned Python 3.12 analysis environment (PLF-036 / #26). + +The analyzer chain clickmd/code2logic -> pfix -> pipreqs cannot run on the +Python 3.13 application venv: pipreqs 0.5.0 declares Requires-Python +>=3.8.1,<3.13 and no newer release exists. These tests keep the analysis +environment script and its documentation discoverable and pinned. +""" +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "analysis-env.sh" +DOCS = ROOT / "docs" / "dependencies.md" + + +def test_analysis_env_script_exists_and_is_executable(): + assert SCRIPT.is_file() + assert SCRIPT.stat().st_mode & 0o111, "analysis-env.sh must be executable" + + +def test_analysis_env_script_pins_python_312_and_locked_sync(): + content = SCRIPT.read_text(encoding="utf-8") + assert 'ANALYSIS_PYTHON="${ANALYSIS_PYTHON:-3.12}"' in content + assert "uv sync --locked" in content + # The application venv must never be the target of the sync. + assert "UV_PROJECT_ENVIRONMENT" in content + assert "--all-extras" in content + + +@pytest.mark.parametrize( + ("fragment",), + [ + ("pipreqs",), + (">=3.8.1,<3.13",), + ("scripts/analysis-env.sh",), + (".venv-analysis",), + ], +) +def test_dependency_docs_explain_the_analysis_environment(fragment): + assert fragment in DOCS.read_text(encoding="utf-8") + + +def test_gitignore_covers_analysis_environment(): + ignores = (ROOT / ".gitignore").read_text(encoding="utf-8").splitlines() + assert ".venv-analysis/" in ignores