"""Config loading for the memory plugin hooks (deep module). Replaces the duplicated inline YAML parsers in the four bash hooks with a single load_config() call returning a frozen Config dataclass. Fail-open: a missing or malformed config file yields defaults, never an exception. """ from __future__ import annotations import os from dataclasses import dataclass, field try: import yaml except Exception: # pragma: no cover - yaml is provided via graphify yaml = None CONFIG_PATH = os.path.expanduser("~/.claude/plugins/os-vault/config.yaml") DEFAULT_VAULT_PATH = "~/Documents/SecondBrain" DEFAULT_GRAPHIFY_OUTPUT_DIR = "~/Documents/SecondBrain/.graphify" DEFAULT_MODEL = "qwen25-coder-7b-16k" DEFAULT_STALE_DAYS = 7 DEFAULT_MEMSEARCH_DIR = "~/.memsearch" @dataclass(frozen=True) class Config: vault_path: str graphify_output_dir: str model: str stale_days: int memsearch_dir: str env: dict = field(default_factory=dict) def load_config(path: str = CONFIG_PATH) -> "Config": cfg = {} if yaml is not None: try: with open(os.path.expanduser(path)) as f: cfg = yaml.safe_load(f) or {} except Exception: cfg = {} if not isinstance(cfg, dict): cfg = {} def expand(value): return os.path.expanduser(str(value)) try: stale_days = int(cfg.get("stale_threshold_days", DEFAULT_STALE_DAYS)) except Exception: stale_days = DEFAULT_STALE_DAYS env_block = cfg.get("env") or {} if not isinstance(env_block, dict): env_block = {} return Config( vault_path=expand(cfg.get("vault_path", DEFAULT_VAULT_PATH)), graphify_output_dir=expand(cfg.get("graphify_output_dir", DEFAULT_GRAPHIFY_OUTPUT_DIR)), model=str(cfg.get("ollama_model", DEFAULT_MODEL)), stale_days=stale_days, memsearch_dir=expand(cfg.get("memsearch_dir", DEFAULT_MEMSEARCH_DIR)), env={str(k): str(v) for k, v in env_block.items()}, )