#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PmatSubAgent {
ComplexityAnalyst,
MutationTester,
SATDDetector,
DeadCodeEliminator,
DocumentationEnforcer,
RustQualityExpert,
PythonQualityExpert,
TypeScriptQualityExpert,
WasmDeepInspector,
RefactoringAdvisor,
TestCoverageAnalyst,
QualityGateOrchestrator,
}
impl PmatSubAgent {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn name(&self) -> &'static str {
match self {
Self::ComplexityAnalyst => "complexity-analyst",
Self::MutationTester => "mutation-tester",
Self::SATDDetector => "satd-detector",
Self::DeadCodeEliminator => "dead-code-eliminator",
Self::DocumentationEnforcer => "documentation-enforcer",
Self::RustQualityExpert => "rust-quality-expert",
Self::PythonQualityExpert => "python-quality-expert",
Self::TypeScriptQualityExpert => "typescript-quality-expert",
Self::WasmDeepInspector => "wasm-deep-inspector",
Self::RefactoringAdvisor => "refactoring-advisor",
Self::TestCoverageAnalyst => "test-coverage-analyst",
Self::QualityGateOrchestrator => "quality-gate-orchestrator",
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn description(&self) -> &'static str {
match self {
Self::ComplexityAnalyst => {
"Expert in cyclomatic and cognitive complexity analysis, suggests refactorings"
}
Self::MutationTester => {
"Mutation testing specialist with ML prediction and test improvement suggestions"
}
Self::SATDDetector => {
"Technical debt identifier tracking TODO, FIXME, and HACK comments"
}
Self::DeadCodeEliminator => {
"Unused code removal specialist identifying safe-to-delete code"
}
Self::DocumentationEnforcer => {
"Generic description detector enforcing documentation quality standards"
}
Self::RustQualityExpert => {
"Rust-specific quality expert covering ownership, lifetimes, and idiomatic patterns"
}
Self::PythonQualityExpert => {
"Python quality expert for type hints, PEP compliance, and best practices"
}
Self::TypeScriptQualityExpert => {
"TypeScript/JavaScript quality expert for type safety and modern patterns"
}
Self::WasmDeepInspector => {
"WebAssembly bytecode analyst for compiler debugging and optimization"
}
Self::RefactoringAdvisor => {
"AI-powered refactoring advisor using historical pattern learning"
}
Self::TestCoverageAnalyst => {
"Test coverage gap identifier suggesting missing test cases"
}
Self::QualityGateOrchestrator => {
"Coordinates multiple quality checks and synthesizes results"
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_mvp(&self) -> bool {
matches!(
self,
Self::ComplexityAnalyst
| Self::MutationTester
| Self::SATDDetector
| Self::DeadCodeEliminator
| Self::DocumentationEnforcer
)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn primary_tools(&self) -> Vec<&'static str> {
match self {
Self::ComplexityAnalyst => vec!["analyze_complexity", "analyze_cognitive_complexity"],
Self::MutationTester => {
vec!["mutation_test", "mutation_predict", "equivalent_detector"]
}
Self::SATDDetector => vec!["analyze_satd", "analyze_context"],
Self::DeadCodeEliminator => vec!["analyze_dead_code", "analyze_imports"],
Self::DocumentationEnforcer => vec!["check_generic_docs", "analyze_context"],
Self::RustQualityExpert => vec!["analyze_complexity", "analyze_borrow_checker"],
Self::PythonQualityExpert => vec!["analyze_complexity", "analyze_context"],
Self::TypeScriptQualityExpert => vec!["analyze_complexity", "analyze_context"],
Self::WasmDeepInspector => vec!["deep_wasm_analyze", "wasm_disassemble"],
Self::RefactoringAdvisor => vec!["suggest_refactorings", "query_patterns"],
Self::TestCoverageAnalyst => vec!["analyze_coverage", "suggest_tests"],
Self::QualityGateOrchestrator => vec!["run_quality_gates", "aggregate_metrics"],
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn all_mvp() -> Vec<Self> {
vec![
Self::ComplexityAnalyst,
Self::MutationTester,
Self::SATDDetector,
Self::DeadCodeEliminator,
Self::DocumentationEnforcer,
]
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn all() -> Vec<Self> {
vec![
Self::ComplexityAnalyst,
Self::MutationTester,
Self::SATDDetector,
Self::DeadCodeEliminator,
Self::DocumentationEnforcer,
Self::RustQualityExpert,
Self::PythonQualityExpert,
Self::TypeScriptQualityExpert,
Self::WasmDeepInspector,
Self::RefactoringAdvisor,
Self::TestCoverageAnalyst,
Self::QualityGateOrchestrator,
]
}
}
impl std::str::FromStr for PmatSubAgent {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"complexity-analyst" => Ok(Self::ComplexityAnalyst),
"mutation-tester" => Ok(Self::MutationTester),
"satd-detector" => Ok(Self::SATDDetector),
"dead-code-eliminator" => Ok(Self::DeadCodeEliminator),
"documentation-enforcer" => Ok(Self::DocumentationEnforcer),
"rust-quality-expert" => Ok(Self::RustQualityExpert),
"python-quality-expert" => Ok(Self::PythonQualityExpert),
"typescript-quality-expert" => Ok(Self::TypeScriptQualityExpert),
"wasm-deep-inspector" => Ok(Self::WasmDeepInspector),
"refactoring-advisor" => Ok(Self::RefactoringAdvisor),
"test-coverage-analyst" => Ok(Self::TestCoverageAnalyst),
"quality-gate-orchestrator" => Ok(Self::QualityGateOrchestrator),
_ => bail!("Unknown sub-agent: {}", s),
}
}
}
impl std::fmt::Display for PmatSubAgent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
pub struct SubAgentGenerator {
_template_dir: PathBuf,
}
impl SubAgentGenerator {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
_template_dir: PathBuf::from("server/src/scaffold/agent/subagent_templates"),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn with_template_dir(template_dir: PathBuf) -> Self {
Self {
_template_dir: template_dir,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn generate_subagent(&self, agent: PmatSubAgent) -> Result<String> {
if !agent.is_mvp() {
bail!(
"Sub-agent {} is not yet implemented (future phase)",
agent.name()
);
}
match agent {
PmatSubAgent::ComplexityAnalyst => self.generate_complexity_analyst(),
PmatSubAgent::MutationTester => self.generate_mutation_tester(),
PmatSubAgent::SATDDetector => self.generate_satd_detector(),
PmatSubAgent::DeadCodeEliminator => self.generate_dead_code_eliminator(),
PmatSubAgent::DocumentationEnforcer => self.generate_documentation_enforcer(),
_ => bail!("Sub-agent {} not implemented in MVP", agent.name()),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn export_for_claude_code(
&self,
agent: PmatSubAgent,
output_dir: &Path,
) -> Result<PathBuf> {
let content = self.generate_subagent(agent)?;
let filename = format!("{}.md", agent.name());
let output_path = output_dir.join(filename);
std::fs::create_dir_all(output_dir)?;
std::fs::write(&output_path, content)?;
Ok(output_path)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn export_all_mvp(&self, output_dir: &Path) -> Result<Vec<PathBuf>> {
let mut paths = Vec::new();
for agent in PmatSubAgent::all_mvp() {
let path = self.export_for_claude_code(agent, output_dir)?;
paths.push(path);
}
Ok(paths)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_tool_mapping() -> HashMap<PmatSubAgent, Vec<&'static str>> {
let mut mapping = HashMap::new();
for agent in PmatSubAgent::all() {
mapping.insert(agent, agent.primary_tools());
}
mapping
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
fn generate_complexity_analyst(&self) -> Result<String> {
Ok(include_str!("subagent_templates/complexity_analyst.md.tmpl").to_string())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
fn generate_mutation_tester(&self) -> Result<String> {
Ok(include_str!("subagent_templates/mutation_tester.md.tmpl").to_string())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
fn generate_satd_detector(&self) -> Result<String> {
Ok(include_str!("subagent_templates/satd_detector.md.tmpl").to_string())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
fn generate_dead_code_eliminator(&self) -> Result<String> {
Ok(include_str!("subagent_templates/dead_code_eliminator.md.tmpl").to_string())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
fn generate_documentation_enforcer(&self) -> Result<String> {
Ok(include_str!("subagent_templates/documentation_enforcer.md.tmpl").to_string())
}
}
impl Default for SubAgentGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subagent_name() {
assert_eq!(PmatSubAgent::ComplexityAnalyst.name(), "complexity-analyst");
assert_eq!(PmatSubAgent::MutationTester.name(), "mutation-tester");
}
#[test]
fn test_subagent_from_str() {
let agent: PmatSubAgent = "complexity-analyst".parse().unwrap();
assert_eq!(agent, PmatSubAgent::ComplexityAnalyst);
let agent: PmatSubAgent = "mutation-tester".parse().unwrap();
assert_eq!(agent, PmatSubAgent::MutationTester);
}
#[test]
fn test_subagent_from_str_invalid() {
let result: Result<PmatSubAgent> = "invalid-agent".parse();
assert!(result.is_err());
}
#[test]
fn test_all_mvp_agents() {
let agents = PmatSubAgent::all_mvp();
assert_eq!(agents.len(), 5);
assert!(agents.iter().all(|a| a.is_mvp()));
}
#[test]
fn test_primary_tools() {
let tools = PmatSubAgent::ComplexityAnalyst.primary_tools();
assert!(tools.contains(&"analyze_complexity"));
let tools = PmatSubAgent::MutationTester.primary_tools();
assert!(tools.contains(&"mutation_test"));
assert!(tools.contains(&"mutation_predict"));
}
#[test]
fn test_tool_mapping() {
let mapping = SubAgentGenerator::get_tool_mapping();
assert!(!mapping.is_empty());
let complexity_tools = mapping.get(&PmatSubAgent::ComplexityAnalyst).unwrap();
assert!(complexity_tools.contains(&"analyze_complexity"));
}
#[test]
fn test_generator_creation() {
let gen = SubAgentGenerator::new();
assert!(gen
._template_dir
.to_string_lossy()
.contains("subagent_templates"));
}
#[test]
fn test_non_mvp_agent_generates_error() {
let gen = SubAgentGenerator::new();
let result = gen.generate_subagent(PmatSubAgent::RustQualityExpert);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("not yet implemented"));
}
#[test]
fn test_name_covers_all_variants() {
for agent in PmatSubAgent::all() {
let name = agent.name();
assert!(!name.is_empty(), "variant name is empty");
let parsed: PmatSubAgent = name.parse().expect("round-trip parse");
assert_eq!(parsed, agent, "FromStr inverse of name failed");
}
}
#[test]
fn test_description_non_empty_and_unique_across_variants() {
let descs: Vec<&'static str> = PmatSubAgent::all()
.iter()
.map(|a| a.description())
.collect();
assert_eq!(descs.len(), 12);
for d in &descs {
assert!(!d.is_empty(), "description is empty");
}
let mut sorted = descs.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), descs.len(), "descriptions not unique");
}
#[test]
fn test_is_mvp_false_for_future_variants() {
let future = [
PmatSubAgent::RustQualityExpert,
PmatSubAgent::PythonQualityExpert,
PmatSubAgent::TypeScriptQualityExpert,
PmatSubAgent::WasmDeepInspector,
PmatSubAgent::RefactoringAdvisor,
PmatSubAgent::TestCoverageAnalyst,
PmatSubAgent::QualityGateOrchestrator,
];
for a in future {
assert!(!a.is_mvp(), "{} should not be MVP", a.name());
}
}
#[test]
fn test_primary_tools_non_empty_for_all_variants() {
for agent in PmatSubAgent::all() {
let tools = agent.primary_tools();
assert!(
!tools.is_empty(),
"primary_tools empty for {}",
agent.name()
);
for t in tools {
assert!(!t.is_empty());
}
}
}
#[test]
fn test_primary_tools_specific_mappings() {
assert_eq!(
PmatSubAgent::SATDDetector.primary_tools(),
vec!["analyze_satd", "analyze_context"]
);
assert_eq!(
PmatSubAgent::DeadCodeEliminator.primary_tools(),
vec!["analyze_dead_code", "analyze_imports"]
);
assert_eq!(
PmatSubAgent::DocumentationEnforcer.primary_tools(),
vec!["check_generic_docs", "analyze_context"]
);
assert_eq!(
PmatSubAgent::WasmDeepInspector.primary_tools(),
vec!["deep_wasm_analyze", "wasm_disassemble"]
);
assert_eq!(
PmatSubAgent::QualityGateOrchestrator.primary_tools(),
vec!["run_quality_gates", "aggregate_metrics"]
);
}
#[test]
fn test_all_returns_full_roster() {
let all = PmatSubAgent::all();
assert_eq!(all.len(), 12);
assert_eq!(&all[..5], &PmatSubAgent::all_mvp()[..]);
let mut sorted = all.clone();
sorted.sort_by_key(|a| a.name());
sorted.dedup();
assert_eq!(sorted.len(), 12);
}
#[test]
fn test_from_str_round_trip_all_variants() {
for a in PmatSubAgent::all() {
let parsed: PmatSubAgent = a.name().parse().expect("parse");
assert_eq!(parsed.name(), a.name());
}
}
#[test]
fn test_display_matches_name() {
for a in PmatSubAgent::all() {
assert_eq!(format!("{}", a), a.name());
}
}
#[test]
fn test_from_str_error_message_names_input() {
let err = "not-a-real-agent"
.parse::<PmatSubAgent>()
.unwrap_err()
.to_string();
assert!(err.contains("not-a-real-agent"), "err was: {err}");
}
#[test]
fn test_with_template_dir_sets_field() {
let dir = std::path::PathBuf::from("/tmp/custom-subagent-templates");
let gen = SubAgentGenerator::with_template_dir(dir.clone());
assert_eq!(gen._template_dir, dir);
}
#[test]
fn test_default_matches_new() {
let a = SubAgentGenerator::default();
let b = SubAgentGenerator::new();
assert_eq!(a._template_dir, b._template_dir);
}
#[test]
fn test_generate_subagent_returns_non_empty_for_all_mvp() {
let gen = SubAgentGenerator::new();
for agent in PmatSubAgent::all_mvp() {
let content = gen.generate_subagent(agent).expect("MVP generate ok");
assert!(!content.is_empty(), "empty content for {}", agent.name());
}
}
#[test]
fn test_export_for_claude_code_writes_file_and_returns_path() {
let tmp = tempfile::TempDir::new().expect("tmp");
let gen = SubAgentGenerator::new();
let path = gen
.export_for_claude_code(PmatSubAgent::ComplexityAnalyst, tmp.path())
.expect("export");
assert_eq!(path, tmp.path().join("complexity-analyst.md"));
let disk = std::fs::read_to_string(&path).expect("read");
let expected = gen
.generate_subagent(PmatSubAgent::ComplexityAnalyst)
.unwrap();
assert_eq!(disk, expected);
}
#[test]
fn test_export_for_claude_code_creates_missing_directory() {
let tmp = tempfile::TempDir::new().expect("tmp");
let nested = tmp.path().join("does/not/exist/yet");
assert!(!nested.exists());
let gen = SubAgentGenerator::new();
let path = gen
.export_for_claude_code(PmatSubAgent::SATDDetector, &nested)
.expect("export with nested");
assert!(path.exists());
assert!(nested.is_dir());
}
#[test]
fn test_export_for_claude_code_non_mvp_propagates_error() {
let tmp = tempfile::TempDir::new().expect("tmp");
let gen = SubAgentGenerator::new();
let err = gen
.export_for_claude_code(PmatSubAgent::RefactoringAdvisor, tmp.path())
.unwrap_err()
.to_string();
assert!(err.contains("not yet implemented"), "err was: {err}");
assert!(!tmp.path().join("refactoring-advisor.md").exists());
}
#[test]
fn test_export_all_mvp_writes_five_files() {
let tmp = tempfile::TempDir::new().expect("tmp");
let gen = SubAgentGenerator::new();
let paths = gen.export_all_mvp(tmp.path()).expect("export all");
assert_eq!(paths.len(), 5);
for p in &paths {
assert!(p.exists(), "missing {}", p.display());
assert!(p.starts_with(tmp.path()));
}
for agent in PmatSubAgent::all_mvp() {
let expected = tmp.path().join(format!("{}.md", agent.name()));
assert!(
paths.contains(&expected),
"missing path for {}",
agent.name()
);
}
}
#[test]
fn test_get_tool_mapping_covers_every_variant() {
let mapping = SubAgentGenerator::get_tool_mapping();
assert_eq!(mapping.len(), 12);
for agent in PmatSubAgent::all() {
let tools = mapping.get(&agent).expect("entry");
assert_eq!(tools, &agent.primary_tools());
}
}
}