Skip to main content

oxi_agent/tools/
review_tool.rs

1/// Review tool — request code review with focus areas and priorities.
2///
3/// Submits a structured code review request. Supports specifying files to
4/// review, a focus area (security, performance, correctness, style, all),
5/// and finding priority level. Outputs a formatted review request that can
6/// be used with a subagent reviewer.
7use super::{AgentTool, AgentToolResult, ToolContext, ToolError, ToolExecutionMode, ToolTier};
8use async_trait::async_trait;
9use serde_json::{Value, json};
10use tokio::sync::oneshot;
11
12/// ReviewTool — request code review.
13pub struct ReviewTool;
14
15#[async_trait]
16impl AgentTool for ReviewTool {
17    fn name(&self) -> &str {
18        "review"
19    }
20
21    fn label(&self) -> &str {
22        "Review"
23    }
24
25    fn description(&self) -> &str {
26        concat!(
27            "Request code review with focus areas and finding priorities. ",
28            "Specify files to review, the focus area (security, performance, ",
29            "correctness, style, or all), and the finding priority level. ",
30            "Use the subagent tool to dispatch the actual review."
31        )
32    }
33
34    fn essential(&self) -> bool {
35        false
36    }
37
38    fn parameters_schema(&self) -> Value {
39        json!({
40            "type": "object",
41            "properties": {
42                "files": {
43                    "type": "array",
44                    "items": {"type": "string"},
45                    "description": "Files to review (paths relative to workspace root)."
46                },
47                "context": {
48                    "type": "string",
49                    "description": "Additional context for the review (e.g., recent changes, known issues)."
50                },
51                "focus": {
52                    "type": "string",
53                    "enum": ["security", "performance", "correctness", "style", "all"],
54                    "description": "Focus area for the review."
55                },
56                "priority": {
57                    "type": "string",
58                    "enum": ["P0", "P1", "P2", "P3"],
59                    "description": "Finding priority threshold. P0=critical, P3=informational."
60                }
61            }
62        })
63    }
64
65    fn intent(&self) -> Option<&str> {
66        Some("Request code review")
67    }
68
69    fn execution_mode(&self) -> ToolExecutionMode {
70        ToolExecutionMode::SequentialOnly
71    }
72
73    fn tool_tier(&self) -> ToolTier {
74        ToolTier::Read
75    }
76
77    async fn execute(
78        &self,
79        _tool_call_id: &str,
80        params: Value,
81        _signal: Option<oneshot::Receiver<()>>,
82        _ctx: &ToolContext,
83    ) -> Result<AgentToolResult, ToolError> {
84        let files: Vec<String> = params
85            .get("files")
86            .and_then(|v| v.as_array())
87            .map(|a| {
88                a.iter()
89                    .filter_map(|v| v.as_str().map(String::from))
90                    .collect()
91            })
92            .unwrap_or_default();
93
94        let context = params
95            .get("context")
96            .and_then(|v| v.as_str())
97            .map(String::from);
98
99        let focus = params
100            .get("focus")
101            .and_then(|v| v.as_str())
102            .unwrap_or("all");
103
104        let priority = params
105            .get("priority")
106            .and_then(|v| v.as_str())
107            .unwrap_or("P2");
108
109        let mut lines = vec!["## Code Review Request".to_string()];
110        lines.push(format!("- Focus: {}", focus));
111        lines.push(format!("- Priority threshold: {}", priority));
112
113        if files.is_empty() {
114            lines.push("\nFiles: (no specific files — review recent changes)".to_string());
115        } else {
116            lines.push(format!("\nFiles ({}):", files.len()));
117            for file in &files {
118                lines.push(format!("  - {}", file));
119            }
120        }
121
122        if let Some(ref ctx) = context {
123            lines.push(format!("\nContext:\n{}", ctx));
124        }
125
126        lines.push("\n## How to proceed".to_string());
127        lines.push("To perform the actual review, use the subagent tool:".to_string());
128        lines.push(
129            "1. Create a reviewer agent with `subagent` tool and agent: 'reviewer'".to_string(),
130        );
131        lines.push("2. Pass the review files and focus area in the task description".to_string());
132        lines.push("3. The reviewer will return findings with P0-P3 priorities".to_string());
133
134        Ok(AgentToolResult::success(lines.join("\n")))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[tokio::test]
143    async fn test_review_basic() {
144        let tool = ReviewTool;
145        let params = json!({
146            "files": ["src/auth.rs", "src/api.rs"],
147            "focus": "security",
148            "priority": "P0"
149        });
150        let result = tool
151            .execute("id", params, None, &ToolContext::default())
152            .await
153            .unwrap();
154        assert!(result.success);
155        assert!(result.output.contains("security"));
156        assert!(result.output.contains("P0"));
157        assert!(result.output.contains("src/auth.rs"));
158    }
159
160    #[tokio::test]
161    async fn test_review_all_focus() {
162        let tool = ReviewTool;
163        let params = json!({
164            "context": "Recent refactor of the database layer",
165            "focus": "all"
166        });
167        let result = tool
168            .execute("id", params, None, &ToolContext::default())
169            .await
170            .unwrap();
171        assert!(result.success);
172        assert!(result.output.contains("Review Request"));
173        assert!(result.output.contains("subagent"));
174    }
175}