Skip to content

Commit 3647186

Browse files
author
Jeshua ben Joseph
committed
docs: Update README and configuration for enhanced command options and output formats
feat: Add agent output format for report generation and implement test pattern rule for CDD compliance refactor: Standardize configuration file naming and improve logging in pattern engine
1 parent 5f29b45 commit 3647186

File tree

5 files changed

+218
-41
lines changed

5 files changed

+218
-41
lines changed

README.md

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,56 @@ cargo add rust-guardian
3737
# Check entire project
3838
rust-guardian check
3939

40-
# Check specific path
41-
rust-guardian check src/
42-
43-
# Output JSON for CI/CD
44-
rust-guardian check --format json --severity error
45-
46-
# Watch for changes during development
47-
rust-guardian watch
48-
49-
# Validate configuration
50-
rust-guardian validate-config guardian.yaml
51-
52-
# Use custom .guardianignore file
53-
rust-guardian check --guardianignore .custom-ignore
54-
55-
# Ignore all .guardianignore files
56-
rust-guardian check --no-ignore
57-
58-
# Add temporary exclusions
59-
rust-guardian check --exclude "**/*.tmp" --exclude "staging/"
60-
61-
# Explain a specific rule
62-
rust-guardian explain todo_comments
40+
# Check specific paths
41+
rust-guardian check src/ lib.rs
42+
43+
# Output formats
44+
rust-guardian check --format json # JSON for tooling
45+
rust-guardian check --format agent # Agent-friendly: [line:path] violation
46+
rust-guardian check --format junit # JUnit XML for CI/CD
47+
rust-guardian check --format sarif # SARIF for security tools
48+
rust-guardian check --format github # GitHub Actions format
49+
50+
# Filter by severity
51+
rust-guardian check --severity error # Only errors
52+
rust-guardian check --severity warning # Warnings and errors
53+
rust-guardian check --severity info # All violations
54+
55+
# Performance and caching
56+
rust-guardian check --cache # Enable caching
57+
rust-guardian check --cache-file /tmp/cache # Custom cache location
58+
rust-guardian check --no-parallel # Disable parallel processing
59+
rust-guardian check --max-violations 50 # Limit output
60+
61+
# File filtering
62+
rust-guardian check --exclude "**/*.tmp" # Additional exclude patterns
63+
rust-guardian check --exclude "legacy/" --exclude "vendor/"
64+
rust-guardian check --guardianignore .custom # Custom ignore file
65+
rust-guardian check --no-ignore # Ignore all .guardianignore files
66+
67+
# Configuration and debugging
68+
rust-guardian check -c custom.yaml # Custom config file
69+
rust-guardian check --verbose # Enable debug logging
70+
rust-guardian check --no-color # Disable colors
71+
rust-guardian check --fail-fast # Stop on first error
72+
73+
# Watch mode for development
74+
rust-guardian watch src/ # Watch directory for changes
75+
rust-guardian watch --debounce 500 # Custom debounce ms
76+
77+
# Configuration management
78+
rust-guardian validate-config # Validate guardian.yaml
79+
rust-guardian validate-config custom.yaml # Validate custom config
80+
81+
# Rule management
82+
rust-guardian rules # List all rules
83+
rust-guardian rules --enabled-only # Only show enabled rules
84+
rust-guardian rules --category placeholders # Filter by category
85+
rust-guardian explain todo_comments # Explain specific rule
86+
87+
# Cache management
88+
rust-guardian cache stats # Show cache statistics
89+
rust-guardian cache clear # Clear cache
6390
```
6491

6592
### Library Usage
@@ -345,15 +372,28 @@ Colored terminal output with context:
345372
```
346373
❌ Code Quality Violations Found
347374
348-
src/api/handlers.rs:45:12
349-
→ todo_comments: Placeholder comment detected: TODO
350-
351-
45│ // TODO: Implement error handling
352-
│ ^^^^
375+
📁 src/api/handlers.rs
376+
45:12:todo_comments [error] Placeholder comment detected: TODO
377+
│ // TODO: Implement error handling
353378
354379
📊 Summary: 1 error, 2 warnings in 156 files (1.2s)
355380
```
356381

382+
### Agent Format
383+
Simplified format for automated processing and agent consumption:
384+
385+
```
386+
[45:src/api/handlers.rs]
387+
Placeholder comment detected: TODO
388+
389+
[102:src/lib.rs]
390+
Traditional tests violate CDD - consciousness architecture should be self-validating
391+
392+
[67:src/models.rs]
393+
Function returns Ok(()) with no meaningful implementation
394+
395+
```
396+
357397
### JSON
358398
Machine-readable format for tooling:
359399

guardian.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@ patterns:
116116
message: "Magic number found: {match} - consider using a named constant"
117117
enabled: false
118118

119+
# Test pattern violations for CDD compliance
120+
test_patterns:
121+
severity: error
122+
enabled: true
123+
rules:
124+
- id: no_traditional_tests
125+
type: regex
126+
pattern: '(mod\s+tests?\s*\{|#\[test\]|#\[cfg\(test\)\])'
127+
message: "Traditional tests violate CDD principles - use integration tests instead: {match}"
128+
severity: error
129+
case_sensitive: true
130+
exclude_if:
131+
file_patterns:
132+
- "**/integration_tests/**"
133+
- "**/tests/**"
134+
119135
# Reporting configuration for CI
120136
reporting:
121137
show_context_lines: 2

src/main.rs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ enum OutputFormatArg {
161161
Junit,
162162
Sarif,
163163
Github,
164+
Agent,
164165
}
165166

166167
impl From<OutputFormatArg> for OutputFormat {
@@ -171,6 +172,7 @@ impl From<OutputFormatArg> for OutputFormat {
171172
OutputFormatArg::Junit => OutputFormat::Junit,
172173
OutputFormatArg::Sarif => OutputFormat::Sarif,
173174
OutputFormatArg::Github => OutputFormat::GitHub,
175+
OutputFormatArg::Agent => OutputFormat::Agent,
174176
}
175177
}
176178
}
@@ -280,9 +282,9 @@ async fn run_check(
280282
} else {
281283
// Try to find default config file
282284
let default_configs = [
283-
"rust_guardian.yaml",
284-
"rust_guardian.yml",
285-
".rust_guardian.yaml",
285+
"guardian.yaml",
286+
"guardian.yml",
287+
".guardian.yaml",
286288
];
287289
let mut config = None;
288290

@@ -526,10 +528,10 @@ fn is_config_change(event: &notify::Event) -> Option<PathBuf> {
526528
// Check for common config file names
527529
if matches!(
528530
file_name,
529-
"rust_guardian.yaml"
530-
| "rust_guardian.yml"
531-
| ".rust_guardian.yaml"
532-
| ".rust_guardian.yml"
531+
"guardian.yaml"
532+
| "guardian.yml"
533+
| ".guardian.yaml"
534+
| ".guardian.yml"
533535
) {
534536
return Some(path.clone());
535537
}
@@ -564,9 +566,9 @@ async fn run_watch_analysis_with_config(
564566
} else {
565567
// Try to find default config file
566568
let default_configs = [
567-
"rust_guardian.yaml",
568-
"rust_guardian.yml",
569-
".rust_guardian.yaml",
569+
"guardian.yaml",
570+
"guardian.yml",
571+
".guardian.yaml",
570572
];
571573
let mut config = None;
572574

@@ -665,7 +667,7 @@ async fn run_watch_analysis_with_config(
665667
}
666668

667669
fn run_validate_config(config_path: Option<PathBuf>) -> GuardianResult<i32> {
668-
let config_path = config_path.unwrap_or_else(|| PathBuf::from("rust_guardian.yaml"));
670+
let config_path = config_path.unwrap_or_else(|| PathBuf::from("guardian.yaml"));
669671

670672
println!("Validating configuration: {}", config_path.display());
671673

@@ -815,7 +817,22 @@ fn run_list_rules(
815817
let config = if let Some(path) = config_path {
816818
GuardianConfig::load_from_file(path)?
817819
} else {
818-
GuardianConfig::default()
820+
// Try to find default config file
821+
let default_configs = [
822+
"guardian.yaml",
823+
"guardian.yml",
824+
".guardian.yaml",
825+
];
826+
let mut config = None;
827+
828+
for config_name in &default_configs {
829+
if Path::new(config_name).exists() {
830+
config = Some(GuardianConfig::load_from_file(config_name)?);
831+
break;
832+
}
833+
}
834+
835+
config.unwrap_or_else(GuardianConfig::default)
819836
};
820837

821838
println!("📋 Available Rules\n");

src/patterns/mod.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,12 @@ impl PatternEngine {
104104
rule: &PatternRule,
105105
effective_severity: Severity,
106106
) -> GuardianResult<()> {
107+
tracing::debug!("Adding rule '{}' of type {:?} with pattern '{}' and severity {:?}",
108+
rule.id, rule.rule_type, rule.pattern, effective_severity);
109+
107110
match rule.rule_type {
108111
RuleType::Regex => {
112+
tracing::debug!("Compiling regex pattern '{}' for rule '{}'", rule.pattern, rule.id);
109113
let regex = if rule.case_sensitive {
110114
Regex::new(&rule.pattern)
111115
} else {
@@ -261,9 +265,14 @@ impl PatternEngine {
261265
let file_path = file_path.as_ref();
262266
let mut matches = Vec::new();
263267

268+
tracing::debug!("Analyzing file '{}' with {} regex patterns and {} AST patterns",
269+
file_path.display(), self.regex_patterns.len(), self.ast_patterns.len());
270+
264271
// Apply regex patterns
265272
for pattern in self.regex_patterns.values() {
273+
tracing::debug!("Processing regex pattern '{}'", pattern.rule_id);
266274
let pattern_matches = self.apply_regex_pattern(pattern, file_path, content)?;
275+
tracing::debug!("Pattern '{}' found {} matches", pattern.rule_id, pattern_matches.len());
267276
matches.extend(pattern_matches);
268277
}
269278

@@ -285,10 +294,15 @@ impl PatternEngine {
285294
file_path: &Path,
286295
content: &str,
287296
) -> GuardianResult<Vec<PatternMatch>> {
297+
tracing::debug!("Applying regex pattern '{}' to file '{}'", pattern.rule_id, file_path.display());
298+
tracing::debug!("Pattern regex: '{}'", pattern.regex.as_str());
299+
tracing::debug!("Content length: {} characters", content.len());
300+
288301
let mut matches = Vec::new();
289302

290303
// Find all matches in the content
291304
for regex_match in pattern.regex.find_iter(content) {
305+
tracing::debug!("Found regex match: '{}' at offset {}", regex_match.as_str(), regex_match.start());
292306
let matched_text = regex_match.as_str().to_string();
293307
let (line_num, col_num, context) =
294308
self.get_match_location(content, regex_match.start());
@@ -301,6 +315,7 @@ impl PatternEngine {
301315
content,
302316
regex_match.start(),
303317
) {
318+
tracing::debug!("Match '{}' excluded by conditions", matched_text);
304319
continue;
305320
}
306321

@@ -1014,13 +1029,16 @@ impl PatternEngine {
10141029
&self,
10151030
conditions: Option<&ExcludeConditions>,
10161031
file_path: &Path,
1017-
_matched_text: &str,
1032+
matched_text: &str,
10181033
_content: &str,
10191034
_offset: usize,
10201035
) -> bool {
10211036
if let Some(conditions) = conditions {
1037+
tracing::debug!("Checking exclude conditions for match '{}' in file '{}'", matched_text, file_path.display());
1038+
10221039
// Check if in test files
10231040
if conditions.in_tests && self.is_test_file(file_path) {
1041+
tracing::debug!("Match excluded: in_tests=true and file is test file");
10241042
return true;
10251043
}
10261044

@@ -1029,13 +1047,17 @@ impl PatternEngine {
10291047
for pattern in patterns {
10301048
if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
10311049
if glob_pattern.matches_path(file_path) {
1050+
tracing::debug!("Match excluded: file matches pattern '{}'", pattern);
10321051
return true;
10331052
}
10341053
}
10351054
}
10361055
}
10371056

10381057
// Additional condition checks can be added here when AST context is available
1058+
tracing::debug!("Match not excluded by any conditions");
1059+
} else {
1060+
tracing::debug!("No exclude conditions to check");
10391061
}
10401062

10411063
false
@@ -1842,4 +1864,59 @@ mod tests {
18421864
.unwrap();
18431865
assert_eq!(matches.len(), 0);
18441866
}
1867+
1868+
#[test]
1869+
fn test_custom_test_pattern_matching() {
1870+
let mut engine = PatternEngine::new();
1871+
1872+
// Create the test pattern rule as it appears in config
1873+
let test_pattern_rule = PatternRule {
1874+
id: "no_traditional_tests".to_string(),
1875+
rule_type: crate::config::RuleType::Regex,
1876+
pattern: r"(^\s*mod\s+tests?\s*\{|#\[test\]|#\[cfg\(test\)\])".to_string(),
1877+
message: "Traditional tests violate CDD principles - use integration tests instead: {match}".to_string(),
1878+
severity: Some(crate::domain::violations::Severity::Error),
1879+
enabled: true,
1880+
case_sensitive: true,
1881+
exclude_if: Some(crate::config::ExcludeConditions {
1882+
attribute: None,
1883+
in_tests: false,
1884+
file_patterns: Some(vec![
1885+
"**/integration_tests/**".to_string(),
1886+
"**/tests/**".to_string()
1887+
]),
1888+
}),
1889+
};
1890+
1891+
engine.add_rule(&test_pattern_rule, crate::domain::violations::Severity::Error).unwrap();
1892+
1893+
let test_content = r#"//! Test file
1894+
pub fn some_function() -> String {
1895+
"Hello, World!".to_string()
1896+
}
1897+
1898+
#[test]
1899+
fn test_some_function() {
1900+
assert_eq!(some_function(), "Hello, World!");
1901+
}
1902+
1903+
#[cfg(test)]
1904+
mod tests {
1905+
use super::*;
1906+
1907+
#[test]
1908+
fn another_test() {
1909+
assert_eq!(some_function().len(), 13);
1910+
}
1911+
}"#;
1912+
1913+
let matches = engine.analyze_file(Path::new("test_file.rs"), test_content).unwrap();
1914+
println!("Found {} matches in test content", matches.len());
1915+
for (i, m) in matches.iter().enumerate() {
1916+
println!("Match {}: {} at {}:{}", i, m.matched_text, m.line_number.unwrap_or(0), m.column_number.unwrap_or(0));
1917+
}
1918+
1919+
// Should find at least the #[test] and #[cfg(test)] patterns
1920+
assert!(!matches.is_empty(), "Should find test patterns in content");
1921+
}
18451922
}

0 commit comments

Comments
 (0)