forge-guard 0.3.6

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! AI-powered auditing — integrates OpenAI, Anthropic Claude, and Ollama
//! for intelligent vulnerability analysis of Solidity smart contracts.
//!
//! # Architecture
//!
//! ```text
//! CLI  ──→  ConsensusEngine  ──→  AuditorAgent(s)
//!               │                      │
//!               │                      └── LlmProvider (HTTP)
//!               │                           ├── OpenAiProvider
//!               │                           ├── ClaudeProvider
//!               │                           └── OllamaProvider
//!//!               └── ConsensusReport (dedup + confidence scoring)
//! ```
//!
//! # Usage
//!
//! ```ignore
//! forge-guard audit --ai                          # use default provider (OpenAI)
//! forge-guard audit --ai --ai-provider claude     # use Claude
//! forge-guard audit --ai --ai-provider ollama     # use local Ollama
//! ```

pub mod auditors;
pub mod consensus;
pub mod providers;

use crate::core::Severity;
use std::collections::HashMap;

// ─────────────────────────────────────────────────────────────
// Shared types
// ─────────────────────────────────────────────────────────────

/// Configuration for AI auditors.
#[derive(Debug, Clone)]
pub struct AiAuditorConfig {
    /// Provider type: "openai", "claude", "ollama".
    pub provider: String,
    /// Model identifier (e.g. "gpt-5", "claude-5-sonnet-20260701").
    pub model: String,
    /// Sampling temperature (0.0–1.0).
    pub temperature: f64,
    /// Maximum tokens per response.
    pub max_tokens: u32,
    /// Minimum confidence (0.0–1.0) for a finding to be included.
    pub min_confidence: f64,
    /// Optional API key override. Reads from env var when `None`.
    pub api_key: Option<String>,
    /// Optional Ollama endpoint override.
    pub ollama_endpoint: Option<String>,
    /// Whether to include gas and logic auditors in addition to security.
    pub full_audit: bool,
}

impl Default for AiAuditorConfig {
    fn default() -> Self {
        Self {
            provider: "openai".into(),
            model: "gpt-5".into(),
            temperature: 0.1,
            max_tokens: 4000,
            min_confidence: 0.5,
            api_key: None,
            ollama_endpoint: None,
            full_audit: false,
        }
    }
}

/// Context passed to each auditor agent for analysis.
#[derive(Debug, Clone)]
pub struct AuditContext {
    /// Full Solidity source code to analyze.
    pub source_code: String,
    /// File name (for context in prompts).
    pub file_name: String,
    /// Compiler version string (e.g. "^0.8.20").
    pub compiler_version: String,
    /// Additional context key-value pairs.
    pub additional: HashMap<String, String>,
}

impl AuditContext {
    /// Create a new audit context.
    pub fn new(source_code: &str, file_name: &str, compiler_version: &str) -> Self {
        Self {
            source_code: source_code.to_owned(),
            file_name: file_name.to_owned(),
            compiler_version: compiler_version.to_owned(),
            additional: HashMap::new(),
        }
    }
}

/// A single finding produced by an AI auditor.
#[derive(Debug, Clone)]
pub struct AuditorFinding {
    /// Human-readable title.
    pub title: String,
    /// Detailed description of the vulnerability.
    pub description: String,
    /// Model confidence (0.0 – 1.0).
    pub confidence: f64,
    /// Assigned severity.
    pub severity: Severity,
    /// Suggested remediation.
    pub suggestion: String,
    /// Line numbers where the issue appears.
    pub line_numbers: Vec<usize>,
    /// Vulnerability category (e.g. "Reentrancy", "AccessControl").
    pub category: String,
}

/// A finding with consensus metadata.
#[derive(Debug, Clone)]
pub struct ConsensusFinding {
    /// Name of the auditor that produced this finding.
    pub auditor: String,
    /// Domain of the auditor (e.g. "Security", "Gas").
    pub domain: String,
    /// The finding itself.
    pub finding: AuditorFinding,
    /// Whether this finding was cross-validated by multiple auditors.
    pub cross_validated: bool,
}

/// Convert a consensus finding into a core Finding for the audit pipeline.
pub fn consensus_to_core_finding(cf: &ConsensusFinding, file_name: &str) -> crate::core::Finding {
    use crate::core::FindingBuilder;

    let line = cf.finding.line_numbers.first().copied();

    FindingBuilder::default()
        .id(&format!("AI-{:04}", rand_id()))
        .title(&cf.finding.title)
        .description(&format!(
            "[AI {}] {} (confidence: {:.0}%){}",
            cf.auditor,
            cf.finding.description,
            cf.finding.confidence * 100.0,
            if cf.cross_validated {
                " [cross-validated]"
            } else {
                ""
            }
        ))
        .severity(cf.finding.severity)
        .file(file_name)
        .location(line.unwrap_or(0), 0)
        .code(&format!(
            "AI-auditor '{}' ({}) — {}",
            cf.auditor, cf.domain, cf.finding.title
        ))
        .recommendation(&cf.finding.suggestion)
        .category(&cf.finding.category)
        .build()
}

fn rand_id() -> u16 {
    use std::time::{SystemTime, UNIX_EPOCH};
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .subsec_nanos();
    (nanos % 9999) as u16
}

// ── Re-exports ───────────────────────────────────────────────
pub use auditors::*;
pub use consensus::*;
pub use providers::*;