routa-core 0.17.1

Routa.js core domain — models, stores, protocols, and JSON-RPC (transport-agnostic)
use serde::{Deserialize, Serialize};

pub const FEATURE_TREE_SPEC_MANIFEST_RESOURCE_URI: &str =
    "resource://routa/specialists/feature-tree/manifest";

const FEATURE_TREE_SPEC_MANIFEST_JSON: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/manifest.json"
));
const FEATURE_TREE_SPEC_NEXTJS: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/nextjs.spec.md"
));
const FEATURE_TREE_SPEC_AXUM: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/axum.spec.md"
));
const FEATURE_TREE_SPEC_EXPRESS: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/express.spec.md"
));
const FEATURE_TREE_SPEC_SPRING_BOOT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/spring-boot.spec.md"
));
const FEATURE_TREE_SPEC_GIN: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/gin.spec.md"
));
const FEATURE_TREE_SPEC_EGGJS: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../resources/specialists/specs/feature-tree/eggjs.spec.md"
));

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct FeatureTreeFrameworkSpecEntry {
    pub id: String,
    pub title: String,
    pub resource_uri: String,
    pub file_name: String,
    pub description: String,
    pub signals: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct FeatureTreeSpecManifest {
    pub schema_version: usize,
    pub id: String,
    pub description: String,
    pub base_rules_in_prompt: bool,
    pub available_spec_ids: Vec<String>,
    pub selection_rules: Vec<String>,
    pub specs: Vec<FeatureTreeFrameworkSpecEntry>,
}

#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct FeatureTreeSpecResolvedResource {
    pub uri: String,
    pub mime_type: String,
    pub text: String,
}

const FEATURE_TREE_SPEC_FILES: &[(&str, &str)] = &[
    ("nextjs", FEATURE_TREE_SPEC_NEXTJS),
    ("axum", FEATURE_TREE_SPEC_AXUM),
    ("express", FEATURE_TREE_SPEC_EXPRESS),
    ("spring-boot", FEATURE_TREE_SPEC_SPRING_BOOT),
    ("gin", FEATURE_TREE_SPEC_GIN),
    ("eggjs", FEATURE_TREE_SPEC_EGGJS),
];

pub fn get_feature_tree_spec_manifest() -> FeatureTreeSpecManifest {
    serde_json::from_str(FEATURE_TREE_SPEC_MANIFEST_JSON)
        .expect("feature-tree specialist spec manifest should parse")
}

pub fn get_feature_tree_spec_resource_uris() -> Vec<String> {
    get_feature_tree_spec_manifest()
        .specs
        .into_iter()
        .map(|spec| spec.resource_uri)
        .collect()
}

pub fn read_feature_tree_spec_resource(uri: &str) -> Option<FeatureTreeSpecResolvedResource> {
    if uri == FEATURE_TREE_SPEC_MANIFEST_RESOURCE_URI {
        return Some(FeatureTreeSpecResolvedResource {
            uri: uri.to_string(),
            mime_type: "application/json".to_string(),
            text: format!("{FEATURE_TREE_SPEC_MANIFEST_JSON}\n"),
        });
    }

    let manifest = get_feature_tree_spec_manifest();
    let spec = manifest
        .specs
        .iter()
        .find(|entry| entry.resource_uri == uri)?;
    FEATURE_TREE_SPEC_FILES
        .iter()
        .find(|(id, _)| *id == spec.id)
        .map(|(_, source)| FeatureTreeSpecResolvedResource {
            uri: uri.to_string(),
            mime_type: "text/markdown".to_string(),
            text: (*source).to_string(),
        })
}

pub fn build_feature_tree_spec_prompt_section() -> String {
    let manifest = get_feature_tree_spec_manifest();
    let example_uris = manifest
        .specs
        .iter()
        .map(|spec| format!("- {}: {}", spec.id, spec.resource_uri))
        .collect::<Vec<_>>()
        .join("\n");

    [
        "Framework overlay spec access:".to_string(),
        "- Base feature-grouping rules are already built into this system prompt.".to_string(),
        format!(
            "- Read the manifest first: `{FEATURE_TREE_SPEC_MANIFEST_RESOURCE_URI}`."
        ),
        "- If your provider supports MCP resources/read directly, use that.".to_string(),
        "- Otherwise call MCP tool `read_specialist_spec_resource` with the same URI.".to_string(),
        format!(
            "- Current bundled framework spec ids: {}.",
            manifest.available_spec_ids.join(", ")
        ),
        "- After reading the manifest, load only the framework specs supported by repository evidence.".to_string(),
        "- A repository may need multiple framework specs when it has multiple runtime surfaces, such as Next.js plus Axum.".to_string(),
        "- If no matching framework spec exists, continue with the built-in path model and repository evidence only.".to_string(),
        "- Available framework spec resources:".to_string(),
        example_uris,
    ]
    .join("\n")
}

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

    #[test]
    fn reads_feature_tree_spec_manifest_resource() {
        let resource = read_feature_tree_spec_resource(FEATURE_TREE_SPEC_MANIFEST_RESOURCE_URI)
            .expect("expected manifest resource");

        assert_eq!(resource.mime_type, "application/json");
        assert!(resource
            .text
            .contains("\"id\": \"feature-tree-framework-specs\""));
        assert!(resource.text.contains(
            "\"resourceUri\": \"resource://routa/specialists/feature-tree/specs/nextjs\""
        ));
    }

    #[test]
    fn reads_feature_tree_spec_resource() {
        let resource =
            read_feature_tree_spec_resource("resource://routa/specialists/feature-tree/specs/axum")
                .expect("expected axum resource");

        assert_eq!(resource.mime_type, "text/markdown");
        assert!(resource.text.contains("# Axum Feature Surface Overlay"));
        assert!(resource.text.contains("Router::new()"));
    }

    #[test]
    fn builds_feature_tree_prompt_section_with_manifest_uri() {
        let prompt = build_feature_tree_spec_prompt_section();

        assert!(prompt.contains("read_specialist_spec_resource"));
        assert!(prompt.contains(FEATURE_TREE_SPEC_MANIFEST_RESOURCE_URI));
        assert!(prompt.contains("nextjs"));
        assert!(prompt.contains("axum"));
    }
}