fledge 0.9.1

Dev-lifecycle CLI — scaffolding, tasks, lanes, plugins, and more.
use anyhow::{bail, Result};
use std::process::Command;

pub struct AskOptions {
    pub question: String,
    pub json: bool,
}

pub fn run(options: AskOptions) -> Result<()> {
    crate::github::ensure_claude_cli()?;

    let prompt = build_prompt(&options.question, options.json);

    let sp = crate::spinner::Spinner::start("Thinking:");

    let output = Command::new("claude").args(["--print", &prompt]).output()?;

    sp.finish();
    println!();

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        if !stderr.is_empty() {
            eprintln!("{stderr}");
        }
        bail!("claude CLI exited with an error.");
    }

    let stdout = String::from_utf8_lossy(&output.stdout);

    if options.json {
        let response = serde_json::json!({
            "question": options.question,
            "answer": stdout.trim(),
        });
        println!("{}", serde_json::to_string_pretty(&response)?);
    } else {
        print!("{stdout}");
    }

    Ok(())
}

fn build_prompt(question: &str, json: bool) -> String {
    let mut prompt = String::from(
        "You are a helpful assistant answering questions about a codebase.\n\
        The user is in a project directory and wants to understand their code.\n\
        Be concise and use markdown formatting.\n",
    );
    if json {
        prompt.push_str("Return your answer as plain text (it will be wrapped in JSON).\n");
    }
    prompt.push_str(&format!("\nQuestion: {question}"));
    prompt
}

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

    #[test]
    fn build_prompt_contains_question() {
        let prompt = build_prompt("how does init work?", false);
        assert!(prompt.contains("how does init work?"));
        assert!(prompt.contains("Question:"));
    }

    #[test]
    fn build_prompt_json_flag_adds_instruction() {
        let prompt = build_prompt("test", true);
        assert!(prompt.contains("plain text"));
    }

    #[test]
    fn build_prompt_no_json_flag_omits_instruction() {
        let prompt = build_prompt("test", false);
        assert!(!prompt.contains("plain text"));
    }

    #[test]
    fn ask_options_stores_question() {
        let opts = AskOptions {
            question: "what is this?".to_string(),
            json: false,
        };
        assert_eq!(opts.question, "what is this?");
        assert!(!opts.json);
    }
}