1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// ---------------- [ File: workspacer-file-filter/src/apply.rs ]
crate::ix!();
pub async fn apply_text_filter_to_files(
model_type: &LanguageModelType,
list_path: impl AsRef<Path>,
user_instructions: &str,
plant: bool,
config: &FileFilterConfig
) -> Result<(), AiFileFilterError>
{
// 1) Read the list of paths from the `list_path` file
let p = list_path.as_ref();
let content = tokio::fs::read_to_string(p).await.map_err(|io_err| {
AiFileFilterError::IoError {
io_error: std::sync::Arc::new(io_err),
context: format!("Failed to read file list at {}", p.display()),
}
})?;
// 2) Collect the lines (paths) into a vector, skipping empty lines
let mut raw_paths = Vec::new();
for (idx, line) in content.lines().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
warn!("Skipping empty line #{} in file list", idx);
continue;
}
raw_paths.push(PathBuf::from(trimmed));
}
// 3) If no valid lines, bail early
if raw_paths.is_empty() {
info!("No valid files were provided, so nothing to do.");
return Ok(());
}
// ----------------------------------------------------------------
// 4) Enforce uniqueness of final basenames (file_stem).
//
// For example, if we have "src/foo.rs" and "tests/foo.rs",
// both have final basename "foo", so we error out.
//
// This is done before building the AiFileFilterRequest so that
// we fail fast if there's a collision.
// ----------------------------------------------------------------
use std::collections::HashMap;
let mut stem_to_path_map: HashMap<String, PathBuf> = HashMap::new();
for path in &raw_paths {
// Extract the final file name (no directories). E.g. from "src/foo.rs" => "foo.rs"
let file_name = match path.file_name() {
Some(fname) => fname.to_string_lossy().to_string(),
None => {
let msg = format!("Path '{}' has no valid file name", path.display());
return Err(AiFileFilterError::IoError {
io_error: std::sync::Arc::new(std::io::Error::new(std::io::ErrorKind::Other, &*msg)),
context: msg,
});
}
};
// Now extract the "stem" portion, ignoring extension. E.g. "foo.rs" => "foo"
let raw_stem = match path.file_stem() {
Some(stem) if !stem.is_empty() => stem.to_string_lossy().to_string(),
_ => {
let msg = format!(
"Path '{}' has no valid file stem (maybe empty or starts with '.')",
path.display()
);
return Err(AiFileFilterError::IoError {
io_error: std::sync::Arc::new(std::io::Error::new(std::io::ErrorKind::Other, &*msg)),
context: msg,
});
}
};
// Check if we already have that stem in the map
if let Some(existing_path) = stem_to_path_map.get(&raw_stem) {
// If so, throw an error because we want uniqueness
let msg = format!(
"Collision: the file '{}' and '{}' both have the same stem '{}'.",
existing_path.display(),
path.display(),
raw_stem
);
error!("{}", msg);
return Err(AiFileFilterError::GenericError);
} else {
stem_to_path_map.insert(raw_stem, path.clone());
}
}
// ----------------------------------------------------------------
// 5) Now that we've validated uniqueness, we can build requests.
// ----------------------------------------------------------------
let mut requests = Vec::new();
for path_obj in raw_paths {
let req = AiFileFilterRequest::async_try_from_path(path_obj, user_instructions, config).await?;
requests.push(req);
}
// 6) Acquire the AiFileFilter object and process
let mut writer = AiFileFilter::with_model(model_type).await?;
execute_ai_file_filter_requests(&mut writer, &requests, plant).await?;
info!("apply_text_filter_to_files completed successfully.");
Ok(())
}