Skip to main content

tsift_resolution/
blocklist.rs

1use std::path::Path;
2
3pub fn relative_path_is_generated_artifact(relative: &str) -> bool {
4    let relative = relative.trim_start_matches("./").replace('\\', "/");
5    relative == ".tsift"
6        || relative.starts_with(".tsift/")
7        || relative.ends_with("/.tsift")
8        || relative.contains("/.tsift/")
9        || relative == ".agent-doc"
10        || relative.starts_with(".agent-doc/")
11        || relative.ends_with("/.agent-doc")
12        || relative.contains("/.agent-doc/")
13        || relative == "target"
14        || relative.starts_with("target/")
15        || relative.contains("/target/")
16}
17
18pub fn path_is_generated_artifact(root: &Path, source_root: &Path, path: &Path) -> bool {
19    let mut relatives = Vec::new();
20    if let Ok(relative) = path.strip_prefix(source_root) {
21        relatives.push(relative.to_string_lossy().replace('\\', "/"));
22    }
23    if let Ok(relative) = path.strip_prefix(root) {
24        relatives.push(relative.to_string_lossy().replace('\\', "/"));
25    }
26    if !path.is_absolute() {
27        relatives.push(path.to_string_lossy().replace('\\', "/"));
28    }
29    relatives
30        .iter()
31        .any(|relative| relative_path_is_generated_artifact(relative))
32}
33
34pub fn index_snapshot_part_is_generated(root: &Path, source_root: &Path, part: &str) -> bool {
35    let Some(rest) = part.strip_prefix("file:") else {
36        return false;
37    };
38    let path = rest.split(':').next().unwrap_or(rest);
39    path_is_generated_artifact(root, source_root, Path::new(path))
40}
41
42pub fn is_planner_config_path(path: &str) -> bool {
43    let normalized = path.replace('\\', "/").to_ascii_lowercase();
44    let file_name = normalized.rsplit('/').next().unwrap_or(normalized.as_str());
45    normalized.starts_with(".github/")
46        || normalized.starts_with(".codex/")
47        || normalized.starts_with(".agent-doc/")
48        || normalized.contains("/.github/")
49        || matches!(
50            file_name,
51            "agents.md"
52                | "claude.md"
53                | "cargo.toml"
54                | "cargo.lock"
55                | "package.json"
56                | "package-lock.json"
57                | "pnpm-lock.yaml"
58                | "yarn.lock"
59                | "makefile"
60                | "justfile"
61                | "dockerfile"
62                | "docker-compose.yml"
63                | "docker-compose.yaml"
64                | "config.toml"
65                | "tsconfig.json"
66        )
67        || file_name.ends_with(".config.js")
68        || file_name.ends_with(".config.ts")
69        || file_name.ends_with(".yml")
70        || file_name.ends_with(".yaml")
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use std::path::PathBuf;
77
78    #[test]
79    fn relative_path_is_tsift_dir() {
80        assert!(relative_path_is_generated_artifact(".tsift"));
81        assert!(relative_path_is_generated_artifact(".tsift/graph.db"));
82        assert!(relative_path_is_generated_artifact("foo/.tsift"));
83        assert!(relative_path_is_generated_artifact("foo/.tsift/bar"));
84    }
85
86    #[test]
87    fn relative_path_is_agent_doc_dir() {
88        assert!(relative_path_is_generated_artifact(".agent-doc"));
89        assert!(relative_path_is_generated_artifact(".agent-doc/snapshots"));
90    }
91
92    #[test]
93    fn relative_path_is_target_dir() {
94        assert!(relative_path_is_generated_artifact("target"));
95        assert!(relative_path_is_generated_artifact("target/debug"));
96        assert!(relative_path_is_generated_artifact("foo/target/bar"));
97    }
98
99    #[test]
100    fn relative_path_not_generated() {
101        assert!(!relative_path_is_generated_artifact("src/main.rs"));
102        assert!(!relative_path_is_generated_artifact("lib/index.ts"));
103    }
104
105    #[test]
106    fn relative_path_normalizes_backslashes() {
107        assert!(relative_path_is_generated_artifact("target\\debug"));
108    }
109
110    #[test]
111    fn relative_path_strips_dot_slash() {
112        assert!(relative_path_is_generated_artifact("./.tsift"));
113    }
114
115    #[test]
116    fn path_is_generated_artifact_with_source_root() {
117        let root = PathBuf::from("/project");
118        let source_root = PathBuf::from("/project/src");
119        let path = PathBuf::from("/project/src/.tsift/config");
120        assert!(path_is_generated_artifact(&root, &source_root, &path));
121    }
122
123    #[test]
124    fn path_is_generated_artifact_not_generated() {
125        let root = PathBuf::from("/project");
126        let source_root = PathBuf::from("/project/src");
127        let path = PathBuf::from("/project/src/main.rs");
128        assert!(!path_is_generated_artifact(&root, &source_root, &path));
129    }
130
131    #[test]
132    fn index_snapshot_part_generated() {
133        let root = PathBuf::from("/project");
134        let source_root = PathBuf::from("/project");
135        assert!(index_snapshot_part_is_generated(
136            &root,
137            &source_root,
138            "file:target/debug/out.rs:line"
139        ));
140    }
141
142    #[test]
143    fn index_snapshot_part_not_generated() {
144        let root = PathBuf::from("/project");
145        let source_root = PathBuf::from("/project");
146        assert!(!index_snapshot_part_is_generated(
147            &root,
148            &source_root,
149            "file:src/main.rs:line"
150        ));
151    }
152
153    #[test]
154    fn index_snapshot_part_no_file_prefix() {
155        let root = PathBuf::from("/project");
156        let source_root = PathBuf::from("/project");
157        assert!(!index_snapshot_part_is_generated(
158            &root,
159            &source_root,
160            "scope:main"
161        ));
162    }
163
164    #[test]
165    fn is_planner_config_github() {
166        assert!(is_planner_config_path(".github/workflows/ci.yml"));
167    }
168
169    #[test]
170    fn is_planner_config_agents_md() {
171        assert!(is_planner_config_path("AGENTS.md"));
172    }
173
174    #[test]
175    fn is_planner_config_cargo() {
176        assert!(is_planner_config_path("Cargo.toml"));
177    }
178
179    #[test]
180    fn is_planner_config_yml_extension() {
181        assert!(is_planner_config_path("config/production.yml"));
182    }
183
184    #[test]
185    fn is_planner_config_config_ts() {
186        assert!(is_planner_config_path("vite.config.ts"));
187    }
188
189    #[test]
190    fn is_planner_config_not_config() {
191        assert!(!is_planner_config_path("src/main.rs"));
192        assert!(!is_planner_config_path("lib/index.ts"));
193    }
194
195    #[test]
196    fn is_planner_config_case_insensitive() {
197        assert!(is_planner_config_path(".GitHub/test.yml"));
198    }
199
200    #[test]
201    fn is_planner_config_codex() {
202        assert!(is_planner_config_path(".codex/hooks.json"));
203    }
204}