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

@@ -0,0 +1,110 @@
use archlens_d2::D2Renderer;
use archlens_domain::{
CodeElement, CodeElementKind, CodeGraph, DiagramLevel, FilePath, ModuleName, Relationship,
RelationshipKind, ports::DiagramRenderer,
};
fn make_el(name: &str, module: Option<&str>) -> CodeElement {
let mut el = CodeElement::new(
name,
CodeElementKind::Class,
FilePath::new(&format!("src/{name}.rs")).unwrap(),
1,
)
.unwrap();
if let Some(m) = module {
el = el.with_module(ModuleName::new(m).unwrap());
}
el
}
#[test]
fn type_level_emits_class_shapes() {
let mut graph = CodeGraph::new();
graph.add_element(make_el("OrderService", Some("App")));
graph.add_element(make_el("Order", Some("Domain")));
graph.add_relationship(
Relationship::new("OrderService", "Order", RelationshipKind::Composition).unwrap(),
);
let graph = graph.qualify();
let renderer = D2Renderer::new();
let output = renderer.render(&graph).unwrap();
let content = output.files()[0].content();
assert!(
content.contains("shape: class"),
"expected class shape: {content}"
);
assert!(
content.contains("OrderService"),
"expected OrderService: {content}"
);
assert!(content.contains("Order"), "expected Order: {content}");
}
#[test]
fn module_level_emits_module_nodes_and_edges() {
let mut graph = CodeGraph::new();
graph.add_element(make_el("Service", Some("App")));
graph.add_element(make_el("Order", Some("Domain")));
graph.add_relationship(
Relationship::new("Service", "Order", RelationshipKind::Composition).unwrap(),
);
let graph = graph.qualify();
let renderer = D2Renderer::with_level(DiagramLevel::Module);
let output = renderer.render(&graph).unwrap();
let content = output.files()[0].content();
assert!(content.contains("App"), "expected App module: {content}");
assert!(
content.contains("Domain"),
"expected Domain module: {content}"
);
assert!(
content.contains("->"),
"expected dependency arrow: {content}"
);
}
#[test]
fn project_level_groups_by_module() {
let mut graph = CodeGraph::new();
graph.add_element(
CodeElement::new(
"my-api",
CodeElementKind::Project,
FilePath::new("api/pyproject.toml").unwrap(),
1,
)
.unwrap()
.with_module(ModuleName::new("Backend").unwrap()),
);
graph.add_element(
CodeElement::new(
"my-commons",
CodeElementKind::Project,
FilePath::new("commons/pyproject.toml").unwrap(),
1,
)
.unwrap(),
);
graph.add_relationship(
Relationship::new("my-api", "my-commons", RelationshipKind::Composition).unwrap(),
);
let renderer = D2Renderer::with_level(DiagramLevel::Project);
let output = renderer.render(&graph).unwrap();
let content = output.files()[0].content();
assert!(
content.contains("Backend"),
"expected Backend group: {content}"
);
assert!(
content.contains("my-api") || content.contains("my_api"),
"expected my-api: {content}"
);
assert!(content.contains("->"), "expected dep arrow: {content}");
}