1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Templates compiled into the binary, so `rhei templates` is populated on a
// bare `cargo install` with no companion asset directory to install alongside
// it. §FS-rhei-templates.1
use include_dir::{include_dir, Dir};
/// The shipped template library, embedded at compile time.
///
/// Living under the CLI package (rather than the repo's `.agents/`) is what
/// makes these files part of the published crate: `cargo install` extracts the
/// package, and a directory outside it would simply not be there.
static BUILTIN_TEMPLATES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
/// Names of the built-in templates, in listing order.
pub(super) fn builtin_template_names() -> Vec<String> {
let mut names: Vec<String> = BUILTIN_TEMPLATES
.dirs()
.filter_map(|dir| dir.path().file_name()?.to_str().map(ToOwned::to_owned))
.collect();
names.sort();
names
}
/// Whether a built-in template of this name exists.
pub(super) fn builtin_template_exists(name: &str) -> bool {
BUILTIN_TEMPLATES.get_dir(name).is_some()
}
/// A built-in template extracted to disk. `path()` is the template directory —
/// named after the template, because manifest validation checks that the
/// directory name and the manifest `name` agree.
pub(super) struct ExtractedTemplate {
_root: tempfile::TempDir,
path: PathBuf,
}
impl ExtractedTemplate {
pub(super) fn path(&self) -> &Path {
&self.path
}
}
/// Extract a built-in template into a temporary directory and return it.
///
/// The instantiation pipeline reads templates from the filesystem — manifest,
/// plan skeleton, states, settings, task files. Rather than teach every step to
/// read from two kinds of source, a built-in is materialized once and then
/// behaves exactly like a project or user template. The returned handle owns
/// the extraction: it must outlive the use of the path.
pub(super) fn materialize_builtin_template(name: &str) -> MietteResult<ExtractedTemplate> {
let dir = BUILTIN_TEMPLATES
.get_dir(name)
.ok_or_else(|| {
miette!(
help = did_you_mean(name, &builtin_template_names())
.unwrap_or_else(|| "this binary carries no templates.".to_string()),
"no built-in template named '{name}'"
)
})?;
let temp = tempfile::Builder::new()
.prefix("rhei-builtin-template-")
.tempdir()
.map_err(|err| miette!(
help = embedded_extraction_help(),
"failed to create a temporary directory: {err}"))?;
let root = temp.path().join(name);
fs::create_dir_all(&root)
.map_err(|err| miette!(
help = embedded_extraction_help(),
"failed to create '{}': {err}", root.display()))?;
// Entries carry paths relative to the embedded root (`<name>/tasks/01.md`),
// so stripping the template's own segment lands them at the temp root.
let mut files = Vec::new();
collect_embedded_files(dir, &mut files);
for file in files {
let relative = file
.path()
.strip_prefix(name)
.map_err(|_| miette!(
help = internal_error_help(),
"built-in template '{name}' has an unexpected entry"))?;
let out = root.join(relative);
if let Some(parent) = out.parent() {
fs::create_dir_all(parent)
.map_err(|err| miette!(
help = embedded_extraction_help(),
"failed to create '{}': {err}", parent.display()))?;
}
fs::write(&out, file.contents())
.map_err(|err| miette!(
help = embedded_extraction_help(),
"failed to write '{}': {err}", out.display()))?;
}
Ok(ExtractedTemplate { _root: temp, path: root })
}
/// Every file under `dir`, at any depth. A workspace template nests task files
/// under `tasks/`, which may itself contain subdirectories.
fn collect_embedded_files<'a>(dir: &'a Dir<'a>, out: &mut Vec<&'a include_dir::File<'a>>) {
out.extend(dir.files());
for nested in dir.dirs() {
collect_embedded_files(nested, out);
}
}