From 336c26d5e9965b66c5965663401e7a88ffc6a61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9B=E4=BA=A5?= Date: Thu, 18 Jun 2026 19:02:05 +0800 Subject: [PATCH] fix(sync): filter git-status fast path through ignore matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The incremental sync's git fast path consumed `git status --porcelain` output without filtering through `buildDefaultIgnore`, so tracked files in default-ignored dirs (node_modules/, vendor/, etc.) leaked back into the index on every sync — disagreeing with a full `index --force`. Build the matcher once and skip ignored paths in the modified+added loop. Deleted files stay unfiltered (removal is always correct for stale entries). Closes #766 --- __tests__/sync.test.ts | 32 ++++++++++++++++++++++++++++++++ src/extraction/index.ts | 8 ++++++++ 2 files changed, 40 insertions(+) diff --git a/__tests__/sync.test.ts b/__tests__/sync.test.ts index 708a92a42..5abe9f33e 100644 --- a/__tests__/sync.test.ts +++ b/__tests__/sync.test.ts @@ -302,5 +302,37 @@ describe('Sync Module', () => { expect(result.filesRemoved).toBe(0); expect(result.changedFilePaths).toBeUndefined(); }); + + it('should not index tracked files in default-ignored dirs (issue #766)', async () => { + // Create a committed file inside node_modules/ (a default-ignored dir). + // git status reports changes to tracked files even when they match + // DEFAULT_IGNORE_PATTERNS — the sync git fast path must filter them. + const nmDir = path.join(testDir, 'node_modules', 'pkg'); + fs.mkdirSync(nmDir, { recursive: true }); + fs.writeFileSync( + path.join(nmDir, 'index.ts'), + `export function depFunc() { return 'original'; }` + ); + git('add', '-A'); + git('commit', '-m', 'add dependency'); + + // Full index excludes node_modules/ — depFunc is NOT in the graph. + await cg.indexAll(); + expect(cg.searchNodes('depFunc').length).toBe(0); + + // Modify the tracked file inside node_modules/. + fs.writeFileSync( + path.join(nmDir, 'index.ts'), + `export function depFunc() { return 'modified'; }` + ); + + // sync() must NOT pick up the change — the file is in an ignored dir. + const result = await cg.sync(); + expect(result.filesAdded).toBe(0); + expect(result.filesModified).toBe(0); + + // depFunc is still not in the graph. + expect(cg.searchNodes('depFunc').length).toBe(0); + }); }); }); diff --git a/src/extraction/index.ts b/src/extraction/index.ts index 643634d66..82f868ae0 100644 --- a/src/extraction/index.ts +++ b/src/extraction/index.ts @@ -1810,7 +1810,15 @@ export class ExtractionOrchestrator { // files stay untracked in git even after indexing, so they must be // hash-compared like modified files instead of always counting as added — // otherwise status reports them as pending forever. (See issue #206.) + // + // Filter through the same ignore matcher used by getGitVisibleFiles so + // tracked files in excluded dirs (e.g. a committed `vendor/`) don't leak + // back into the index. git status reports changes to tracked files even + // when they match DEFAULT_IGNORE_PATTERNS or a .gitignore entry. (#766) + const ig = buildDefaultIgnore(this.rootDir); for (const filePath of [...gitChanges.modified, ...gitChanges.added]) { + if (ig.ignores(filePath)) continue; + const fullPath = path.join(this.rootDir, filePath); let content: string; try {