Context
Discovered while fixing #2512 (incremental fast-path never re-applying ignore-dir filtering) and confirmed via Greptile review on PR #2576.
Problem
The two engines' own full-filesystem-walk implementations disagree on whether the ignore-dir name check (IGNORE_DIRS/DEFAULT_IGNORE_DIRS) applies to file entries, not just directories:
- TypeScript (
src/domain/graph/builder/helpers.ts, shouldSkipEntry): checks ignoreSet.has(entry.name) unconditionally for every entry, file or directory (line 69, pre-existing since at least 2026-03-30/2026-06-20). A file literally named e.g. vendor (no extension) would be skipped by name alone.
- Rust (
crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs, collect_files's filter_entry closure): only applies the ignore_set.contains(name) check when entry.file_type().is_some_and(|ft| ft.is_dir()) — files are never matched by name against the ignore set.
In practice this rarely matters for real source files, since a collected file must have a supported extension (EXTENSIONS/SUPPORTED_EXTENSIONS), and no IGNORE_DIRS entry contains a ., so an exact string match between a bare ignore-dir name and a real name.ext file can never occur. It could matter for an extensionless file if either engine's extension-detection logic ever changes to admit extensionless source files.
Fix direction
Needs a decision on which behavior is authoritative (directory-only, matching Rust's narrower and arguably more correct semantics, since IGNORE_DIRS's own name — "ignore dirs" — implies directory-only), then align the other engine's full walk to match. Filed as its own issue per scope discipline — out of scope for #2512, which is about fast-path/full-walk consistency within each engine, not normalizing the two engines' full-walk semantics against each other.
Context
Discovered while fixing #2512 (incremental fast-path never re-applying ignore-dir filtering) and confirmed via Greptile review on PR #2576.
Problem
The two engines' own full-filesystem-walk implementations disagree on whether the ignore-dir name check (
IGNORE_DIRS/DEFAULT_IGNORE_DIRS) applies to file entries, not just directories:src/domain/graph/builder/helpers.ts,shouldSkipEntry): checksignoreSet.has(entry.name)unconditionally for every entry, file or directory (line 69, pre-existing since at least 2026-03-30/2026-06-20). A file literally named e.g.vendor(no extension) would be skipped by name alone.crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs,collect_files'sfilter_entryclosure): only applies theignore_set.contains(name)check whenentry.file_type().is_some_and(|ft| ft.is_dir())— files are never matched by name against the ignore set.In practice this rarely matters for real source files, since a collected file must have a supported extension (
EXTENSIONS/SUPPORTED_EXTENSIONS), and noIGNORE_DIRSentry contains a., so an exact string match between a bare ignore-dir name and a realname.extfile can never occur. It could matter for an extensionless file if either engine's extension-detection logic ever changes to admit extensionless source files.Fix direction
Needs a decision on which behavior is authoritative (directory-only, matching Rust's narrower and arguably more correct semantics, since IGNORE_DIRS's own name — "ignore dirs" — implies directory-only), then align the other engine's full walk to match. Filed as its own issue per scope discipline — out of scope for #2512, which is about fast-path/full-walk consistency within each engine, not normalizing the two engines' full-walk semantics against each other.