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).
#![allow(dead_code)]

//! Built-in code analyzers.
//!
//! Despite the module name, this is *not* a dynamic plugin system — there's
//! no dlopen, no WASM runtime, no `.so` loader. It's a registry of compiled-in
//! analyzers that conform to a common trait. Earlier revisions of this file
//! pretended to load `.so` files from disk by `println!`-ing their names; that
//! has been removed because it misled users into thinking arbitrary plugins
//! could be installed.
//!
//! To add a new analyzer: implement `AnalyzerPlugin`, then register it in
//! [`PluginManager::register_builtin_plugins`]. Recompile to ship.

use anyhow::Result;
use serde::{Deserialize, Serialize};

pub trait AnalyzerPlugin: Send + Sync {
    fn name(&self) -> &str;
    fn analyze(&self, context: &PluginContext) -> Result<PluginResult>;
    fn priority(&self) -> u8 { 100 }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginContext {
    pub code: String,
    pub language: String,
    pub file_path: Option<String>,
    pub metadata: std::collections::HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginResult {
    pub issues: Vec<PluginIssue>,
    pub metrics: std::collections::HashMap<String, f64>,
    pub suggestions: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginIssue {
    pub severity: IssueSeverity,
    pub category: String,
    pub message: String,
    pub line: Option<usize>,
    pub suggestion: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum IssueSeverity {
    Info,
    Warning,
    Error,
    Critical,
}

pub struct PluginManager {
    pub plugins: Vec<Box<dyn AnalyzerPlugin>>,
}

impl PluginManager {
    pub fn new() -> Self {
        Self {
            plugins: Vec::new(),
        }
    }

    /// Register a programmatically-constructed analyzer. Used by tests and by
    /// callers that want to compose a custom subset of built-ins.
    pub fn add_plugin(&mut self, plugin: Box<dyn AnalyzerPlugin>) {
        self.plugins.push(plugin);
    }

    /// Register every analyzer compiled into this build.
    pub fn register_builtin_plugins(&mut self) {
        self.add_plugin(Box::new(SecurityPatternPlugin::new()));
        self.add_plugin(Box::new(PerformancePatternPlugin::new()));
        self.add_plugin(Box::new(DocumentationPlugin::new()));
    }

    pub fn analyze(&self, context: &PluginContext) -> Result<Vec<PluginResult>> {
        let mut results = Vec::new();

        let mut plugins: Vec<_> = self.plugins.iter().collect();
        plugins.sort_by_key(|p| p.priority());

        for plugin in plugins {
            match plugin.analyze(context) {
                Ok(result) => results.push(result),
                Err(e) => {
                    tracing::warn!("analyzer {} failed: {}", plugin.name(), e);
                }
            }
        }

        Ok(results)
    }
}

impl Default for PluginManager {
    fn default() -> Self {
        Self::new()
    }
}

struct SecurityPatternPlugin {
    patterns: Vec<(&'static str, &'static str)>,
}

impl SecurityPatternPlugin {
    fn new() -> Self {
        Self {
            patterns: vec![
                ("eval(", "Avoid eval() - code injection risk"),
                ("exec(", "Avoid exec() - code injection risk"),
                ("password", "Potential hardcoded password"),
                ("secret", "Potential hardcoded secret"),
                ("api_key", "Potential hardcoded API key"),
            ],
        }
    }
}

impl AnalyzerPlugin for SecurityPatternPlugin {
    fn name(&self) -> &str {
        "security_patterns"
    }

    fn priority(&self) -> u8 {
        10
    }

    fn analyze(&self, context: &PluginContext) -> Result<PluginResult> {
        let mut issues = Vec::new();
        
        for (line_num, line) in context.code.lines().enumerate() {
            for (pattern, message) in &self.patterns {
                if line.contains(pattern) {
                    issues.push(PluginIssue {
                        severity: IssueSeverity::Warning,
                        category: "security".to_string(),
                        message: message.to_string(),
                        line: Some(line_num + 1),
                        suggestion: None,
                    });
                }
            }
        }

        Ok(PluginResult {
            issues,
            metrics: std::collections::HashMap::new(),
            suggestions: Vec::new(),
        })
    }
}

struct PerformancePatternPlugin {
    patterns: Vec<(&'static str, &'static str)>,
}

impl PerformancePatternPlugin {
    fn new() -> Self {
        Self {
            patterns: vec![
                (".keys()", "Unnecessary .keys() call"),
                ("+ str(", "String concatenation in loop"),
                ("== None", "Use 'is None' for None comparison"),
            ],
        }
    }
}

impl AnalyzerPlugin for PerformancePatternPlugin {
    fn name(&self) -> &str {
        "performance_patterns"
    }

    fn priority(&self) -> u8 {
        20
    }

    fn analyze(&self, context: &PluginContext) -> Result<PluginResult> {
        let mut issues = Vec::new();
        
        if context.language == "python" {
            for (line_num, line) in context.code.lines().enumerate() {
                for (pattern, message) in &self.patterns {
                    if line.contains(pattern) {
                        issues.push(PluginIssue {
                            severity: IssueSeverity::Info,
                            category: "performance".to_string(),
                            message: message.to_string(),
                            line: Some(line_num + 1),
                            suggestion: None,
                        });
                    }
                }
            }
        }

        Ok(PluginResult {
            issues,
            metrics: std::collections::HashMap::new(),
            suggestions: Vec::new(),
        })
    }
}

struct DocumentationPlugin;

impl DocumentationPlugin {
    fn new() -> Self {
        Self
    }
}

impl AnalyzerPlugin for DocumentationPlugin {
    fn name(&self) -> &str {
        "documentation"
    }

    fn priority(&self) -> u8 {
        50
    }

    fn analyze(&self, context: &PluginContext) -> Result<PluginResult> {
        let mut issues = Vec::new();
        
        let has_docstring = context.code.contains("\"\"\"") || context.code.contains("'''");
        let has_comments = context.code.contains("//") || context.code.contains("#");
        
        if !has_docstring && !has_comments && context.code.lines().count() > 20 {
            issues.push(PluginIssue {
                severity: IssueSeverity::Info,
                category: "documentation".to_string(),
                message: "Consider adding documentation".to_string(),
                line: None,
                suggestion: Some("Add docstrings or comments to explain the code".to_string()),
            });
        }

        Ok(PluginResult {
            issues,
            metrics: std::collections::HashMap::new(),
            suggestions: Vec::new(),
        })
    }
}