docs.py python
68 lines 2.5 KB
Raw
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