Skip to main content

luft_core/contract/
skill.rs

1//! Skill contract (§1.5) — reusable agent-facing guidance, shared at the
2//! library level.
3//!
4//! A [`Skill`] bundles markdown content (and optional reference files) that a
5//! *consuming* crate embeds via `include_str!` and hands to whatever agent it
6//! drives. `luft-core` only defines the shape; it owns no content itself —
7//! see `luft_skills::WORKFLOW_SKILL` for the first instance. Consumers with
8//! their own skill format (e.g. a `BuiltinSkill` with triggers and tool
9//! requirements) build their richer type from these fields rather than
10//! parsing/duplicating the markdown.
11
12/// A named piece of agent-facing guidance, plus any files it references.
13///
14/// All fields are `&'static str` because every known instance is compiled in
15/// via `include_str!` — there is no owned-`String` constructor because a
16/// dynamically-built `Skill` has no crate that would outlive the borrow.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Skill {
19    /// Short identifier, e.g. `"workflow"`.
20    pub name: &'static str,
21    /// One-line summary of when this skill applies.
22    pub description: &'static str,
23    /// The skill body (markdown). No required frontmatter — consumers that
24    /// need YAML frontmatter (name/triggers/tags) wrap this content rather
25    /// than expecting it embedded here.
26    pub content: &'static str,
27    /// Bundled reference files as `(relative_path, content)` pairs, e.g.
28    /// `("references/examples.md", "...")`. Empty for skills with no
29    /// supporting files.
30    pub references: &'static [(&'static str, &'static str)],
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    const SAMPLE: Skill = Skill {
38        name: "sample",
39        description: "a sample skill",
40        content: "# Sample\n\nBody.",
41        references: &[("references/extra.md", "extra content")],
42    };
43
44    #[test]
45    fn fields_are_reachable() {
46        assert_eq!(SAMPLE.name, "sample");
47        assert_eq!(SAMPLE.description, "a sample skill");
48        assert!(SAMPLE.content.contains("Body"));
49        assert_eq!(SAMPLE.references.len(), 1);
50        assert_eq!(SAMPLE.references[0].0, "references/extra.md");
51    }
52
53    #[test]
54    fn empty_references_is_valid() {
55        const NO_REFS: Skill = Skill {
56            name: "bare",
57            description: "no references",
58            content: "content",
59            references: &[],
60        };
61        assert!(NO_REFS.references.is_empty());
62    }
63
64    #[test]
65    fn skill_is_copy_and_comparable() {
66        let copied = SAMPLE;
67        assert_eq!(SAMPLE, copied);
68    }
69}