fix(fermata): .botignore as fallback root, not ignored; extract SVGs to files
- Walk-up: .botignore no longer stops the search but is remembered as a fallback when no strong marker (.git, botignore.toml, .botignore.toml) is found — prevents fail-open regression for projects without .git - Extract inline SVGs to policy-layers.svg and architecture.svg - READMEs reference SVGs via <img> tags Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-7
@@ -1,12 +1,14 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Markers checked in priority order when walking up from a target path.
|
/// Strong markers that definitively identify a project root.
|
||||||
const MARKERS: &[&str] = &["botignore.toml", ".botignore.toml", ".git"];
|
const STRONG_MARKERS: &[&str] = &["botignore.toml", ".botignore.toml", ".git"];
|
||||||
|
|
||||||
/// Walk upward from `target` (or its parent if `target` is a file) looking
|
/// Walk upward from `target` (or its parent if `target` is a file) looking
|
||||||
/// for the nearest project root. Roots are identified by the presence of
|
/// for the nearest project root. Strong markers (`botignore.toml`,
|
||||||
/// any marker in `MARKERS`. Walks from the **target file's location**, not
|
/// `.botignore.toml`, `.git`) stop the walk immediately. A `.botignore`
|
||||||
/// from cwd, because agents `cd` around.
|
/// file is remembered as a fallback but does not stop the walk — the search
|
||||||
|
/// continues upward for a stronger boundary. If none is found, the
|
||||||
|
/// `.botignore` location is used.
|
||||||
pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
||||||
let start = if target.is_file() {
|
let start = if target.is_file() {
|
||||||
target.parent()?
|
target.parent()?
|
||||||
@@ -14,14 +16,18 @@ pub fn find_project_root(target: &Path) -> Option<PathBuf> {
|
|||||||
target
|
target
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut fallback: Option<PathBuf> = None;
|
||||||
let mut current = Some(start);
|
let mut current = Some(start);
|
||||||
while let Some(dir) = current {
|
while let Some(dir) = current {
|
||||||
for marker in MARKERS {
|
for marker in STRONG_MARKERS {
|
||||||
if dir.join(marker).exists() {
|
if dir.join(marker).exists() {
|
||||||
return Some(dir.to_path_buf());
|
return Some(dir.to_path_buf());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fallback.is_none() && dir.join(".botignore").exists() {
|
||||||
|
fallback = Some(dir.to_path_buf());
|
||||||
|
}
|
||||||
current = dir.parent();
|
current = dir.parent();
|
||||||
}
|
}
|
||||||
None
|
fallback
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-5
@@ -51,18 +51,36 @@ fn botignore_alone_does_not_stop_walk() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn botignore_only_returns_none() {
|
fn botignore_used_as_fallback() {
|
||||||
// If only .botignore exists with no real root marker, no project root is found.
|
// If only .botignore exists (no strong marker), it serves as a fallback
|
||||||
|
// root so that policy is still enforced.
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
let root = tmp.path();
|
let root = tmp.path();
|
||||||
fs::create_dir_all(root.join("sub")).unwrap();
|
fs::create_dir_all(root.join("sub")).unwrap();
|
||||||
fs::write(root.join(".botignore"), "").unwrap();
|
fs::write(root.join(".botignore"), "*.secret").unwrap();
|
||||||
|
|
||||||
let target = root.join("sub/file.rs");
|
let target = root.join("sub/file.rs");
|
||||||
fs::write(&target, "").unwrap();
|
fs::write(&target, "").unwrap();
|
||||||
|
|
||||||
// .botignore alone should NOT define a project root
|
let found = find_project_root(&target).unwrap();
|
||||||
assert!(find_project_root(&target).is_none());
|
assert_eq!(found, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strong_marker_preferred_over_botignore_fallback() {
|
||||||
|
// .botignore at a/b/, .git at root — walk past .botignore, use root.
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let root = tmp.path();
|
||||||
|
fs::create_dir_all(root.join("a/b/c")).unwrap();
|
||||||
|
fs::create_dir_all(root.join(".git")).unwrap();
|
||||||
|
fs::write(root.join("a/b/.botignore"), "*.key").unwrap();
|
||||||
|
|
||||||
|
let target = root.join("a/b/c/file.rs");
|
||||||
|
fs::write(&target, "").unwrap();
|
||||||
|
|
||||||
|
// Should find root (with .git), not a/b (with .botignore)
|
||||||
|
let found = find_project_root(&target).unwrap();
|
||||||
|
assert_eq!(found, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user