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    /// Glob pattern(s) that cover all output files produced by this target.
63    ///
64    /// Used to auto-populate `paths.generated` in the normalized model so that
65    /// users do not need to list output files in both `outputs:` and
66    /// `paths.generated:`.
67    #[must_use]
68    pub fn generated_glob(self) -> &'static str {
69        match self {
70            TargetId::AgentsMd => "AGENTS.md",
71            TargetId::ClaudeMd => "CLAUDE.md",
72            // cursor-rules emits default.mdc plus one file per role
73            TargetId::CursorRules => ".cursor/rules/**",
74            TargetId::GeminiMd => "GEMINI.md",
75            TargetId::CopilotInstructions => ".github/copilot-instructions.md",
76        }
77    }
78
79    /// Support tier: `"stable"` or `"experimental"`.
80    #[must_use]
81    pub fn tier(self) -> &'static str {
82        match self {
83            TargetId::AgentsMd
84            | TargetId::ClaudeMd
85            | TargetId::CursorRules
86            | TargetId::GeminiMd
87            | TargetId::CopilotInstructions => "stable",
88        }
89    }
90}
91
92/// Which output files the policy is configured to generate.
93#[allow(clippy::struct_excessive_bools)]
94#[derive(Debug, Clone, Serialize)]
95pub struct OutputTargets {
96    /// Generate `AGENTS.md` (default: true when `outputs` is omitted).
97    pub agents_md: bool,
98    /// Generate `CLAUDE.md`.
99    pub claude_md: bool,
100    /// Generate `.cursor/rules/default.mdc`.
101    pub cursor_rules: bool,
102    /// Generate `GEMINI.md`.
103    pub gemini_md: bool,
104    /// Generate `.github/copilot-instructions.md`.
105    pub copilot_instructions: bool,
106}
107
108impl Default for OutputTargets {
109    fn default() -> Self {
110        Self {
111            agents_md: true,
112            claude_md: false,
113            cursor_rules: false,
114            gemini_md: false,
115            copilot_instructions: false,
116        }
117    }
118}
119
120impl OutputTargets {
121    /// Returns `true` if no outputs are enabled.
122    #[must_use]
123    pub fn is_empty(&self) -> bool {
124        !self.agents_md
125            && !self.claude_md
126            && !self.cursor_rules
127            && !self.gemini_md
128            && !self.copilot_instructions
129    }
130
131    /// Returns the list of enabled [`TargetId`]s in stable order.
132    #[must_use]
133    pub fn enabled(&self) -> Vec<TargetId> {
134        let mut out = Vec::new();
135        if self.agents_md {
136            out.push(TargetId::AgentsMd);
137        }
138        if self.claude_md {
139            out.push(TargetId::ClaudeMd);
140        }
141        if self.cursor_rules {
142            out.push(TargetId::CursorRules);
143        }
144        if self.gemini_md {
145            out.push(TargetId::GeminiMd);
146        }
147        if self.copilot_instructions {
148            out.push(TargetId::CopilotInstructions);
149        }
150        out
151    }
152}