Files
zesdex/crates/zesdex-backend/src/app/subagent/auto/paths.rs
T

125 lines
4.0 KiB
Rust
Raw Normal View History

//! Path classification helpers for auto-subagent orchestration.
//!
//! Determines whether a file path is reviewable and whether it represents
//! production code (vs. tests, config, or documentation) — used to decide
//! which background subagents should fire for a given set of modified files.
/// File extensions that should not trigger auto-review (config, lock, data).
pub(crate) const SKIP_REVIEW_EXTENSIONS: &[&str] = &[
".lock",
".md",
".txt",
".json",
".toml",
".yaml",
".yml",
".svg",
".png",
".jpg",
".ico",
".woff",
".woff2",
];
/// File names that should not trigger auto-review.
pub(crate) const SKIP_REVIEW_FILES: &[&str] = &[
"Cargo.lock",
"yarn.lock",
"package-lock.json",
".gitignore",
".env",
".env.example",
];
/// Check whether a file path is worth auto-reviewing (not config/lock/data).
///
/// Vendored/generated directories are matched by path *segment* rather than
/// a `/target/`-style substring check — the substring form misses paths
/// where the directory is the first component (e.g. `target/debug/build.rs`,
/// which has no leading slash), the same class of bug fixed in
/// `is_production_code` below.
pub fn is_reviewable_path(path: &str) -> bool {
let lower = path.to_lowercase();
if SKIP_REVIEW_FILES.iter().any(|f| lower.ends_with(f)) {
return false;
}
if SKIP_REVIEW_EXTENSIONS.iter().any(|e| lower.ends_with(e)) {
return false;
}
// Skip paths that are clearly generated or vendored
let in_vendored_dir = std::path::Path::new(&lower).components().any(|c| {
matches!(
c,
std::path::Component::Normal(seg)
if matches!(seg.to_str(), Some("target" | "node_modules" | ".git" | "vendor"))
)
});
if in_vendored_dir {
return false;
}
true
}
/// Determine whether a file change looks like it modifies production logic
/// (vs. tests, config, or documentation) — used to decide if a test-gen
/// or security-review background subagent should fire.
///
/// Matches test-ness by path *segment* (a directory literally named
/// "test"/"tests"/"__tests__") or by filename convention
/// (`foo_test.rs`, `foo.test.ts`, `test_foo.py`, `foo_spec.rb`), not by a
/// raw substring check — a plain `.contains("test")` would wrongly exclude
/// legitimate production files like `src/attestation.rs` or
/// `src/latest/foo.rs`.
pub(crate) fn is_production_code(path: &str) -> bool {
let lower = path.to_lowercase();
let path_obj = std::path::Path::new(&lower);
let in_test_dir = path_obj.components().any(|c| {
matches!(
c,
std::path::Component::Normal(seg)
if matches!(seg.to_str(), Some("test" | "tests" | "__tests__"))
)
});
let file_stem = path_obj.file_stem().and_then(|s| s.to_str()).unwrap_or("");
let is_test_filename = file_stem.starts_with("test_")
|| file_stem.ends_with("_test")
|| std::path::Path::new(file_stem)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("test"))
|| file_stem == "spec"
|| file_stem.ends_with("_spec")
|| std::path::Path::new(file_stem)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("spec"));
if in_test_dir || is_test_filename {
return false;
}
// Only source files — use Path::extension() to avoid clippy
// case_sensitive_file_extension_comparisons lint
path_obj
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| {
matches!(
ext,
"rs" | "ts"
| "tsx"
| "js"
| "jsx"
| "go"
| "py"
| "java"
| "kt"
| "swift"
| "c"
| "cpp"
| "h"
| "hpp"
)
})
}