driven/emitter/
windsurf.rs1use super::{RuleEmitter, ensure_parent_dir, format_bullet_list, format_heading};
4use crate::{Editor, Result, parser::UnifiedRule};
5use std::path::Path;
6
7#[derive(Debug, Default)]
9pub struct WindsurfEmitter;
10
11impl WindsurfEmitter {
12 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl RuleEmitter for WindsurfEmitter {
19 fn emit_file(&self, rules: &[UnifiedRule], path: &Path) -> Result<()> {
20 ensure_parent_dir(path)?;
21 let content = self.emit_string(rules)?;
22 std::fs::write(path, content)?;
23 Ok(())
24 }
25
26 fn emit_string(&self, rules: &[UnifiedRule]) -> Result<String> {
27 let mut output = String::new();
28
29 output.push_str(&format_heading(1, "Windsurf Rules"));
31
32 let mut standards: Vec<String> = Vec::new();
33 let mut context: Vec<String> = Vec::new();
34
35 for rule in rules {
36 match rule {
37 UnifiedRule::Persona {
38 name, role, traits, ..
39 } => {
40 output.push_str(&format_heading(2, &format!("Role: {}", name)));
41 output.push_str(role);
42 output.push_str("\n\n");
43
44 if !traits.is_empty() {
45 output.push_str(&format_bullet_list(traits));
46 output.push('\n');
47 }
48 }
49 UnifiedRule::Standard { description, .. } => {
50 standards.push(description.clone());
51 }
52 UnifiedRule::Context {
53 focus, includes, ..
54 } => {
55 context.extend(focus.clone());
56 context.extend(includes.clone());
57 }
58 UnifiedRule::Workflow { name, steps } => {
59 output.push_str(&format_heading(2, name));
60 for step in steps {
61 output.push_str(&format!("- {}: {}\n", step.name, step.description));
62 }
63 output.push('\n');
64 }
65 UnifiedRule::Raw { content } => {
66 output.push_str(content);
67 output.push_str("\n\n");
68 }
69 }
70 }
71
72 if !context.is_empty() {
73 output.push_str(&format_heading(2, "Project Context"));
74 output.push_str(&format_bullet_list(&context));
75 output.push('\n');
76 }
77
78 if !standards.is_empty() {
79 output.push_str(&format_heading(2, "Coding Guidelines"));
80 output.push_str(&format_bullet_list(&standards));
81 output.push('\n');
82 }
83
84 Ok(output.trim().to_string())
85 }
86
87 fn editor(&self) -> Editor {
88 Editor::Windsurf
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_emit_windsurf() {
98 let rules = vec![UnifiedRule::Standard {
99 category: crate::format::RuleCategory::Style,
100 priority: 0,
101 description: "Use TypeScript strict mode".to_string(),
102 pattern: None,
103 }];
104
105 let emitter = WindsurfEmitter::new();
106 let output = emitter.emit_string(&rules).unwrap();
107
108 assert!(output.contains("Windsurf Rules"));
109 assert!(output.contains("TypeScript strict mode"));
110 }
111}