docs.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
3 days ago
| 1 | """Living-doc reads for the app API (§Q0.7.3).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | from adapters.config import OverseerConfig, load_config |
| 8 | from adapters.errors import ConfigError |
| 9 | from cli.digest import sha256_hex |
| 10 | from cli.docs_paths import living_doc_abs |
| 11 | from cli.paths import is_within_repo, repo_relative, resolve_config_path, resolve_repo_root |
| 12 | from cli.sanitize import format_config_error |
| 13 | from tools.app.envelope import ApiEnvelope, engine_failure, engine_success |
| 14 | |
| 15 | |
| 16 | def read_living_doc( |
| 17 | *, |
| 18 | repo_root: Path, |
| 19 | config: OverseerConfig, |
| 20 | doc_name: str, |
| 21 | ) -> ApiEnvelope: |
| 22 | """Read a configured living doc with path confinement.""" |
| 23 | try: |
| 24 | abs_path = living_doc_abs(repo_root, config, doc_name) |
| 25 | except ConfigError as exc: |
| 26 | return engine_failure(exit_code=2, error="config", result={"message": str(exc)}) |
| 27 | |
| 28 | if not is_within_repo(repo_root, abs_path): |
| 29 | return engine_failure(exit_code=4, error="path", result=None) |
| 30 | |
| 31 | if not abs_path.is_file(): |
| 32 | return engine_failure(exit_code=4, error="missing", result=None) |
| 33 | |
| 34 | raw = abs_path.read_bytes() |
| 35 | rel = repo_relative(repo_root, abs_path) |
| 36 | return engine_success( |
| 37 | { |
| 38 | "path": rel, |
| 39 | "text": raw.decode("utf-8"), |
| 40 | "sha256": sha256_hex(raw), |
| 41 | } |
| 42 | ) |
| 43 | |
| 44 | |
| 45 | def read_roadmap(*, repo_root: Path, config: OverseerConfig) -> ApiEnvelope: |
| 46 | """Return the configured ROADMAP living doc.""" |
| 47 | return read_living_doc(repo_root=repo_root, config=config, doc_name=config.docs.roadmap) |
| 48 | |
| 49 | |
| 50 | def read_handover(*, repo_root: Path, config: OverseerConfig) -> ApiEnvelope: |
| 51 | """Return the configured HANDOVER living doc.""" |
| 52 | return read_living_doc(repo_root=repo_root, config=config, doc_name=config.docs.handover) |
| 53 | |
| 54 | |
| 55 | def load_repo_config(repo_root: Path, config_arg: str | None = None) -> tuple[OverseerConfig | None, ApiEnvelope | None]: |
| 56 | """Load config or return a refusal envelope.""" |
| 57 | config_path = resolve_config_path(repo_root, config_arg) |
| 58 | if not is_within_repo(repo_root, config_path): |
| 59 | return None, engine_failure(exit_code=4, error="config", result=None) |
| 60 | try: |
| 61 | return load_config(config_path), None |
| 62 | except ConfigError as exc: |
| 63 | return None, engine_failure(exit_code=2, error="config", result={"message": format_config_error(exc, repo_root)}) |
| 64 | |
| 65 | |
| 66 | def resolve_app_repo(cwd: Path, repo_arg: str | None) -> Path: |
| 67 | """Resolve repo root for app handlers.""" |
| 68 | return resolve_repo_root(cwd=cwd, repo_arg=repo_arg, command="app") |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
3 days ago