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>
This commit is contained in:
2026-10-06 20:40:13 +02:00
co-authored by Claude Fable 5.1
parent 4fec637599
commit 96780317a7
37 changed files with 1858 additions and 899 deletions
+10 -8
View File
@@ -20,6 +20,8 @@ class ExternalVaultTests(unittest.TestCase):
self.base = Path(self.tmp.name)
self.framework = self.base / "framework space"
shutil.copytree(FRAMEWORK / "scripts", self.framework / "scripts")
shutil.copytree(FRAMEWORK / "kb_framework", self.framework / "kb_framework",
ignore=shutil.ignore_patterns("__pycache__"))
shutil.copy2(FRAMEWORK / "Makefile", self.framework / "Makefile")
self.vault = self.base / "vault space"
self.other = self.base / "other"
@@ -115,15 +117,14 @@ class ExternalVaultTests(unittest.TestCase):
("write_note", {"path": "../escape.md", "content": "escape"})]:
self.assertTrue(self.tool(tool, args).get("isError"))
def test_refresh_uses_absolute_framework_generator_and_writes_when_validating(self):
def test_refresh_validates_in_memory_and_never_runs_vault_tools(self):
(self.vault / "scripts").mkdir()
(self.vault / "scripts/generate-graph.py").write_text("raise RuntimeError('vault tool used')\n")
result = self.tool("refresh_graph", {"validate": True})
self.assertFalse(result.get("isError"), result)
command = result["structuredContent"]["command"]
self.assertEqual(command[1], str(self.framework / "scripts/generate-graph.py"))
self.assertEqual(command[command.index("--vault-root") + 1], str(self.vault))
self.assertTrue((self.vault / "index/graph.json").exists())
self.assertEqual(result["structuredContent"]["vault_root"], str(self.vault))
self.assertEqual(result["structuredContent"]["node_count"], 1)
self.assertFalse((self.vault / "index/graph.json").exists())
summary = self.tool("graph_summary")
self.assertFalse(summary.get("isError"), summary)
@@ -178,16 +179,17 @@ class ExternalVaultTests(unittest.TestCase):
def make(*args):
return subprocess.run(["make", "-C", str(self.framework), f"VAULT_ROOT={self.vault}", *args],
env=self.env, capture_output=True, text=True)
proc = make("graph", "install-hooks")
proc = make("validate-graph", "install-hooks")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
hook = self.vault / ".git/hooks/pre-commit"
self.assertTrue(hook.exists())
self.git(self.vault, "add", "index/graph.json")
proc = subprocess.run([str(hook)], cwd=self.other, env=self.env, capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
(self.vault / "03_resources/new.md").write_text("# new\n")
(self.vault / "03_resources/new.md").write_text("# new\n[gone](nowhere)\n")
proc = subprocess.run([str(hook)], cwd=self.other, env=self.env, capture_output=True, text=True)
self.assertNotEqual(proc.returncode, 0)
self.assertIn("nowhere", proc.stdout + proc.stderr)
self.assertFalse((self.vault / "index/graph.json").exists())
self.assertFalse((self.framework / "index/graph.json").exists())
def test_installer_preserves_unmanaged_hooks_and_reinstalls_managed_hook(self):