repopilot 0.11.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
mod frameworks;
mod helpers;
mod paradigms;
mod roles;
mod runtimes;

use crate::audits::context::model::{AuditContext, FileRole, LanguageKind};
use crate::knowledge::language::language_kind_for_file;
use crate::scan::facts::FileFacts;
use helpers::is_test_file;

pub fn classify_file(file: &FileFacts) -> AuditContext {
    let content = file.content.as_deref().unwrap_or("");
    let language = classify_language(file);
    let is_test = is_test_file(&file.path, file.has_inline_tests);

    let mut frameworks = Vec::new();
    frameworks::classify_frameworks(&mut frameworks, &file.path, content, language);

    let mut roles = Vec::new();
    roles::classify_roles(
        &mut roles,
        &file.path,
        content,
        language,
        &frameworks,
        is_test,
    );

    if roles.is_empty() {
        roles.push(FileRole::Unknown);
    }

    let mut paradigms = Vec::new();
    paradigms::classify_paradigms(
        &mut paradigms,
        &file.path,
        content,
        language,
        &frameworks,
        &roles,
    );

    let mut runtimes = Vec::new();
    runtimes::classify_runtimes(&mut runtimes, &file.path, content, language, &frameworks);

    AuditContext {
        language,
        frameworks,
        roles,
        paradigms,
        runtimes,
        is_test,
    }
}

