ai-agent-sdk 0.4.0

Idiomatic agent sdk inspired by the claude code source leak
Documentation
//! AI.md types

use serde::{Deserialize, Serialize};

/// Type of AI.md memory file
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AiMdType {
    /// Managed memory - global policy (e.g., /etc/claude-code/AI.md)
    Managed,
    /// User memory - private global (~/.claude/AI.md)
    User,
    /// Project memory - checked into codebase (./AI.md, .claude/AI.md)
    Project,
    /// Local memory - private project-specific (./AI.local.md)
    Local,
}

impl std::fmt::Display for AiMdType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AiMdType::Managed => write!(f, "managed"),
            AiMdType::User => write!(f, "user"),
            AiMdType::Project => write!(f, "project"),
            AiMdType::Local => write!(f, "local"),
        }
    }
}

/// Parsed frontmatter from AI.md file
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AiMdFrontmatter {
    /// Optional glob patterns for conditional rules
    #[serde(default)]
    pub paths: Option<Vec<String>>,
    /// Optional name field
    #[serde(default)]
    pub name: Option<String>,
    /// Optional description field
    #[serde(default)]
    pub description: Option<String>,
    /// Optional type field
    #[serde(default)]
    pub r#type: Option<String>,
}

/// AI.md file information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiMdFile {
    /// Absolute path to the file
    pub path: String,
    /// Type of memory file
    pub md_type: AiMdType,
    /// Parsed content (after stripping frontmatter)
    pub content: String,
    /// Raw content from disk (if different after processing)
    #[serde(default)]
    pub raw_content: Option<String>,
    /// Glob patterns from frontmatter for conditional rules
    #[serde(default)]
    pub globs: Option<Vec<String>>,
    /// Path of the file that included this one
    #[serde(default)]
    pub parent: Option<String>,
    /// Whether content differs from disk (partial view)
    #[serde(default)]
    pub content_differs_from_disk: bool,
}

/// Content with description
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiMdContent {
    /// File path
    pub path: String,
    /// Content text
    pub content: String,
    /// Type description
    pub type_description: String,
}

impl AiMdContent {
    /// Create a new AiMdContent
    pub fn new(path: String, content: String, md_type: AiMdType) -> Self {
        let type_description = match md_type {
            AiMdType::Managed => "(global policy instructions)".to_string(),
            AiMdType::User => "(user's private global instructions for all projects)".to_string(),
            AiMdType::Project => "(project instructions, checked into the codebase)".to_string(),
            AiMdType::Local => "(user's private project instructions, not checked in)".to_string(),
        };
        Self {
            path,
            content,
            type_description,
        }
    }
}

/// Result of parsing an AI.md file
#[derive(Debug)]
pub struct ParsedAiMd {
    pub frontmatter: AiMdFrontmatter,
    pub content: String,
}

/// File extensions allowed for @include directives
pub const TEXT_FILE_EXTENSIONS: &[&str] = &[
    // Markdown and text
    ".md", ".txt", ".text",
    // Data formats
    ".json", ".yaml", ".yml", ".toml", ".xml", ".csv",
    // Web
    ".html", ".htm", ".css", ".scss", ".sass", ".less",
    // JavaScript/TypeScript
    ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts",
    // Python
    ".py", ".pyi", ".pyw",
    // Ruby
    ".rb", ".erb", ".rake",
    // Go
    ".go",
    // Rust
    ".rs",
    // Java/Kotlin/Scala
    ".java", ".kt", ".kts", ".scala",
    // C/C++
    ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx",
    // C#
    ".cs",
    // Swift
    ".swift",
    // Shell
    ".sh", ".bash", ".zsh", ".fish", ".ps1", ".bat", ".cmd",
    // Config
    ".env", ".ini", ".cfg", ".conf", ".config", ".properties",
    // Database
    ".sql", ".graphql", ".gql",
    // Protocol
    ".proto",
    // Frontend frameworks
    ".vue", ".svelte", ".astro",
    // Templating
    ".ejs", ".hbs", ".pug", ".jade",
    // Other languages
    ".php", ".pl", ".pm", ".lua", ".r", ".R", ".dart",
    ".ex", ".exs", ".erl", ".hrl",
    ".clj", ".cljs", ".cljc", ".edn",
    ".hs", ".lhs", ".elm", ".ml", ".mli",
    ".f", ".f90", ".f95", ".for",
    // Build files
    ".cmake", ".make", ".makefile", ".gradle", ".sbt",
    // Documentation
    ".rst", ".adoc", ".asciidoc", ".org", ".tex", ".latex",
    // Lock files
    ".lock",
    // Misc
    ".log", ".diff", ".patch",
];

/// Check if a file extension is allowed for @include
pub fn is_allowed_extension(ext: &str) -> bool {
    TEXT_FILE_EXTENSIONS.contains(&ext.to_lowercase().as_str())
}