repopilot 0.8.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use crate::audits::traits::FileAudit;
use crate::findings::types::{Evidence, Finding, FindingCategory, Severity};
use crate::scan::config::ScanConfig;
use crate::scan::facts::FileFacts;
use crate::scan::path_classification::is_low_signal_audit_path;
use std::path::Path;

mod brace;
mod python;

pub struct LongFunctionAudit;

impl FileAudit for LongFunctionAudit {
    fn audit(&self, file: &FileFacts, config: &ScanConfig) -> Vec<Finding> {
        if is_low_signal_audit_path(&file.path) {
            return vec![];
        }
        let content = file.content.as_deref().unwrap_or("");
        if content.is_empty() {
            return vec![];
        }
        let language = match file.language.as_deref() {
            Some(l) if is_supported(l) => l,
            _ => return vec![],
        };
        detect_long_functions(
            content,
            language,
            &file.path,
            config.long_function_loc_threshold,
        )
    }
}

fn is_supported(language: &str) -> bool {
    matches!(
        language,
        "Rust"
            | "Go"
            | "Python"
            | "TypeScript"
            | "TypeScript React"
            | "JavaScript"
            | "JavaScript React"
            | "Java"
            | "Kotlin"
    )
}

fn detect_long_functions(
    content: &str,
    language: &str,
    path: &Path,
    threshold: usize,
) -> Vec<Finding> {
    if language == "Python" {
        python::detect_python(content, path, threshold)
    } else {
        brace::detect_brace_based(content, language, path, threshold)
    }
}

fn build_finding(
    path: &Path,
    start_line: usize,
    end_line: usize,
    fn_name: &str,
    fn_len: usize,
    threshold: usize,
) -> Finding {
    let (title, name_display) = if fn_name.is_empty() {
        (
            "Long inline function".to_string(),
            "inline function".to_string(),
        )
    } else {
        (
            format!("Long function: `{fn_name}`"),
            format!("`{fn_name}`"),
        )
    };

    Finding {
        id: String::new(),
        rule_id: "code-quality.long-function".to_string(),
        title,
        description: format!(
            "Function {name_display} spans {fn_len} lines, exceeding the {threshold}-line threshold. \
             Long functions are harder to test and reason about — consider extracting helper functions."
        ),
        category: FindingCategory::CodeQuality,
        severity: Severity::Medium,
        evidence: vec![Evidence {
            path: path.to_path_buf(),
            line_start: start_line,
            line_end: Some(end_line),
            snippet: format!("function spans lines {start_line}{end_line} ({fn_len} lines)"),
        }],
        workspace_package: None,
        docs_url: None,
    }
}