use agileplus_domain::builder::*;
use agileplus_domain::intent_graph::*;
use anyhow::Result;
use chrono::Utc;
use super::slug::{decompose_features, extract_title, slugify};
pub fn generate_intent_graph(
prompt: &str,
source_system: &str,
agent_id: &str,
) -> Result<IntentGraph> {
let now = Utc::now().to_rfc3339();
let intent_title = extract_title(prompt);
let intent_slug = slugify(&intent_title);
let intent_id = format!("Intent#{}", intent_slug);
let builder = IntentGraphBuilder::new().with_default_metadata();
let (builder, plan_id) = add_plan_node(
builder,
&intent_id,
&intent_slug,
&intent_title,
source_system,
agent_id,
&now,
);
let (builder, feature_nodes) = add_feature_nodes(
builder,
&intent_id,
&intent_slug,
prompt,
source_system,
agent_id,
&now,
);
let (builder, test_nodes) = add_test_nodes(
builder,
&feature_nodes,
&intent_slug,
source_system,
agent_id,
&now,
);
let builder = add_intent_node(
builder,
&intent_id,
&intent_title,
prompt,
source_system,
agent_id,
&now,
);
let builder = wire_edges(
builder,
&intent_id,
&plan_id,
&feature_nodes,
&test_nodes,
source_system,
agent_id,
&now,
);
let mut graph = builder.build().map_err(anyhow::Error::msg)?;
graph.update_counts();
Ok(graph)
}
#[allow(clippy::too_many_arguments)]
fn add_intent_node(
builder: IntentGraphBuilder,
intent_id: &str,
intent_title: &str,
prompt: &str,
source_system: &str,
agent_id: &str,
now: &str,
) -> IntentGraphBuilder {
let intent_meta = MetaBuilder::new()
.source(source_system)
.timestamp(now)
.agent_id(agent_id)
.confidence(0.95)
.build()
.expect("valid intent meta");
let node = NodeBuilder::new()
.id(intent_id)
.node_type(NodeType::Intent)
.dag_stage(DagStage::Intent)
.title(intent_title)
.description(prompt)
.status(Status::Active)
.meta(intent_meta)
.build()
.expect("valid intent node");
builder.node(node)
}
#[allow(clippy::too_many_arguments)]
fn add_plan_node(
builder: IntentGraphBuilder,
_intent_id: &str,
intent_slug: &str,
intent_title: &str,
source_system: &str,
agent_id: &str,
now: &str,
) -> (IntentGraphBuilder, String) {
let plan_id = format!("Plan#{}", intent_slug);
let plan_meta = MetaBuilder::new()
.source(source_system)
.timestamp(now)
.agent_id(agent_id)
.confidence(0.90)
.build()
.expect("valid plan meta");
let node = NodeBuilder::new()
.id(&plan_id)
.node_type(NodeType::Plan)
.dag_stage(DagStage::Plan)
.title(format!("Plan: {}", intent_title))
.status(Status::Draft)
.meta(plan_meta)
.build()
.expect("valid plan node");
(builder.node(node), plan_id)
}
#[allow(clippy::too_many_arguments)]
fn add_feature_nodes(
builder: IntentGraphBuilder,
_intent_id: &str,
intent_slug: &str,
prompt: &str,
source_system: &str,
agent_id: &str,
now: &str,
) -> (IntentGraphBuilder, Vec<Node>) {
let mut builder = builder;
let features = decompose_features(prompt);
let mut nodes = Vec::new();
for (idx, feat_title) in features.iter().enumerate() {
let feat_id = format!("Feature#{}-sub{}", intent_slug, idx + 1);
let meta = MetaBuilder::new()
.source(source_system)
.timestamp(now)
.agent_id(agent_id)
.confidence(0.85)
.build()
.expect("valid feature meta");
let node = NodeBuilder::new()
.id(&feat_id)
.node_type(NodeType::Feature)
.dag_stage(DagStage::Feature)
.title(feat_title)
.status(Status::Draft)
.meta(meta)
.build()
.expect("valid feature node");
nodes.push(node.clone());
builder = builder.node(node);
}
(builder, nodes)
}
#[allow(clippy::too_many_arguments)]
fn add_test_nodes(
builder: IntentGraphBuilder,
feature_nodes: &[Node],
intent_slug: &str,
source_system: &str,
agent_id: &str,
now: &str,
) -> (IntentGraphBuilder, Vec<Node>) {
let mut builder = builder;
let mut nodes = Vec::new();
for (idx, feat) in feature_nodes.iter().enumerate() {
let test_id = format!("Test#{}-sub{}-test", intent_slug, idx + 1);
let meta = MetaBuilder::new()
.source(source_system)
.timestamp(now)
.agent_id(agent_id)
.confidence(0.80)
.build()
.expect("valid test meta");
let node = NodeBuilder::new()
.id(&test_id)
.node_type(NodeType::Test)
.dag_stage(DagStage::Test)
.title(format!("Tests for {}", feat.title))
.status(Status::Draft)
.meta(meta)
.build()
.expect("valid test node");
nodes.push(node.clone());
builder = builder.node(node);
}
(builder, nodes)
}
#[allow(clippy::too_many_arguments)]
fn wire_edges(
builder: IntentGraphBuilder,
intent_id: &str,
plan_id: &str,
feature_nodes: &[Node],
test_nodes: &[Node],
source_system: &str,
agent_id: &str,
now: &str,
) -> IntentGraphBuilder {
let mut builder = builder;
builder = add_edge(
builder,
intent_id,
plan_id,
RelationshipType::TracesTo,
CanonicalLinkType::ParentOf,
Direction::Forward,
0.90,
source_system,
agent_id,
now,
);
for feat in feature_nodes {
builder = add_edge(
builder,
intent_id,
&feat.id.0,
RelationshipType::Implements,
CanonicalLinkType::ParentOf,
Direction::Forward,
0.80,
source_system,
agent_id,
now,
);
}
for feat in feature_nodes {
builder = add_edge(
builder,
plan_id,
&feat.id.0,
RelationshipType::TracesTo,
CanonicalLinkType::ParentOf,
Direction::Forward,
0.75,
source_system,
agent_id,
now,
);
}
for (idx, feat) in feature_nodes.iter().enumerate() {
let test_node = &test_nodes[idx];
builder = add_edge(
builder,
&feat.id.0,
&test_node.id.0,
RelationshipType::Tests,
CanonicalLinkType::Verifies,
Direction::Forward,
0.70,
source_system,
agent_id,
now,
);
}
builder
}
#[allow(clippy::too_many_arguments)]
fn add_edge(
builder: IntentGraphBuilder,
source: &str,
target: &str,
rel: RelationshipType,
link: CanonicalLinkType,
dir: Direction,
confidence: f64,
source_system: &str,
agent_id: &str,
now: &str,
) -> IntentGraphBuilder {
let meta = MetaBuilder::new()
.source(source_system)
.timestamp(now)
.agent_id(agent_id)
.confidence(confidence)
.build()
.expect("valid edge meta");
let edge = EdgeBuilder::new()
.auto_id()
.source(source)
.target(target)
.relationship_type(rel)
.canonical_map(
CanonicalMapBuilder::new()
.link_type(link)
.direction(dir)
.build(),
)
.meta(meta)
.build()
.expect("valid edge");
builder.edge(edge)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_intent_graph_basic() {
let prompt = "Build a CLI for the intent ontology. It should validate the graph and store it.";
let graph = generate_intent_graph(prompt, "test", "test-agent").unwrap();
assert!(!graph.nodes.is_empty(), "graph should have nodes");
let has_intent = graph
.nodes
.iter()
.any(|n| matches!(n.node_type, NodeType::Intent));
let has_plan = graph
.nodes
.iter()
.any(|n| matches!(n.node_type, NodeType::Plan));
assert!(has_intent, "graph should have an intent node");
assert!(has_plan, "graph should have a plan node");
}
}