87 lines
3.2 KiB
Python
87 lines
3.2 KiB
Python
"""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 ""
|