agent_policy/render/
windsurf_rules.rs1use camino::Utf8PathBuf;
2use minijinja::Environment;
3
4use crate::{
5 error::{Error, Result},
6 model::normalized::Policy,
7 render::RenderedOutput,
8};
9
10const DEFAULT_TARGET: &str = ".windsurf/rules/default.md";
11const DEFAULT_TEMPLATE: &str = include_str!("../../templates/windsurf_default.md.j2");
12const ROLE_TEMPLATE: &str = include_str!("../../templates/windsurf_role.md.j2");
13
14pub fn render(policy: &Policy) -> Result<Vec<RenderedOutput>> {
20 let mut outputs = Vec::new();
21
22 let mut env = Environment::new();
23 env.add_template("default.md", DEFAULT_TEMPLATE)
24 .map_err(|e| Error::Render {
25 target: DEFAULT_TARGET.to_owned(),
26 source: e,
27 })?;
28 let tmpl = env.get_template("default.md").map_err(|e| Error::Render {
29 target: DEFAULT_TARGET.to_owned(),
30 source: e,
31 })?;
32 let commands_defined = !policy.commands.is_empty();
33 let content = tmpl
34 .render(minijinja::context! {
35 project => &policy.project,
36 commands => &policy.commands,
37 commands_defined => commands_defined,
38 paths => &policy.paths,
39 roles => &policy.roles,
40 constraints => &policy.constraints,
41 })
42 .map_err(|e| Error::Render {
43 target: DEFAULT_TARGET.to_owned(),
44 source: e,
45 })?;
46 outputs.push(RenderedOutput {
47 path: Utf8PathBuf::from(DEFAULT_TARGET),
48 content,
49 });
50
51 if !policy.roles.is_empty() {
52 let mut role_env = Environment::new();
53 role_env
54 .add_template("role.md", ROLE_TEMPLATE)
55 .map_err(|e| Error::Render {
56 target: "windsurf role".to_owned(),
57 source: e,
58 })?;
59 let role_tmpl = role_env
60 .get_template("role.md")
61 .map_err(|e| Error::Render {
62 target: "windsurf role".to_owned(),
63 source: e,
64 })?;
65
66 for (name, role) in &policy.roles {
67 if role.editable.is_empty() {
68 continue;
69 }
70 let target = format!(".windsurf/rules/{name}.md");
71 let role_content = role_tmpl
72 .render(minijinja::context! {
73 project => &policy.project,
74 role_name => name,
75 role => role,
76 })
77 .map_err(|e| Error::Render {
78 target: target.clone(),
79 source: e,
80 })?;
81 outputs.push(RenderedOutput {
82 path: Utf8PathBuf::from(&target),
83 content: role_content,
84 });
85 }
86 }
87
88 Ok(outputs)
89}