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 agileplus_domain::intent_graph::{IntentGraph, NodeType};
use anyhow::{Context, Result};
use clap::Args;
use serde_json;
use std::io::Write;

/// Query nodes from an intent graph.
#[derive(Debug, Args)]
#[command(name = "query")]
pub struct QueryCommand {
    /// Path to the JSON graph file.
    #[arg(short, long)]
    input: String,

    /// Filter by node type (e.g., Intent, Feature, Test).
    #[arg(short, long)]
    node_type: Option<String>,

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

#[derive(Debug, serde::Serialize)]
struct QueryResult {
    nodes: Vec<agileplus_domain::intent_graph::Node>,
    total: usize,
    filtered: usize,
}

impl QueryCommand {
    pub fn run(&self) -> Result<()> {
        let json = std::fs::read_to_string(&self.input)
            .with_context(|| format!("Failed to read input: {}", self.input))?;

        let graph: IntentGraph = serde_json::from_str(&json)
            .with_context(|| format!("Failed to parse JSON from: {}", self.input))?;

        let filtered_nodes: Vec<_> = match &self.node_type {
            Some(nt) => {
                let target_type = nt.parse::<NodeType>().map_err(|e| {
                    anyhow::anyhow!("Invalid node type '{}': {}", nt, e)
                })?;
                graph.nodes.into_iter().filter(|n| n.node_type == target_type).collect()
            }
            None => graph.nodes,
        };

        let result = QueryResult {
            total: graph.metadata.node_count.unwrap_or(0),
            filtered: filtered_nodes.len(),
            nodes: filtered_nodes,
        };

        let out = serde_json::to_string_pretty(&result)?;

        match &self.output {
            Some(path) => {
                std::fs::write(path, &out)
                    .with_context(|| format!("Failed to write output: {}", path))?;
                eprintln!("Query result written to {}", path);
            }
            None => {
                std::io::stdout().write_all(out.as_bytes())?;
                std::io::stdout().write_all(b"\n")?;
            }
        }

        Ok(())
    }
}