Skip to main content

rover/meta/
mod.rs

1//! Agent-harness wiring: `rover meta use <harness>` and the runtime hook handler.
2//!
3//! See `docs/superpowers/specs/2026-06-24-rover-meta-use-harness-design.md`.
4
5pub mod claude;
6pub mod edits;
7pub mod general;
8pub mod hook;
9
10use std::path::{Path, PathBuf};
11
12/// A single file action performed by `run_use`, for the summary printout.
13pub struct Change {
14    pub path: PathBuf,
15    pub action: &'static str,
16}
17
18impl Change {
19    pub fn new(path: PathBuf, action: &'static str) -> Self {
20        Self { path, action }
21    }
22}
23
24/// Configuration scope, mirroring the Claude CLI's `--scope`.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
26pub enum Scope {
27    Local,
28    User,
29    Project,
30}
31
32impl Scope {
33    pub fn as_str(self) -> &'static str {
34        match self {
35            Scope::Local => "local",
36            Scope::User => "user",
37            Scope::Project => "project",
38        }
39    }
40}
41
42/// The agent harness to wire Rover into.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
44pub enum Harness {
45    Claude,
46    General,
47}
48
49/// Orchestrate `rover meta use`: validate-then-apply, then print a summary.
50pub fn run_use(harness: Harness, scope: Scope, root: &Path) -> anyhow::Result<i32> {
51    let changes = match harness {
52        Harness::Claude => {
53            claude::preflight(scope, root)?; // aborts before any write
54            claude::apply(scope, root)?
55        }
56        Harness::General => {
57            if !matches!(scope, Scope::Project) {
58                eprintln!(
59                    "note: `general` supports project scope only; writing to the project root"
60                );
61            }
62            general::preflight(root)?;
63            general::apply(root)?
64        }
65    };
66    print_summary(harness, &changes);
67    Ok(0)
68}
69
70fn print_summary(harness: Harness, changes: &[Change]) {
71    eprintln!("✓ wired Rover into `{}`:", harness_name(harness));
72    for c in changes {
73        eprintln!("  - {} ({})", c.path.display(), c.action);
74    }
75}
76
77fn harness_name(harness: Harness) -> &'static str {
78    match harness {
79        Harness::Claude => "claude",
80        Harness::General => "general",
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use tempfile::tempdir;
88
89    #[test]
90    fn run_use_general_writes_files_and_returns_zero() {
91        let tmp = tempdir().unwrap();
92        let code = run_use(Harness::General, Scope::Project, tmp.path()).unwrap();
93        assert_eq!(code, 0);
94        assert!(tmp.path().join("mcp.json").exists());
95        assert!(tmp.path().join("AGENTS.md").exists());
96    }
97}