Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions packages/mcp/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class ToolHandlers {
try {
const results = await vectorDb.query(
collectionName,
'', // Empty filter to get all results
undefined as any, // Don't pass empty filter
['metadata'], // Only fetch metadata field
1 // Only need one result to extract codebasePath
);
Expand Down Expand Up @@ -140,14 +140,24 @@ export class ToolHandlers {
// Remove local codebases that don't exist in cloud
for (const localCodebase of localCodebases) {
if (!cloudCodebases.has(localCodebase)) {
this.snapshotManager.removeIndexedCodebase(localCodebase);
this.snapshotManager.removeCodebaseCompletely(localCodebase);
hasChanges = true;
console.log(`[SYNC-CLOUD] ➖ Removed local codebase (not in cloud): ${localCodebase}`);
}
}

// Note: We don't add cloud codebases that are missing locally (as per user requirement)
console.log(`[SYNC-CLOUD] ℹ️ Skipping addition of cloud codebases not present locally (per sync policy)`);
// Add cloud codebases that are missing from local snapshot (recovery)
for (const cloudCodebase of cloudCodebases) {
if (!localCodebases.has(cloudCodebase)) {
this.snapshotManager.setCodebaseIndexed(cloudCodebase, {
indexedFiles: 0,
totalChunks: 0,
status: 'completed' as const
});
hasChanges = true;
console.log(`[SYNC-CLOUD] ➕ Recovered codebase from cloud: ${cloudCodebase}`);
}
}

if (hasChanges) {
this.snapshotManager.saveCodebaseSnapshot();
Expand Down Expand Up @@ -228,8 +238,18 @@ export class ToolHandlers {
}

//Check if the snapshot and cloud index are in sync
if (this.snapshotManager.getIndexedCodebases().includes(absolutePath) !== await this.context.hasIndex(absolutePath)) {
console.warn(`[INDEX-VALIDATION] ❌ Snapshot and cloud index mismatch: ${absolutePath}`);
const snapshotHasIndex = this.snapshotManager.getIndexedCodebases().includes(absolutePath);
const vectorDbHasIndex = await this.context.hasIndex(absolutePath);
if (snapshotHasIndex !== vectorDbHasIndex) {
if (vectorDbHasIndex && !snapshotHasIndex) {
console.warn(`[INDEX-VALIDATION] Recovering missing snapshot for '${absolutePath}'`);
this.snapshotManager.setCodebaseIndexed(absolutePath, { indexedFiles: 0, totalChunks: 0, status: 'completed' as const });
this.snapshotManager.saveCodebaseSnapshot();
} else if (!vectorDbHasIndex && snapshotHasIndex) {
console.warn(`[INDEX-VALIDATION] Clearing stale snapshot for '${absolutePath}'`);
this.snapshotManager.removeCodebaseCompletely(absolutePath);
this.snapshotManager.saveCodebaseSnapshot();
}
}

// Check if already indexed (unless force is true)
Expand Down Expand Up @@ -478,13 +498,22 @@ export class ToolHandlers {
const isIndexing = this.snapshotManager.getIndexingCodebases().includes(absolutePath);

if (!isIndexed && !isIndexing) {
return {
content: [{
type: "text",
text: `Error: Codebase '${absolutePath}' is not indexed. Please index it first using the index_codebase tool.`
}],
isError: true
};
// Fallback: check VectorDB directly in case snapshot is out of sync
const hasVectorIndex = await this.context.hasIndex(absolutePath);
if (hasVectorIndex) {
console.warn(`[SEARCH] Snapshot missing but VectorDB has index for '${absolutePath}', recovering snapshot`);
this.snapshotManager.setCodebaseIndexed(absolutePath, { indexedFiles: 0, totalChunks: 0, status: 'completed' as const });
this.snapshotManager.saveCodebaseSnapshot();
// Continue with search (don't return error)
} else {
return {
content: [{
type: "text",
text: `Error: Codebase '${absolutePath}' is not indexed. Please index it first using the index_codebase tool.`
}],
isError: true
};
}
}

// Show indexing status if codebase is being indexed
Expand Down
8 changes: 8 additions & 0 deletions packages/mcp/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class SnapshotManager {
private indexingCodebases: Map<string, number> = new Map(); // Map of codebase path to progress percentage
private codebaseFileCount: Map<string, number> = new Map(); // Map of codebase path to indexed file count
private codebaseInfoMap: Map<string, CodebaseInfo> = new Map(); // Map of codebase path to complete info
private recentlyRemoved: Set<string> = new Set(); // Tracks codebases removed since last save

constructor() {
// Initialize snapshot file path
Expand Down Expand Up @@ -437,6 +438,9 @@ export class SnapshotManager {
this.codebaseFileCount.delete(codebasePath);
this.codebaseInfoMap.delete(codebasePath);

// Track removal so mergeExternalEntry won't re-add it from disk
this.recentlyRemoved.add(codebasePath);

console.log(`[SNAPSHOT-DEBUG] Completely removed codebase from snapshot: ${codebasePath}`);
}

Expand Down Expand Up @@ -500,6 +504,7 @@ export class SnapshotManager {

private mergeExternalEntry(codebasePath: string, info: CodebaseInfo): void {
if (this.codebaseInfoMap.has(codebasePath)) return; // we already know about it
if (this.recentlyRemoved.has(codebasePath)) return; // explicitly removed, don't re-add from disk
this.codebaseInfoMap.set(codebasePath, info);
if (info.status === 'indexed') {
if (!this.indexedCodebases.includes(codebasePath)) {
Expand Down Expand Up @@ -563,6 +568,9 @@ export class SnapshotManager {

fs.writeFileSync(this.snapshotFilePath, JSON.stringify(snapshot, null, 2));

// Clear recently removed set after successful save
this.recentlyRemoved.clear();

const indexedCount = this.indexedCodebases.length;
const indexingCount = this.indexingCodebases.size;
const failedCount = this.getFailedCodebases().length;
Expand Down
Loading