Back to .md Directory

MEMORY.md - AI Learning Reference for Caro

> **Purpose**: This document teaches AI systems (Claude, GPT, Copilot) the patterns, idioms, debugging workflows, and common errors specific to this codebase. Read this first before generating code.

May 2, 2026
0 downloads
1 views
ai llm prompt claude copilot workflow safety
View source

MEMORY.md - AI Learning Reference for Caro

Purpose: This document teaches AI systems (Claude, GPT, Copilot) the patterns, idioms, debugging workflows, and common errors specific to this codebase. Read this first before generating code.

Teaching AI Systems Caro

Three-layer learning approach:

  1. Read MEMORY.md first - Patterns, idioms, and error fixes (this document)
  2. Reference docs/development/CLAUDE.md - Architecture and project structure
  3. Study src/ and tests/ - Working examples of idiomatic usage

Core Principles

1. Safety-First Design

All command generation MUST go through safety validation. There is exactly one canonical way to validate commands:

// CANONICAL: Always use SafetyValidator
use caro::safety::SafetyValidator;

let validator = SafetyValidator::new();
let result = validator.validate(&command)?;

match result.risk_level {
    RiskLevel::Safe => execute(command),
    RiskLevel::Critical => return Err(SafetyError::Blocked),
    _ => prompt_user_confirmation(command, result),
}

NEVER generate or execute commands without safety validation.

2. Async-First Architecture

All I/O operations use async/await with tokio. No blocking calls in async contexts:

// CORRECT: Async HTTP call
let response = client.post(&url).json(&request).send().await?;

// WRONG: Blocking in async context
let response = std::thread::spawn(|| blocking_call()).join(); // NEVER

3. Error Handling with Context

Use anyhow for application errors and thiserror for library errors. Always provide context:

// CORRECT: Context for debugging
let config = load_config()
    .context("Failed to load configuration from ~/.config/caro/config.toml")?;

// WRONG: Bare error propagation
let config = load_config()?; // Missing context

4. Trait-Based Extensibility

All backends implement the CommandGenerator trait. This is the ONLY way to add new backends:

#[async_trait]
pub trait CommandGenerator: Send + Sync {
    async fn generate_command(&self, request: &CommandRequest) -> Result<GeneratedCommand>;
    async fn is_available(&self) -> bool;
    fn backend_info(&self) -> BackendInfo;
}

Quick Reference

Module Structure

ModulePurposeKey Types
backends/LLM inferenceCommandGenerator, BackendInfo
safety/Command validationSafetyValidator, RiskLevel
config/ConfigurationConfig, BackendConfig
cli/User interfaceArgument parsing, output formatting
execution/Command executionShell detection, safe execution

Common Patterns

1. Backend Fallback Chain

// CANONICAL: Try backends in order, fallback gracefully
async fn generate_with_fallback(request: &CommandRequest) -> Result<GeneratedCommand> {
    let backends = vec![
        Box::new(EmbeddedBackend::new()) as Box<dyn CommandGenerator>,
        Box::new(OllamaBackend::new()),
        Box::new(VllmBackend::new()),
    ];

    for backend in backends {
        if backend.is_available().await {
            match backend.generate_command(request).await {
                Ok(cmd) => return Ok(cmd),
                Err(e) => log::warn!("Backend failed: {}", e),
            }
        }
    }

    Err(anyhow!("All backends unavailable"))
}

2. JSON Response Parsing

LLM responses may contain markdown or extra text. Use the canonical extraction pattern:

// CANONICAL: Extract JSON from potentially wrapped responses
fn extract_command_json(response: &str) -> Result<GeneratedCommand> {
    // Try direct parse first
    if let Ok(cmd) = serde_json::from_str(response) {
        return Ok(cmd);
    }

    // Try extracting from code blocks
    if let Some(json_str) = extract_json_block(response) {
        return serde_json::from_str(&json_str)
            .context("Failed to parse extracted JSON");
    }

    // Last resort: regex extraction for {"cmd": "..."} pattern
    extract_command_regex(response)
        .context("No valid command JSON found in response")
}

3. Platform-Specific Logic

Always use runtime detection, not compile-time conditionals for command generation:

// CORRECT: Runtime platform detection
let platform = ExecutionContext::detect();
match platform.os {
    Os::MacOS => generate_bsd_compatible(request),
    Os::Linux => generate_gnu_compatible(request),
    Os::Windows => generate_powershell(request),
}

// WRONG: Compile-time flags for runtime behavior
#[cfg(target_os = "macos")]  // Only use for actual compilation differences
fn generate_command() { }

4. Safety Pattern Matching

Use pre-compiled regex patterns for performance:

// CANONICAL: Pre-compiled patterns in lazy_static
lazy_static! {
    static ref DANGEROUS_PATTERNS: Vec<Regex> = vec![
        Regex::new(r"rm\s+-rf\s+/").unwrap(),
        Regex::new(r"mkfs\.").unwrap(),
        Regex::new(r"dd\s+if=.*/dev/zero").unwrap(),
        // ... 52+ patterns
    ];
}

fn is_dangerous(cmd: &str) -> bool {
    DANGEROUS_PATTERNS.iter().any(|p| p.is_match(cmd))
}

5. Configuration Loading

// CANONICAL: Layered config with defaults
fn load_config() -> Result<Config> {
    let default = Config::default();
    let user_config = load_user_config()?;
    let env_overrides = load_env_overrides()?;

    // Merge in order: default < user < env
    default.merge(user_config).merge(env_overrides)
}

