- pipeline: re-resolve imports for every file on update (not just changed), so add/remove/rename of a target fixes unchanged importers; propagate hash and read errors instead of treating unreadable files as empty - discovery: report unreadable walk entries to stderr instead of dropping them - imports: strip `pub` visibility so `pub use` re-exports resolve; share extension/module-entry probe lists (adds cc,cxx,hpp,hh,hxx,mts,cts,jsx,mjs,cjs) - store: DISTINCT + ORDER in importers_of to dedup repeated edges - mcp: validate required tool args (explicit isError), emit JSON-RPC -32700 parse errors and -32600 invalid-request instead of silently skipping - README: document the 13-tool surface vs toktoken's 27 and known gaps - tests: regression coverage for update re-resolution, pub-use re-exports, importer dedup, and MCP error reporting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
68 lines
2.0 KiB
Rust
68 lines
2.0 KiB
Rust
//! Filesystem walk with smart filtering, producing the list of source files to index.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::Result;
|
|
|
|
use crate::config::{SKIP_DIRS, language_for};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct SourceFile {
|
|
/// Absolute path on disk.
|
|
pub path: PathBuf,
|
|
/// Path relative to the project root (forward slashes).
|
|
pub rel: String,
|
|
pub language: &'static str,
|
|
}
|
|
|
|
pub fn discover(project: &Path) -> Result<Vec<SourceFile>> {
|
|
let project_abs = project
|
|
.canonicalize()
|
|
.unwrap_or_else(|_| project.to_path_buf());
|
|
let mut builder = ignore::WalkBuilder::new(&project_abs);
|
|
builder.standard_filters(true);
|
|
builder.require_git(false);
|
|
builder.filter_entry(|entry| {
|
|
if entry.file_type().is_some_and(|t| t.is_dir()) {
|
|
let name = entry.file_name().to_string_lossy();
|
|
!SKIP_DIRS.iter().any(|d| *d == name)
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
let mut out = Vec::new();
|
|
let mut skipped = Vec::new();
|
|
for entry in builder.build() {
|
|
let entry = match entry {
|
|
Ok(e) => e,
|
|
Err(e) => {
|
|
// Report unreadable entries instead of dropping them silently.
|
|
skipped.push(e.to_string());
|
|
continue;
|
|
}
|
|
};
|
|
let path = entry.path();
|
|
if entry.file_type().is_some_and(|t| t.is_dir()) {
|
|
continue;
|
|
}
|
|
let Some(language) = language_for(path) else {
|
|
continue;
|
|
};
|
|
let rel = path
|
|
.strip_prefix(&project_abs)
|
|
.map(|p| p.to_string_lossy().replace('\\', "/"))
|
|
.unwrap_or_else(|_| path.to_string_lossy().replace('\\', "/"));
|
|
out.push(SourceFile {
|
|
path: path.to_path_buf(),
|
|
rel,
|
|
language,
|
|
});
|
|
}
|
|
for s in &skipped {
|
|
eprintln!("rustytokens: skipped unreadable entry: {s}");
|
|
}
|
|
out.sort_by(|a, b| a.rel.cmp(&b.rel));
|
|
Ok(out)
|
|
}
|