yana-rt 0.42.2

Yana AI Runtime — safety CLI for AI agents: scan, graph, vault, hunt, ci, map, fix, doctor
use serde::{Deserialize, Serialize};

pub const SCHEMA_VERSION: &str = "1.0";
pub const GRAPH_DIR: &str = ".yana-ai/graph";
pub const GRAPH_FILE: &str = "knowledge-graph.json";

pub const IGNORE_DIRS: &[&str] = &[
    "node_modules", ".git", ".yana-ai", "dist", "build", "__pycache__",
    ".cache", "coverage", ".next", "target", "venv", ".venv",
    "vendor", "tmp", ".tmp", "releases", ".claude-plugin",
];

pub const IGNORE_EXTS: &[&str] = &[
    ".pyc", ".class", ".o", ".so", ".dylib", ".dll", ".exe",
    ".zip", ".tar", ".gz", ".png", ".jpg", ".jpeg", ".gif",
    ".woff", ".woff2", ".ttf", ".pdf", ".lock",
];

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Node {
    pub id:         String,
    #[serde(rename = "type")]
    pub node_type:  String,
    pub name:       String,
    pub file_path:  String,
    pub language:   String,
    #[serde(default)]
    pub summary:    String,
    #[serde(default)]
    pub complexity: String,
    #[serde(default)]
    pub tags:       Vec<String>,
    #[serde(default)]
    pub line_range: Option<[usize; 2]>,
    #[serde(default)]
    pub category:   String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Edge {
    pub source:    String,
    pub target:    String,
    #[serde(rename = "type")]
    pub edge_type: String,
    #[serde(default = "default_weight")]
    pub weight:    f32,
}

fn default_weight() -> f32 { 1.0 }

#[derive(Debug, Serialize, Deserialize)]
pub struct GraphMeta {
    pub project:        String,
    pub root:           String,
    pub languages:      Vec<String>,
    pub frameworks:     Vec<String>,
    pub total_files:    usize,
    pub analysed_at:    String,
    pub schema_version: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GraphData {
    pub meta:  GraphMeta,
    pub nodes: Vec<Node>,
    pub edges: Vec<Edge>,
    #[serde(default)]
    pub tour:  Vec<TourStep>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TourStep {
    pub order:       usize,
    pub node_id:     String,
    pub name:        String,
    pub file_path:   String,
    pub language:    String,
    pub reason:      String,
    pub layer:       String,
}

pub fn lang_from_ext(ext: &str) -> &'static str {
    match ext {
        "py"                  => "Python",
        "ts" | "tsx"          => "TypeScript",
        "js" | "jsx" | "mjs"  => "JavaScript",
        "rs"                  => "Rust",
        "go"                  => "Go",
        "java"                => "Java",
        "kt"                  => "Kotlin",
        "cs"                  => "C#",
        "rb"                  => "Ruby",
        "sh" | "bash"         => "Shell",
        "yml" | "yaml"        => "YAML",
        "json"                => "JSON",
        "toml"                => "TOML",
        "md" | "mdx"          => "Markdown",
        "html"                => "HTML",
        "css" | "scss"        => "CSS",
        _                     => "Other",
    }
}

pub fn layer_from_path(path: &str) -> &'static str {
    if path.contains("/test") || path.contains("_test.") || path.contains(".test.") || path.contains("spec.") {
        "Test Layer"
    } else if path.contains("docs/") || path.ends_with(".md") {
        "Documentation"
    } else if path.contains("utils/") || path.contains("helpers/") || path.contains("lib/") {
        "Utilities"
    } else if path.contains("src/") || path.contains("core/") || path.contains("app/") {
        "Service Layer"
    } else {
        "Uncategorized"
    }
}

pub fn category_from_path(path: &str, lang: &str) -> &'static str {
    if path.contains("test") || path.contains("spec") { return "test"; }
    if path.ends_with(".md") { return "docs"; }
    if matches!(lang, "YAML" | "JSON" | "TOML") { return "config"; }
    if path.contains("script") || matches!(lang, "Shell") { return "scripts"; }
    "source"
}