Files
knowledge-base-framework/kb_framework/vault.py
T
TotenKnopfandClaude Fable 5.1 96780317a7 refactor: extract kb_framework package, build graph in memory
Scripts become thin CLIs over kb_framework (graph, query, query_tools,
vault, instructions) so the hosted read-only connector shares one
implementation with the stdio server. The MCP server builds the graph
from Markdown on demand and invalidates it on writes; refresh_graph
rebuilds in memory and reports validation problems. Pre-commit hook
validates only; index/graph.json is no longer read, written or staged
(generate-graph.py --write remains for operators). Golden fixtures pin
graph and tool payloads to the previous output. Agent and skill docs
updated to match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 20:40:13 +02:00

58 lines
2.2 KiB
Python

"""Filesystem access to a vault, confined to its root.
`resolve_in_root` is the single place that turns a vault-relative path into an
absolute one and refuses empty, absolute or escaping values, so no tool can
reach outside the vault. `read_note_payload` returns the `read_note` payload
shape shared by the stdio server and the connector: UTF-8 text inline, or a
binary descriptor (size, sha256, hint) for captured originals under `raw/`.
"""
from __future__ import annotations
import hashlib
from pathlib import Path
from typing import Any, Dict
def resolve_in_root(root: Path, raw_path: Any, *, label: str = "path") -> Path:
"""Turn a vault-relative path into an absolute path inside `root`."""
value = str(raw_path or "").strip()
if not value:
raise ValueError(f"'{label}' is required")
candidate = Path(value)
if candidate.is_absolute():
raise ValueError(f"'{label}' must be vault-relative, got absolute '{value}'")
root = Path(root).resolve()
target = (root / candidate).resolve()
if target != root and not target.is_relative_to(root):
raise ValueError(f"'{label}' resolves outside repository root: '{value}'")
return target
def relative_to_root(root: Path, target: Path) -> str:
return target.relative_to(Path(root).resolve()).as_posix()
def read_note_payload(root: Path, raw_path: Any) -> Dict[str, Any]:
"""The `read_note` payload for a vault-relative path."""
target = resolve_in_root(root, raw_path)
if not target.exists():
raise FileNotFoundError(f"note not found at {target}")
relative = relative_to_root(root, target)
data = target.read_bytes()
try:
text = data.decode("utf-8")
except UnicodeDecodeError:
return {
"path": relative,
"binary": True,
"bytes": len(data),
"sha256": hashlib.sha256(data).hexdigest(),
"hint": (
f"{target} is not UTF-8 text (e.g. a PDF or image captured under "
"raw/). Open it with a file-reading tool that handles binaries; "
"this server returns text only."
),
}
return {"path": relative, "binary": False, "content": text}