capo-agent 0.6.0

Coding-agent library built on motosan-agent-loop. Composable, embeddable.
Documentation
use crate::skills::{render_available_skills_xml, Skill};

pub fn build_system_prompt(tool_names: &[String], skills: &[Skill]) -> String {
    let tools_line = if tool_names.is_empty() {
        "You have no tools available.".to_string()
    } else {
        format!("Available tools: {}.", tool_names.join(", "))
    };

    let mut prompt = format!(
        "You are Capo, a Rust-native coding agent running as a terminal CLI. Keep replies concise. Prefer reading the file before suggesting changes. When you need to run a shell command, use the `bash` tool.\n\n{tools_line}"
    );

    // Skills only meaningful when `read` is available — the model loads
    // skill bodies via the read tool.
    if tool_names.iter().any(|t| t == "read") {
        prompt.push_str(&render_available_skills_xml(skills));
    }
    prompt
}

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

    #[test]
    fn includes_tool_names() {
        let p = build_system_prompt(&["read".into(), "bash".into()], &[]);
        assert!(p.contains("read"));
        assert!(p.contains("bash"));
        assert!(p.contains("Capo"));
    }

    #[test]
    fn degrades_gracefully_with_no_tools() {
        let p = build_system_prompt(&[], &[]);
        assert!(p.contains("no tools"));
    }

    #[test]
    fn omits_skills_block_when_read_tool_absent() {
        use crate::skills::types::{Skill, SkillSource};
        use std::path::PathBuf;
        let s = Skill {
            name: "x".into(),
            description: "d".into(),
            file_path: PathBuf::from("/x.md"),
            base_dir: PathBuf::from("/"),
            disable_model_invocation: false,
            source: SkillSource::Global,
        };
        let p = build_system_prompt(&["bash".into()], &[s]);
        assert!(!p.contains("<available_skills>"));
    }

    #[test]
    fn includes_skills_block_when_read_tool_present() {
        use crate::skills::types::{Skill, SkillSource};
        use std::path::PathBuf;
        let s = Skill {
            name: "x".into(),
            description: "d".into(),
            file_path: PathBuf::from("/x.md"),
            base_dir: PathBuf::from("/"),
            disable_model_invocation: false,
            source: SkillSource::Global,
        };
        let p = build_system_prompt(&["read".into()], &[s]);
        assert!(p.contains("<available_skills>"));
        assert!(p.contains("<name>x</name>"));
    }
}