Skip to main content

claude_wrapper/command/
ultrareview.rs

1//! `claude ultrareview` -- cloud-hosted multi-agent code review.
2
3#[cfg(feature = "async")]
4use crate::Claude;
5use crate::command::ClaudeCommand;
6#[cfg(feature = "async")]
7use crate::error::Result;
8#[cfg(feature = "async")]
9use crate::exec;
10use crate::exec::CommandOutput;
11
12/// Run `claude ultrareview [target] [--json] [--timeout <minutes>]`.
13///
14/// Runs a cloud-hosted multi-agent code review and prints the findings.
15/// With no target, reviews the current branch; pass a PR number/URL or a
16/// base branch name to review that instead.
17///
18/// The review runs in the cloud and can take many minutes. Size the
19/// [`Claude`] client timeout to at least the value passed to
20/// [`Self::timeout`] (the CLI default is 30 minutes) so the wrapper does
21/// not kill the process before the review finishes.
22///
23/// # Example
24///
25/// ```no_run
26/// # #[cfg(feature = "async")] {
27/// use claude_wrapper::{Claude, ClaudeCommand, UltrareviewCommand};
28///
29/// # async fn example() -> claude_wrapper::Result<()> {
30/// let claude = Claude::builder().build()?;
31/// let out = UltrareviewCommand::new()
32///     .target("123")
33///     .json()
34///     .timeout(45)
35///     .execute(&claude)
36///     .await?;
37/// println!("{}", out.stdout);
38/// # Ok(()) }
39/// # }
40/// ```
41#[derive(Debug, Clone, Default)]
42pub struct UltrareviewCommand {
43    target: Option<String>,
44    json: bool,
45    timeout_minutes: Option<u32>,
46}
47
48impl UltrareviewCommand {
49    /// Create a new ultrareview command targeting the current branch.
50    #[must_use]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Review a specific target instead of the current branch: a PR
56    /// number/URL or a base branch name.
57    #[must_use]
58    pub fn target(mut self, target: impl Into<String>) -> Self {
59        self.target = Some(target.into());
60        self
61    }
62
63    /// Print the raw `bugs.json` payload instead of formatted findings
64    /// (`--json`).
65    #[must_use]
66    pub fn json(mut self) -> Self {
67        self.json = true;
68        self
69    }
70
71    /// Maximum minutes to wait for the review to finish (`--timeout`).
72    /// The CLI default is 30.
73    #[must_use]
74    pub fn timeout(mut self, minutes: u32) -> Self {
75        self.timeout_minutes = Some(minutes);
76        self
77    }
78}
79
80impl ClaudeCommand for UltrareviewCommand {
81    type Output = CommandOutput;
82
83    fn args(&self) -> Vec<String> {
84        let mut args = vec!["ultrareview".to_string()];
85        if self.json {
86            args.push("--json".to_string());
87        }
88        if let Some(minutes) = self.timeout_minutes {
89            args.push("--timeout".to_string());
90            args.push(minutes.to_string());
91        }
92        if let Some(ref target) = self.target {
93            args.push(target.clone());
94        }
95        args
96    }
97
98    #[cfg(feature = "async")]
99    async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
100        exec::run_claude(claude, self.args()).await
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn ultrareview_defaults() {
110        assert_eq!(
111            ClaudeCommand::args(&UltrareviewCommand::new()),
112            vec!["ultrareview"]
113        );
114    }
115
116    #[test]
117    fn ultrareview_with_target_json_timeout() {
118        let cmd = UltrareviewCommand::new().target("123").json().timeout(45);
119        assert_eq!(
120            ClaudeCommand::args(&cmd),
121            vec!["ultrareview", "--json", "--timeout", "45", "123"]
122        );
123    }
124
125    #[test]
126    fn ultrareview_target_only() {
127        let cmd = UltrareviewCommand::new().target("main");
128        assert_eq!(ClaudeCommand::args(&cmd), vec!["ultrareview", "main"]);
129    }
130
131    #[test]
132    fn ultrareview_json_only() {
133        let cmd = UltrareviewCommand::new().json();
134        assert_eq!(ClaudeCommand::args(&cmd), vec!["ultrareview", "--json"]);
135    }
136}