Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions autoassistant/audit_skill_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,11 +806,20 @@ def write_baseline(root: Path, *, allow_dev_stack: bool = False) -> Path:


def check_version(root: Path) -> int:
"""Compare the installed stack against the committed baseline.

Returns 0 when versions and API hashes match, 1 on any drift (or a missing
baseline). Prints a short human-readable summary. Intentionally cheap — no
Markdown scan — so it is safe to run at session start.
"""Compare the installed stack's public API surface against the committed baseline.

Returns 0 when the public-API-surface hashes match, 1 on any hash drift (or a
missing baseline). Prints a short human-readable summary. Intentionally cheap
— no Markdown scan — so it is safe to run at session start.

The per-module ``__version__`` equality this check used to *gate* on was
dropped (PyAutoMind build-chain #155 Phase 4 task 3). Since PyAutoConf#119 /
PyAutoBuild#121 a release no longer commits the ``__version__`` stamp back to
the library ``main``, so a source checkout reports a frozen stamp while the
baseline is wheel-derived — a structurally-permanent version mismatch,
independent of release cadence, that the API-surface hash already proves is
spurious (the public surface is byte-identical). Versions are still shown for
context; only the API-surface hash gates.
"""
path = root / BASELINE_REL_PATH
if not path.exists():
Expand All @@ -823,7 +832,10 @@ def check_version(root: Path) -> int:
baseline = json.loads(path.read_text(encoding="utf-8"))
current = compute_baseline()

version_drift = [
# Informational only — a version-stamp difference no longer gates (frozen
# source stamps vs a wheel-derived baseline false-positive; the API-surface
# hash is the real signal).
version_changes = [
(m, baseline["versions"].get(m, "(absent)"), current["versions"][m])
for m in VERSIONED_MODULES
if baseline["versions"].get(m) != current["versions"][m]
Expand All @@ -835,10 +847,17 @@ def check_version(root: Path) -> int:
!= current["api_surface"][m]["hash"]
]

if not version_drift and not hash_drift:
if not hash_drift:
note = ""
if version_changes:
note = (
" (version stamp differs but public API surface is identical: "
+ ", ".join(f"{m} {old}->{new}" for m, old, new in version_changes)
+ ")"
)
print(
f"[drift] clean — installed stack matches baseline "
f"(autofit {current['versions']['autofit']}, generated {baseline.get('generated')})."
f"[drift] clean — installed public API surface matches baseline "
f"(autofit {current['versions']['autofit']}, generated {baseline.get('generated')}){note}."
)
return 0

Expand All @@ -847,16 +866,12 @@ def check_version(root: Path) -> int:
f"(baseline generated {baseline.get('generated')}):",
file=sys.stderr,
)
for m, old, new in version_drift:
print(f" - {m}: {old} -> {new}", file=sys.stderr)
if hash_drift:
print(
f" - public API surface changed: {', '.join(hash_drift)}", file=sys.stderr
)
print(f" - public API surface changed: {', '.join(hash_drift)}", file=sys.stderr)
for m, old, new in version_changes:
print(f" - {m} version: {old} -> {new} (informational)", file=sys.stderr)
print(
" The skills/wiki were validated against the baseline. Upgrade/downgrade to the "
"pinned version (`pip install -U autofit`), or run `--scope all` to audit drift "
"and `--write-baseline` to re-pin once fixed.",
" The skills/wiki were validated against the baseline. Run `--scope all` to audit "
"drift and `--write-baseline` to re-pin once fixed.",
file=sys.stderr,
)
return 1
Expand Down
57 changes: 57 additions & 0 deletions autoassistant/tests/test_check_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""test_check_version.py — --check-version gates on the API-surface hash only.

The per-module ``__version__`` equality was dropped (PyAutoMind build-chain #155
Phase 4 task 3): releases no longer commit the stamp back to library ``main``, so
a source checkout's frozen stamp vs a wheel-derived baseline is a permanent false
positive that the API-surface hash already proves spurious. These tests
monkeypatch ``compute_baseline`` so they need no installed stack.
"""

from __future__ import annotations

import json
from pathlib import Path

from autoassistant import audit_skill_apis as a


def _bl(versions, hashes):
return {
"generated": "2026-07-09",
"versions": versions,
"api_surface": {m: {"hash": h, "n_symbols": 1} for m, h in hashes.items()},
}


def _write_baseline(root, versions, hashes):
path = root / a.BASELINE_REL_PATH
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(_bl(versions, hashes)), encoding="utf-8")


def test_version_differs_but_api_surface_matches_is_clean(tmp_path, monkeypatch, capsys):
base_versions = {m: "2026.7.9.1" for m in a.VERSIONED_MODULES}
hashes = {m: "deadbeef" for m in a.BASELINE_MODULES}
_write_baseline(tmp_path, base_versions, hashes)
cur_versions = {m: "2026.7.15.1" for m in a.VERSIONED_MODULES}
monkeypatch.setattr(a, "compute_baseline", lambda: _bl(cur_versions, hashes))
assert a.check_version(tmp_path) == 0 # was 1 before task 3
out = capsys.readouterr().out
assert "clean" in out and "public API surface is identical" in out


def test_api_surface_hash_drift_is_flagged(tmp_path, monkeypatch, capsys):
versions = {m: "2026.7.9.1" for m in a.VERSIONED_MODULES}
base_hashes = {m: "deadbeef" for m in a.BASELINE_MODULES}
_write_baseline(tmp_path, versions, base_hashes)
moved = a.BASELINE_MODULES[-1]
cur_hashes = dict(base_hashes)
cur_hashes[moved] = "cafef00d"
monkeypatch.setattr(a, "compute_baseline", lambda: _bl(versions, cur_hashes))
assert a.check_version(tmp_path) == 1
err = capsys.readouterr().err
assert "public API surface changed" in err and moved in err


def test_missing_baseline_is_drift(tmp_path):
assert a.check_version(tmp_path) == 1
13 changes: 9 additions & 4 deletions skills/af_audit_skill_apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,13 @@ loops.

### 3. Version baseline + drift-check

This workspace is pinned to an autofit version via `wiki/core/api_audit_baseline.json`
(per-module `__version__` + a hash of each module's public `dir()`). Two commands manage
it:
This workspace is pinned to an autofit API surface via `wiki/core/api_audit_baseline.json`
(a hash of each module's public `dir()`, plus per-module `__version__` recorded for
context). `--check-version` **gates on the API-surface hash only** — the version stamp is
shown but no longer fails the check, because since PyAutoConf#119 / PyAutoBuild#121 a
release does not commit the stamp back to library `main`, so a source checkout's frozen
stamp vs the wheel-derived baseline is a structurally-permanent false positive that the
hash already proves spurious. Two commands manage it:

```bash
# Cheap drift-check — compares the installed stack to the committed baseline.
Expand All @@ -99,7 +103,8 @@ baseline from a clean venv holding the released wheel
(`env -u PYTHONPATH <venv>/bin/python autoassistant/audit_skill_apis.py --write-baseline`);
`--allow-dev-stack` overrides deliberately.

The workflow is: `--check-version` flags that the installed autofit moved → run the full
The workflow is: `--check-version` flags that the installed autofit **public API surface**
moved → run the full
`--scope all` audit to find what broke → fix the references (per the Branch section below)
→ `--write-baseline` to re-pin once the audit is clean. **Only re-pin after fixing**, never
to silence a red drift-check on a stack you haven't audited. Because a wiki refresh
Expand Down
Loading