fix(sync): filesystem-based change detection (catch git pull & non-git edits) (#414)
* fix(sync): detect changes via filesystem, not git status Incremental sync detected changes with `git status --porcelain`, which only sees uncommitted working-tree changes — so committed changes from git pull/checkout/merge/rebase (clean tree afterward) were never reconciled, and non-git projects leaned on a slow full rescan. Change detection is now filesystem-based and git-independent: a (size, mtime) stat pre-filter skips unchanged files, then a content hash confirms the rest; removals are checked against the filesystem (git ls-files still lists deleted-but-unstaged files). Also adds a non-blocking catch-up sync on MCP connect so changes made while the server was down (e.g. a terminal git pull) are reconciled on connect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(changelog): add 0.9.5 entry for filesystem-based sync fix Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
1be8e7830f
commit
4a94696e44
@@ -243,6 +243,7 @@ export class MCPServer {
|
||||
this.cg = await CodeGraph.open(resolvedRoot);
|
||||
this.toolHandler.setDefaultCodeGraph(this.cg);
|
||||
this.startWatching();
|
||||
this.catchUpSync();
|
||||
} catch (err) {
|
||||
// Log the error so transient failures are diagnosable (see issue #47)
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
@@ -301,6 +302,7 @@ export class MCPServer {
|
||||
this.projectPath = resolvedRoot;
|
||||
this.toolHandler.setDefaultCodeGraph(this.cg);
|
||||
this.startWatching();
|
||||
this.catchUpSync();
|
||||
} catch {
|
||||
// Still failing — will retry on next tool call
|
||||
}
|
||||
@@ -370,6 +372,32 @@ export class MCPServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile the index with the current filesystem once, right after connect —
|
||||
* catches edits, adds, deletes, and `git pull`/`checkout` changes made while
|
||||
* no watcher was running. Runs in the background so it never delays the
|
||||
* `initialize` response; `sync()` is incremental (a stat pre-filter skips
|
||||
* unchanged files) and mutex-guarded, so it can't collide with the live
|
||||
* watcher or a git-hook sync. Runs even when the watcher is unavailable
|
||||
* (e.g. WSL2 /mnt drives), where catch-up matters most.
|
||||
*/
|
||||
private catchUpSync(): void {
|
||||
const cg = this.cg;
|
||||
if (!cg) return;
|
||||
void cg
|
||||
.sync()
|
||||
.then((result) => {
|
||||
const changed = result.filesAdded + result.filesModified + result.filesRemoved;
|
||||
if (changed > 0) {
|
||||
process.stderr.write(`[CodeGraph MCP] Caught up ${changed} file(s) changed since last run\n`);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
process.stderr.write(`[CodeGraph MCP] Catch-up sync failed: ${msg}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user