Error Taxonomy

Compilation Errors

ErrorCauseFix
trait bound CommandGenerator is not satisfiedMissing #[async_trait] macroAdd use async_trait::async_trait; and #[async_trait]
cannot borrow as mutableTrying to mutate through shared referenceUse Arc<Mutex<T>> or RwLock<T>
future cannot be sent between threads safelyNon-Send type in asyncEnsure all types implement Send + Sync
lifetime may not live long enoughReference outlives scopeUse owned types or add lifetime bounds
unresolved importMissing dependencyCheck Cargo.toml and feature flags

Runtime Errors

ErrorCauseFix
Backend unavailableService not runningStart Ollama/vLLM or use embedded backend
JSON parse errorLLM returned malformed responseUse extract_command_json() with fallbacks
Command blocked by safetyMatched dangerous patternReview command, adjust safety level if needed
Config not foundMissing config fileUse Config::default() as fallback
Timeout waiting for responseSlow inferenceIncrease timeout or use smaller model

Logic Errors

SymptomCauseFix
Commands fail on macOSGNU vs BSD differenceUse ExecutionContext for platform detection
Safety false positivesPattern too broadAdd context-aware validation
Wrong shell syntaxShell not detectedExplicitly specify shell in config
Commands not executingMissing execution permissionCheck --confirm flag handling

Debugging Workflow

Follow this 5-step process for any issue:

1. Reproduce with Logging

RUST_LOG=debug cargo run -- "your command"

2. Check Component in Isolation

// Unit test the specific component
#[tokio::test]
async fn test_failing_component() {
    let backend = EmbeddedBackend::new();
    let result = backend.generate_command(&request).await;
    assert!(result.is_ok());
}

3. Verify Safety Validation

cargo test --package caro --lib safety::tests

4. Check Platform Detection

let ctx = ExecutionContext::detect();
println!("OS: {:?}, Shell: {:?}", ctx.os, ctx.shell);

5. Inspect Generated Command

Add --verbose or --output json to see full generation details.

Idioms for AI Code Generation

DO Use These Patterns

  1. Struct builders for complex config
BackendConfig::new()
    .with_timeout(Duration::from_secs(30))
    .with_model("qwen2.5-coder")
    .build()
  1. Result types for all fallible operations
fn parse_command(input: &str) -> Result<Command> { }
  1. Explicit type annotations on complex closures
let processor: Box<dyn Fn(&str) -> Result<String>> = Box::new(|s| Ok(s.to_string()));
  1. Constants for magic values
const DEFAULT_TIMEOUT_SECS: u64 = 30;
const MAX_RETRIES: u32 = 3;
  1. Descriptive error messages
return Err(anyhow!(
    "Failed to connect to Ollama at {}. Is the service running?",
    base_url
));

DON'T Use These Anti-Patterns

  1. Unwrap in production code
// WRONG
let config = load_config().unwrap();

// CORRECT
let config = load_config().context("Loading configuration")?;
  1. Panics for recoverable errors
// WRONG
panic!("Backend not available");

// CORRECT
return Err(BackendError::Unavailable);
  1. Stringly-typed APIs
// WRONG
fn set_level(level: &str) { }

// CORRECT
fn set_level(level: RiskLevel) { }
  1. Blocking in async
// WRONG
async fn fetch() {
    std::thread::sleep(Duration::from_secs(1)); // Blocks executor!
}

// CORRECT
async fn fetch() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}
  1. Ignoring errors silently
// WRONG
let _ = risky_operation();

// CORRECT
if let Err(e) = risky_operation() {
    log::warn!("Operation failed: {}", e);
}

Testing Patterns

Unit Tests

Every module has tests in the same file:

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

    #[test]
    fn test_pattern_matching() {
        let validator = SafetyValidator::new();
        assert!(validator.is_dangerous("rm -rf /"));
        assert!(!validator.is_dangerous("ls -la"));
    }
}

Integration Tests

Located in tests/ directory:

// tests/backend_integration.rs
#[tokio::test]
async fn test_backend_fallback() {
    let generator = MultiBackendGenerator::new();
    let result = generator.generate(&request).await;
    assert!(result.is_ok());
}

Property-Based Tests

Use proptest for edge cases:

proptest! {
    #[test]
    fn safety_never_false_negative(cmd in any::<String>()) {
        let validator = SafetyValidator::new();
        // Critical patterns must ALWAYS be detected
        if cmd.contains("rm -rf /") {
            prop_assert!(validator.is_dangerous(&cmd));
        }
    }
}

File Locations

What You NeedWhere To Find It
CLI entry pointsrc/main.rs
Backend traitsrc/backends/mod.rs
Safety patternssrc/safety/mod.rs
Configurationsrc/config/mod.rs
Integration teststests/
Project specsspecs/
Feature worktreeskitty-specs/

Machine-Readable Diagnostics

For AI agents, use these flags for structured output:

# JSON output for programmatic parsing
caro --output json "list files"

# Verbose mode with timing
caro --verbose "find large files"

# Check safety without executing
caro --dry-run "remove old logs"

Version History

VersionKey Changes
v1.1.0Embedded backends, agentic loop, platform detection
v1.0.xInitial release, basic CLI, safety validation

Last updated: 2026-01-12 For project architecture, see docs/development/CLAUDE.md

Related Documents