feat: implement all P1/P2/P3/P4 improvements from issue backlog
Some checks failed
CI / Check / Test (push) Failing after 1m33s
Architecture Docs / Generate diagrams (push) Successful in 3m21s

P1 correctness:
- filter test files by default (--include-tests to opt in)
- per-module diagrams show cross-module dependency arrows
- qualified type names (Module::TypeName) fix false edges from duplicate names

P2 output richness:
- method parameter types and return types in class diagrams (Rust + Python)
- Python pyproject.toml project analyzer (--level project for monorepos)

P3 unique value:
- boundary rules in archlens.toml ([rules] allow/deny, --strict enforcement)

P4 nice to have:
- dependency weight labels on module arrows (--no-weights to disable)
- --watch mode with 500ms debounce
- D2 renderer adapter (--format d2)
- interactive self-contained HTML viewer (--format html)
- git-aware incremental analysis (--since <ref>)
This commit is contained in:
2026-06-17 09:50:50 +02:00
parent 27197062eb
commit fdd85011a4
42 changed files with 2767 additions and 92 deletions

View File

@@ -42,6 +42,27 @@ impl WalkdirDiscovery {
}
}
fn is_test_file(path: &Path, language: Language) -> bool {
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_default();
let in_tests_dir = path
.parent()
.map(|p| p.components().any(|c| c.as_os_str() == "tests"))
.unwrap_or(false);
if in_tests_dir {
return true;
}
match language {
Language::Rust => stem.ends_with("_test") || stem.ends_with("_tests"),
Language::Python => stem.starts_with("test_") || stem.ends_with("_test"),
Language::CSharp => stem.ends_with("Tests") || stem.ends_with("Test"),
}
}
fn is_excluded(path: &Path, root: &Path, excludes: &[String]) -> bool {
let relative = path.strip_prefix(root).unwrap_or(path);
let relative_str = relative.to_string_lossy();
@@ -88,6 +109,18 @@ impl FileDiscovery for WalkdirDiscovery {
}
if let Some(language) = Self::detect_language(path) {
if !config.include_tests() && Self::is_test_file(path, language) {
continue;
}
if let Some(changed) = config.changed_files() {
let relative = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
if !changed
.iter()
.any(|c| relative.ends_with(c.as_str()) || c.ends_with(relative.as_ref()))
{
continue;
}
}
let absolute = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
let file_path = FilePath::new(&absolute.to_string_lossy())
.map_err(|e| DomainError::IoError(e.to_string()))?;