Skip to content

Commit 57f6624

Browse files
bb-connorclaude
andcommitted
Phase 1.2 (c): chio-pack registers as a real kb-engine plugin
End-to-end engine ↔ pack boundary now exercised. chio-pack imports kb_engine; kb_engine never imports chio_pack (verified by AST-level test + ops/ci/check-imports.py). chio_pack/schema.py Namespaced labels: ChioCapability, ChioReceipt, ChioGuard, ChioPolicy, ChioProtocol, ChioStandard (concept), ChioFile, ChioSymbol, ChioCrate, ChioDoc, ChioSpec, ChioSection (structural), ChioConcept, ChioEntity (hubs). Edge types: CONTAINS, CALLS, IMPORTS, DEPENDS_ON, DOCUMENTED_IN, HAS_DOC, CANONICAL_DOC, IMPLEMENTS, TESTED_BY, HAS_TEST, DEFINES, GUARDS, VALIDATES, SUPERSEDES, OWNED_BY, VALIDATED_BY, USES_CONCEPT, MENTIONS. Plus GRAPHITI_TYPES / NEO4J_TYPES vault-note vocabulary and a label_for_chio_node() lookup. chio_pack/plugin.py Four hook implementations: rust_source_ingester .rs files; stubs symbol extraction until Phase 1.3+ tree-sitter chio_graph_projector emits ChioFile + ChioCrate + CONTAINS edge per parsed Rust file chio_tool_registrar stub (Phase 1.3+ registers 10 kb_* tools) chio_frontmatter_handler routes vault frontmatter: episode-* types → graphiti target spec/adr/playbook + → neo4j target chio-node (with namespaced label resolution) Per AGENTS.md hard rule #1, returns DerivedRecord objects ONLY. Vault-sync daemon (Phase 1.4) is the lone Graphiti writer. `register(registry)` is the entry point declared in pyproject.toml. chio-pack/pyproject.toml [project.entry-points."kb_engine.plugins"] chio = "chio_pack.plugin" [tool.uv.sources] kb-engine = { path = "../kb-engine", editable = true } Version bumped 0.1.0 → 0.2.0 (first release with kb-engine integration). Added kb-engine to runtime dependencies. chio-pack/tests/test_plugin.py 11 tests covering: register populates all four hook types, Rust ingester recognizes/skips, graph projector emits ChioFile and ChioCrate with CONTAINS, frontmatter handler routes episodes to graphiti and chio-node-bearing specs to neo4j with correct labels, unknown chio-node skipped, unknown type produces no records, AST-level boundary check (kb_engine doesn't import chio_*), entry-point loading via Registry.load_entry_points(). Total chio-pack tests: 26 passing (8 heuristic + 4 fixture-runner + 11 plugin + 3 session_log). The boundary is now load-bearing in three independent ways: 1. ops/ci/check-imports.py (CI step, fails the build on violation) 2. test_engine_does_not_import_chio_modules (unit test, AST-based) 3. Type system: kb_engine.types contains no Chio* symbols by construction (the namespaced labels live in chio_pack/schema.py) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dea5872 commit 57f6624

4 files changed

Lines changed: 433 additions & 1 deletion

File tree

