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