Skip to main content

driven/parser/
windsurf.rs

1//! Windsurf rules parser (.windsurfrules)
2
3use super::{RuleParser, UnifiedRule, extract_bullet_points, parse_markdown_sections};
4use crate::{DrivenError, Editor, Result};
5use std::path::Path;
6
7/// Parser for Windsurf .windsurfrules files
8#[derive(Debug, Default)]
9pub struct WindsurfParser;
10
11impl WindsurfParser {
12    /// Create a new Windsurf parser
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl RuleParser for WindsurfParser {
19    fn parse_file(&self, path: &Path) -> Result<Vec<UnifiedRule>> {
20        let content = std::fs::read_to_string(path)
21            .map_err(|e| DrivenError::Parse(format!("Failed to read {}: {}", path.display(), e)))?;
22        self.parse_content(&content)
23    }
24
25    fn parse_content(&self, content: &str) -> Result<Vec<UnifiedRule>> {
26        let mut rules = Vec::new();
27        let sections = parse_markdown_sections(content);
28
29        if sections.is_empty() {
30            // No markdown structure, treat as raw content
31            if !content.trim().is_empty() {
32                rules.push(UnifiedRule::raw(content.trim()));
33            }
34            return Ok(rules);
35        }
36
37        // Windsurf format is similar to Cursor, reuse same logic
38        for (heading, body) in sections {
39            let lower = heading.to_lowercase();
40
41            if lower.contains("rule") || lower.contains("guideline") || lower.contains("standard") {
42                let points = extract_bullet_points(&body);
43                for (i, point) in points.into_iter().enumerate() {
44                    rules.push(UnifiedRule::Standard {
45                        category: crate::format::RuleCategory::Style,
46                        priority: i as u8,
47                        description: point,
48                        pattern: None,
49                    });
50                }
51            } else if lower.contains("context") || lower.contains("project") {
52                let points = extract_bullet_points(&body);
53                rules.push(UnifiedRule::Context {
54                    includes: Vec::new(),
55                    excludes: Vec::new(),
56                    focus: points,
57                });
58            } else {
59                // Store as raw
60                if !body.trim().is_empty() {
61                    rules.push(UnifiedRule::raw(format!("# {}\n{}", heading, body)));
62                }
63            }
64        }
65
66        Ok(rules)
67    }
68
69    fn editor(&self) -> Editor {
70        Editor::Windsurf
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_parse_windsurfrules() {
80        let content = r#"# Coding Rules
81
82- Always use TypeScript strict mode
83- Prefer functional components
84- Use proper error boundaries
85
86# Project Context
87
88- This is a Next.js application
89- Using Tailwind CSS for styling
90"#;
91
92        let parser = WindsurfParser::new();
93        let rules = parser.parse_content(content).unwrap();
94
95        assert!(!rules.is_empty());
96    }
97}