Skip to main content

khive_pack_template/
lib.rs

1//! khive-pack-template — reference scaffold for new packs (ADR-023 §8).
2//!
3//! # How to create a new pack
4//!
5//! 1. Copy this crate directory to `crates/khive-pack-<name>/`.
6//! 2. Rename the crate in `Cargo.toml` (name, description).
7//! 3. Set `PACK_NAME` to your pack's canonical name (e.g. `"exp"`).
8//! 4. Update `NOTE_KINDS` / `ENTITY_KINDS` in `vocab.rs`.
9//! 5. Add your verbs to `HANDLERS` below; fill in `handlers.rs`.
10//! 6. Add the crate to the workspace `Cargo.toml`.
11//! 7. Force-link in `khive-mcp/src/pack.rs` and `kkernel/src/lib.rs`.
12//! 8. Add the crate dep to `khive-mcp/Cargo.toml` and `kkernel/Cargo.toml`.
13//!
14//! Reference implementation: `crates/khive-pack-kg/`.
15//!
16//! No macros, no DSLs. Plain Rust — rust-analyzer, debugger, and LLMs all
17//! work directly on this code without expansion.
18
19pub mod handlers;
20pub mod vocab;
21
22use async_trait::async_trait;
23use serde_json::Value;
24
25use khive_runtime::pack::PackRuntime;
26use khive_runtime::{KhiveRuntime, NamespaceToken, RuntimeError, VerbRegistry};
27use khive_types::{HandlerDef, Pack, Visibility};
28
29/// Canonical pack name. Must match the factory below and `PackFactory::name()`.
30const PACK_NAME: &str = "template";
31
32/// Template pack — replace with your pack's struct name and logic.
33pub struct TemplatePack {
34    runtime: KhiveRuntime,
35}
36
37impl Pack for TemplatePack {
38    const NAME: &'static str = PACK_NAME;
39    /// Declare note kinds this pack contributes. Must not overlap with other packs.
40    const NOTE_KINDS: &'static [&'static str] = vocab::NOTE_KINDS;
41    /// Declare entity kinds this pack contributes. Must not overlap with other packs.
42    const ENTITY_KINDS: &'static [&'static str] = vocab::ENTITY_KINDS;
43    /// Handler table. Each entry is one verb or subhandler the pack can dispatch.
44    const HANDLERS: &'static [HandlerDef] = &TEMPLATE_HANDLERS;
45    /// Pack dependencies. The named packs must be in the configured `KHIVE_PACKS` list.
46    const REQUIRES: &'static [&'static str] = &["kg"];
47}
48
49/// Handler table. Add one `HandlerDef` per verb.
50///
51/// `Visibility::Verb`       = exposed on the MCP `request` tool (agent-facing).
52/// `Visibility::Subhandler` = CLI-only / internal; not on the MCP wire.
53static TEMPLATE_HANDLERS: [HandlerDef; 1] = [HandlerDef {
54    name: "my_verb",
55    description: "Replace with your verb's description.",
56    visibility: Visibility::Verb,
57    category: khive_types::VerbCategory::Directive,
58    params: &[],
59}];
60
61impl TemplatePack {
62    pub fn new(runtime: KhiveRuntime) -> Self {
63        Self { runtime }
64    }
65    #[allow(dead_code)]
66    fn runtime(&self) -> &KhiveRuntime {
67        &self.runtime
68    }
69}
70
71// ── ADR-027: inventory self-registration ─────────────────────────────────────
72//
73// This block registers the pack factory so the linker includes it in the
74// binary's inventory at startup. One `inventory::submit!` per pack crate.
75
76struct TemplatePackFactory;
77
78impl khive_runtime::PackFactory for TemplatePackFactory {
79    fn name(&self) -> &'static str {
80        PACK_NAME
81    }
82    fn requires(&self) -> &'static [&'static str] {
83        &["kg"]
84    }
85    fn create(&self, runtime: KhiveRuntime) -> Box<dyn khive_runtime::PackRuntime> {
86        Box::new(TemplatePack::new(runtime))
87    }
88}
89
90inventory::submit! { khive_runtime::PackRegistration(&TemplatePackFactory) }
91
92// ── PackRuntime impl ─────────────────────────────────────────────────────────
93
94#[async_trait]
95impl PackRuntime for TemplatePack {
96    fn name(&self) -> &str {
97        <TemplatePack as Pack>::NAME
98    }
99    fn note_kinds(&self) -> &'static [&'static str] {
100        <TemplatePack as Pack>::NOTE_KINDS
101    }
102    fn entity_kinds(&self) -> &'static [&'static str] {
103        <TemplatePack as Pack>::ENTITY_KINDS
104    }
105    fn handlers(&self) -> &'static [HandlerDef] {
106        &TEMPLATE_HANDLERS
107    }
108    fn requires(&self) -> &'static [&'static str] {
109        <TemplatePack as Pack>::REQUIRES
110    }
111
112    /// Dispatch a verb call. Add a match arm for each entry in `HANDLERS`.
113    async fn dispatch(
114        &self,
115        verb: &str,
116        params: Value,
117        _registry: &VerbRegistry,
118        token: &NamespaceToken,
119    ) -> Result<Value, RuntimeError> {
120        match verb {
121            "my_verb" => handlers::handle_my_verb(self.runtime(), token, params).await,
122            _ => Err(RuntimeError::InvalidInput(format!(
123                "{PACK_NAME} pack does not handle verb {verb:?}"
124            ))),
125        }
126    }
127}