sparrow-cli 0.10.0

A local-first Rust agent cockpit — route, run, replay, rewind
use anyhow::Context;
use std::path::{Path, PathBuf};

pub fn prepare_release_docs(root: &Path) -> anyhow::Result<Vec<PathBuf>> {
    let launch_dir = root.join("docs").join("launch");
    std::fs::create_dir_all(&launch_dir)?;
    let ring_release = launch_dir.join("ring-release.md");
    let migration = root.join("docs").join("migration-v0.9.2.md");
    std::fs::create_dir_all(migration.parent().unwrap_or(root))?;

    let perf = read_optional(root.join("artifacts").join("perf-report.md"));
    let changelog = read_optional(root.join("CHANGELOG.md"));
    std::fs::write(&ring_release, ring_release_markdown(&perf, &changelog))
        .with_context(|| format!("failed to write {}", ring_release.display()))?;
    std::fs::write(&migration, migration_markdown())
        .with_context(|| format!("failed to write {}", migration.display()))?;

    Ok(vec![ring_release, migration])
}

pub fn planned_release_doc_paths(root: &Path) -> Vec<PathBuf> {
    vec![
        root.join("docs").join("launch").join("ring-release.md"),
        root.join("docs").join("migration-v0.9.2.md"),
    ]
}

fn ring_release_markdown(perf: &str, changelog: &str) -> String {
    format!(
        "# Sparrow v0.9.2 \"The Ring\"\n\n\
         ## Demo 30s\n\n\
         1. Run `sparrow audit` to map the repo and catch stubs.\n\
         2. Run `sparrow test` to detect and execute the project runner.\n\
         3. Open `sparrow console --fast` for a lean cockpit startup.\n\n\
         ## Measured Before/After\n\n\
         The release notes use only measured values from `artifacts/perf-report.md`.\n\n\
         ```text\n{}\n```\n\n\
         ## What Changed\n\n\
         ```text\n{}\n```\n\n\
         ## Install\n\n\
         ```powershell\ncargo install sparrow-cli\n```\n",
        excerpt(perf, 5000),
        excerpt(changelog, 4000)
    )
}

fn migration_markdown() -> &'static str {
    "# Migration Notes: Sparrow v0.9.2\n\n\
     No breaking config migration is expected for v0.9.2.\n\n\
     - Existing `config.toml` files continue to load.\n\
     - Existing transcripts remain additive-serde compatible.\n\
     - `sparrow console` keeps normal browser auto-open behavior.\n\
     - `sparrow console --fast` is new and intentionally does not auto-open a browser.\n"
}

fn read_optional(path: PathBuf) -> String {
    std::fs::read_to_string(path).unwrap_or_else(|_| "(not available yet)".into())
}

fn excerpt(input: &str, max_chars: usize) -> String {
    input.chars().take(max_chars).collect()
}

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

    #[test]
    fn release_prep_writes_expected_docs() {
        let temp = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(temp.path().join("artifacts")).unwrap();
        std::fs::write(temp.path().join("artifacts/perf-report.md"), "perf values").unwrap();
        std::fs::write(temp.path().join("CHANGELOG.md"), "change values").unwrap();
        let files = prepare_release_docs(temp.path()).unwrap();
        assert_eq!(files.len(), 2);
        assert!(files[0].ends_with("ring-release.md"));
        assert!(
            std::fs::read_to_string(&files[0])
                .unwrap()
                .contains("perf values")
        );
        assert!(
            std::fs::read_to_string(&files[1])
                .unwrap()
                .contains("No breaking config migration")
        );
    }
}