Skip to main content

khive_dialect_kg/
lib.rs

1//! KG dialect for khive-mcp (ADR-025 + ADR-027).
2//!
3//! Bundles the `kg` and `gtd` packs into a single registrar so `khive-mcp`
4//! does not need to import either pack crate directly. `khive-mcp` is a
5//! generic dispatch shell; this crate owns the knowledge of which packs
6//! the KG dialect provides and what names they answer to.
7//!
8//! ## Adding a new dialect
9//!
10//! New dialects can be added to `khive-mcp` by:
11//! 1. Adding a Cargo dependency from `khive-mcp` to the new dialect crate.
12//! 2. Adding a `register(server, runtime)` call in `khive-mcp/src/server.rs`.
13//!
14//! A future ADR may introduce a generic registrar composition API; that work
15//! is not in this PR.
16
17pub use khive_pack_gtd::GtdPack;
18pub use khive_pack_kg::KgPack;
19pub use khive_pack_memory::MemoryPack;
20
21use khive_runtime::{KhiveRuntime, VerbRegistryBuilder};
22
23/// Pack names this dialect knows about, in canonical lowercase form.
24///
25/// Used by `khive-mcp` to build its `BUILTIN_PACKS` constant and error
26/// messages without importing either pack crate directly.
27pub const BUILTIN_PACK_NAMES: &[&str] = &["kg", "gtd", "memory"];
28
29/// Register a named pack from this dialect into `builder`.
30///
31/// Returns `Ok(())` when the name is recognised; returns `Err(name)` when the
32/// name is unknown so `khive-mcp` can keep its existing pack-not-found error
33/// without needing to know which packs this dialect provides.
34pub fn register_pack(
35    name: &str,
36    runtime: KhiveRuntime,
37    builder: &mut VerbRegistryBuilder,
38) -> Result<(), String> {
39    match name {
40        "kg" => {
41            builder.register(KgPack::new(runtime));
42            Ok(())
43        }
44        "gtd" => {
45            builder.register(GtdPack::new(runtime));
46            Ok(())
47        }
48        "memory" => {
49            builder.register(MemoryPack::new(runtime));
50            Ok(())
51        }
52        other => Err(other.to_string()),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    use khive_runtime::{KhiveRuntime, RuntimeConfig};
61
62    fn make_runtime() -> KhiveRuntime {
63        KhiveRuntime::new(RuntimeConfig {
64            db_path: None,
65            embedding_model: None,
66            ..RuntimeConfig::default()
67        })
68        .expect("in-memory runtime")
69    }
70
71    #[test]
72    fn builtin_pack_names_includes_kg_and_gtd() {
73        assert!(BUILTIN_PACK_NAMES.contains(&"kg"));
74        assert!(BUILTIN_PACK_NAMES.contains(&"gtd"));
75        assert!(BUILTIN_PACK_NAMES.contains(&"memory"));
76    }
77
78    #[test]
79    fn register_kg_pack_succeeds() {
80        let rt = make_runtime();
81        let mut builder = VerbRegistryBuilder::new();
82        register_pack("kg", rt, &mut builder).expect("kg registration must succeed");
83        let registry = builder.build().expect("registry builds");
84        let verb_names: Vec<&str> = registry.all_verbs().iter().map(|v| v.name).collect();
85        // KG pack registers 11 verbs; spot-check a few.
86        assert!(verb_names.contains(&"create"));
87        assert!(verb_names.contains(&"link"));
88        assert!(verb_names.contains(&"traverse"));
89    }
90
91    #[test]
92    fn register_gtd_pack_succeeds() {
93        let rt = make_runtime();
94        let mut builder = VerbRegistryBuilder::new();
95        register_pack("gtd", rt, &mut builder).expect("gtd registration must succeed");
96        let registry = builder.build().expect("registry builds");
97        let verb_names: Vec<&str> = registry.all_verbs().iter().map(|v| v.name).collect();
98        assert!(verb_names.contains(&"assign"));
99        assert!(verb_names.contains(&"next"));
100        assert!(verb_names.contains(&"complete"));
101    }
102
103    #[test]
104    fn register_unknown_pack_returns_err() {
105        let rt = make_runtime();
106        let mut builder = VerbRegistryBuilder::new();
107        let err = register_pack("nosuchpack", rt, &mut builder)
108            .expect_err("unknown pack must return Err");
109        assert_eq!(err, "nosuchpack");
110    }
111
112    #[test]
113    fn both_packs_register_together() {
114        let rt1 = make_runtime();
115        let rt2 = make_runtime();
116        let rt3 = make_runtime();
117        let mut builder = VerbRegistryBuilder::new();
118        register_pack("kg", rt1, &mut builder).unwrap();
119        register_pack("gtd", rt2, &mut builder).unwrap();
120        register_pack("memory", rt3, &mut builder).unwrap();
121        let registry = builder.build().expect("registry builds");
122        let verb_names: Vec<&str> = registry.all_verbs().iter().map(|v| v.name).collect();
123        // KG verbs present
124        assert!(verb_names.contains(&"link"));
125        // GTD verbs present
126        assert!(verb_names.contains(&"transition"));
127        // Memory verbs present
128        assert!(verb_names.contains(&"remember"));
129        assert!(verb_names.contains(&"recall"));
130    }
131}