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/// Integration snippets a consumer copies into their own files.
22pub static SNIPPETS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/instance/snippets");
23/// The method chapters and glossary.
24pub static METHOD: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/method");
25/// The cross-agent skills, one `SKILL.md` per directory.
26pub static SKILLS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skills");
27/// The artifacts every skill shares, installed once outside the skill roots.
28pub static SKILL_SHARED: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/skill-shared");
29/// The vendored `SimpleEnglish` dependency surface, byte-for-byte from upstream.
30pub static SIMPLE_ENGLISH: Dir<'static> =
31    include_dir!("$CARGO_MANIFEST_DIR/third-party/simpleenglish");
32
33/// The combined license statement naming both halves.
34pub static LICENSE: &str = include_str!("../LICENSE");
35/// The MIT license covering the distribution.
36pub static LICENSE_MIT: &str = include_str!("../LICENSE-MIT");
37/// The CC BY 4.0 license covering the method.
38pub static LICENSE_CC_BY: &str = include_str!("../LICENSE-CC-BY-4.0");
39/// The attribution notice for every vendored dependency the payload carries.
40pub static THIRD_PARTY_NOTICES: &str = include_str!("../THIRD_PARTY_NOTICES.md");
41
42/// Every embedded root paired with the authored path it came from, in
43/// [`PAYLOAD_ROOTS`] order. A unit test holds the two equal, so a root
44/// embedded here but missing from the declaration — or the reverse — fails
45/// the build rather than shipping unscanned.
46const EMBEDDED_ROOTS: &[(&str, &Dir<'static>)] = &[
47    ("_docs/specs", &SPECS),
48    ("templates", &TEMPLATES),
49    (".markdownlint", &MARKDOWNLINT),
50    ("instance/snippets", &SNIPPETS),
51    ("method", &METHOD),
52    ("skills", &SKILLS),
53    ("skill-shared", &SKILL_SHARED),
54    ("third-party/simpleenglish", &SIMPLE_ENGLISH),
55];
56
57/// Every skill name, sorted; a name is the skill's directory.
58#[must_use]
59pub fn skill_names() -> Vec<&'static str> {
60    let mut names: Vec<&'static str> = SKILLS
61        .dirs()
62        .filter_map(|dir| dir.path().as_os_str().to_str())
63        .collect();
64    names.sort_unstable();
65    names
66}
67
68/// One skill's `SKILL.md` text, by skill name.
69#[must_use]
70pub fn skill(name: &str) -> Option<&'static str> {
71    SKILLS
72        .get_file(format!("{name}/SKILL.md"))
73        .and_then(include_dir::File::contents_utf8)
74}
75
76/// Every artifact the skills share, as `(path under the root, bytes)`,
77/// sorted by path.
78///
79/// These land once, outside the agent skill roots, because every skill names
80/// the same absolute path for them. A copy per skill would be one file to
81/// correct per agent root per skill; one copy is one.
82#[must_use]
83pub fn shared_artifacts() -> Vec<(String, &'static [u8])> {
84    fn walk(dir: &Dir<'static>, out: &mut Vec<(String, &'static [u8])>) {
85        for file in dir.files() {
86            if let Some(path) = file.path().to_str() {
87                out.push((path.to_string(), file.contents()));
88            }
89        }
90        for sub in dir.dirs() {
91            walk(sub, out);
92        }
93    }
94    let mut out = Vec::new();
95    walk(&SKILL_SHARED, &mut out);
96    out.sort_by(|a, b| a.0.cmp(&b.0));
97    out
98}
99
100/// Resolve a payload source path — as a profile projection names it — to its
101/// embedded bytes.
102#[must_use]
103pub fn asset(source: &str) -> Option<&'static [u8]> {
104    EMBEDDED_ROOTS.iter().find_map(|(root, dir)| {
105        let rest = source.strip_prefix(root)?.strip_prefix('/')?;
106        dir.get_file(rest).map(include_dir::File::contents)
107    })
108}
109
110/// Every `` ### `domain:rule` `` requirement address the embedded specs define.
111#[must_use]
112pub fn spec_rule_ids() -> BTreeSet<String> {
113    let mut ids = BTreeSet::new();
114    for file in SPECS.files() {
115        let Some(text) = file.contents_utf8() else {
116            continue;
117        };
118        ids.extend(rule_ids_in(text));
119    }
120    ids
121}
122
123/// The requirement addresses one spec document defines.
124pub fn rule_ids_in(text: &str) -> impl Iterator<Item = String> + '_ {
125    text.lines().filter_map(|line| {
126        let candidate = line.strip_prefix("### `")?;
127        let (id, _) = candidate.split_once('`')?;
128        let (domain, rule) = id.split_once(':')?;
129        let is_slug = |part: &str| {
130            !part.is_empty()
131                && part
132                    .bytes()
133                    .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
134        };
135        (is_slug(domain) && is_slug(rule)).then(|| id.to_string())
136    })
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::domain::profile::ProfileId;
143    use crate::domain::rule_id::RuleId;
144
145    #[test]
146    fn rule_id_enum_matches_the_embedded_specs() {
147        let from_specs = spec_rule_ids();
148        let from_enum: BTreeSet<String> = RuleId::ALL
149            .iter()
150            .map(|rule| rule.as_str().to_string())
151            .collect();
152        assert_eq!(
153            from_specs, from_enum,
154            "RuleId and the specs disagree; update the enum and the specs together"
155        );
156    }
157
158    #[test]
159    fn the_embedded_roots_are_the_declared_payload_roots() {
160        let embedded: Vec<&str> = EMBEDDED_ROOTS.iter().map(|(root, _)| *root).collect();
161        assert_eq!(
162            embedded,
163            PAYLOAD_ROOTS.to_vec(),
164            "payload_roots.rs and the embedded statics disagree; a root missing from the declaration ships unscanned"
165        );
166    }
167
168    #[test]
169    fn every_profile_projection_resolves_to_an_embedded_asset() {
170        for id in [ProfileId::Codebase, ProfileId::KnowledgeBase] {
171            let profile = id.profile();
172            for entry in profile.managed.iter().chain(profile.adopted) {
173                assert!(
174                    asset(entry.source).is_some(),
175                    "{id}: {} is not embedded",
176                    entry.source
177                );
178            }
179        }
180    }
181
182    #[test]
183    fn the_method_and_licenses_are_carried() {
184        assert!(METHOD.get_file("glossary.md").is_some());
185        assert!(METHOD.files().count() >= 15);
186        assert!(LICENSE.contains("LICENSE-MIT") && LICENSE.contains("LICENSE-CC-BY-4.0"));
187    }
188
189    #[test]
190    fn rule_id_parser_matches_heading_shape_only() {
191        let text = "### `a-b:c-d` — Title\n### `Bad:Id`\nplain ### `x:y`\n### `no-colon`\n";
192        let ids: Vec<String> = rule_ids_in(text).collect();
193        assert_eq!(ids, vec!["a-b:c-d".to_string()]);
194    }
195}