agileplus-cli 0.3.0

Typed intent graphs for engineering work — convert prompts into validated DAGs of intents, features, tasks, tests, and edges, then store, query, and serve them from Rust, HTTP, or MCP.
use std::path::PathBuf;

use agileplus_domain::IntentGraph;
use agileplus_sqlite::Storage;
use anyhow::{anyhow, Context, Result};
use clap::Args;

/// Load a stored intent graph and write it as JSON.
#[derive(Debug, Args)]
#[command(name = "dump")]
pub struct DumpCommand {
    /// Path to the SQLite database.
    #[arg(short, long)]
    db: PathBuf,

    /// Id of the graph to load.
    #[arg(short, long)]
    id: String,

    /// Output file path. If omitted, writes to stdout.
    #[arg(short, long)]
    output: Option<PathBuf>,

    /// Emit compact (single-line) JSON instead of pretty-printed.
    #[arg(long)]
    compact: bool,
}

impl DumpCommand {
    pub fn run(&self) -> Result<()> {
        let storage = Storage::open(&self.db)
            .with_context(|| format!("Failed to open storage at: {}", self.db.display()))?;

        let graph: IntentGraph = storage
            .load_graph(&self.id)
            .with_context(|| format!("Failed to load graph: {}", self.id))?
            .ok_or_else(|| anyhow!("No graph stored with id: {}", self.id))?;

        let json = if self.compact {
            serde_json::to_string(&graph)?
        } else {
            serde_json::to_string_pretty(&graph)?
        };

        match &self.output {
            Some(path) => {
                std::fs::write(path, &json)
                    .with_context(|| format!("Failed to write: {}", path.display()))?;
                println!("Wrote {} bytes to {}", json.len(), path.display());
            }
            None => {
                println!("{json}");
            }
        }
        Ok(())
    }
}