125 lines
4.0 KiB
Rust
125 lines
4.0 KiB
Rust
//! 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"
|
||
|
|
)
|
||
|
|
})
|
||
|
|
}
|