chio-pack/chio_pack/plugin.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""Registration of chio-pack's hooks against kb_engine.Registry.
2+
3+
Per PLAN.md "kb-engine ↔ chio-pack boundary", chio-pack is the canonical
4+
schema pack. This module registers four hook implementations:
5+
6+
- SourceIngester for Rust files (.rs) — minimal today; Phase 1.3+
7+
uses tree-sitter for symbol extraction.
8+
- GraphProjector emitting ChioFile/ChioCrate nodes + CONTAINS edges.
9+
- ToolRegistrar — stub today; Phase 1.3+ registers 10 kb_* tools.
10+
- FrontmatterHandler routing vault-note frontmatter to the right
11+
derived-store target (graphiti / neo4j) based on `type` and
12+
`chio-node` fields.
13+
14+
The `register(registry)` entry point is declared in
15+
chio-pack/pyproject.toml under [project.entry-points."kb_engine.plugins"]
16+
so kb_engine.Registry.load_entry_points() picks it up automatically
17+
when chio-pack is installed.
18+
19+
This module proves the engine ↔ pack boundary works end-to-end:
20+
chio_pack imports kb_engine; kb_engine never imports chio_pack
21+
(enforced by ops/ci/check-imports.py).
22+
"""
23+
from __future__ import annotations
24+
25+
from typing import Any
26+
27+
from kb_engine import (
28+
DerivedRecord,
29+
Edge,
30+
Node,
31+
ParsedFile,
32+
Registry,
33+
)
34+
35+
from . import schema
36+
37+
38+
# === SourceIngester ===
39+
40+
41+
def rust_source_ingester(file_path: str) -> ParsedFile | None:
42+
"""Recognize .rs files. Today returns a stub ParsedFile so the
43+
GraphProjector has something to project. Phase 1.3+ reads file
44+
content and extracts symbols via tree-sitter.
45+
"""
46+
if not file_path.endswith(".rs"):
47+
return None
48+
return ParsedFile(
49+
path=file_path,
50+
language="rust",
51+
text="", # Phase 1.3+
52+
symbols=[], # Phase 1.3+
53+
properties={"crate": _crate_from_path(file_path)},
54+
)
55+
56+
57+
def _crate_from_path(path: str) -> str:
58+
"""Best-effort crate name from path 'crates/<name>/src/...'."""
59+
parts = path.split("/")
60+
if len(parts) >= 2 and parts[0] == "crates":
61+
return parts[1]
62+
return "unknown"
63+
64+
65+
# === GraphProjector ===
66+
67+
68+
def chio_graph_projector(parsed: ParsedFile) -> list[Node | Edge]:
69+
"""Emit ChioFile + ChioCrate nodes plus a CONTAINS edge.
70+
71+
Phase 1.3+ extends to ChioSymbol nodes (one per parsed.symbols
72+
entry) plus CALLS/IMPORTS/DEFINES edges where the parser surfaces
73+
them.
74+
"""
75+
if parsed.language != "rust":
76+
return []
77+
out: list[Node | Edge] = []
78+
file_id = f"file:{parsed.path}"
79+
out.append(Node(
80+
id=file_id,
81+
label=schema.FILE,
82+
properties={"path": parsed.path},
83+
))
84+
crate = parsed.properties.get("crate", "unknown")
85+
if crate != "unknown":
86+
crate_id = f"crate:{crate}"
87+
out.append(Node(
88+
id=crate_id,
89+
label=schema.CRATE,
90+
properties={"name": crate},
91+
))
92+
out.append(Edge(
93+
src_id=crate_id,
94+
dst_id=file_id,
95+
relationship=schema.CONTAINS,
96+
))
97+
return out
98+
99+
100+
# === ToolRegistrar ===
101+
102+
103+
def chio_tool_registrar(server: object) -> None:
104+
"""Phase 1.3+: register kb_search_code, kb_search_docs, kb_neighbors,
105+
kb_context, kb_impact, kb_brief_feature, kb_eval, kb_add_episode,
106+
kb_find_tests, kb_find_docs on the MCP server framework.
107+
108+
Today stub — the MCP server framework is Phase 1.3+. Stub keeps the
109+
registration call from erroring at boot so the rest of the pipeline
110+
can be tested end-to-end.
111+
"""
112+
# Future: server.register_tool("kb_search_code", ...)
113+
pass
114+
115+
116+
# === FrontmatterHandler ===
117+
118+
119+
def chio_frontmatter_handler(
120+
type: str, frontmatter: dict[str, Any]
121+
) -> list[DerivedRecord]:
122+
"""Route a vault-note frontmatter to derived-store targets.
123+
124+
Per AGENTS.md hard rule #1, this MUST NOT write directly to
125+
Graphiti. It returns DerivedRecord objects; the vault-sync daemon
126+
(Phase 1.4) is the only writer.
127+
"""
128+
out: list[DerivedRecord] = []
129+
if type in schema.GRAPHITI_TYPES:
130+
out.append(DerivedRecord(
131+
target="graphiti",
132+
payload={
133+
"name": frontmatter.get(
134+
"graphiti_episode_name", frontmatter.get("title", "")
135+
),
136+
"type": type,
137+
"scope": frontmatter.get("scope", "chio-repo"),
138+
"source_description": frontmatter.get("source_description", ""),
139+
"frontmatter": frontmatter,
140+
},
141+
))
142+
if type in schema.NEO4J_TYPES:
143+
label = schema.label_for_chio_node(frontmatter.get("chio-node"))
144+
if label:
145+
out.append(DerivedRecord(
146+
target="neo4j",
147+
payload={
148+
"id": frontmatter.get("id", ""),
149+
"label": label,
150+
"properties": frontmatter,
151+
},
152+
))
153+
return out
154+
155+
156+
# === Registry entry point ===
157+
158+
159+
def register(registry: Registry) -> None:
160+
"""Called by `kb_engine.Registry.load_entry_points()`.
161+
162+
Wired in chio-pack/pyproject.toml under
163+
`[project.entry-points."kb_engine.plugins"]`.
164+
"""
165+
registry.register_source_ingester(rust_source_ingester)
166+
registry.register_graph_projector(chio_graph_projector)
167+
registry.register_tool_registrar(chio_tool_registrar)
168+
for type_ in schema.GRAPHITI_TYPES | schema.NEO4J_TYPES:
169+
registry.register_frontmatter_handler(type_, chio_frontmatter_handler)

chio-pack/chio_pack/schema.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""chio-pack schema: namespaced labels and edge types for the Chio property graph.
2+
3+
Per AGENTS.md hard rule #3, `kb_engine` cannot import `chio_*`. The
4+
dependency flows the OTHER way: chio_pack imports kb_engine to declare
5+
its own schema using the engine's plugin protocols.
6+
7+
All node labels are namespaced with the `Chio` prefix to prevent
8+
collisions if other adopter packs (platform-pack, opus-pack, etc.) join
9+
the same Neo4j instance later.
10+
"""
11+
from __future__ import annotations
12+
13+
# === High-level concept nodes ===
14+
CAPABILITY = "ChioCapability"
15+
RECEIPT = "ChioReceipt"
16+
GUARD = "ChioGuard"
17+
POLICY = "ChioPolicy"
18+
PROTOCOL = "ChioProtocol"
19+
STANDARD = "ChioStandard"
20+
21+
# === Structural nodes (from source-code projector) ===
22+
FILE = "ChioFile"
23+
SYMBOL = "ChioSymbol"
24+
CRATE = "ChioCrate"
25+
DOC = "ChioDoc"
26+
SPEC = "ChioSpec"
27+
SECTION = "ChioSection"
28+
29+
# === Concept hubs ===
30+
CONCEPT = "ChioConcept"
31+
ENTITY = "ChioEntity"
32+
33+
ALL_NODE_LABELS = {
34+
CAPABILITY, RECEIPT, GUARD, POLICY, PROTOCOL, STANDARD,
35+
FILE, SYMBOL, CRATE, DOC, SPEC, SECTION,
36+
CONCEPT, ENTITY,
37+
}
38+
39+
# === Edge relationship vocabulary ===
40+
41+
# Structural
42+
CONTAINS = "CONTAINS"
43+
CALLS = "CALLS"
44+
IMPORTS = "IMPORTS"
45+
DEPENDS_ON = "DEPENDS_ON"
46+
47+
# Documentation
48+
DOCUMENTED_IN = "DOCUMENTED_IN"
49+
HAS_DOC = "HAS_DOC"
50+
CANONICAL_DOC = "CANONICAL_DOC"
51+
52+
# Implementation
53+
IMPLEMENTS = "IMPLEMENTS"
54+
TESTED_BY = "TESTED_BY"
55+
HAS_TEST = "HAS_TEST"
56+
DEFINES = "DEFINES"
57+
58+
# Chio-protocol-specific
59+
GUARDS = "GUARDS"
60+
VALIDATES = "VALIDATES"
61+
SUPERSEDES = "SUPERSEDES"
62+
OWNED_BY = "OWNED_BY"
63+
VALIDATED_BY = "VALIDATED_BY"
64+
USES_CONCEPT = "USES_CONCEPT"
65+
MENTIONS = "MENTIONS"
66+
67+
68+
def is_chio_label(label: str) -> bool:
69+
"""True if the label is in this pack's namespace."""
70+
return label in ALL_NODE_LABELS
71+
72+
73+
# === Vault-note type → graph-target mapping ===
74+
75+
# Episode types are derived to Graphiti (temporal memory).
76+
GRAPHITI_TYPES = frozenset({
77+
"episode-architecture-summary",
78+
"episode-architecture-decision",
79+
"episode-workflow-constraint",
80+
"episode-release-note",
81+
"episode-pr-repair",
82+
"episode-session-note",
83+
})
84+
85+
# Spec/ADR/playbook types are derived to Neo4j (the property graph).
86+
NEO4J_TYPES = frozenset({
87+
"spec",
88+
"adr",
89+
"playbook",
90+
})
91+
92+
93+
def label_for_chio_node(chio_node: str | None) -> str | None:
94+
"""Map a vault-note `chio-node` frontmatter value to its graph label."""
95+
if not chio_node:
96+
return None
97+
return {
98+
"Capability": CAPABILITY,
99+
"Receipt": RECEIPT,
100+
"Guard": GUARD,
101+
"Policy": POLICY,
102+
"Protocol": PROTOCOL,
103+
"Standard": STANDARD,
104+
}.get(chio_node)

chio-pack/pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
[project]
22
name = "chio-pack"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
description = "Chio-specific schema, MCP tools, and outcome evals for chio-developer-base"
55
requires-python = ">=3.11"
66
authors = [{ name = "Backbay" }]
77
dependencies = [
88
"pyyaml>=6.0",
9+
"kb-engine",
910
]
1011

12+
[project.entry-points."kb_engine.plugins"]
13+
chio = "chio_pack.plugin"
14+
15+
[tool.uv.sources]
16+
kb-engine = { path = "../kb-engine", editable = true }
17+
1118
[project.scripts]
1219
# Phase 0+: outcome eval runner (skeleton; runners are Phase 1).
1320
chio-pack-eval = "chio_pack.eval.runner:main"

0 commit comments

Comments
 (0)