Skip to main content

spec_driven_docs/
embedded.rs

1//! Compile-time embedded assets: the payload and the method, in the binary.
2//!
3//! Every `include_dir!`/`include_str!` in the crate lives here, and each
4//! embeds from its canonical authored path, so the repository file and the
5//! shipped copy cannot diverge — the build reads the real thing. This module
6//! only holds bytes and typed accessors; deciding where an asset lands in an
7//! instance is the profiles' and installer's business.
8
9use std::collections::BTreeSet;
10
11use include_dir::{Dir, include_dir};
12
13pub use crate::payload_roots::PAYLOAD_ROOTS;
14
15/// The spec seeds an instance adopts, and the canon-only specs beside them.
16pub static SPECS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/_docs/specs");
17/// The stable document templates.
18pub static TEMPLATES: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/templates");
19/// The markdownlint configurations the instance receives managed.
20pub static MARKDOWNLINT: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/.markdownlint");
21/// What a release says about itself, plus what it seeds and splices.
22///
23/// One root rather than a root per subdirectory: the projection
24/// declaration sits beside the seeds it describes, and a root per
25/// subdirectory would make the declaration a one-file exception to the
26/// payload inventory.
27pub static INSTANCE: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/instance");
28/// The method chapters and glossary.
29pub static METHOD: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/method");
30/// The cross-agent skills, one `SKILL.md` per directory.
31pub static SKILLS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skills");
32/// The artifacts every skill shares, installed once outside the skill roots.
33pub static SKILL_SHARED: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skill-shared");
34/// The comparison documents, which a stage carries as reference material.
35pub static COMPARISON_DOCS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/comparison-docs");
36/// The prior-art shelf, which a stage carries as reference material.
37pub static PRIOR_ART: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/reference/prior-art");
38/// The tracker-markup shelf, which the format chapter routes a reader to.
39pub static TRACKER_MARKUP: Dir<'static> =
40    include_dir!("$CARGO_MANIFEST_DIR/reference/tracker-markup");
41
42/// The combined license statement naming both halves.
43pub static LICENSE: &str = include_str!("../LICENSE");
44/// The MIT license covering the distribution.
45pub static LICENSE_MIT: &str = include_str!("../LICENSE-MIT");
46/// The CC BY 4.0 license covering the method.
47pub static LICENSE_CC_BY: &str = include_str!("../LICENSE-CC-BY-4.0");
48/// The attribution notice for every third-party source the payload derives from.
49pub static THIRD_PARTY_NOTICES: &str = include_str!("../THIRD_PARTY_NOTICES.md");
50/// The release notes, which a stage carries as this version's own history.
51pub static CHANGELOG: &str = include_str!("../CHANGELOG.md");
52
53/// Every embedded root paired with the authored path it came from, in
54/// [`PAYLOAD_ROOTS`] order. A unit test holds the two equal, so a root
55/// embedded here but missing from the declaration — or the reverse — fails
56/// the build rather than shipping unscanned.
57const EMBEDDED_ROOTS: &[(&str, &Dir<'static>)] = &[
58    ("_docs/specs", &SPECS),
59    ("templates", &TEMPLATES),
60    (".markdownlint", &MARKDOWNLINT),
61    ("instance", &INSTANCE),
62    ("method", &METHOD),
63    ("skills", &SKILLS),
64    ("skill-shared", &SKILL_SHARED),
65    ("comparison-docs", &COMPARISON_DOCS),
66    ("reference/prior-art", &PRIOR_ART),
67    ("reference/tracker-markup", &TRACKER_MARKUP),
68];
69
70/// Every embedded root paired with the authored path it came from.
71///
72/// The candidate reads its sources through this, so a root added to the
73/// declaration reaches the projection without a second list.
74#[must_use]
75pub const fn roots() -> &'static [(&'static str, &'static Dir<'static>)] {
76    EMBEDDED_ROOTS
77}
78
79/// Every skill name, sorted; a name is the skill's directory.
80#[must_use]
81pub fn skill_names() -> Vec<&'static str> {
82    let mut names: Vec<&'static str> = SKILLS
83        .dirs()
84        .filter_map(|dir| dir.path().as_os_str().to_str())
85        .collect();
86    names.sort_unstable();
87    names
88}
89
90/// One skill's `SKILL.md` text, by skill name.
91#[must_use]
92pub fn skill(name: &str) -> Option<&'static str> {
93    SKILLS
94        .get_file(format!("{name}/SKILL.md"))
95        .and_then(include_dir::File::contents_utf8)
96}
97
98/// Every file of one installed skill package, as `(path relative to the
99/// package root, bytes)`, sorted by path.
100///
101/// A package is the unit the Agent Skills format and every documented host
102/// resolve against: one directory holding `SKILL.md` and supporting files
103/// beside it. The shared artifacts are authored once under `skill-shared/`
104/// and materialized here into every package, so a fix lands in one file and
105/// reaches every root the installer writes.
106#[must_use]
107pub fn skill_package(name: &str) -> Option<Vec<(String, &'static [u8])>> {
108    use crate::domain::paths::{SKILL_FILE, SKILL_REFERENCES_DIR};
109
110    let manual = SKILLS.get_file(format!("{name}/{SKILL_FILE}"))?;
111    let mut files = vec![(SKILL_FILE.to_string(), manual.contents())];
112    for (path, bytes) in shared_artifacts() {
113        files.push((format!("{SKILL_REFERENCES_DIR}/{path}"), bytes));
114    }
115    files.sort_by(|left, right| left.0.cmp(&right.0));
116    Some(files)
117}
118
119/// Every artifact the skills share, as `(path under the root, bytes)`,
120/// sorted by path.
121///
122/// This is the authored view. What lands is [`skill_package`], which copies
123/// each of these into every package as a reference relative to the skill's
124/// own root.
125#[must_use]
126pub fn shared_artifacts() -> Vec<(String, &'static [u8])> {
127    fn walk(dir: &Dir<'static>, out: &mut Vec<(String, &'static [u8])>) {
128        for file in dir.files() {
129            if let Some(path) = file.path().to_str() {
130                out.push((path.to_string(), file.contents()));
131            }
132        }
133        for sub in dir.dirs() {
134            walk(sub, out);
135        }
136    }
137    let mut out = Vec::new();
138    walk(&SKILL_SHARED, &mut out);
139    out.sort_by(|a, b| a.0.cmp(&b.0));
140    out
141}
142
143/// Resolve a payload source path — as a profile projection names it — to its
144/// embedded bytes.
145#[must_use]
146pub fn asset(source: &str) -> Option<&'static [u8]> {
147    EMBEDDED_ROOTS.iter().find_map(|(root, dir)| {
148        let rest = source.strip_prefix(root)?.strip_prefix('/')?;
149        dir.get_file(rest).map(include_dir::File::contents)
150    })
151}
152
153/// Every file under one embedded root, by the logical path that names it.
154///
155/// The paths come back sorted, so a caller that copies them writes the same
156/// tree every time.
157#[must_use]
158pub fn assets_under(root: &str) -> Vec<(String, &'static [u8])> {
159    let Some((name, dir)) = EMBEDDED_ROOTS.iter().find(|(name, _)| *name == root) else {
160        return Vec::new();
161    };
162    let mut out = Vec::new();
163    collect_under(name, dir, &mut out);
164    out.sort_by(|a, b| a.0.cmp(&b.0));
165    out
166}
167
168fn collect_under(root: &str, dir: &'static Dir<'static>, out: &mut Vec<(String, &'static [u8])>) {
169    for file in dir.files() {
170        if let Some(rest) = file.path().to_str() {
171            out.push((format!("{root}/{rest}"), file.contents()));
172        }
173    }
174    for sub in dir.dirs() {
175        collect_under(root, sub, out);
176    }
177}
178
179/// Every `` ### `domain:rule` `` requirement address the embedded specs define.
180#[must_use]
181pub fn spec_rule_ids() -> BTreeSet<String> {
182    let mut ids = BTreeSet::new();
183    for file in SPECS.files() {
184        let Some(text) = file.contents_utf8() else {
185            continue;
186        };
187        ids.extend(rule_ids_in(text));
188    }
189    ids
190}
191
192/// The requirement addresses one spec document defines.
193pub fn rule_ids_in(text: &str) -> impl Iterator<Item = String> + '_ {
194    text.lines().filter_map(|line| {
195        let candidate = line.strip_prefix("### `")?;
196        let (id, _) = candidate.split_once('`')?;
197        let (domain, rule) = id.split_once(':')?;
198        let is_slug = |part: &str| {
199            !part.is_empty()
200                && part
201                    .bytes()
202                    .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
203        };
204        (is_slug(domain) && is_slug(rule)).then(|| id.to_string())
205    })
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use crate::domain::profile::ProfileId;
212    use crate::domain::rule_id::RuleId;
213
214    #[test]
215    fn rule_id_enum_matches_the_embedded_specs() {
216        let from_specs = spec_rule_ids();
217        let from_enum: BTreeSet<String> = RuleId::ALL
218            .iter()
219            .map(|rule| rule.as_str().to_string())
220            .collect();
221        assert_eq!(
222            from_specs, from_enum,
223            "RuleId and the specs disagree; update the enum and the specs together"
224        );
225    }
226
227    #[test]
228    fn the_embedded_roots_are_the_declared_payload_roots() {
229        let embedded: Vec<&str> = EMBEDDED_ROOTS.iter().map(|(root, _)| *root).collect();
230        assert_eq!(
231            embedded,
232            PAYLOAD_ROOTS.to_vec(),
233            "payload_roots.rs and the embedded statics disagree; a root missing from the declaration ships unscanned"
234        );
235    }
236
237    #[test]
238    fn every_profile_projection_resolves_to_an_embedded_asset() {
239        for id in [ProfileId::Codebase, ProfileId::KnowledgeBase] {
240            let profile = id.profile();
241            for entry in profile.managed.iter().chain(profile.adopted) {
242                assert!(
243                    asset(&entry.source).is_some(),
244                    "{id}: {} is not embedded",
245                    entry.source
246                );
247            }
248        }
249    }
250
251    #[test]
252    fn the_method_and_licenses_are_carried() {
253        assert!(METHOD.get_file("glossary.md").is_some());
254        assert!(METHOD.files().count() >= 15);
255        assert!(LICENSE.contains("LICENSE-MIT") && LICENSE.contains("LICENSE-CC-BY-4.0"));
256    }
257
258    #[test]
259    fn rule_id_parser_matches_heading_shape_only() {
260        let text = "### `a-b:c-d` — Title\n### `Bad:Id`\nplain ### `x:y`\n### `no-colon`\n";
261        let ids: Vec<String> = rule_ids_in(text).collect();
262        assert_eq!(ids, vec!["a-b:c-d".to_string()]);
263    }
264}