"""Sea Haven engineering-handbook conventions loader (WS5 handbook seam). Reads a handbook directory (``SEA_HAVEN_HANDBOOK_DIR`` env or ``~/.sea-haven/engineering-handbook``) and returns a formatted summary of its ``*.md`` convention files for optional injection into prompt contexts. Safe no-op contract: if the directory is absent, empty, or any read fails, :func:`load_handbook_conventions` returns ``""`` and never raises. Callers treat an empty return as "no handbook available" and omit the context block rather than failing. The rsync that delivers the handbook from the Mac to the R720 box, and the nightly ``.github`` pull (systemd timers), are ATTENDED box steps and are NOT implemented here. """ from __future__ import annotations import os from pathlib import Path from typing import Optional __all__ = [ "SEA_HAVEN_HANDBOOK_ENV", "load_handbook_conventions", ] SEA_HAVEN_HANDBOOK_ENV = "SEA_HAVEN_HANDBOOK_DIR" _DEFAULT_HANDBOOK_DIR = Path.home() / ".sea-haven" / "engineering-handbook" # Maximum number of convention files to load (defense-in-depth: avoid # accidentally summarising a huge handbook dir on first setup). _MAX_FILES = 20 # Max bytes per file to include in the formatted summary (truncated if larger). _MAX_FILE_BYTES = 4096 def _handbook_dir(path: Optional[Path | str]) -> Path: """Resolve the handbook directory from an explicit path, env, or default.""" if path is not None: return Path(path) env = os.environ.get(SEA_HAVEN_HANDBOOK_ENV, "").strip() if env: return Path(env) return _DEFAULT_HANDBOOK_DIR def load_handbook_conventions(path: Optional[Path | str] = None) -> str: """Load engineering-handbook conventions and return a formatted summary. Reads all ``*.md`` files in the resolved handbook directory (up to :data:`_MAX_FILES`) and returns a single formatted string suitable for prepending to a model prompt. Returns ``""`` — never raises — when: * the directory does not exist or is not a directory; * no ``*.md`` files are found; * any read error occurs (the file is skipped silently). The returned string includes a header and one section per convention file; callers should include it only when it is non-empty. """ try: hdir = _handbook_dir(path) if not hdir.is_dir(): return "" files = sorted(hdir.glob("*.md"))[:_MAX_FILES] if not files: return "" sections: list[str] = [] for fpath in files: try: raw = fpath.read_bytes()[:_MAX_FILE_BYTES].decode( "utf-8", errors="replace" ) sections.append(f"### {fpath.stem}\n{raw.strip()}") except Exception: # noqa: BLE001 - safe no-op continue if not sections: return "" header = ( "## Sea Haven engineering-handbook conventions\n" "These conventions are from the engineering handbook. Apply them " "when planning or clarifying. They may not cover every scenario." ) return header + "\n\n" + "\n\n---\n\n".join(sections) except Exception: # noqa: BLE001 - safe no-op in all failure modes return ""