pub mod adapters;
pub mod assets;
pub mod config;
pub mod export;
pub mod protocol_harness;
pub mod router;
pub mod runner;
pub mod server;
pub mod showcase;
pub mod templates;
pub use runner::{detect_repository, resolve_repository, DemoReport, DemoRunner, DemoStep};
pub use server::{DemoContent, Hotspot, LocalDemoServer};
use anyhow::Result;
use tracing::{debug, info};
pub async fn run_demo(
args: DemoArgs,
server: std::sync::Arc<crate::stateless_server::StatelessTemplateServer>,
) -> Result<()> {
let config = load_demo_config(args, server).await?;
let analyzer = create_analyzer(config.clone())?;
let results = run_analyses(analyzer, &config).await?;
let output = generate_output(results, config.args.protocol)?;
handle_protocol_output(output, &config).await
}
async fn load_demo_config(
args: DemoArgs,
server: std::sync::Arc<crate::stateless_server::StatelessTemplateServer>,
) -> Result<DemoConfig> {
let repo_path =
runner::resolve_repository_async(args.path.clone(), args.url.clone(), args.repo.clone())
.await?;
Ok(DemoConfig {
repo_path,
args,
server,
})
}
fn create_analyzer(config: DemoConfig) -> Result<DemoAnalyzer> {
use adapters::{cli::CliDemoAdapter, http::HttpDemoAdapter, mcp::McpDemoAdapter};
use protocol_harness::DemoEngine;
let mut engine = DemoEngine::new();
engine.register_protocol("cli".to_string(), CliDemoAdapter::new());
engine.register_protocol("http".to_string(), HttpDemoAdapter::new());
engine.register_protocol("mcp".to_string(), McpDemoAdapter::new());
Ok(DemoAnalyzer { engine, config })
}
async fn run_analyses(analyzer: DemoAnalyzer, config: &DemoConfig) -> Result<AnalysisResults> {
if config.args.web {
return Ok(AnalysisResults::Web);
}
#[cfg(feature = "tui")]
if config.args.protocol == Protocol::Tui {
return Ok(AnalysisResults::Tui);
}
if config.args.protocol == Protocol::All {
run_all_protocols(analyzer, config).await
} else {
run_single_protocol(analyzer, config).await
}
}
fn generate_output(results: AnalysisResults, _protocol: Protocol) -> Result<DemoOutput> {
match results {
AnalysisResults::Web => Ok(DemoOutput::Web),
#[cfg(feature = "tui")]
AnalysisResults::Tui => Ok(DemoOutput::Tui),
AnalysisResults::Single(trace) => Ok(DemoOutput::Single(trace)),
AnalysisResults::Multiple(traces) => Ok(DemoOutput::Multiple(traces)),
}
}
async fn handle_protocol_output(output: DemoOutput, config: &DemoConfig) -> Result<()> {
match output {
DemoOutput::Web => {
run_web_demo(
config.repo_path.clone(),
config.server.clone(),
config.args.no_browser,
config.args.port,
)
.await
}
#[cfg(feature = "tui")]
DemoOutput::Tui => run_tui_demo(config.repo_path.clone()).await,
DemoOutput::Single(trace) => {
format_and_print_output(&trace.response, &config.args.format)?;
if config.args.show_api {
print_api_metadata(&trace.protocol_name).await?;
}
Ok(())
}
DemoOutput::Multiple(traces) => {
for trace in traces {
println!("\n=== {} Protocol ===", trace.protocol_name.to_uppercase());
format_and_print_output(&trace.response, &config.args.format)?;
}
Ok(())
}
}
}
fn build_protocol_request(
protocol: &str,
repo_path: &std::path::Path,
show_api: bool,
) -> serde_json::Value {
let path_str = repo_path.to_str().unwrap();
match protocol {
"cli" => serde_json::json!({
"path": path_str,
"show_api": show_api
}),
"http" => serde_json::json!({
"method": "GET",
"path": "/demo/analyze",
"query": {"path": path_str},
"headers": {"Accept": "application/json"}
}),
"mcp" => serde_json::json!({
"jsonrpc": "2.0",
"method": "demo.analyze",
"params": {
"path": path_str,
"include_trace": show_api
},
"id": 1
}),
_ => serde_json::json!({}),
}
}
fn format_and_print_output(
response: &serde_json::Value,
format: &crate::cli::OutputFormat,
) -> Result<()> {
match format {
crate::cli::OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(response)?);
}
crate::cli::OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(response)?);
}
crate::cli::OutputFormat::Table => {
println!("{response:#?}");
}
}
Ok(())
}
async fn print_api_metadata(protocol_name: &str) -> Result<()> {
println!("\n📊 API Introspection");
println!("Protocol: {protocol_name}");
Ok(())
}
async fn run_all_protocols(analyzer: DemoAnalyzer, config: &DemoConfig) -> Result<AnalysisResults> {
println!("🎯 All Protocols Demo");
let mut traces = Vec::new();
for protocol_name in analyzer.engine.list_protocols() {
let request =
build_protocol_request(&protocol_name, &config.repo_path, config.args.show_api);
match analyzer.engine.execute_demo(&protocol_name, request).await {
Ok(trace) => traces.push(ProtocolTrace {
protocol_name: protocol_name.clone(),
response: trace.response,
}),
Err(e) => eprintln!("Error executing {protocol_name} protocol: {e}"),
}
}
Ok(AnalysisResults::Multiple(traces))
}
async fn run_single_protocol(
analyzer: DemoAnalyzer,
config: &DemoConfig,
) -> Result<AnalysisResults> {
let protocol_name = protocol_to_string(&config.args.protocol);
print_protocol_banner(&config.args.protocol);
let request = build_protocol_request(&protocol_name, &config.repo_path, config.args.show_api);
let trace = analyzer
.engine
.execute_demo(&protocol_name, request)
.await?;
Ok(AnalysisResults::Single(ProtocolTrace {
protocol_name,
response: trace.response,
}))
}
fn protocol_to_string(protocol: &Protocol) -> String {
match protocol {
Protocol::Cli => "cli".to_string(),
Protocol::Http => "http".to_string(),
Protocol::Mcp => "mcp".to_string(),
#[cfg(feature = "tui")]
Protocol::Tui => "tui".to_string(),
Protocol::All => "all".to_string(),
}
}
fn print_protocol_banner(protocol: &Protocol) {
match protocol {
Protocol::Cli => println!("🚀 CLI Protocol Demo"),
Protocol::Http => println!("🌐 HTTP Protocol Demo"),
Protocol::Mcp => println!("🔌 MCP Protocol Demo"),
#[cfg(feature = "tui")]
Protocol::Tui => println!("📺 TUI Protocol Demo"),
Protocol::All => println!("🎯 All Protocols Demo"),
}
}
#[derive(Clone)]
struct DemoConfig {
repo_path: std::path::PathBuf,
args: DemoArgs,
server: std::sync::Arc<crate::stateless_server::StatelessTemplateServer>,
}
struct DemoAnalyzer {
engine: protocol_harness::DemoEngine,
#[allow(dead_code)]
config: DemoConfig,
}
enum AnalysisResults {
Web,
#[cfg(feature = "tui")]
Tui,
Single(ProtocolTrace),
Multiple(Vec<ProtocolTrace>),
}
#[derive(Clone)]
struct ProtocolTrace {
protocol_name: String,
response: serde_json::Value,
}
enum DemoOutput {
Web,
#[cfg(feature = "tui")]
Tui,
Single(ProtocolTrace),
Multiple(Vec<ProtocolTrace>),
}
#[allow(dead_code)]
fn extract_analysis_from_demo_report(
demo_report: &crate::demo::DemoReport,
) -> (
Option<crate::services::complexity::ComplexityReport>,
Option<crate::models::dag::DependencyGraph>,
(u64, u64, u64, u64), // timings: (ast, complexity, dag, churn)
) {
let mut complexity_result = None;
let mut dag_result = None;
let mut timings = (0u64, 0u64, 0u64, 0u64);
for step in &demo_report.steps {
process_demo_step(step, &mut complexity_result, &mut dag_result, &mut timings);
}
(complexity_result, dag_result, timings)
}
fn process_demo_step(
step: &crate::demo::DemoStep,
complexity_result: &mut Option<crate::services::complexity::ComplexityReport>,
dag_result: &mut Option<crate::models::dag::DependencyGraph>,
timings: &mut (u64, u64, u64, u64),
) {
match step.capability {
"AST Context Analysis" => process_ast_step(step, timings),
"Code Complexity Analysis" => process_complexity_step(step, complexity_result, timings),
"DAG Visualization" => process_dag_step(step, dag_result, timings),
"Code Churn Analysis" => process_churn_step(step, timings),
_ => {} }
}
fn process_ast_step(step: &crate::demo::DemoStep, timings: &mut (u64, u64, u64, u64)) {
timings.0 = step.elapsed_ms;
}
fn process_complexity_step(
step: &crate::demo::DemoStep,
complexity_result: &mut Option<crate::services::complexity::ComplexityReport>,
timings: &mut (u64, u64, u64, u64),
) {
timings.1 = step.elapsed_ms;
if let Some(result) = &step.response.result {
if let Some(complexity_report) = extract_complexity_from_result(result) {
*complexity_result = Some(complexity_report);
}
}
}
fn process_dag_step(
step: &crate::demo::DemoStep,
dag_result: &mut Option<crate::models::dag::DependencyGraph>,
timings: &mut (u64, u64, u64, u64),
) {
timings.2 = step.elapsed_ms;
if let Some(result) = &step.response.result {
if let Some(dag) = extract_dag_from_result(result) {
*dag_result = Some(dag);
}
}
}
fn process_churn_step(step: &crate::demo::DemoStep, timings: &mut (u64, u64, u64, u64)) {
timings.3 = step.elapsed_ms;
}
fn extract_complexity_from_result(
result: &serde_json::Value,
) -> Option<crate::services::complexity::ComplexityReport> {
let complexity_data = serde_json::from_value::<serde_json::Value>(result.clone()).ok()?;
let report_value = complexity_data.get("report")?;
serde_json::from_value::<crate::services::complexity::ComplexityReport>(report_value.clone())
.ok()
}
fn extract_dag_from_result(
result: &serde_json::Value,
) -> Option<crate::models::dag::DependencyGraph> {
let dag_data = serde_json::from_value::<serde_json::Value>(result.clone()).ok()?;
parse_dag_data(&dag_data)
}
#[allow(dead_code)]
fn parse_dag_data(dag_data: &serde_json::Value) -> Option<crate::models::dag::DependencyGraph> {
let node_count = dag_data.get("nodes")?.as_u64().unwrap_or(0) as usize;
let edge_count = dag_data.get("edges")?.as_u64().unwrap_or(0) as usize;
if node_count > 0 || edge_count > 0 {
return Some(crate::models::dag::DependencyGraph {
nodes: (0..node_count)
.map(|i| {
let node_id = format!("node_{i}");
(
node_id.clone(),
crate::models::dag::NodeInfo {
id: node_id,
label: format!("Module {i}"),
node_type: crate::models::dag::NodeType::Module,
file_path: format!("module_{i}.rs"),
line_number: 1,
complexity: 1,
metadata: Default::default(),
},
)
})
.collect(),
edges: (0..edge_count)
.map(|i| crate::models::dag::Edge {
from: format!("node_{}", i % node_count),
to: format!("node_{}", (i + 1) % node_count),
edge_type: crate::models::dag::EdgeType::Imports,
weight: 1,
})
.collect(),
});
}
None
}
#[allow(dead_code)]
async fn run_web_demo(
repo_path: std::path::PathBuf,
server: std::sync::Arc<crate::stateless_server::StatelessTemplateServer>,
no_browser: bool,
_port: Option<u16>,
) -> Result<()> {
use std::time::Instant;
let version = env!("CARGO_PKG_VERSION");
println!("🎯 PAIML MCP Agent Toolkit Demo v{version}");
println!("📁 Repository: {}", repo_path.display());
println!("\n🔍 Analyzing codebase...");
info!("Starting codebase analysis");
let start = Instant::now();
debug!("Starting demo runner analysis");
let mut demo_runner = DemoRunner::new(server);
let demo_report = demo_runner.execute_with_diagram(&repo_path, None).await?;
let elapsed = start.elapsed().as_millis() as u64;
info!(elapsed_ms = elapsed, "Analysis completed");
let (complexity_result, dag_result, actual_timings) =
extract_analysis_from_demo_report(&demo_report);
let files_analyzed = complexity_result
.as_ref()
.map_or(demo_report.steps.len() * 10, |c| c.files.len()); let avg_complexity = complexity_result
.as_ref()
.map_or(2.5, |c| f64::from(c.summary.median_cyclomatic)); let tech_debt_hours = complexity_result
.as_ref()
.map_or((files_analyzed / 10) as u32, |c| c.summary.technical_debt_hours as u32);
let hotspots = complexity_result
.as_ref()
.map(|c| {
let mut all_functions: Vec<_> = c
.files
.iter()
.flat_map(|file| {
file.functions.iter().map(move |func| Hotspot {
file: format!("{}::{}", file.path, func.name),
complexity: u32::from(func.metrics.cyclomatic),
churn_score: u32::from(func.metrics.cognitive), })
})
.collect();
all_functions.sort_by(|a, b| b.complexity.cmp(&a.complexity));
all_functions.truncate(10);
all_functions
})
.unwrap_or_default();
let dag = dag_result.clone().unwrap_or_default();
let mut content = DemoContent::from_analysis_results(
&dag,
files_analyzed,
avg_complexity,
tech_debt_hours,
hotspots,
actual_timings.0, actual_timings.1,
actual_timings.2,
actual_timings.3,
);
content.system_diagram = demo_report.system_diagram;
let (_demo_server, port) = LocalDemoServer::spawn_with_results(
content,
complexity_result,
None, dag_result,
)
.await?;
let url = format!("http://127.0.0.1:{port}");
println!("\n📊 Demo server running at: {url}");
println!(" Analysis completed in {elapsed} ms");
#[cfg(feature = "demo")]
if !no_browser {
if let Err(e) = webbrowser::open(&url) {
println!(" Please open {url} in your browser (auto-open failed: {e})");
}
}
#[cfg(not(feature = "demo"))]
let _ = no_browser;
println!("\nPress Ctrl+C to stop the demo server");
tokio::signal::ctrl_c().await?;
println!("\n👋 Shutting down demo server...");
Ok(())
}
#[allow(dead_code)]
async fn analyze_context(
repo_path: &std::path::Path,
) -> Result<crate::services::context::ProjectContext> {
crate::services::context::analyze_project(repo_path, "rust")
.await
.map_err(|e| anyhow::anyhow!("Error analyzing project: {e}"))
}
async fn analyze_complexity(
repo_path: &std::path::Path,
) -> Result<crate::services::complexity::ComplexityReport> {
use crate::services::ast_rust::analyze_rust_file_with_complexity;
use crate::services::complexity::aggregate_results;
use walkdir::WalkDir;
let mut file_metrics = Vec::new();
for entry in WalkDir::new(repo_path)
.follow_links(false)
.into_iter()
.filter_map(std::result::Result::ok)
{
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("rs") {
if let Ok(metrics) = analyze_rust_file_with_complexity(path).await {
file_metrics.push(metrics);
}
}
}
Ok(aggregate_results(file_metrics))
}
async fn analyze_dag(repo_path: &std::path::Path) -> Result<crate::models::dag::DependencyGraph> {
use crate::services::dag_builder::DagBuilder;
let context = crate::services::context::analyze_project(repo_path, "rust")
.await
.map_err(|e| anyhow::anyhow!("Error analyzing project: {e}"))?;
let graph = DagBuilder::build_from_project(&context);
Ok(graph)
}
#[allow(dead_code)]
async fn analyze_churn(
repo_path: &std::path::Path,
) -> Result<crate::models::churn::CodeChurnAnalysis> {
crate::services::git_analysis::GitAnalysisService::analyze_code_churn(repo_path, 30)
.map_err(|e| anyhow::anyhow!("Error analyzing churn: {e}"))
}
#[allow(dead_code)]
async fn analyze_system_architecture(
repo_path: &std::path::Path,
) -> Result<crate::services::canonical_query::QueryResult> {
use crate::services::canonical_query::{
AnalysisContext, CallGraph, CanonicalQuery, SystemArchitectureQuery,
};
use rustc_hash::FxHashMap;
let _context_result = analyze_context(repo_path).await?;
let dag_result = analyze_dag(repo_path).await?;
let complexity_result = analyze_complexity(repo_path).await?;
let churn_result = analyze_churn(repo_path).await.ok();
let mut complexity_map = FxHashMap::default();
for file in &complexity_result.files {
for function in &file.functions {
complexity_map.insert(function.name.clone(), function.metrics);
}
}
let context = AnalysisContext {
project_path: repo_path.to_path_buf(),
ast_dag: dag_result,
call_graph: CallGraph::default(), complexity_map,
churn_analysis: churn_result,
};
let query = SystemArchitectureQuery;
query
.execute(&context)
.map_err(|e| anyhow::anyhow!("Error analyzing architecture: {e}"))
}
#[allow(dead_code)]
async fn analyze_defect_probability(
repo_path: &std::path::Path,
) -> Result<crate::services::defect_probability::ProjectDefectAnalysis> {
use crate::services::defect_probability::{
DefectProbabilityCalculator, FileMetrics, ProjectDefectAnalysis,
};
use walkdir::WalkDir;
let calculator = DefectProbabilityCalculator::new();
let mut file_metrics = Vec::new();
let complexity_result = analyze_complexity(repo_path).await?;
let churn_result = analyze_churn(repo_path).await.ok();
let churn_map: std::collections::HashMap<String, f32> = churn_result
.as_ref()
.map(|churn| {
churn
.files
.iter()
.map(|f| (f.relative_path.clone(), f.churn_score))
.collect()
})
.unwrap_or_default();
for entry in WalkDir::new(repo_path)
.follow_links(false)
.into_iter()
.filter_map(std::result::Result::ok)
{
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("rs") {
let relative_path = path
.strip_prefix(repo_path)
.unwrap_or(path)
.to_string_lossy()
.to_string();
if let Some(file_complexity) = complexity_result
.files
.iter()
.find(|f| f.path == relative_path)
{
let churn_score = churn_map.get(&relative_path).copied().unwrap_or(0.0);
let total_complexity: f32 = file_complexity
.functions
.iter()
.map(|f| f32::from(f.metrics.cyclomatic))
.sum();
let avg_complexity = if file_complexity.functions.is_empty() {
1.0
} else {
total_complexity / file_complexity.functions.len() as f32
};
let max_cyclomatic = file_complexity
.functions
.iter()
.map(|f| f.metrics.cyclomatic)
.max()
.unwrap_or(1);
let max_cognitive = file_complexity
.functions
.iter()
.map(|f| f.metrics.cognitive)
.max()
.unwrap_or(1);
let total_loc: usize = file_complexity
.functions
.iter()
.map(|f| f.metrics.lines as usize)
.sum();
let metrics = FileMetrics {
file_path: relative_path,
churn_score,
complexity: avg_complexity,
duplicate_ratio: 0.0, afferent_coupling: 0.0, efferent_coupling: 0.0,
lines_of_code: total_loc,
cyclomatic_complexity: u32::from(max_cyclomatic),
cognitive_complexity: u32::from(max_cognitive),
};
file_metrics.push(metrics);
}
}
}
let scores = calculator.calculate_batch(&file_metrics);
Ok(ProjectDefectAnalysis::from_scores(scores))
}
#[derive(Debug, Clone)]
pub struct DemoArgs {
pub path: Option<std::path::PathBuf>,
pub url: Option<String>,
pub repo: Option<String>,
pub format: crate::cli::OutputFormat,
pub no_browser: bool,
pub port: Option<u16>,
pub web: bool,
pub target_nodes: usize,
pub centrality_threshold: f64,
pub merge_threshold: usize,
pub protocol: Protocol,
pub show_api: bool,
pub debug: bool,
pub debug_output: Option<std::path::PathBuf>,
pub skip_vendor: bool,
pub max_line_length: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Protocol {
Cli,
Http,
Mcp,
#[cfg(feature = "tui")]
Tui,
All,
}
#[cfg(feature = "tui")]
async fn run_tui_demo(repo_path: std::path::PathBuf) -> Result<()> {
use adapters::tui::TuiDemoAdapter;
println!("📺 Starting TUI Demo for: {}", repo_path.display());
let mut adapter = TuiDemoAdapter::new()
.map_err(|e| anyhow::anyhow!("Failed to create TUI adapter: {}", e))?;
adapter
.initialize()
.await
.map_err(|e| anyhow::anyhow!("Failed to initialize TUI: {}", e))?;
let analyze_request = crate::demo::adapters::tui::TuiRequest {
action: "analyze".to_string(),
params: {
let mut params = std::collections::HashMap::new();
params.insert(
"path".to_string(),
serde_json::Value::String(repo_path.to_string_lossy().into_owned()),
);
params
},
};
let _response = adapter
.handle_request(analyze_request)
.await
.map_err(|e| anyhow::anyhow!("Failed to start analysis: {}", e))?;
adapter
.run_event_loop()
.await
.map_err(|e| anyhow::anyhow!("TUI event loop failed: {}", e))?;
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_mod_basic() {
assert_eq!(1 + 1, 2);
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}