car-reason 0.20.0

Code reasoning engine for Common Agent Runtime — adaptive, graph-driven, learning
Documentation
//! Problem classification — determines what kind of code problem this is.
//!
//! Uses the smallest available model (or the classify API) for fast classification.
//! Falls back to keyword heuristics when no model is available.

use crate::types::ProblemClass;
use crate::ReasonError;

/// Classify a problem. Uses keyword heuristics as the primary classifier
/// since they're faster, deterministic, and more reliable for question patterns
/// than small local models. The model classifier is used as a tiebreaker when
/// keywords are ambiguous.
///
/// Pre-#189 this took an `&InferenceEngine` parameter (`_engine` —
/// unused). Dropped during the daemon-routed refactor because the
/// param was effectively documentation that the engine *might* be
/// used here — that intent never materialized, and keeping the
/// parameter forced every caller to provide an engine handle even
/// when no inference happens.
pub async fn classify_problem(problem: &str) -> Result<ProblemClass, ReasonError> {
    let result = classify_by_keywords(problem);
    Ok(result)
}

/// Keyword-based classification fallback.
pub fn classify_by_keywords(problem: &str) -> ProblemClass {
    let lower = problem.to_lowercase();

    // Question patterns — strong signal for explanation/architecture
    let question_starters = [
        "why ",
        "why does",
        "how does",
        "what is",
        "what are",
        "what's the",
        "can you explain",
        "describe how",
        "trade-off",
        "tradeoff",
        "pros and cons",
        "compared to",
        "versus",
        "vs ",
    ];
    let is_question = question_starters.iter().any(|q| lower.contains(q));

    // "How would you" is a design/feature question, not explanation
    let is_how_would = lower.contains("how would")
        || lower.contains("how to add")
        || lower.contains("how can we")
        || lower.contains("how do you");

    // If it's clearly a question about design/reasoning, classify early
    if is_question && !is_how_would {
        // Check if it's about architecture specifically
        let arch_terms = [
            "architecture",
            "design",
            "trade-off",
            "tradeoff",
            "pattern",
            "approach",
            "decision",
            "structure",
            "versus",
            "vs ",
        ];
        if arch_terms.iter().any(|t| lower.contains(t)) {
            return ProblemClass::Architecture;
        }
        return ProblemClass::Explanation;
    }

    let scores = [
        (
            ProblemClass::BugFix,
            &[
                "bug", "fix", "broken", "error", "crash", "wrong", "fail", "issue", "debug",
            ][..],
        ),
        (
            ProblemClass::Refactor,
            &[
                "refactor",
                "clean",
                "simplify",
                "restructure",
                "rename",
                "extract",
            ],
        ),
        (
            ProblemClass::Performance,
            &[
                "slow",
                "optimize",
                "performance",
                "latency",
                "throughput",
                "bottleneck",
            ],
        ),
        (
            ProblemClass::TestWriting,
            &[
                "test",
                "coverage",
                "assert",
                "spec",
                "unit test",
                "integration test",
            ],
        ),
        (
            ProblemClass::Architecture,
            &[
                "architecture",
                "design",
                "pattern",
                "structure",
                "approach",
                "trade-off",
            ],
        ),
        // NewFeature: removed overly generic words ("add", "new", "build", "support")
        // that match too many non-feature queries
        (
            ProblemClass::NewFeature,
            &[
                "implement",
                "create",
                "feature",
                "endpoint",
                "module",
                "component",
            ],
        ),
        (
            ProblemClass::Explanation,
            &["explain", "understand", "describe", "clarify", "meaning"],
        ),
    ];

    let mut best = ProblemClass::Unknown;
    let mut best_score = 0usize;

    for (class, keywords) in &scores {
        let score = keywords.iter().filter(|kw| lower.contains(*kw)).count();
        if score > best_score {
            best_score = score;
            best = *class;
        }
    }

    // "How would you add/implement X" → NewFeature even without the generic keywords
    if best == ProblemClass::Unknown && is_how_would {
        best = ProblemClass::NewFeature;
    }

    best
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn keyword_classification() {
        assert_eq!(
            classify_by_keywords("fix this broken test"),
            ProblemClass::BugFix
        );
        assert_eq!(
            classify_by_keywords("refactor the authentication module"),
            ProblemClass::Refactor
        );
        assert_eq!(
            classify_by_keywords("optimize the database query performance"),
            ProblemClass::Performance
        );
        assert_eq!(
            classify_by_keywords("implement a new endpoint for user profiles"),
            ProblemClass::NewFeature
        );
        assert_eq!(
            classify_by_keywords("explain how the routing works"),
            ProblemClass::Explanation
        );
        assert_eq!(
            classify_by_keywords("write unit tests for the parser"),
            ProblemClass::TestWriting
        );
        assert_eq!(
            classify_by_keywords("design the architecture for the new service"),
            ProblemClass::Architecture
        );
    }

    #[test]
    fn ambiguous_defaults_to_strongest() {
        assert_eq!(
            classify_by_keywords("fix this error in the code"),
            ProblemClass::BugFix
        );
    }

    #[test]
    fn question_patterns_classify_correctly() {
        // "Why does X use Y" → Explanation (not NewFeature)
        assert_eq!(
            classify_by_keywords("Why does car-reason use memgine skills for reasoning actions instead of a hardcoded pipeline?"),
            ProblemClass::Explanation,
        );
        // "What are the trade-offs" → Architecture
        assert_eq!(
            classify_by_keywords("What are the trade-offs of this design?"),
            ProblemClass::Architecture,
        );
        // "How would you add X" → NewFeature (not Explanation)
        assert_eq!(
            classify_by_keywords(
                "How would you add action-aware routing so simple actions use small models?"
            ),
            ProblemClass::NewFeature,
        );
    }
}