Skip to main content

agent_policy/model/
targets.rs

1// Output target flags — which compatibility files to generate.
2
3use serde::Serialize;
4
5/// A stable identifier for each supported output target.
6#[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}
15
16impl TargetId {
17    /// All targets in a defined stable order.
18    pub const ALL: &'static [TargetId] = &[
19        TargetId::AgentsMd,
20        TargetId::ClaudeMd,
21        TargetId::CursorRules,
22        TargetId::GeminiMd,
23        TargetId::CopilotInstructions,
24    ];
25
26    /// The YAML ID string used in `outputs:` lists.
27    #[must_use]
28    pub fn id(self) -> &'static str {
29        match self {
30            TargetId::AgentsMd => "agents-md",
31            TargetId::ClaudeMd => "claude-md",
32            TargetId::CursorRules => "cursor-rules",
33            TargetId::GeminiMd => "gemini-md",
34            TargetId::CopilotInstructions => "copilot-instructions",
35        }
36    }
37
38    /// A human-readable display label.
39    #[must_use]
40    pub fn label(self) -> &'static str {
41        match self {
42            TargetId::AgentsMd => "AGENTS.md",
43            TargetId::ClaudeMd => "CLAUDE.md",
44            TargetId::CursorRules => ".cursor/rules/",
45            TargetId::GeminiMd => "GEMINI.md",
46            TargetId::CopilotInstructions => ".github/copilot-instructions.md",
47        }
48    }
49
50    /// Primary output path produced by this target.
51    #[must_use]
52    pub fn primary_path(self) -> &'static str {
53        match self {
54            TargetId::AgentsMd => "AGENTS.md",
55            TargetId::ClaudeMd => "CLAUDE.md",
56            TargetId::CursorRules => ".cursor/rules/default.mdc",
57            TargetId::GeminiMd => "GEMINI.md",
58            TargetId::CopilotInstructions => ".github/copilot-instructions.md",
59        }
60    }
61
62    /// Support tier: `"stable"` or `"experimental"`.
63    #[must_use]
64    pub fn tier(self) -> &'static str {
65        match self {
66            TargetId::AgentsMd
67            | TargetId::ClaudeMd
68            | TargetId::CursorRules
69            | TargetId::GeminiMd
70            | TargetId::CopilotInstructions => "stable",
71        }
72    }
73}
74
75/// Which output files the policy is configured to generate.
76#[allow(clippy::struct_excessive_bools)]
77#[derive(Debug, Clone, Serialize)]
78pub struct OutputTargets {
79    /// Generate `AGENTS.md` (default: true when `outputs` is omitted).
80    pub agents_md: bool,
81    /// Generate `CLAUDE.md`.
82    pub claude_md: bool,
83    /// Generate `.cursor/rules/default.mdc`.
84    pub cursor_rules: bool,
85    /// Generate `GEMINI.md`.
86    pub gemini_md: bool,
87    /// Generate `.github/copilot-instructions.md`.
88    pub copilot_instructions: bool,
89}
90
91impl Default for OutputTargets {
92    fn default() -> Self {
93        Self {
94            agents_md: true,
95            claude_md: false,
96            cursor_rules: false,
97            gemini_md: false,
98            copilot_instructions: false,
99        }
100    }
101}
102
103impl OutputTargets {
104    /// Returns `true` if no outputs are enabled.
105    #[must_use]
106    pub fn is_empty(&self) -> bool {
107        !self.agents_md
108            && !self.claude_md
109            && !self.cursor_rules
110            && !self.gemini_md
111            && !self.copilot_instructions
112    }
113
114    /// Returns the list of enabled [`TargetId`]s in stable order.
115    #[must_use]
116    pub fn enabled(&self) -> Vec<TargetId> {
117        let mut out = Vec::new();
118        if self.agents_md {
119            out.push(TargetId::AgentsMd);
120        }
121        if self.claude_md {
122            out.push(TargetId::ClaudeMd);
123        }
124        if self.cursor_rules {
125            out.push(TargetId::CursorRules);
126        }
127        if self.gemini_md {
128            out.push(TargetId::GeminiMd);
129        }
130        if self.copilot_instructions {
131            out.push(TargetId::CopilotInstructions);
132        }
133        out
134    }
135}