fn classify_language(file: &FileFacts) -> LanguageKind {
    language_kind_for_file(file)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::audits::context::model::{
        FileRole, FrameworkKind, LanguageKind, ProgrammingParadigm, RuntimeKind,
    };
    use crate::scan::facts::FileFacts;
    use std::path::PathBuf;

    #[test]
    fn classifies_react_tsx_component() {
        let file = facts(
            "src/components/ProfileCard.tsx",
            Some("TypeScript"),
            "import React from 'react';\nexport function ProfileCard() { return <View />; }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::TypeScript);
        assert!(context.has_framework(FrameworkKind::React));
        assert!(context.has_role(FileRole::ReactComponent));
        assert!(context.has_paradigm(ProgrammingParadigm::DeclarativeUi));
        assert!(context.has_paradigm(ProgrammingParadigm::Functional));
        assert!(context.has_runtime(RuntimeKind::Browser));
        assert!(!context.is_test);
    }

    #[test]
    fn classifies_react_hook() {
        let file = facts(
            "src/hooks/useProfile.ts",
            Some("TypeScript"),
            "import { useEffect, useState } from 'react';\nexport function useProfile() { useEffect(() => {}, []); }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::TypeScript);
        assert!(context.has_framework(FrameworkKind::React));
        assert!(context.has_role(FileRole::ReactHook));
        assert!(context.has_paradigm(ProgrammingParadigm::Functional));
        assert!(context.has_paradigm(ProgrammingParadigm::Reactive));
        assert!(context.has_runtime(RuntimeKind::Browser));
    }

    #[test]
    fn classifies_unity_monobehaviour() {
        let file = facts(
            "Assets/Scripts/PlayerController.cs",
            Some("CSharp"),
            "using UnityEngine;\npublic class PlayerController : MonoBehaviour { void Update() {} }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::CSharp);
        assert!(context.has_framework(FrameworkKind::Unity));
        assert!(context.has_role(FileRole::UnityMonoBehaviour));
        assert!(context.has_paradigm(ProgrammingParadigm::ObjectOriented));
        assert!(context.has_paradigm(ProgrammingParadigm::DataOriented));
        assert!(context.has_runtime(RuntimeKind::Unity));
    }

    #[test]
    fn classifies_dotnet_controller() {
        let file = facts(
            "src/Controllers/UsersController.cs",
            Some("CSharp"),
            "using Microsoft.AspNetCore.Mvc;\n[ApiController]\npublic class UsersController : ControllerBase {}\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::CSharp);
        assert!(context.has_framework(FrameworkKind::DotNet));
        assert!(context.has_role(FileRole::DotNetController));
        assert!(context.has_paradigm(ProgrammingParadigm::ObjectOriented));
        assert!(context.has_runtime(RuntimeKind::DotNet));
    }

    #[test]
    fn classifies_dotnet_service() {
        let file = facts(
            "src/Services/UserService.cs",
            Some("CSharp"),
            "public class UserService { public Task Run() => Task.CompletedTask; }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::CSharp);
        assert!(context.has_framework(FrameworkKind::DotNet));
        assert!(context.has_role(FileRole::DotNetService));
        assert!(context.has_runtime(RuntimeKind::DotNet));
    }

    #[test]
    fn classifies_rust_inline_test_file() {
        let file = facts(
            "src/domain/user.rs",
            Some("Rust"),
            "#[cfg(test)]\nmod tests { #[test] fn works() {} }\n",
            true,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.has_role(FileRole::RustTest));
        assert!(context.has_role(FileRole::Test));
        assert!(context.has_runtime(RuntimeKind::RustLibrary));
        assert!(context.is_test);
    }

    #[test]
    fn classifies_rust_main_as_cli_runtime() {
        let file = facts(
            "src/main.rs",
            Some("Rust"),
            "fn main() { println!(\"hello\"); }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.has_runtime(RuntimeKind::RustCli));
        assert!(context.has_paradigm(ProgrammingParadigm::Procedural));
    }

    #[test]
    fn classifies_rust_lib_as_library_runtime() {
        let file = facts("src/lib.rs", Some("Rust"), "pub fn parse() {}\n", false);

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.has_runtime(RuntimeKind::RustLibrary));
        assert!(!context.is_test);
    }

    #[test]
    fn classifies_rust_domain_file_role() {
        let file = facts(
            "src/domain/user.rs",
            Some("Rust"),
            "pub struct User { id: String }\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.has_role(FileRole::Domain));
        assert!(context.has_runtime(RuntimeKind::RustLibrary));
    }

    #[test]
    fn classifies_rust_test_path() {
        let file = facts(
            "tests/parser_test.rs",
            Some("Rust"),
            "#[test]\nfn parses() {}\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.has_role(FileRole::Test));
        assert!(context.has_role(FileRole::RustTest));
        assert!(context.is_test);
    }

    #[test]
    fn classifies_rust_iterator_pipeline_as_functional_without_marking_it_bad() {
        let file = facts(
            "src/domain/users.rs",
            Some("Rust"),
            "let names = users.iter().filter(|user| user.is_active).map(|user| user.name.clone()).collect::<Vec<_>>();\n",
            false,
        );

        let context = classify_file(&file);

        assert_eq!(context.language, LanguageKind::Rust);
        assert!(context.is_functional_code());
        assert!(!context.has_role(FileRole::Config));
    }

    #[test]
    fn classifies_node_runtime_from_process_env_and_node_imports() {
        for content in [
            "const value = process.env.NODE_ENV;\n",
            "import fs from \"node:fs\";\n",
            "import path from 'node:path';\n",
        ] {
            let file = facts("src/server.ts", Some("TypeScript"), content, false);

            let context = classify_file(&file);

            assert!(context.has_framework(FrameworkKind::NodeJs));
            assert!(context.has_runtime(RuntimeKind::Node));
        }
    }

    #[test]
    fn classifies_python_go_and_jvm_contexts() {
        let python = classify_file(&facts(
            "app/views.py",
            Some("Python"),
            "from fastapi import FastAPI\napp = FastAPI()\n",
            false,
        ));
        assert_eq!(python.language, LanguageKind::Python);
        assert!(python.has_framework(FrameworkKind::FastApi));
        assert!(python.has_runtime(RuntimeKind::Python));

        let go = classify_file(&facts(
            "cmd/server/main.go",
            Some("Go"),
            "package main\nimport \"github.com/gin-gonic/gin\"\nfunc main() {}\n",
            false,
        ));
        assert_eq!(go.language, LanguageKind::Go);
        assert!(go.has_framework(FrameworkKind::Gin));
        assert!(go.has_runtime(RuntimeKind::Go));
        assert!(go.has_role(FileRole::Script));

        let java = classify_file(&facts(
            "src/main/java/com/acme/UserService.java",
            Some("Java"),
            "import org.springframework.stereotype.Service;\npublic class UserService {}\n",
            false,
        ));
        assert_eq!(java.language, LanguageKind::Java);
        assert!(java.has_framework(FrameworkKind::Spring));
        assert!(java.has_paradigm(ProgrammingParadigm::ObjectOriented));
        assert!(java.has_runtime(RuntimeKind::Jvm));
    }

    #[test]
    fn classifies_generated_files_as_non_production() {
        let file = facts(
            "src/generated/schema.rs",
            Some("Rust"),
            "// generated by schema tool\npub fn value() {}\n",
            false,
        );

        let context = classify_file(&file);

        assert!(context.has_role(FileRole::Generated));
        assert!(!context.is_production_code());
    }

    #[test]
    fn classifies_config_file() {
        let file = facts(
            "tsconfig.json",
            None,
            "{ \"compilerOptions\": {} }\n",
            false,
        );

        let context = classify_file(&file);

        assert!(context.has_role(FileRole::Config));
        assert!(context.has_runtime(RuntimeKind::Unknown));
        assert!(!context.is_production_code());
    }

    fn facts(
        path: &str,
        language: Option<&str>,
        content: &str,
        has_inline_tests: bool,
    ) -> FileFacts {
        FileFacts {
            path: PathBuf::from(path),
            language: language.map(str::to_string),
            lines_of_code: content.lines().count(),
            branch_count: 0,
            imports: Vec::new(),
            content: Some(content.to_string()),
            has_inline_tests,
        }
    }
}