1use serde::Serialize;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum TargetId {
9 AgentsMd,
10 ClaudeMd,
11 CursorRules,
12 GeminiMd,
13 CopilotInstructions,
14 Clinerules,
15 WindsurfRules,
16 CopilotInstructionsScoped,
17 JunieGuidelines,
18}
19
20impl TargetId {
21 pub const ALL: &'static [TargetId] = &[
23 TargetId::AgentsMd,
24 TargetId::ClaudeMd,
25 TargetId::CursorRules,
26 TargetId::GeminiMd,
27 TargetId::CopilotInstructions,
28 TargetId::Clinerules,
29 TargetId::WindsurfRules,
30 TargetId::CopilotInstructionsScoped,
31 TargetId::JunieGuidelines,
32 ];
33
34 #[must_use]
36 pub fn id(self) -> &'static str {
37 match self {
38 TargetId::AgentsMd => "agents-md",
39 TargetId::ClaudeMd => "claude-md",
40 TargetId::CursorRules => "cursor-rules",
41 TargetId::GeminiMd => "gemini-md",
42 TargetId::CopilotInstructions => "copilot-instructions",
43 TargetId::Clinerules => "clinerules",
44 TargetId::WindsurfRules => "windsurf-rules",
45 TargetId::CopilotInstructionsScoped => "copilot-instructions-scoped",
46 TargetId::JunieGuidelines => "junie-guidelines",
47 }
48 }
49
50 #[must_use]
52 pub fn from_id(id: &str) -> Option<Self> {
53 match id {
54 "agents-md" => Some(TargetId::AgentsMd),
55 "claude-md" => Some(TargetId::ClaudeMd),
56 "cursor-rules" => Some(TargetId::CursorRules),
57 "gemini-md" => Some(TargetId::GeminiMd),
58 "copilot-instructions" => Some(TargetId::CopilotInstructions),
59 "clinerules" => Some(TargetId::Clinerules),
60 "windsurf-rules" => Some(TargetId::WindsurfRules),
61 "copilot-instructions-scoped" => Some(TargetId::CopilotInstructionsScoped),
62 "junie-guidelines" => Some(TargetId::JunieGuidelines),
63 _ => None,
64 }
65 }
66
67 #[must_use]
69 pub fn label(self) -> &'static str {
70 match self {
71 TargetId::AgentsMd => "AGENTS.md",
72 TargetId::ClaudeMd => "CLAUDE.md",
73 TargetId::CursorRules => ".cursor/rules/",
74 TargetId::GeminiMd => "GEMINI.md",
75 TargetId::CopilotInstructions => ".github/copilot-instructions.md",
76 TargetId::Clinerules => ".clinerules/",
77 TargetId::WindsurfRules => ".windsurf/rules/",
78 TargetId::CopilotInstructionsScoped => ".github/instructions/",
79 TargetId::JunieGuidelines => ".junie/guidelines.md",
80 }
81 }
82
83 #[must_use]
85 pub fn primary_path(self) -> &'static str {
86 match self {
87 TargetId::AgentsMd => "AGENTS.md",
88 TargetId::ClaudeMd => "CLAUDE.md",
89 TargetId::CursorRules => ".cursor/rules/default.mdc",
90 TargetId::GeminiMd => "GEMINI.md",
91 TargetId::CopilotInstructions => ".github/copilot-instructions.md",
92 TargetId::Clinerules => ".clinerules/default.md",
93 TargetId::WindsurfRules => ".windsurf/rules/default.md",
94 TargetId::CopilotInstructionsScoped => ".github/instructions/default.md",
95 TargetId::JunieGuidelines => ".junie/guidelines.md",
96 }
97 }
98
99 #[must_use]
105 pub fn generated_glob(self) -> &'static str {
106 match self {
107 TargetId::AgentsMd => "AGENTS.md",
108 TargetId::ClaudeMd => "CLAUDE.md",
109 TargetId::CursorRules => ".cursor/rules/**",
110 TargetId::GeminiMd => "GEMINI.md",
111 TargetId::CopilotInstructions => ".github/copilot-instructions.md",
112 TargetId::Clinerules => ".clinerules/**",
113 TargetId::WindsurfRules => ".windsurf/rules/**",
114 TargetId::CopilotInstructionsScoped => ".github/instructions/**",
115 TargetId::JunieGuidelines => ".junie/guidelines.md",
116 }
117 }
118
119 #[must_use]
121 pub fn tier(self) -> Tier {
122 match self {
123 TargetId::AgentsMd
124 | TargetId::ClaudeMd
125 | TargetId::CursorRules
126 | TargetId::GeminiMd
127 | TargetId::CopilotInstructions => Tier::Stable,
128 TargetId::Clinerules
129 | TargetId::WindsurfRules
130 | TargetId::CopilotInstructionsScoped
131 | TargetId::JunieGuidelines => Tier::Experimental,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
138#[serde(rename_all = "kebab-case")]
139pub enum Tier {
140 Stable,
141 Experimental,
142}
143
144impl Tier {
145 #[must_use]
147 pub fn as_str(self) -> &'static str {
148 match self {
149 Tier::Stable => "stable",
150 Tier::Experimental => "experimental",
151 }
152 }
153}
154
155#[allow(clippy::struct_excessive_bools)]
157#[derive(Debug, Clone, Serialize)]
158pub struct OutputTargets {
159 pub agents_md: bool,
161 pub claude_md: bool,
163 pub cursor_rules: bool,
165 pub gemini_md: bool,
167 pub copilot_instructions: bool,
169 pub clinerules: bool,
171 pub windsurf_rules: bool,
173 pub copilot_instructions_scoped: bool,
175 pub junie_guidelines: bool,
177}
178
179impl Default for OutputTargets {
180 fn default() -> Self {
181 Self {
182 agents_md: true,
183 claude_md: false,
184 cursor_rules: false,
185 gemini_md: false,
186 copilot_instructions: false,
187 clinerules: false,
188 windsurf_rules: false,
189 copilot_instructions_scoped: false,
190 junie_guidelines: false,
191 }
192 }
193}
194
195impl OutputTargets {
196 #[must_use]
198 pub fn is_empty(&self) -> bool {
199 !self.agents_md
200 && !self.claude_md
201 && !self.cursor_rules
202 && !self.gemini_md
203 && !self.copilot_instructions
204 && !self.clinerules
205 && !self.windsurf_rules
206 && !self.copilot_instructions_scoped
207 && !self.junie_guidelines
208 }
209
210 #[must_use]
212 pub fn enabled(&self) -> Vec<TargetId> {
213 let mut out = Vec::new();
214 if self.agents_md {
215 out.push(TargetId::AgentsMd);
216 }
217 if self.claude_md {
218 out.push(TargetId::ClaudeMd);
219 }
220 if self.cursor_rules {
221 out.push(TargetId::CursorRules);
222 }
223 if self.gemini_md {
224 out.push(TargetId::GeminiMd);
225 }
226 if self.copilot_instructions {
227 out.push(TargetId::CopilotInstructions);
228 }
229 if self.clinerules {
230 out.push(TargetId::Clinerules);
231 }
232 if self.windsurf_rules {
233 out.push(TargetId::WindsurfRules);
234 }
235 if self.copilot_instructions_scoped {
236 out.push(TargetId::CopilotInstructionsScoped);
237 }
238 if self.junie_guidelines {
239 out.push(TargetId::JunieGuidelines);
240 }
241 out
242 }
243
244 #[must_use]
246 pub fn from_targets(targets: &[TargetId]) -> Self {
247 let mut out = Self {
248 agents_md: false,
249 claude_md: false,
250 cursor_rules: false,
251 gemini_md: false,
252 copilot_instructions: false,
253 clinerules: false,
254 windsurf_rules: false,
255 copilot_instructions_scoped: false,
256 junie_guidelines: false,
257 };
258 for t in targets {
259 match t {
260 TargetId::AgentsMd => out.agents_md = true,
261 TargetId::ClaudeMd => out.claude_md = true,
262 TargetId::CursorRules => out.cursor_rules = true,
263 TargetId::GeminiMd => out.gemini_md = true,
264 TargetId::CopilotInstructions => out.copilot_instructions = true,
265 TargetId::Clinerules => out.clinerules = true,
266 TargetId::WindsurfRules => out.windsurf_rules = true,
267 TargetId::CopilotInstructionsScoped => out.copilot_instructions_scoped = true,
268 TargetId::JunieGuidelines => out.junie_guidelines = true,
269 }
270 }
271 out
272 }
273}