mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
fix(search): surface warning when FTS indexes are missing (#1403)
searchFTSFromLbug now returns { results, ftsAvailable } so callers
can distinguish "zero matches" from "indexes do not exist". When
FTS is unavailable the existing query-response warning fires,
telling the user to re-run gitnexus analyze.
This commit is contained in:
parent
4e362ba70a
commit
e2cfa7e6dd
5 changed files with 46 additions and 18 deletions
|
|
@ -104,7 +104,7 @@ export async function augment(pattern: string, cwd?: string): Promise<string> {
|
|||
}
|
||||
|
||||
// Step 1: BM25 search (fast, no embeddings)
|
||||
const bm25Results = await searchFTSFromLbug(pattern, 10, repoId);
|
||||
const { results: bm25Results } = await searchFTSFromLbug(pattern, 10, repoId);
|
||||
|
||||
if (bm25Results.length === 0) return '';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,17 +15,28 @@ export interface BM25SearchResult {
|
|||
nodeIds?: string[];
|
||||
}
|
||||
|
||||
export interface FTSSearchResponse {
|
||||
results: BM25SearchResult[];
|
||||
/** True when at least one FTS index query succeeded (index exists). */
|
||||
ftsAvailable: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single FTS query via a custom executor (for MCP connection pool).
|
||||
* Returns the same shape as core queryFTS (from LadybugDB adapter).
|
||||
*/
|
||||
/**
|
||||
* Execute a single FTS query via a custom executor (for MCP connection pool).
|
||||
* Returns `null` when the query fails (e.g. FTS index does not exist) so the
|
||||
* caller can distinguish "zero matches" from "index missing".
|
||||
*/
|
||||
async function queryFTSViaExecutor(
|
||||
executor: (cypher: string) => Promise<any[]>,
|
||||
tableName: string,
|
||||
indexName: string,
|
||||
query: string,
|
||||
limit: number,
|
||||
): Promise<Array<{ filePath: string; score: number; nodeId: string }>> {
|
||||
): Promise<Array<{ filePath: string; score: number; nodeId: string }> | null> {
|
||||
// Escape single quotes and backslashes to prevent Cypher injection
|
||||
const escapedQuery = query.replace(/\\/g, '\\\\').replace(/'/g, "''");
|
||||
const cypher = `
|
||||
|
|
@ -46,7 +57,7 @@ async function queryFTSViaExecutor(
|
|||
};
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +76,9 @@ export const searchFTSFromLbug = async (
|
|||
query: string,
|
||||
limit: number = 20,
|
||||
repoId?: string,
|
||||
): Promise<BM25SearchResult[]> => {
|
||||
): Promise<FTSSearchResponse> => {
|
||||
const resultsByIndex: any[][] = [];
|
||||
let queriesSucceeded = 0;
|
||||
|
||||
if (repoId) {
|
||||
// Use MCP connection pool via dynamic import
|
||||
|
|
@ -77,15 +89,27 @@ export const searchFTSFromLbug = async (
|
|||
const executor = (cypher: string) => executeQuery(repoId, cypher);
|
||||
|
||||
for (const { table, indexName } of FTS_INDEXES) {
|
||||
resultsByIndex.push(await queryFTSViaExecutor(executor, table, indexName, query, limit));
|
||||
const result = await queryFTSViaExecutor(executor, table, indexName, query, limit);
|
||||
if (result !== null) {
|
||||
queriesSucceeded++;
|
||||
resultsByIndex.push(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use core lbug adapter (CLI / pipeline context) — also sequential for safety.
|
||||
for (const { table, indexName } of FTS_INDEXES) {
|
||||
resultsByIndex.push(await queryFTS(table, indexName, query, limit, false).catch(() => []));
|
||||
try {
|
||||
const result = await queryFTS(table, indexName, query, limit, false);
|
||||
queriesSucceeded++;
|
||||
resultsByIndex.push(result);
|
||||
} catch {
|
||||
// FTS index may not exist — count as failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ftsAvailable = queriesSucceeded > 0;
|
||||
|
||||
// Collect all node scores per filePath to track which nodes actually matched
|
||||
const fileNodeScores = new Map<string, Array<{ score: number; nodeId: string }>>();
|
||||
|
||||
|
|
@ -116,10 +140,13 @@ export const searchFTSFromLbug = async (
|
|||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, limit);
|
||||
|
||||
return sorted.map((r, index) => ({
|
||||
filePath: r.filePath,
|
||||
score: r.score,
|
||||
rank: index + 1,
|
||||
nodeIds: r.nodeIds,
|
||||
}));
|
||||
return {
|
||||
results: sorted.map((r, index) => ({
|
||||
filePath: r.filePath,
|
||||
score: r.score,
|
||||
rank: index + 1,
|
||||
nodeIds: r.nodeIds,
|
||||
})),
|
||||
ftsAvailable,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export const hybridSearch = async (
|
|||
) => Promise<SemanticSearchResult[]>,
|
||||
): Promise<HybridSearchResult[]> => {
|
||||
// Use LadybugDB FTS for always-fresh BM25 results
|
||||
const bm25Results = await searchFTSFromLbug(query, limit);
|
||||
const { results: bm25Results } = await searchFTSFromLbug(query, limit);
|
||||
const semanticResults = await semanticSearch(executeQuery, query, limit);
|
||||
return mergeWithRRF(bm25Results, semanticResults, limit);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -986,15 +986,16 @@ export class LocalBackend {
|
|||
limit: number,
|
||||
): Promise<{ results: any[]; ftsUsed: boolean }> {
|
||||
const { searchFTSFromLbug } = await import('../../core/search/bm25-index.js');
|
||||
let bm25Results;
|
||||
let ftsResponse;
|
||||
try {
|
||||
bm25Results = await searchFTSFromLbug(query, limit, repo.id);
|
||||
ftsResponse = await searchFTSFromLbug(query, limit, repo.id);
|
||||
} catch (err: any) {
|
||||
console.error('GitNexus: BM25/FTS search failed (FTS indexes may not exist) -', err.message);
|
||||
return { results: [], ftsUsed: false };
|
||||
}
|
||||
|
||||
const ftsUsed = bm25Results.length === 0 || bm25Results[0]?.ftsUsed !== false;
|
||||
const bm25Results = ftsResponse.results;
|
||||
const ftsUsed = ftsResponse.ftsAvailable;
|
||||
|
||||
const results: any[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1078,7 +1078,7 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
|
|||
sources: ['semantic'],
|
||||
}));
|
||||
} else if (mode === 'bm25') {
|
||||
searchResults = await searchFTSFromLbug(query, limit);
|
||||
searchResults = (await searchFTSFromLbug(query, limit)).results;
|
||||
searchResults = searchResults.map((r: any, i: number) => ({
|
||||
...r,
|
||||
rank: i + 1,
|
||||
|
|
@ -1092,7 +1092,7 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
|
|||
await import('../core/embeddings/embedding-pipeline.js');
|
||||
searchResults = await hybridSearch(query, limit, executeQuery, semSearch);
|
||||
} else {
|
||||
searchResults = await searchFTSFromLbug(query, limit);
|
||||
searchResults = (await searchFTSFromLbug(query, limit)).results;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue