feat(best_practice): add code quality scanning and commit message validation
- Implemented a code quality scanner that checks for common clean-code violations in Rust source files, including missing documentation, usage of `.unwrap()` in production code, and commented-out code. - Introduced a commit message validator that follows the Conventional Commits specification, ensuring proper formatting and providing suggestions for invalid messages. - Created a unified BestPracticeEngine to encapsulate the functionalities of skills, architecture audits, code quality checks, and commit message validation. - Added tests for both the code quality scanner and commit message validator to ensure reliability and correctness.
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
//! 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<BestPracticeEngine> = 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<String> {
|
||||
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<String> {
|
||||
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): <imperative description>\n");
|
||||
out.push_str(" fix(scope): <imperative description>\n");
|
||||
out.push_str(" chore: <imperative description>\n");
|
||||
out.push_str(" docs: <imperative description>\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}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user