//! Built-in best-practice tools for zesdex. //! //! These tools allow the LLM agent to run architecture audits, code-quality //! scans, commit-message validation, and skill lookups — all as compiled-in //! features of the zesdex binary. //! //! # Tools //! //! | Tool name | Action | //! |-----------|--------| //! | `best_practice` | Run architecture audit, code-quality scan, or skill lookup | //! | `commit_convention` | Validate or suggest commit messages | use crate::best_practice::BestPracticeEngine; use crate::tools::{arg_str, Tool, ToolCtx}; use anyhow::{bail, Result}; use serde_json::{json, Value}; use std::sync::OnceLock; use tracing::instrument; // --------------------------------------------------------------------------- // Shared engine instance (lazy, created once) // --------------------------------------------------------------------------- fn engine() -> &'static BestPracticeEngine { static ENGINE: OnceLock = OnceLock::new(); ENGINE.get_or_init(BestPracticeEngine::new) } // ─────────────────────────────────────────────────────────────────────────── // best_practice // ─────────────────────────────────────────────────────────────────────────── /// Run best-practice audits (architecture, code quality, skills). pub struct BestPractice; impl Tool for BestPractice { fn name(&self) -> &'static str { "best_practice" } fn description(&self) -> &'static str { "Run architecture audit, code-quality scan, embedded-skills lookup, \ or commit-message validation. Sub-actions: 'audit_all', 'audit_layering', \ 'scan_quality', 'list_skills', 'get_skill', 'validate_commit', 'suggest_commit'." } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "action": { "type": "string", "enum": [ "audit_all", "audit_layering", "scan_quality", "list_skills", "get_skill", "validate_commit", "suggest_commit" ], "description": "Which action to perform" }, "workspace": { "type": "string", "description": "Path to workspace root (required for audit/scan actions)" }, "skill_name": { "type": "string", "description": "Skill name to retrieve (required for 'get_skill')" }, "commit_message": { "type": "string", "description": "Commit message to validate (required for 'validate_commit')" }, "commit_type": { "type": "string", "description": "Commit type for template suggestion (e.g. 'feat', 'fix')" }, "commit_scope": { "type": "string", "description": "Optional scope for template suggestion" } }, "required": ["action"] }) } #[instrument(skip(self, _ctx, args))] fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result { let action = arg_str(args, "action")?; let eng = engine(); match action.as_str() { "list_skills" => { let skills = eng.list_skills(); let summaries = eng.skill_summaries(); let mut out = String::from("=== Embedded Best-Practice Skills ===\n\n"); for (name, desc) in &summaries { out.push_str(&format!(" {name:<40} {desc}\n")); } if skills.is_empty() { out.push_str(" (no skills embedded)\n"); } Ok(out) } "get_skill" => { let name = arg_str(args, "skill_name")?; match eng.get_skill(&name) { Some(content) => Ok(content.to_string()), None => { let available = eng.list_skills(); bail!( "Skill '{name}' not found. Available skills: {}", available.join(", ") ) } } } "validate_commit" => { let msg = arg_str(args, "commit_message")?; match eng.validate_commit(&msg) { Ok(()) => Ok("✅ Commit message is valid.".to_string()), Err(errors) => { let mut out = String::from("❌ Commit message validation failed:\n"); for err in &errors { out.push_str(&format!(" - {err}\n")); } // Suggest a template. if let Some(parsed) = eng.parse_commit(&msg) { let tpl = eng.suggest_commit_template(&parsed.type_, parsed.scope.as_deref()); out.push_str(&format!("\nTemplate: {tpl}\n")); } Ok(out) } } } "suggest_commit" => { let type_ = arg_str(args, "commit_type")?; let scope = args.get("commit_scope").and_then(|v| v.as_str()); let tpl = eng.suggest_commit_template(&type_, scope); Ok(format!( "Suggested commit template:\n\n {tpl}\n\n\ Valid types: feat, fix, chore, docs, refactor, test, style, perf, ci, build, revert" )) } "audit_layering" | "audit_all" | "scan_quality" => { let workspace = match args.get("workspace").and_then(|v| v.as_str()) { Some(w) => w.to_string(), None => bail!("'workspace' argument is required for '{action}'"), }; let ws_path = std::path::Path::new(&workspace); // Use the host's dirs data dir when workspace is the default data directory. let report = match action.as_str() { "audit_layering" => { let r = eng.audit_layering(ws_path)?; let mut out = format!( "=== Architecture Layering Audit ===\n\ Files scanned: {}\n", r.files_scanned ); if r.violations.is_empty() { out.push_str(" ✅ No layering violations found.\n"); } else { out.push_str(&format!(" Errors: {}\n", r.error_count())); out.push_str(&format!(" Warnings: {}\n", r.warning_count())); for v in &r.violations { out.push_str(&format!( " [{}] {}:{} — {}\n", v.severity, v.file, v.line, v.message )); } } out } "scan_quality" => { let r = eng.scan_quality(ws_path)?; let mut out = format!( "=== Code Quality Scan ===\n\ Files scanned: {}\n", r.files_scanned ); if r.findings.is_empty() { out.push_str(" ✅ No code-quality issues found.\n"); } else { let by_rule = r.count_by_rule(); out.push_str(" By rule:\n"); for (rule, count) in &by_rule { out.push_str(&format!(" {rule}: {count}\n")); } for f in r.findings.iter().take(15) { out.push_str(&format!( " [{}] {}:{} — {}: {}\n", f.severity, f.file, f.line, f.rule, f.message )); } if r.findings.len() > 15 { out.push_str(&format!( " ... and {} more findings.\n", r.findings.len() - 15 )); } } out } _ => { let combined = eng.audit_all(ws_path)?; combined.format() } }; Ok(report) } other => bail!("Unknown action '{other}'"), } } } // ─────────────────────────────────────────────────────────────────────────── // commit_convention // ─────────────────────────────────────────────────────────────────────────── /// Dedicated commit-message validation tool. pub struct CommitConvention; impl Tool for CommitConvention { fn name(&self) -> &'static str { "commit_convention" } fn description(&self) -> &'static str { "Validate a git commit message against Conventional Commits format (Bahasa Indonesia). \ Checks type, scope, description casing, length, and punctuation." } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "message": { "type": "string", "description": "The full commit message to validate" } }, "required": ["message"] }) } #[instrument(skip(self, _ctx, args))] fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result { let msg = arg_str(args, "message")?; let eng = engine(); // Try to parse for additional context. let info = eng.parse_commit(&msg); match eng.validate_commit(&msg) { Ok(()) => { let mut out = String::from("✅ Valid Conventional Commit.\n"); if let Some(i) = info { out.push_str(&format!(" Type: {}\n", i.type_)); if let Some(ref s) = i.scope { out.push_str(&format!(" Scope: {s}\n")); } out.push_str(&format!(" Breaking: {}\n", i.breaking)); out.push_str(&format!(" Description: {}\n", i.description)); } Ok(out) } Err(errors) => { let mut out = String::from("❌ Invalid commit message:\n"); for err in &errors { out.push_str(&format!(" - {err}\n")); } // Provide a template suggestion. out.push_str("\nExpected format:\n"); out.push_str(" feat(scope): \n"); out.push_str(" fix(scope): \n"); out.push_str(" chore: \n"); out.push_str(" docs: \n"); Ok(out) } } } } #[cfg(test)] mod tests { use super::*; fn test_ctx() -> ToolCtx { // Minimal context for unit tests let temp = std::env::temp_dir().join("zesdex-bptest"); let _ = std::fs::create_dir_all(&temp); ToolCtx::builder() .workspaces(vec![temp.clone()]) .session_dir(temp.clone()) .build() } #[test] fn best_practice_list_skills() { let tool = BestPractice; let args = json!({"action": "list_skills"}); let result = tool.run(&test_ctx(), &args).unwrap(); assert!(result.contains("clean-code"), "should list clean-code: {result}"); assert!(result.contains("commit-convention"), "should list commit-convention: {result}"); } #[test] fn best_practice_get_skill() { let tool = BestPractice; let args = json!({"action": "get_skill", "skill_name": "clean-code"}); let result = tool.run(&test_ctx(), &args).unwrap(); assert!(result.contains("Clean Code"), "should contain skill content: {result}"); } #[test] fn commit_convention_valid() { let tool = CommitConvention; let args = json!({"message": "feat(tool): add best practice audit"}); let result = tool.run(&test_ctx(), &args).unwrap(); assert!(result.contains("✅"), "valid commit should succeed: {result}"); } #[test] fn commit_convention_invalid() { let tool = CommitConvention; let args = json!({"message": "Add new feature"}); let result = tool.run(&test_ctx(), &args).unwrap(); assert!(result.contains("❌"), "invalid commit should fail: {result}"); } }