Standardize on the .agents/ directory (shared with project skills):
phases/, user_stories/, reports/, screenshots/, validate.sh, and
phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves
history; runtime artifacts move alongside).
Updates every reference in AGENTS.md, README.md, .gitignore, app
docstrings, and test story headers. Historical KB content in data/
and the runtime pipeline.log transcript are left untouched.
Phase 33 (story: .agent/user_stories/cache-busting.md).
- app/core/caching.py: asset_version() — git short SHA (a commit is a
deploy), stable content-hash fallback for non-git checkouts, "dev"
for a missing static dir; computed once per process. CachingMiddleware
— the five HTML pages revalidate (no-cache) with ?v=<token> asset refs
rewritten in flight; /assets/* is public, max-age=31536000, immutable;
everything else (all /api/*, the SSE chat stream in particular) passes
through byte-identical.
- tests/e2e/test_cache_busting.py: fresh-Chromium wire assertions —
document no-cache, versioned CSS/JS request URLs sharing one token,
immutable asset headers, /api/health baseline headers, SSE chat to
done (mock LLM).
- README 'Caching / deploys' section + story file.
Also fixed two prod-image defects surfaced by this phase's podman smoke
(the full app would not boot):
- Containerfile: ship the scripts/ package — app/api/sync.py (phase 32)
imports scripts.git_sync / scripts.import_docs at module level, so the
container crashed on boot (ModuleNotFoundError: No module named
'scripts').
- compose.yaml: pass BOR_ADMIN_PASSWORD / BOR_SESSION_SECRET through to
the app service (:- defaults keep 'podman compose up -d db' working;
the app's own fail-loud gate still names missing admin auth).
Smoke: podman compose --profile prod up -d on a fresh image + a fresh
Chromium profile — /, /sources.html and /login.html all served
Cache-Control: no-cache; all 8 asset requests versioned with one shared
token (content-hash fallback inside the image — no .git there);
/assets/* immutable for a year.