i-self 0.4.3

Personal developer-companion CLI: scans your repos, indexes code semantically, watches your activity, and moves AI-agent sessions between tools (Claude Code, Aider, Goose, OpenAI Codex CLI, Continue.dev, OpenCode).
use crate::review::{CodeReviewer, CodeReviewRequest, ReviewType, DeveloperPatterns, NamingPatterns, ConventionType, StylePreferences, IndentStyle, QuoteStyle, BraceStyle};
use anyhow::Result;
use clap::Parser;
use std::fs;

#[derive(Parser)]
pub struct ReviewCommand {
    #[arg(short, long)]
    pub file: Option<String>,

    #[arg(short, long)]
    pub code: Option<String>,

    #[arg(short, long, default_value = "rust")]
    pub language: String,

    #[arg(short, long, default_value = "self")]
    pub review_type: String,

    #[arg(short, long)]
    pub context: Option<String>,

    #[arg(short, long)]
    pub use_patterns: bool,
}

impl ReviewCommand {
    pub async fn run(&self) -> Result<()> {
        let code = if let Some(ref file) = self.file {
            fs::read_to_string(file)?
        } else if let Some(ref code) = self.code {
            code.clone()
        } else {
            println!("Please provide --file or --code");
            return Ok(());
        };

        let review_type = match self.review_type.as_str() {
            "pre-commit" => ReviewType::PreCommit,
            "pr" | "pull-request" => ReviewType::PullRequest,
            "security" => ReviewType::Security,
            _ => ReviewType::SelfReview,
        };

        let request = CodeReviewRequest {
            code: code.clone(),
            language: self.language.clone(),
            context: self.context.clone(),
            file_path: self.file.clone(),
            review_type,
        };

        let reviewer = if self.use_patterns {
            let patterns = get_sample_patterns();
            CodeReviewer::with_patterns(patterns)
        } else {
            CodeReviewer::new()
        };

        let result = reviewer.review(&request);

        println!("\n=== Code Review Results ===\n");
        println!("Score: {:.0}/100\n", result.score);
        println!("{}\n", result.summary);

        if !result.positive_observations.is_empty() {
            println!("✓ Positive Observations:");
            for obs in &result.positive_observations {
                println!("  - {}", obs);
            }
            println!();
        }

        if !result.issues.is_empty() {
            println!("Issues Found:");
            for issue in &result.issues {
                let severity_icon = match issue.severity {
                    crate::review::IssueSeverity::Critical => "🔴",
                    crate::review::IssueSeverity::Error => "❌",
                    crate::review::IssueSeverity::Warning => "âš ī¸",
                    crate::review::IssueSeverity::Info => "â„šī¸",
                };
                println!("  {} [{}] {}", severity_icon, format!("{:?}", issue.severity), issue.message);
                if let Some(line) = issue.line {
                    println!("    Line: {}", line);
                }
                if let Some(ref suggestion) = issue.suggestion {
                    println!("    Suggestion: {}", suggestion);
                }
            }
            println!();
        }

        if !result.patterns_detected.is_empty() {
            println!("Patterns Detected:");
            for pattern in &result.patterns_detected {
                println!("  - {}", pattern);
            }
            println!();
        }

        if !result.suggestions.is_empty() {
            println!("Suggestions:");
            for suggestion in &result.suggestions {
                println!("  â€ĸ {}", suggestion);
            }
        }

        Ok(())
    }
}

fn get_sample_patterns() -> DeveloperPatterns {
    DeveloperPatterns {
        naming_conventions: NamingPatterns {
            variables: ConventionType::SnakeCase,
            functions: ConventionType::SnakeCase,
            constants: ConventionType::SnakeCase,
            classes: ConventionType::PascalCase,
            files: ConventionType::SnakeCase,
        },
        style_preferences: StylePreferences {
            indent_style: IndentStyle::Spaces,
            indent_size: 4,
            quote_style: QuoteStyle::Double,
            semicolon_usage: false,
            brace_style: BraceStyle::KR,
        },
        common_patterns: vec![
            "async/await".to_string(),
            "error propagation".to_string(),
            "derive macros".to_string(),
        ],
        test_approaches: vec![
            "unit tests".to_string(),
            "integration tests".to_string(),
        ],
        documentation_style: Some("doc comments".to_string()),
    }
}