From 39ef64f95b64327ba696ca75d2a2566cdd4454c1 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Wed, 8 Apr 2026 14:05:17 +0300 Subject: [PATCH] fix: filter false-positive "changed" files in incremental detection getChangedFiles unions git-diff with git-status, but uncommitted files reported by `git status --porcelain` reappear on every run even when their content hash already matches the index. This caused misleading "Incremental: 1 changed" logs and unnecessary read+hash work inside indexFiles on every invocation. Compare content hashes in getChangedFiles itself so only truly modified files enter the changed list. --- .changeset/fix-incremental-false-positive.md | 5 +++++ src/application/index-engine.ts | 17 +++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-incremental-false-positive.md diff --git a/.changeset/fix-incremental-false-positive.md b/.changeset/fix-incremental-false-positive.md new file mode 100644 index 00000000..78bd7c36 --- /dev/null +++ b/.changeset/fix-incremental-false-positive.md @@ -0,0 +1,5 @@ +--- +"@stainless-code/codemap": patch +--- + +Fix incremental detection reporting unchanged files as "changed" on every run when the working tree has uncommitted modifications. `getChangedFiles` now compares content hashes against the index before including candidates, so only truly modified files enter the indexing pipeline. diff --git a/src/application/index-engine.ts b/src/application/index-engine.ts index 21a35940..a883edbc 100644 --- a/src/application/index-engine.ts +++ b/src/application/index-engine.ts @@ -126,23 +126,28 @@ export function getChangedFiles(db: CodemapDatabase): { .filter(Boolean) .map((line: string) => line.slice(3).trim()); - const indexedPaths = new Set(getAllFileHashes(db).keys()); - const allChanged = [...new Set([...diffFiles, ...statusFiles])].filter( + const existingHashes = getAllFileHashes(db); + const allCandidates = [...new Set([...diffFiles, ...statusFiles])].filter( (f) => { const ext = extname(f); - return ext in LANG_MAP || indexedPaths.has(f); + return ext in LANG_MAP || existingHashes.has(f); }, ); const changed: string[] = []; const deleted: string[] = []; - for (const f of allChanged) { + for (const f of allCandidates) { + const absPath = join(getProjectRoot(), f); + let source: string; try { - statSync(join(getProjectRoot(), f)); - changed.push(f); + source = readFileSync(absPath, "utf-8"); } catch { deleted.push(f); + continue; + } + if (existingHashes.get(f) !== hashContent(source)) { + changed.push(f); } }