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:
asepharyana
2026-07-21 07:21:22 +07:00
parent 55677dd671
commit 8ecc588a3e
20 changed files with 3449 additions and 0 deletions
@@ -0,0 +1,389 @@
//! Architecture layering audit for clean-architecture compliance.
//!
//! Scans Rust source files in the workspace and reports violations of the
//! dependency rule: domain must not import from outer layers, application
//! must not import from infrastructure or interfaces, etc.
//!
//! # Flow
//!
//! `audit_layering(workspace_dir)` → walk `apps/` → classify crate by path →
//! scan `use` statements → match against forbidden crates → collect violations.
use anyhow::Result;
use ignore::Walk;
use std::path::Path;
use tracing::instrument;
/// A single layering violation found during the audit.
#[derive(Debug, Clone)]
pub struct Violation {
/// Severity level.
pub severity: Severity,
/// Which crate layer caused the violation.
pub layer: &'static str,
/// File path relative to workspace root.
pub file: String,
/// Line number (1-indexed).
pub line: usize,
/// Human-readable description.
pub message: String,
}
/// How severe a violation is.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
Info,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => write!(f, "ERROR"),
Severity::Warning => write!(f, "WARN"),
Severity::Info => write!(f, "INFO"),
}
}
}
/// Result of an architecture audit.
#[derive(Debug, Clone)]
pub struct AuditReport {
/// All violations found, grouped by severity.
pub violations: Vec<Violation>,
/// Number of source files scanned.
pub files_scanned: usize,
}
impl AuditReport {
/// True if any ERROR-level violations exist.
pub fn has_errors(&self) -> bool {
self.violations.iter().any(|v| v.severity == Severity::Error)
}
/// Number of errors.
pub fn error_count(&self) -> usize {
self.violations.iter().filter(|v| v.severity == Severity::Error).count()
}
/// Number of warnings.
pub fn warning_count(&self) -> usize {
self.violations.iter().filter(|v| v.severity == Severity::Warning).count()
}
}
/// Classify a path into a clean-architecture layer name.
fn classify_layer(crate_path: &Path) -> Option<&'static str> {
let path_str = crate_path.to_string_lossy();
if path_str.contains("/domain") || path_str.ends_with("/domain") {
Some("domain")
} else if path_str.contains("/application") || path_str.ends_with("/application") {
Some("application")
} else if path_str.contains("/infrastructure") || path_str.ends_with("/infrastructure") {
Some("infrastructure")
} else if path_str.contains("/interfaces/") {
Some("interfaces")
} else if path_str.contains("/gateway") {
Some("gateway")
} else {
None
}
}
/// Forbidden import patterns per layer.
///
/// Returns a list of crate prefixes that the given layer must NOT import.
fn forbidden_imports(layer: &str) -> &'static [&'static str] {
match layer {
"domain" => &[
"zesdex_application",
"zesdex_infrastructure",
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
"tokio",
"axum",
"reqwest",
"rusqlite",
"ratatui",
"crossterm",
"tower",
"tower_http",
"argon2",
"jsonwebtoken",
"rmcp",
"lsp_types",
"tiktoken_rs",
"syntect",
"pulldown_cmark",
"serde_yaml_ng",
"ignore",
"dom_smoothie",
"fast_html2md",
"scraper",
"clap",
],
"application" => &[
"zesdex_infrastructure",
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
],
"infrastructure" => &[
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
],
_ => &[],
}
}
/// Scan a single Rust source file for forbidden imports.
fn scan_file(
file_path: &Path,
layer: &'static str,
root: &Path,
) -> Vec<Violation> {
let mut violations = Vec::new();
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(_) => return violations,
};
let forbidden = forbidden_imports(layer);
if forbidden.is_empty() {
return violations;
}
let relative = file_path
.strip_prefix(root)
.unwrap_or(file_path)
.to_string_lossy()
.to_string();
for (line_num, line) in content.lines().enumerate() {
let trimmed = line.trim();
// Match: `use zesdex_application::...` or `use zesdex_infrastructure::...`
if trimmed.starts_with("use ") {
for &forbidden in forbidden {
let pattern = format!("use {forbidden}");
if trimmed.starts_with(&pattern) || trimmed.starts_with(&format!("use crate::")) {
// `use crate::` in domain could reference domain-only items — skip.
continue;
}
if trimmed.starts_with(&pattern) || trimmed.starts_with(&format!("use {forbidden}::")) {
// Skip test code — test modules commonly import outer layers.
let is_test = content[..content.len().saturating_sub(1)]
.contains("#[cfg(test)]");
if is_test {
continue;
}
violations.push(Violation {
severity: Severity::Error,
layer,
file: relative.clone(),
line: line_num + 1,
message: format!(
"Layer '{layer}' must not depend on '{forbidden}': {trimmed}"
),
});
}
}
}
}
violations
}
/// Run a full architecture layering audit on the workspace at `root`.
///
/// Walks all `.rs` files under `root/apps/`, classifies each by its parent
/// crate, and checks `use` statements against the dependency rule.
#[instrument(skip(root))]
pub fn audit_layering(root: &Path) -> Result<AuditReport> {
let apps_dir = root.join("apps");
if !apps_dir.is_dir() {
return Ok(AuditReport {
violations: vec![Violation {
severity: Severity::Warning,
layer: "workspace",
file: "apps/".to_string(),
line: 0,
message: format!("apps/ directory not found at {}", apps_dir.display()),
}],
files_scanned: 0,
});
}
let mut violations = Vec::new();
let mut files_scanned = 0;
for entry in Walk::new(&apps_dir).flatten() {
if entry.file_type().map_or(true, |ft| !ft.is_file()) {
continue;
}
let path = entry.path();
if path.extension().map_or(true, |e| e != "rs") {
continue;
}
// Determine which crate this file belongs to by walking up.
let layer = path
.ancestors()
.skip(1)
.find_map(|p| classify_layer(p));
if let Some(layer) = layer {
files_scanned += 1;
violations.extend(scan_file(path, layer, root));
}
}
Ok(AuditReport {
violations,
files_scanned,
})
}
/// Count lines of code and nesting depth in a Rust source file.
pub fn check_function_metrics(content: &str) -> Vec<Violation> {
let mut violations = Vec::new();
let mut in_function = false;
let mut fn_start = 0;
let mut fn_name = String::new();
let mut brace_depth = 0;
let mut max_nesting = 0;
let mut current_nesting: i32 = 0;
for (i, line) in content.lines().enumerate() {
let line_num = i + 1;
// Track function entry.
if line.trim().starts_with("fn ") && line.trim().ends_with('{') {
in_function = true;
fn_start = line_num;
fn_name = line.trim().to_string();
brace_depth = 1;
max_nesting = 0;
current_nesting = 0;
continue;
}
if in_function {
for ch in line.chars() {
match ch {
'{' => {
brace_depth += 1;
current_nesting += 1;
max_nesting = max_nesting.max(current_nesting);
}
'}' => {
brace_depth -= 1;
current_nesting = (current_nesting.saturating_sub(1)).max(0);
if brace_depth == 0 {
// End of function — check metrics.
let fn_lines = line_num - fn_start;
if fn_lines > 40 {
violations.push(Violation {
severity: Severity::Warning,
layer: "code",
file: String::new(),
line: fn_start,
message: format!(
"Function too long: {} lines (max 40): {}",
fn_lines, fn_name
),
});
}
if max_nesting >= 4 {
violations.push(Violation {
severity: Severity::Warning,
layer: "code",
file: String::new(),
line: fn_start,
message: format!(
"Deep nesting (level {}) in: {}",
max_nesting, fn_name
),
});
}
in_function = false;
break;
}
}
_ => {}
}
}
}
}
violations
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn domain_layer_forbidden_imports() {
let forbidden = forbidden_imports("domain");
assert!(forbidden.contains(&"tokio"));
assert!(forbidden.contains(&"zesdex_application"));
assert!(forbidden.contains(&"axum"));
}
#[test]
fn application_layer_forbidden_imports() {
let forbidden = forbidden_imports("application");
assert!(forbidden.contains(&"zesdex_infrastructure"));
assert!(!forbidden.contains(&"tokio")); // tokio is allowed in application
}
#[test]
fn classify_layer_works() {
let p = Path::new("/root/apps/domain/src/lib.rs");
assert_eq!(classify_layer(p), Some("domain"));
let p = Path::new("/root/apps/application/src/lib.rs");
assert_eq!(classify_layer(p), Some("application"));
let p = Path::new("/root/apps/gateway/src/main.rs");
assert_eq!(classify_layer(p), Some("gateway"));
}
#[test]
fn function_metrics_short_function_ok() {
let content = "fn ok() {\n let x = 1;\n}\n";
let violations = check_function_metrics(content);
let long: Vec<_> = violations.iter().filter(|v| v.message.contains("Function too long")).collect();
assert!(long.is_empty(), "short function should not trigger");
}
#[test]
fn function_metrics_reports_long_function() {
let mut lines = String::from("fn long() {\n");
for _ in 0..45 {
lines.push_str(" let _ = 1;\n");
}
lines.push_str("}\n");
let violations = check_function_metrics(&lines);
let long: Vec<_> = violations.iter().filter(|v| v.message.contains("Function too long")).collect();
assert!(!long.is_empty(), "long function should trigger warning");
}
}
@@ -0,0 +1,280 @@
//! Code-quality analysis for clean-code compliance.
//!
//! Scans Rust source files for common clean-code violations:
//! - Missing doc comments on `pub` items
//! - `.unwrap()` / `.expect()` in production (non-test) code
//! - `#[allow(...)]` / `#[expect(...)]` compiler bypasses
//! - Magic number literals (integer/float constants)
//! - Commented-out code blocks
//!
//! # Flow
//!
//! `scan_file(path, root)` → read source → classify lines into scopes →
//! match each rule → return `Vec<Finding>`.
use anyhow::Result;
use ignore::Walk;
use std::path::Path;
use tracing::instrument;
/// A single code-quality finding.
#[derive(Debug, Clone)]
pub struct Finding {
pub severity: super::arch_audit::Severity,
pub rule: &'static str,
pub file: String,
pub line: usize,
pub message: String,
}
/// Result of a code-quality scan.
#[derive(Debug, Clone)]
pub struct CodeQualityReport {
pub findings: Vec<Finding>,
pub files_scanned: usize,
}
impl CodeQualityReport {
pub fn has_errors(&self) -> bool {
self.findings.iter().any(|f| f.severity == super::arch_audit::Severity::Error)
}
pub fn count_by_rule(&self) -> Vec<(&'static str, usize)> {
let mut counts: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
for f in &self.findings {
*counts.entry(f.rule).or_default() += 1;
}
let mut sorted: Vec<_> = counts.into_iter().collect();
sorted.sort_by_key(|(_, c)| *c);
sorted.reverse();
sorted
}
}
/// Check if a line is inside a `#[cfg(test)]` block.
fn is_in_test_block(content: &[&str], line_idx: usize) -> bool {
let mut depth = 0i32;
let mut in_test = false;
let mut entered_scope = false;
for (i, line) in content.iter().enumerate() {
if i > line_idx {
break;
}
if line.contains("#[cfg(test)]") {
in_test = true;
}
depth += line.matches('{').count() as i32;
if in_test && depth > 0 {
entered_scope = true;
}
depth -= line.matches('}').count() as i32;
if in_test && entered_scope && depth == 0 && i < line_idx {
in_test = false;
entered_scope = false;
}
}
in_test
}
/// Scan a single Rust file for code-quality violations.
pub fn scan_quality_file(file_path: &Path, root: &Path) -> Vec<Finding> {
let mut findings = Vec::new();
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(_) => return findings,
};
let relative = file_path
.strip_prefix(root)
.unwrap_or(file_path)
.to_string_lossy()
.to_string();
let lines: Vec<&str> = content.lines().collect();
// Track pub items for doc-comment checking.
let mut prev_line_doc = false;
let mut prev_line_empty = false;
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
let line_num = i + 1;
let in_test = is_in_test_block(&lines, i);
// ── Rule: Compiler bypass ──────────────────────────────────────
if trimmed.starts_with("#[allow(") || trimmed.starts_with("#[expect(") {
// Skip if this is in the workspace lints config (Cargo.toml)
if trimmed.contains("clippy::") || trimmed.contains("dead_code") {
findings.push(Finding {
severity: super::arch_audit::Severity::Error,
rule: "compiler-bypass",
file: relative.clone(),
line: line_num,
message: format!("Compiler bypass attribute found: {trimmed}"),
});
}
}
// ── Rule: unwrap / expect in production code ───────────────────
if !in_test {
if let Some(col) = trimmed.find(".unwrap(") {
// Allow unwrap in test code and in `#[]` attributes
if !trimmed.starts_with("//") {
let snippet = &trimmed[col..(col + 20).min(trimmed.len())];
findings.push(Finding {
severity: super::arch_audit::Severity::Warning,
rule: "unwrap-in-production",
file: relative.clone(),
line: line_num,
message: format!(
"Use `?` or proper error handling instead of `.unwrap()`: {snippet}..."
),
});
}
}
if let Some(col) = trimmed.find(".expect(") {
if !trimmed.starts_with("//") {
let snippet = &trimmed[col..(col + 20).min(trimmed.len())];
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "expect-in-production",
file: relative.clone(),
line: line_num,
message: format!(".expect() with message: {snippet}..."),
});
}
}
}
// ── Rule: Missing doc comments on pub items ────────────────────
if trimmed.starts_with("pub ") || trimmed.starts_with("pub(") {
if !prev_line_doc && !prev_line_empty {
// Check it's a struct/enum/fn/trait/type/const/mod
let is_item = trimmed.starts_with("pub fn ")
|| trimmed.starts_with("pub struct ")
|| trimmed.starts_with("pub enum ")
|| trimmed.starts_with("pub trait ")
|| trimmed.starts_with("pub type ")
|| trimmed.starts_with("pub const ")
|| trimmed.starts_with("pub mod ")
|| trimmed.starts_with("pub(crate) fn ")
|| trimmed.starts_with("pub(crate) struct ")
|| trimmed.starts_with("pub(crate) enum ")
|| trimmed.starts_with("pub(crate) trait ");
if is_item {
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "missing-doc",
file: relative.clone(),
line: line_num,
message: format!("Missing doc comment on pub item: {trimmed}"),
});
}
}
}
// ── Rule: Commented-out code ───────────────────────────────────
if trimmed.starts_with("// ") && !in_test {
let stripped = trimmed.trim_start_matches("// ");
if stripped.starts_with("fn ")
|| stripped.starts_with("let ")
|| stripped.starts_with("if ")
|| stripped.starts_with("for ")
|| stripped.starts_with("while ")
|| stripped.starts_with("match ")
|| stripped.starts_with("pub ")
|| stripped.starts_with("impl ")
{
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "commented-code",
file: relative.clone(),
line: line_num,
message: format!("Commented-out code detected: {trimmed}"),
});
}
}
// Track doc-comment state
prev_line_doc = trimmed.starts_with("///") || trimmed.starts_with("//!");
prev_line_empty = trimmed.is_empty() || trimmed == "//";
}
findings
}
/// Scan an entire workspace for code-quality violations.
#[instrument(skip(root))]
pub fn scan_quality(root: &Path) -> Result<CodeQualityReport> {
let apps_dir = root.join("apps");
if !apps_dir.is_dir() {
return Ok(CodeQualityReport {
findings: vec![],
files_scanned: 0,
});
}
let mut findings = Vec::new();
let mut files_scanned = 0;
for entry in Walk::new(&apps_dir).flatten() {
if entry.file_type().map_or(true, |ft| !ft.is_file()) {
continue;
}
let path = entry.path();
if path.extension().map_or(true, |e| e != "rs") {
continue;
}
files_scanned += 1;
findings.extend(scan_quality_file(path, root));
}
Ok(CodeQualityReport {
findings,
files_scanned,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_unwrap_in_production_code() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(&file, "fn x() { let y = foo.unwrap(); }\n").unwrap();
let findings = scan_quality_file(&file, &dir);
let unwrap_findings: Vec<_> = findings.iter().filter(|f| f.rule == "unwrap-in-production").collect();
assert!(!unwrap_findings.is_empty(), "should detect unwrap");
}
#[test]
fn skips_unwrap_in_test_block() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(
&file,
"#[cfg(test)]\nmod tests {\n fn x() { let y = foo.unwrap(); }\n}\n",
)
.unwrap();
let findings = scan_quality_file(&file, &dir);
let unwrap_findings: Vec<_> = findings.iter().filter(|f| f.rule == "unwrap-in-production").collect();
assert!(unwrap_findings.is_empty(), "should skip unwrap in test blocks");
}
#[test]
fn detects_allow_attributes() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(&file, "#[allow(clippy::too_many_arguments)]\nfn x() {}\n").unwrap();
let findings = scan_quality_file(&file, &dir);
let bypass_findings: Vec<_> = findings.iter().filter(|f| f.rule == "compiler-bypass").collect();
assert!(!bypass_findings.is_empty(), "should detect allow attributes");
}
}
@@ -0,0 +1,256 @@
//! Commit message validation following Conventional Commits (Bahasa Indonesia).
//!
//! # Flow
//!
//! `validate_commit_message(msg)` → parse subject → check type, scope,
//! description format → return list of errors (empty = valid).
//!
//! # Format
//!
//! ```text
//! feat(scope): description
//! fix(scope): description
//! chore: description
//! docs: description
//! refactor: description
//! test: description
//! style: description
//! perf: description
//! ci: description
//! ```
use regex::Regex;
/// Valid commit types.
const VALID_TYPES: &[&str] = &[
"feat", "fix", "chore", "docs", "refactor", "test", "style", "perf", "ci", "build", "revert",
];
/// Validate a commit message against the Conventional Commits spec.
///
/// Returns `Ok(())` if valid, or `Err(errors)` with human-readable messages.
pub fn validate_commit_message(msg: &str) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
let msg = msg.trim();
if msg.is_empty() {
errors.push("Commit message must not be empty.".to_string());
return Err(errors);
}
// Split subject and body.
let subject = msg.lines().next().unwrap_or(msg);
// Check subject length.
if subject.len() > 72 {
errors.push(format!(
"Subject line is {} characters (max 72).",
subject.len()
));
}
// Parse: `type(scope): description` or `type!: description` or `type: description`
let re = Regex::new(
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s+(?P<desc>.+)$",
)
.expect("valid regex for commit parsing");
match re.captures(subject) {
None => {
errors.push(format!(
"Subject does not match Conventional Commits format.\n\
Expected: `type(scope): description`\n\
Got: {subject}\n\
Valid types: {}",
VALID_TYPES.join(", ")
));
}
Some(caps) => {
let type_ = caps.name("type").map(|m| m.as_str()).unwrap_or("");
let scope = caps.name("scope").map(|m| m.as_str());
let desc = caps.name("desc").map(|m| m.as_str()).unwrap_or("");
// Validate type.
if !VALID_TYPES.contains(&type_) {
errors.push(format!(
"Invalid commit type '{type_}'. Valid types: {}",
VALID_TYPES.join(", ")
));
}
// Scope is lowercase, no spaces.
if let Some(s) = scope {
if s.contains(' ') {
errors.push(format!("Scope must not contain spaces: '{s}'"));
}
if s.chars().any(|c| c.is_uppercase()) {
errors.push(format!("Scope must be lowercase: '{s}'"));
}
}
// Description rules.
if desc.is_empty() {
errors.push("Description must not be empty.".to_string());
} else {
let first_char = desc.chars().next().unwrap_or(' ');
if first_char.is_uppercase() {
errors.push(format!(
"Description must start with lowercase: '{desc}'"
));
}
if desc.ends_with('.') {
errors.push(format!(
"Description must not end with a period: '{desc}'"
));
}
}
// Type-specific rules.
match type_ {
"chore" | "docs" | "refactor" | "test" | "style" | "perf" | "ci" | "build"
| "revert" => {
if scope.is_some() {
errors.push(format!(
"'{type_}' commits should not use a scope. \
Only 'feat' and 'fix' require scopes."
));
}
}
_ => {}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
/// Convert a commit message to a structured representation.
pub struct CommitInfo {
pub type_: String,
pub scope: Option<String>,
pub breaking: bool,
pub description: String,
pub body: Option<String>,
}
/// Parse a commit message into its structured components.
pub fn parse_commit_message(msg: &str) -> Option<CommitInfo> {
let msg = msg.trim();
let subject = msg.lines().next()?;
let re = Regex::new(
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s+(?P<desc>.+)$",
)
.expect("valid regex");
let caps = re.captures(subject)?;
let body_lines: Vec<&str> = msg.lines().skip(1).collect();
let body = if body_lines.is_empty() {
None
} else {
Some(body_lines.join("\n"))
};
Some(CommitInfo {
type_: caps.name("type").map(|m| m.as_str()).unwrap_or("").to_string(),
scope: caps.name("scope").map(|m| m.as_str().to_string()),
breaking: caps.name("breaking").is_some(),
description: caps.name("desc").map(|m| m.as_str()).unwrap_or("").to_string(),
body,
})
}
/// Suggest a commit message template for a given change type.
pub fn suggest_template(type_: &str, scope: Option<&str>) -> String {
match type_ {
"feat" | "fix" if scope.is_some() => {
format!("{}({}): <imperative description>", type_, scope.unwrap())
}
"feat" | "fix" => {
format!("{}(<scope>): <imperative description>", type_)
}
_ => {
format!("{}: <imperative description>", type_)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_feat_commit() {
assert!(validate_commit_message("feat(tool): add batch file delete").is_ok());
}
#[test]
fn valid_fix_commit() {
assert!(validate_commit_message("fix(ipc): reconnect loop on socket timeout").is_ok());
}
#[test]
fn valid_chore_commit() {
assert!(validate_commit_message("chore: bump reqwest to 0.13").is_ok());
}
#[test]
fn valid_docs_commit() {
assert!(validate_commit_message("docs: add architecture diagram to README").is_ok());
}
#[test]
fn rejects_empty_message() {
assert!(validate_commit_message("").is_err());
}
#[test]
fn rejects_missing_colon() {
assert!(validate_commit_message("feat:missing space").is_err());
}
#[test]
fn rejects_uppercase_description() {
let result = validate_commit_message("feat(tool): Add new feature");
assert!(result.is_err(), "should reject uppercase start");
}
#[test]
fn rejects_trailing_period() {
let result = validate_commit_message("feat(tool): add new feature.");
assert!(result.is_err(), "should reject trailing period");
}
#[test]
fn rejects_invalid_type() {
let result = validate_commit_message("wat(tool): something broke");
assert!(result.is_err(), "should reject invalid type");
}
#[test]
fn parses_valid_commit() {
let parsed = parse_commit_message("feat(agent): add parallel execution\n\nWith cycle orchestration.").unwrap();
assert_eq!(parsed.type_, "feat");
assert_eq!(parsed.scope, Some("agent".to_string()));
assert!(!parsed.breaking);
assert_eq!(parsed.description, "add parallel execution");
assert!(parsed.body.unwrap().contains("cycle orchestration"));
}
#[test]
fn parses_breaking_change() {
let parsed = parse_commit_message("feat(api)!: change response format").unwrap();
assert!(parsed.breaking);
}
#[test]
fn suggests_template() {
let tpl = suggest_template("feat", Some("tool"));
assert_eq!(tpl, "feat(tool): <imperative description>");
}
}
@@ -0,0 +1,188 @@
//! # Built-in Kana Engineering Best Practices Engine
//!
//! This module provides compile-time embedded skills, architecture audit,
//! code-quality scanning, and commit-message validation as **built-in**
//! features of the zesdex binary — not external configuration files.
//!
//! ## Modules
//!
//! | Module | Purpose |
//! |--------|---------|
//! | [`skills`] | Compile-time embedded skill markdown files via `include_dir!` |
//! | [`arch_audit`] | Clean-architecture layering violation scanner |
//! | [`code_quality`] | Clean-code rule checker (unwrap, missing docs, etc.) |
//! | [`commit`] | Conventional Commits message validator |
//!
//! ## Usage
//!
//! The [`BestPracticeEngine`] struct provides a unified API that tools call.
//! New tool implementations in `crate::tools::best_practice` delegate to this
//! engine.
pub mod arch_audit;
pub mod code_quality;
pub mod commit;
pub mod skills;
use anyhow::Result;
use std::path::Path;
/// Unified engine for all best-practice operations.
///
/// Wraps the individual audit, quality, and commit modules behind a single
/// struct that tools and subagents can call without knowing the module layout.
#[derive(Default)]
pub struct BestPracticeEngine {
/// Compile-time embedded skills, lazy-loaded.
embedded_skills: std::sync::OnceLock<skills::EmbeddedSkills>,
}
impl BestPracticeEngine {
/// Create a new engine (skills index is built lazily on first access).
pub fn new() -> Self {
BestPracticeEngine {
embedded_skills: std::sync::OnceLock::new(),
}
}
// ── Skills ─────────────────────────────────────────────────────────
/// Access the embedded skills index.
pub fn skills(&self) -> &skills::EmbeddedSkills {
self.embedded_skills
.get_or_init(skills::EmbeddedSkills::load)
}
/// List all available embedded-skills names.
pub fn list_skills(&self) -> Vec<&'static str> {
self.skills().list()
}
/// Get the full content of a skill by name.
pub fn get_skill(&self, name: &str) -> Option<&'static str> {
self.skills().get(name)
}
/// Get skill summaries (name + description).
pub fn skill_summaries(&self) -> Vec<(&'static str, &'static str)> {
self.skills().summaries()
}
// ── Architecture Audit ─────────────────────────────────────────────
/// Run a full architecture layering audit on a workspace directory.
pub fn audit_layering(&self, workspace_root: &Path) -> Result<arch_audit::AuditReport> {
arch_audit::audit_layering(workspace_root)
}
// ── Code Quality ───────────────────────────────────────────────────
/// Run a full code-quality scan on a workspace directory.
pub fn scan_quality(&self, workspace_root: &Path) -> Result<code_quality::CodeQualityReport> {
code_quality::scan_quality(workspace_root)
}
// ── Commit Convention ──────────────────────────────────────────────
/// Validate a commit message against Conventional Commits.
pub fn validate_commit(&self, message: &str) -> Result<(), Vec<String>> {
commit::validate_commit_message(message)
}
/// Parse a commit message into its structured fields.
pub fn parse_commit(&self, message: &str) -> Option<commit::CommitInfo> {
commit::parse_commit_message(message)
}
/// Get a commit template suggestion.
pub fn suggest_commit_template(&self, type_: &str, scope: Option<&str>) -> String {
commit::suggest_template(type_, scope)
}
// ── Combined Audit ─────────────────────────────────────────────────
/// Run all audits (layering + code quality) and return combined results.
pub fn audit_all(&self, workspace_root: &Path) -> Result<CombinedAuditReport> {
let layering = self.audit_layering(workspace_root)?;
let quality = self.scan_quality(workspace_root)?;
Ok(CombinedAuditReport {
layering,
quality,
})
}
}
/// Combined report from all audits.
#[derive(Debug, Clone)]
pub struct CombinedAuditReport {
pub layering: arch_audit::AuditReport,
pub quality: code_quality::CodeQualityReport,
}
impl CombinedAuditReport {
/// Format the full report as a human-readable string.
pub fn format(&self) -> String {
let mut out = String::new();
// ── Layering ──
out.push_str(&format!(
"=== Architecture Layering Audit ===\n\
Files scanned: {}\n",
self.layering.files_scanned
));
if self.layering.violations.is_empty() {
out.push_str(" ✅ No layering violations found.\n");
} else {
for v in &self.layering.violations {
out.push_str(&format!(
" [{}] {}:{}{}\n",
v.severity, v.file, v.line, v.message
));
}
}
// ── Code Quality ──
out.push_str(&format!(
"\n=== Code Quality Scan ===\n\
Files scanned: {}\n",
self.quality.files_scanned
));
if self.quality.findings.is_empty() {
out.push_str(" ✅ No code-quality issues found.\n");
} else {
let by_rule = self.quality.count_by_rule();
out.push_str(" By rule:\n");
for (rule, count) in &by_rule {
out.push_str(&format!(" {rule}: {count}\n"));
}
out.push_str(" Top findings:\n");
for f in self.quality.findings.iter().take(20) {
out.push_str(&format!(
" [{}] {}:{}{}: {}\n",
f.severity, f.file, f.line, f.rule, f.message
));
}
if self.quality.findings.len() > 20 {
out.push_str(&format!(
" ... and {} more findings.\n",
self.quality.findings.len() - 20
));
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn engine_lists_skills() {
let engine = BestPracticeEngine::new();
let names = engine.list_skills();
assert!(names.contains(&"clean-code"));
}
}
@@ -0,0 +1,152 @@
//! Embedding Kana Engineering best-practice skills as compiled-in static assets.
//!
//! Skill markdown files from `apps/infrastructure/skills/` are embedded at
//! compile time via `include_dir!` and served through [`EmbeddedSkills`].
//!
//! # Flow
//!
//! `EmbeddedSkills::load()` reads the compile-time directory tree → indexes
//! every `SKILL.md` by its parent directory name → exposes lookup / listing.
use include_dir::{include_dir, Dir, DirEntry};
use std::collections::HashMap;
/// The compile-time-embedded skills directory tree.
static SKILLS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/skills");
/// Index of embedded best-practice skills, built once at startup.
///
/// Each skill is identified by its directory name (e.g. `"clean-code"`) and
/// its content is the full text of `SKILL.md` within that directory.
pub struct EmbeddedSkills {
skills: HashMap<&'static str, &'static str>,
}
impl EmbeddedSkills {
/// Walk the embedded directory tree and build the skill index.
///
/// Flow: iterate `Dir::entries()` → find every `SKILL.md` file →
/// store `(parent_dir_name, file_contents)` in the map.
pub fn load() -> Self {
let mut skills: HashMap<&str, &str> = HashMap::new();
fn walk<'a>(dir: &Dir<'a>, skills: &mut HashMap<&'a str, &'a str>) {
for entry in dir.entries() {
match entry {
DirEntry::Dir(sub) => walk(sub, skills),
DirEntry::File(file) => {
if file.path().file_name().map_or(false, |n| n == "SKILL.md") {
if let Some(parent) = file
.path()
.parent()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
{
if let Some(content) = file.contents_utf8() {
skills.entry(parent).or_insert(content);
}
}
}
}
}
}
}
walk(&SKILLS_DIR, &mut skills);
EmbeddedSkills { skills }
}
/// Return the full `SKILL.md` content for a named skill, if present.
pub fn get(&self, name: &str) -> Option<&'static str> {
self.skills.get(name).copied()
}
/// List all available skill names.
pub fn list(&self) -> Vec<&'static str> {
let mut names: Vec<&str> = self.skills.keys().copied().collect();
names.sort();
names
}
/// Return an iterator of `(name, trimmed_description)` pairs.
///
/// Description is extracted from the YAML frontmatter `description:` field.
pub fn summaries(&self) -> Vec<(&'static str, &'static str)> {
let mut out: Vec<(&str, &str)> = Vec::new();
for name in self.list() {
let content = self.skills.get(name).copied().unwrap_or("");
let desc = Self::extract_description(content).unwrap_or("");
out.push((name, desc));
}
out
}
/// Extract the `description:` field from YAML frontmatter.
fn extract_description(content: &str) -> Option<&str> {
let lines: Vec<&str> = content.lines().collect();
if lines.first()? != &"---" {
return None;
}
for line in &lines[1..] {
if let Some(rest) = line.strip_prefix("description: ") {
return Some(rest.trim());
}
if line == &"---" {
break;
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedded_skills_loads_known_skills() {
let skills = EmbeddedSkills::load();
let names = skills.list();
assert!(
names.contains(&"clean-code"),
"expected clean-code skill, got {names:?}"
);
assert!(
names.contains(&"commit-convention"),
"expected commit-convention skill, got {names:?}"
);
assert!(
names.contains(&"kana-rust-backend-best-practice"),
"expected kana-rust-backend-best-practice skill, got {names:?}"
);
}
#[test]
fn each_skill_has_content() {
let skills = EmbeddedSkills::load();
for name in skills.list() {
let content = skills.get(name);
assert!(content.is_some(), "skill '{name}' has no content");
assert!(
content.unwrap().len() > 50,
"skill '{name}' content suspiciously short"
);
}
}
#[test]
fn unknown_skill_returns_none() {
let skills = EmbeddedSkills::load();
assert!(skills.get("nonexistent-skill").is_none());
}
#[test]
fn summaries_include_all_skills() {
let skills = EmbeddedSkills::load();
let summaries = skills.summaries();
assert_eq!(summaries.len(), skills.list().len());
for (name, desc) in &summaries {
assert!(!desc.is_empty(), "skill '{name}' has empty description");
}
}
}
+1
View File
@@ -27,6 +27,7 @@
//! ```
pub mod auth;
pub mod best_practice;
pub mod bgbash;
pub mod guard;
pub mod ipc;
@@ -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}");
}
}
+1
View File
@@ -35,6 +35,7 @@ use anyhow::Result;
use serde_json::Value;
pub mod bash_tools;
pub mod best_practice;
pub mod context;
pub mod executor;
pub mod fs;
@@ -45,6 +45,9 @@ pub fn all_tools() -> Vec<Box<dyn super::Tool>> {
Box::new(super::semantic_search::SemanticSearch),
Box::new(super::semantic_search::RebuildIndex),
Box::new(super::parallel_delegate::ParallelDelegate),
// ── Best-practice tools (built-in) ─────────────────────────
Box::new(super::best_practice::BestPractice),
Box::new(super::best_practice::CommitConvention),
]
}