ripl-tui 0.3.3

ripl — a living, breathing TUI framework for AI chat in the shell.
Documentation
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};

/// Load scaffold files from CWD and return combined context string for system prompt.
pub fn load_context() -> Option<String> {
    let files = [
        PathBuf::from("README.md"),
        PathBuf::from(".claude/CLAUDE.md"),
        PathBuf::from("skills/README.md"),
    ];
    let mut parts = Vec::new();
    for path in &files {
        if let Ok(content) = fs::read_to_string(path) {
            let trimmed = content.trim();
            if !trimmed.is_empty() {
                parts.push(format!("# {}\n\n{}", path.display(), trimmed));
            }
        }
    }
    if parts.is_empty() {
        None
    } else {
        Some(parts.join("\n\n---\n\n"))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScaffoldChoice {
    Leave,
    Append,
    Overwrite,
}

/// What the scaffold check determined.
pub enum ScaffoldState {
    /// All files present — nothing to do.
    NoneNeeded,
    /// All files absent — write them silently, no dialog.
    AutoWrite,
    /// Some files exist — ask the user what to do.
    Prompt,
}

pub fn detect_scaffold() -> ScaffoldState {
    let files = scaffold_files();
    let existing = files.iter().filter(|p| p.exists()).count();
    if existing == files.len() {
        ScaffoldState::NoneNeeded
    } else if existing == 0 {
        ScaffoldState::AutoWrite
    } else {
        ScaffoldState::Prompt
    }
}

pub fn apply_scaffold(choice: ScaffoldChoice) -> io::Result<()> {
    let files = scaffold_files();
    for path in files {
        let exists = path.exists();
        match choice {
            ScaffoldChoice::Leave => {
                if !exists {
                    // do nothing
                }
            }
            ScaffoldChoice::Append => {
                ensure_parent(&path)?;
                if exists {
                    let mut file = fs::OpenOptions::new().append(true).open(&path)?;
                    writeln!(file, "\n\n{}", scaffold_template_for(&path))?;
                } else {
                    fs::write(&path, scaffold_template_for(&path))?;
                }
            }
            ScaffoldChoice::Overwrite => {
                ensure_parent(&path)?;
                fs::write(&path, scaffold_template_for(&path))?;
            }
        }
    }
    Ok(())
}

fn scaffold_files() -> Vec<PathBuf> {
    vec![
        PathBuf::from("README.md"),
        PathBuf::from(".claude/CLAUDE.md"),
        PathBuf::from("skills/README.md"),
    ]
}

fn ensure_parent(path: &Path) -> io::Result<()> {
    if let Some(dir) = path.parent() {
        fs::create_dir_all(dir)?;
    }
    Ok(())
}

fn scaffold_template_for(path: &Path) -> String {
    let p = path.to_string_lossy();
    if p.ends_with("README.md") {
        "# Project\n\nDescribe the project, its goals, and how to run it.\n".to_string()
    } else if p.ends_with(".claude/CLAUDE.md") {
        "# CLAUDE.md\n\nInstructions for agents working in this repo.\n".to_string()
    } else if p.ends_with("skills/README.md") {
        "# Skills\n\nList available skills and how to use them.\n".to_string()
    } else {
        "".to_string()
    }
}