arcature-cli 2026.2.0

Developer lifecycle CLI for Arcature applications.
Documentation
//! The whole-application summary projection over the UAG.
//!
//! A coarse count of modules, routes, pages, and services — the data behind
//! `arc inspect app` and the MCP `application_info` tool. Pure over `&Uag`;
//! no filesystem access.

use arcature_build::uag::Uag;

/// The high-level application graph summary: counts of each top-level UAG
/// section. Serialized to JSON for `arc inspect app --json` and the MCP
/// `application_info` tool.
#[derive(Debug, Clone, serde::Serialize)]
pub(crate) struct AppSummary {
    /// The application identity (the `application!` name; empty if absent).
    pub application: String,
    /// The framework version the graph was compiled against.
    pub framework_version: String,
    /// The UAG schema version of the loaded artifact.
    pub schema_version: u32,
    /// Number of feature modules.
    pub modules: usize,
    /// Number of routes (flattened, de-duplicated route table).
    pub routes: usize,
    /// Number of page-contract identities.
    pub pages: usize,
    /// Number of services.
    pub services: usize,
}

/// Compute the application summary from a loaded UAG.
pub(crate) fn summarize(uag: &Uag) -> AppSummary {
    AppSummary {
        application: uag.application.clone(),
        framework_version: uag.framework_version.clone(),
        schema_version: uag.schema_version,
        modules: uag.modules.len(),
        routes: uag.routes.len(),
        pages: uag.pages.len(),
        services: uag.services.len(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use arcature_build::uag::schema::{PageEntry, RouteEntry, ServiceEntry};
    use std::collections::BTreeMap;

    fn uag_with(n_modules: usize, n_routes: usize, n_pages: usize) -> Uag {
        let mut modules = BTreeMap::new();
        for i in 0..n_modules {
            modules.insert(
                format!("M{i}"),
                crate::inspection::tests::empty_module(&format!("M{i}")),
            );
        }
        let routes: Vec<RouteEntry> = (0..n_routes)
            .map(|i| RouteEntry {
                method: "get".into(),
                path: format!("/r{i}"),
                name: format!("r{i}"),
                handler: format!("H::r{i}"),
                pages: vec![],
                action_fields: vec![],
                action_type: String::new(),
                query_fields: vec![],
                query_type: String::new(),
                query_array: false,
                query_string_fields: vec![],
                query_string_type: String::new(),
            })
            .collect();
        let pages: Vec<PageEntry> = (0..n_pages)
            .map(|i| PageEntry {
                name: format!("P{i}"),
            })
            .collect();
        Uag {
            schema_version: 1,
            application: "App".into(),
            framework_version: "2026.1.0".into(),
            modules,
            routes,
            services: vec![ServiceEntry {
                name: "S".into(),
                deps: vec![],
            }],
            pages,
        }
    }

    #[test]
    fn summarize_counts_each_section() {
        let uag = uag_with(3, 5, 2);
        let s = summarize(&uag);
        assert_eq!(s.modules, 3);
        assert_eq!(s.routes, 5);
        assert_eq!(s.pages, 2);
        assert_eq!(s.services, 1);
        assert_eq!(s.schema_version, 1);
        assert_eq!(s.application, "App");
    }

    #[test]
    fn summarize_serializes_to_json() {
        let uag = uag_with(1, 1, 1);
        let s = summarize(&uag);
        let json = serde_json::to_string(&s).expect("serialize");
        assert!(json.contains("\"modules\":1"));
        assert!(json.contains("\"routes\":1"));
        assert!(json.contains("\"schema_version\":1"));
    }

    #[test]
    fn summarize_empty_uag_is_zero() {
        let uag = Uag {
            schema_version: 1,
            application: String::new(),
            framework_version: String::new(),
            modules: BTreeMap::new(),
            routes: vec![],
            services: vec![],
            pages: vec![],
        };
        let s = summarize(&uag);
        assert_eq!(s.modules, 0);
        assert_eq!(s.routes, 0);
        assert_eq!(s.pages, 0);
        assert_eq!(s.services, 0);
    }
}