repopilot 0.19.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
use super::helpers::push_unique;
use super::signals::ContextSignals;
use crate::audits::context::model::{FrameworkKind, LanguageKind, RuntimeKind};
use std::path::Path;

pub fn classify_runtimes(
    runtimes: &mut Vec<RuntimeKind>,
    path: &Path,
    content: &str,
    language: LanguageKind,
    frameworks: &[FrameworkKind],
    signals: &ContextSignals,
) {
    // All patterns are lowercase in their respective source languages — no allocation needed.
    classify_framework_runtimes(runtimes, frameworks);
    classify_node_runtime(runtimes, content, frameworks);

    if language == LanguageKind::Rust {
        classify_rust_runtime(runtimes, path);
    }

    classify_language_runtimes(runtimes, language);

    if signals.is_infrastructure_context() {
        push_unique(runtimes, RuntimeKind::Infrastructure);
    }

    if frameworks.contains(&FrameworkKind::Android) {
        push_unique(runtimes, RuntimeKind::Android);
    }

    if runtimes.is_empty() {
        push_unique(runtimes, RuntimeKind::Unknown);
    }
}

fn classify_framework_runtimes(runtimes: &mut Vec<RuntimeKind>, frameworks: &[FrameworkKind]) {
    if frameworks.contains(&FrameworkKind::ReactNative) || frameworks.contains(&FrameworkKind::Expo)
    {
        push_unique(runtimes, RuntimeKind::ReactNative);
    } else if frameworks.contains(&FrameworkKind::React)
        || frameworks.contains(&FrameworkKind::NextJs)
        || frameworks.contains(&FrameworkKind::Vue)
        || frameworks.contains(&FrameworkKind::Angular)
        || frameworks.contains(&FrameworkKind::Svelte)
    {
        push_unique(runtimes, RuntimeKind::Browser);
    }

    if frameworks.contains(&FrameworkKind::Unity) {
        push_unique(runtimes, RuntimeKind::Unity);
    }

    if frameworks.contains(&FrameworkKind::DotNet) {
        push_unique(runtimes, RuntimeKind::DotNet);
    }
}

fn classify_node_runtime(
    runtimes: &mut Vec<RuntimeKind>,
    content: &str,
    frameworks: &[FrameworkKind],
) {
    if frameworks.contains(&FrameworkKind::NodeJs)
        || content.contains("process.env")
        || content.contains("from 'node:")
        || content.contains("from \"node:")
    {
        push_unique(runtimes, RuntimeKind::Node);
    }
}

fn classify_rust_runtime(runtimes: &mut Vec<RuntimeKind>, path: &Path) {
    let file_name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or_default();

    if file_name == "main.rs" {
        push_unique(runtimes, RuntimeKind::RustCli);
    } else {
        push_unique(runtimes, RuntimeKind::RustLibrary);
    }
}

fn classify_language_runtimes(runtimes: &mut Vec<RuntimeKind>, language: LanguageKind) {
    if language == LanguageKind::Python {
        push_unique(runtimes, RuntimeKind::Python);
    }

    if language == LanguageKind::Go {
        push_unique(runtimes, RuntimeKind::Go);
    }

    if matches!(
        language,
        LanguageKind::Java | LanguageKind::Kotlin | LanguageKind::Scala
    ) {
        push_unique(runtimes, RuntimeKind::Jvm);
    }

    if language == LanguageKind::Shell || language == LanguageKind::PowerShell {
        push_unique(runtimes, RuntimeKind::Shell);
    }

    if matches!(
        language,
        LanguageKind::C | LanguageKind::Cpp | LanguageKind::CHeader | LanguageKind::Swift
    ) {
        push_unique(runtimes, RuntimeKind::Native);
    }
}