Skip to main content

opys_engine/commands/
agent_rules.rs

1//! `opys agent-rules` — generate a rules-based editor's always-on instruction
2//! file from the single canonical rule (`templates::AGENT_RULE`). No per-editor
3//! copies are kept in the repo; this writes the right file in the right place,
4//! adding any host-specific frontmatter.
5
6use std::path::Path;
7
8use crate::cli::AgentTool;
9use crate::error::{usage, Result};
10use crate::templates::AGENT_RULE;
11use crate::Ctx;
12
13/// Cursor scopes the rule to inventory files via `globs`.
14const CURSOR_FRONTMATTER: &str = "---\ndescription: opys feature inventory — operate the opys CLI when the project has a opys/ inventory.\nglobs: opys/**\nalwaysApply: false\n---\n\n";
15/// Copilot path-specific instruction file, scoped with `applyTo`.
16const COPILOT_FRONTMATTER: &str = "---\napplyTo: \"opys/**\"\n---\n\n";
17
18/// (output path relative to the project root, host-specific frontmatter).
19fn target(tool: AgentTool) -> (&'static str, &'static str) {
20    match tool {
21        AgentTool::Cursor => (".cursor/rules/opys.mdc", CURSOR_FRONTMATTER),
22        AgentTool::Windsurf => (".windsurf/rules/opys.md", ""),
23        AgentTool::Cline => (".clinerules/opys.md", ""),
24        AgentTool::Copilot => (
25            ".github/instructions/opys.instructions.md",
26            COPILOT_FRONTMATTER,
27        ),
28        AgentTool::Kiro => (".kiro/steering/opys.md", ""),
29        AgentTool::All => unreachable!("expanded before target()"),
30    }
31}
32
33pub fn run(ctx: &Ctx, tool: AgentTool, stdout: bool) -> Result<()> {
34    let tools = match tool {
35        AgentTool::All => vec![
36            AgentTool::Cursor,
37            AgentTool::Windsurf,
38            AgentTool::Cline,
39            AgentTool::Copilot,
40            AgentTool::Kiro,
41        ],
42        t => vec![t],
43    };
44    if stdout && tools.len() > 1 {
45        return Err(usage("--stdout works with a single --tool, not 'all'"));
46    }
47
48    for t in tools {
49        let (rel, frontmatter) = target(t);
50        let content = format!("{frontmatter}{AGENT_RULE}");
51        if stdout {
52            print!("{content}");
53        } else {
54            let path = Path::new(&ctx.root).join(rel);
55            if let Some(parent) = path.parent() {
56                std::fs::create_dir_all(parent)?;
57            }
58            std::fs::write(&path, content)?;
59            println!("wrote {}", path.display());
60        }
61    }
62    Ok(())
63}