1use 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 ClaudeEmitter;
10
11impl ClaudeEmitter {
12 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl RuleEmitter for ClaudeEmitter {
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, "CLAUDE.md"));
30 output.push_str("Instructions for Claude Code assistant.\n\n");
31
32 let mut instructions: 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 role, principles, ..
39 } => {
40 output.push_str(&format_heading(2, "Role"));
41 output.push_str(role);
42 output.push_str("\n\n");
43
44 if !principles.is_empty() {
45 instructions.extend(principles.clone());
46 }
47 }
48 UnifiedRule::Standard { description, .. } => {
49 instructions.push(description.clone());
50 }
51 UnifiedRule::Context {
52 focus, includes, ..
53 } => {
54 context.extend(focus.clone());
55 context.extend(includes.clone());
56 }
57 UnifiedRule::Workflow { name, steps } => {
58 output.push_str(&format_heading(2, &format!("Workflow: {}", name)));
59 for (i, step) in steps.iter().enumerate() {
60 output.push_str(&format!(
61 "{}. **{}**: {}\n",
62 i + 1,
63 step.name,
64 step.description
65 ));
66 }
67 output.push('\n');
68 }
69 UnifiedRule::Raw { content } => {
70 output.push_str(content);
71 output.push_str("\n\n");
72 }
73 }
74 }
75
76 if !context.is_empty() {
77 output.push_str(&format_heading(2, "About This Project"));
78 output.push_str(&format_bullet_list(&context));
79 output.push('\n');
80 }
81
82 if !instructions.is_empty() {
83 output.push_str(&format_heading(2, "Instructions"));
84 output.push_str(&format_bullet_list(&instructions));
85 output.push('\n');
86 }
87
88 Ok(output.trim().to_string())
89 }
90
91 fn editor(&self) -> Editor {
92 Editor::Claude
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_emit_claude() {
102 let rules = vec![UnifiedRule::Standard {
103 category: crate::format::RuleCategory::Style,
104 priority: 0,
105 description: "Always explain your reasoning".to_string(),
106 pattern: None,
107 }];
108
109 let emitter = ClaudeEmitter::new();
110 let output = emitter.emit_string(&rules).unwrap();
111
112 assert!(output.contains("CLAUDE.md"));
113 assert!(output.contains("explain your reasoning"));
114 }
115}