diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff39a63..577a78a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,11 @@ All notable changes to vouch are documented here. Format follows - demo image build: `hatch_build.py` (the build hook pyproject.toml declares for console bundling) is copied into the docker build context; the image had been unbuildable since the hook landed (#474). +- `vouch recall` excludes archived pages from the session-start digest. + claims were filtered by status but pages were not, so an archived page's + title kept reaching every new session's opening context while its claims + stayed suppressed — the same asymmetry `synthesize` already avoids. draft + and stale pages are unaffected (#490). ## [1.3.0] — 2026-07-14 diff --git a/src/vouch/recall.py b/src/vouch/recall.py index 911d512c..22328976 100644 --- a/src/vouch/recall.py +++ b/src/vouch/recall.py @@ -2,7 +2,7 @@ Claude session's context (memvid-style), via the SessionStart hook. Unlike ``kb.context`` (task-scoped retrieval), this emits EVERY live approved -claim as a compact ``[id] text`` line plus every approved page title, so a +claim as a compact ``[id] text`` line plus every live page title, so a fresh session is aware of the whole reviewed KB from the first turn. Page bodies are fetched on demand with ``kb_read_page`` / ``kb_search``. @@ -17,6 +17,7 @@ import yaml from .context import _RETRACTED_CLAIM_STATUSES +from .models import PageStatus from .storage import KBStore DEFAULT_ENABLED = True @@ -58,7 +59,11 @@ def build_digest(store: KBStore, *, max_chars: int = DEFAULT_MAX_CHARS) -> str: c for c in store.list_claims() if c.status not in _RETRACTED_CLAIM_STATUSES ] - pages = store.list_pages() + # archived pages are retracted knowledge, same as the archived claims + # filtered above — leaking a title back into every session's opening + # context would make the archive control decorative. DRAFT and STALE + # stay: unpolished or aging is still live. + pages = [p for p in store.list_pages() if p.status != PageStatus.ARCHIVED] if not claims and not pages: return "" diff --git a/tests/test_recall.py b/tests/test_recall.py index a08a02ce..2777fae7 100644 --- a/tests/test_recall.py +++ b/tests/test_recall.py @@ -7,7 +7,7 @@ import pytest from vouch import recall -from vouch.models import ClaimStatus +from vouch.models import ClaimStatus, PageStatus from vouch.proposals import approve, propose_claim, propose_page from vouch.storage import KBStore, _starter_config @@ -47,6 +47,32 @@ def test_digest_excludes_retracted_claims(store: KBStore) -> None: assert "archived fact" not in d +def test_digest_excludes_archived_pages(store: KBStore) -> None: + _approve_page(store, "live page") + archived = _approve_page(store, "archived page") + archived.status = PageStatus.ARCHIVED + store.update_page(archived) + d = recall.build_digest(store) + assert "live page" in d + assert "archived page" not in d + assert "1 page(s)" in d + + +def test_digest_keeps_stale_pages(store: KBStore) -> None: + """Only ARCHIVED is retracted — an aging page is still live knowledge.""" + stale = _approve_page(store, "stale page") + stale.status = PageStatus.STALE + store.update_page(stale) + assert "stale page" in recall.build_digest(store) + + +def test_digest_empty_when_only_archived_pages(store: KBStore) -> None: + archived = _approve_page(store, "archived page") + archived.status = PageStatus.ARCHIVED + store.update_page(archived) + assert recall.build_digest(store) == "" + + def test_empty_kb_digest_is_empty(store: KBStore) -> None: assert recall.build_digest(store) == ""