* Get files that have changed since last index. * Uses git status as a fast path when available, falling back to full scan.
()
| 2681 | * Uses git status as a fast path when available, falling back to full scan. |
| 2682 | */ |
| 2683 | getChangedFiles(): { added: string[]; modified: string[]; removed: string[] } { |
| 2684 | const gitChanges = getGitChangedFiles(this.rootDir); |
| 2685 | |
| 2686 | if (gitChanges) { |
| 2687 | // === Git fast path === |
| 2688 | const added: string[] = []; |
| 2689 | const modified: string[] = []; |
| 2690 | const removed: string[] = []; |
| 2691 | |
| 2692 | // Deleted files — only report if tracked in DB |
| 2693 | for (const filePath of gitChanges.deleted) { |
| 2694 | const tracked = this.queries.getFileByPath(filePath); |
| 2695 | if (tracked) { |
| 2696 | removed.push(filePath); |
| 2697 | } |
| 2698 | } |
| 2699 | |
| 2700 | // Modified + added files — read + hash, compare with DB. Untracked (`??`) |
| 2701 | // files stay untracked in git even after indexing, so they must be |
| 2702 | // hash-compared like modified files instead of always counting as added — |
| 2703 | // otherwise status reports them as pending forever. (See issue #206.) |
| 2704 | for (const filePath of [...gitChanges.modified, ...gitChanges.added]) { |
| 2705 | const fullPath = path.join(this.rootDir, filePath); |
| 2706 | let content: string; |
| 2707 | try { |
| 2708 | content = fs.readFileSync(fullPath, 'utf-8'); |
| 2709 | } catch (error) { |
| 2710 | logDebug('Skipping unreadable file while detecting changes', { filePath, error: String(error) }); |
| 2711 | continue; |
| 2712 | } |
| 2713 | |
| 2714 | const contentHash = hashContent(content); |
| 2715 | const tracked = this.queries.getFileByPath(filePath); |
| 2716 | |
| 2717 | if (!tracked) { |
| 2718 | added.push(filePath); |
| 2719 | } else if (tracked.contentHash !== contentHash) { |
| 2720 | modified.push(filePath); |
| 2721 | } |
| 2722 | } |
| 2723 | |
| 2724 | return { added, modified, removed }; |
| 2725 | } |
| 2726 | |
| 2727 | // === Fallback: full scan (non-git project or git failure) === |
| 2728 | const currentFiles = new Set(scanDirectory(this.rootDir)); |
| 2729 | const trackedFiles = this.queries.getAllFiles(); |
| 2730 | |
| 2731 | // Build Map for O(1) lookups |
| 2732 | const trackedMap = new Map<string, FileRecord>(); |
| 2733 | for (const f of trackedFiles) { |
| 2734 | trackedMap.set(f.path, f); |
| 2735 | } |
| 2736 | |
| 2737 | const added: string[] = []; |
| 2738 | const modified: string[] = []; |
| 2739 | const removed: string[] = []; |
| 2740 |
no test coverage detected