harness_skills/lib.rs
1//! `harness-skills` — agentskills.io-compliant skill loader, validator, and registry.
2//!
3//! ## What this crate enforces
4//!
5//! Strict subset of <https://agentskills.io/specification>:
6//!
7//! - `<name>/SKILL.md` directory layout.
8//! - Frontmatter fields: `name`, `description` (required) + `license`,
9//! `compatibility`, `metadata`, `allowed-tools` (optional). Unknown
10//! top-level fields are rejected.
11//! - `name`: 1–64 chars, lowercase `[a-z0-9]` + hyphen, no leading/trailing
12//! hyphen, no `--`, and **must equal the parent directory name**.
13//! - `description`: 1–1024 chars.
14//! - `compatibility`: ≤500 chars.
15//!
16//! Framework extensions live in `metadata.harness.*` per DESIGN.md §6.1.
17
18pub mod export;
19pub mod lint;
20pub mod loader;
21pub mod registry;
22pub mod validate;
23pub mod write;
24
25pub use export::*;
26pub use lint::*;
27pub use loader::*;
28pub use registry::*;
29pub use validate::*;
30pub use write::*;
31
32use harness_core::{Resource, Skill, SkillError, SkillManifest};
33use std::borrow::Cow;
34use std::sync::Arc;
35
36/// A skill loaded from disk: manifest, full body, and resource index.
37#[derive(Debug, Clone)]
38pub struct FileSkill {
39 manifest: SkillManifest,
40 body: String,
41 resources: Vec<Resource>,
42}
43
44impl FileSkill {
45 pub fn new(manifest: SkillManifest, body: String, resources: Vec<Resource>) -> Self {
46 Self {
47 manifest,
48 body,
49 resources,
50 }
51 }
52}
53
54impl Skill for FileSkill {
55 fn manifest(&self) -> &SkillManifest {
56 &self.manifest
57 }
58 fn body(&self) -> Cow<'_, str> {
59 Cow::Borrowed(&self.body)
60 }
61 fn resources(&self) -> &[Resource] {
62 &self.resources
63 }
64}
65
66/// Load a single skill from `<path>/SKILL.md`, returning an opaque trait object.
67pub fn load_skill_dir(path: &std::path::Path) -> Result<Arc<dyn Skill>, SkillError> {
68 let s = loader::load(path)?;
69 Ok(Arc::new(s))
70}