arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
//! `application_info` tool — the whole-application summary projection.
//!
//! Reuses [`crate::inspection::summarize`] (the shared extractor consumed by
//! `arc inspect app` too) so there is one application-summary
//! implementation, not two (master Reservation #6). Takes no arguments.

use crate::commands::mcp::capability::CapabilitySet;
use crate::commands::mcp::error::McpError;
use crate::commands::mcp::registry::ToolContext;
use crate::inspection::summarize;
use serde_json::Value;

pub(crate) fn call(
    _arguments: &Value,
    _capabilities: &CapabilitySet,
    context: &ToolContext,
) -> Result<Value, McpError> {
    let summary = summarize(&context.uag);
    serde_json::to_value(summary).map_err(McpError::from)
}

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

    fn ctx_with(app: &str, modules: usize, routes: usize, pages: usize) -> ToolContext {
        let mut map = BTreeMap::new();
        for i in 0..modules {
            map.insert(format!("M{i}"), empty_module(&format!("M{i}")));
        }
        let routes_v: Vec<RouteEntry> = (0..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_v: Vec<PageEntry> = (0..pages)
            .map(|i| PageEntry {
                name: format!("P{i}"),
            })
            .collect();
        ToolContext {
            uag: Uag {
                schema_version: 1,
                application: app.into(),
                framework_version: "2026.1.0".into(),
                modules: map,
                routes: routes_v,
                services: vec![],
                pages: pages_v,
            },
        }
    }

    #[test]
    fn returns_summary_with_counts() {
        let ctx = ctx_with("LinkHub", 2, 3, 1);
        let value = call(
            &Value::Null,
            &CapabilitySet::from_options(&crate::cli::McpOptions::default()),
            &ctx,
        )
        .expect("ok");
        let obj = value.as_object().expect("object");
        assert_eq!(obj["application"], "LinkHub");
        assert_eq!(obj["modules"], 2);
        assert_eq!(obj["routes"], 3);
        assert_eq!(obj["pages"], 1);
        assert_eq!(obj["schema_version"], 1);
    }

    #[test]
    fn works_on_empty_uag() {
        let ctx = ToolContext {
            uag: Uag {
                schema_version: 1,
                application: String::new(),
                framework_version: String::new(),
                modules: BTreeMap::new(),
                routes: vec![],
                services: vec![],
                pages: vec![],
            },
        };
        let value = call(
            &Value::Null,
            &CapabilitySet::from_options(&crate::cli::McpOptions::default()),
            &ctx,
        )
        .expect("ok");
        assert_eq!(value["modules"], 0);
    }
}