gitnexus 1.6.10-rc.221 → 1.6.10-rc.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -122,6 +122,15 @@ export declare const initLbug: (repoId: string, dbPath: string) => Promise<boole
|
|
|
122
122
|
* repoId already injected it), the existing entry is reused.
|
|
123
123
|
*/
|
|
124
124
|
export declare function initLbugWithDb(repoId: string, existingDb: lbug.Database, dbPath: string): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Lazily load VECTOR for a semantic query.
|
|
127
|
+
*
|
|
128
|
+
* Exact graph reads never call this function, so opening their read pool does
|
|
129
|
+
* not probe or warn about an optional extension they do not use. The promise
|
|
130
|
+
* lives on SharedDB because extension scope is per Database, and also joins
|
|
131
|
+
* concurrent first semantic requests onto one LOAD attempt.
|
|
132
|
+
*/
|
|
133
|
+
export declare function ensureVectorExtension(repoId: string): Promise<boolean>;
|
|
125
134
|
export declare const executeQuery: (repoId: string, cypher: string) => Promise<any[]>;
|
|
126
135
|
/**
|
|
127
136
|
* Execute a parameterized query on a specific repo's connection pool.
|
|
@@ -265,6 +265,7 @@ function closeOne(repoId) {
|
|
|
265
265
|
shared.refCount = 0;
|
|
266
266
|
shared.ftsLoaded = false;
|
|
267
267
|
shared.vectorLoaded = false;
|
|
268
|
+
shared.vectorLoadPromise = undefined;
|
|
268
269
|
}
|
|
269
270
|
else {
|
|
270
271
|
shared.db.close().catch(() => { });
|
|
@@ -692,13 +693,6 @@ async function doInitLbug(repoId, dbPath) {
|
|
|
692
693
|
if (!shared.ftsLoaded) {
|
|
693
694
|
shared.ftsLoaded = await loadFTSExtension(available[0], { policy: 'load-only' });
|
|
694
695
|
}
|
|
695
|
-
// VECTOR too — extension load scope is per-Database, so this one load
|
|
696
|
-
// makes QUERY_VECTOR_INDEX legal on every pooled connection. Same
|
|
697
|
-
// load-only contract as FTS above; on failure the semantic-query lane
|
|
698
|
-
// falls back to the exact scan with its own diagnostic (#2623 follow-up).
|
|
699
|
-
if (!shared.vectorLoaded) {
|
|
700
|
-
shared.vectorLoaded = await loadVectorExtension(available[0], { policy: 'load-only' });
|
|
701
|
-
}
|
|
702
696
|
// Register pool entry only after all connections are pre-warmed and FTS is
|
|
703
697
|
// loaded. Concurrent executeQuery calls see either "not initialized"
|
|
704
698
|
// (and throw cleanly) or a fully ready pool — never a half-built one.
|
|
@@ -761,11 +755,6 @@ export async function initLbugWithDb(repoId, existingDb, dbPath) {
|
|
|
761
755
|
if (!shared.ftsLoaded) {
|
|
762
756
|
shared.ftsLoaded = await loadFTSExtension(available[0], { policy: 'load-only' });
|
|
763
757
|
}
|
|
764
|
-
// VECTOR too — same per-Database scope and load-only contract as the
|
|
765
|
-
// doInitLbug site above (#2623 follow-up).
|
|
766
|
-
if (!shared.vectorLoaded) {
|
|
767
|
-
shared.vectorLoaded = await loadVectorExtension(available[0], { policy: 'load-only' });
|
|
768
|
-
}
|
|
769
758
|
pool.set(repoId, {
|
|
770
759
|
db: existingDb,
|
|
771
760
|
available,
|
|
@@ -780,6 +769,143 @@ export async function initLbugWithDb(repoId, existingDb, dbPath) {
|
|
|
780
769
|
ensureIdleTimer();
|
|
781
770
|
traceRss('init', repoId);
|
|
782
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Lazily load VECTOR for a semantic query.
|
|
774
|
+
*
|
|
775
|
+
* Exact graph reads never call this function, so opening their read pool does
|
|
776
|
+
* not probe or warn about an optional extension they do not use. The promise
|
|
777
|
+
* lives on SharedDB because extension scope is per Database, and also joins
|
|
778
|
+
* concurrent first semantic requests onto one LOAD attempt.
|
|
779
|
+
*/
|
|
780
|
+
export async function ensureVectorExtension(repoId) {
|
|
781
|
+
const entry = pool.get(repoId);
|
|
782
|
+
if (!entry) {
|
|
783
|
+
throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
|
|
784
|
+
}
|
|
785
|
+
const shared = dbCache.get(entry.dbPath);
|
|
786
|
+
if (!shared) {
|
|
787
|
+
throw new Error(`LadybugDB shared handle is unavailable for repo "${repoId}".`);
|
|
788
|
+
}
|
|
789
|
+
if (shared.vectorLoaded)
|
|
790
|
+
return true;
|
|
791
|
+
if (shared.vectorLoadPromise)
|
|
792
|
+
return shared.vectorLoadPromise;
|
|
793
|
+
const loadAttempt = (async () => {
|
|
794
|
+
const conn = await checkout(entry);
|
|
795
|
+
try {
|
|
796
|
+
const loaded = await loadVectorExtension(conn, { policy: 'load-only' });
|
|
797
|
+
shared.vectorLoaded = loaded;
|
|
798
|
+
return loaded;
|
|
799
|
+
}
|
|
800
|
+
finally {
|
|
801
|
+
checkin(entry, conn);
|
|
802
|
+
}
|
|
803
|
+
})();
|
|
804
|
+
const cachedAttempt = loadAttempt.catch((err) => {
|
|
805
|
+
// A transient checkout/load failure must not poison this Database for the
|
|
806
|
+
// rest of its lifetime. Keep resolved false cached, but let a later
|
|
807
|
+
// semantic request retry a rejected attempt.
|
|
808
|
+
if (shared.vectorLoadPromise === cachedAttempt) {
|
|
809
|
+
shared.vectorLoadPromise = undefined;
|
|
810
|
+
}
|
|
811
|
+
throw err;
|
|
812
|
+
});
|
|
813
|
+
shared.vectorLoadPromise = cachedAttempt;
|
|
814
|
+
return shared.vectorLoadPromise;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Detect an actual VECTOR procedure call without treating source text stored in
|
|
818
|
+
* Cypher literals or comments as executable syntax.
|
|
819
|
+
*/
|
|
820
|
+
function callsVectorIndex(cypher) {
|
|
821
|
+
if (!/QUERY_VECTOR_INDEX/i.test(cypher))
|
|
822
|
+
return false;
|
|
823
|
+
let code = '';
|
|
824
|
+
let state = 'code';
|
|
825
|
+
let backtickIdentifier = '';
|
|
826
|
+
for (let i = 0; i < cypher.length; i++) {
|
|
827
|
+
const ch = cypher[i];
|
|
828
|
+
const next = cypher[i + 1];
|
|
829
|
+
if (state === 'code') {
|
|
830
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
831
|
+
state = ch === "'" ? 'single' : ch === '"' ? 'double' : 'backtick';
|
|
832
|
+
if (state === 'backtick')
|
|
833
|
+
backtickIdentifier = '';
|
|
834
|
+
code += ' ';
|
|
835
|
+
}
|
|
836
|
+
else if (ch === '/' && next === '/') {
|
|
837
|
+
state = 'line-comment';
|
|
838
|
+
code += ' ';
|
|
839
|
+
i++;
|
|
840
|
+
}
|
|
841
|
+
else if (ch === '/' && next === '*') {
|
|
842
|
+
state = 'block-comment';
|
|
843
|
+
code += ' ';
|
|
844
|
+
i++;
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
code += ch;
|
|
848
|
+
}
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (state === 'line-comment') {
|
|
852
|
+
if (ch === '\n' || ch === '\r') {
|
|
853
|
+
state = 'code';
|
|
854
|
+
code += ch;
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
code += ' ';
|
|
858
|
+
}
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (state === 'block-comment') {
|
|
862
|
+
if (ch === '*' && next === '/') {
|
|
863
|
+
state = 'code';
|
|
864
|
+
code += ' ';
|
|
865
|
+
i++;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
code += ch === '\n' || ch === '\r' ? ch : ' ';
|
|
869
|
+
}
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (state === 'backtick') {
|
|
873
|
+
if (ch === '`' && next === '`') {
|
|
874
|
+
backtickIdentifier += '`';
|
|
875
|
+
code += ' ';
|
|
876
|
+
i++;
|
|
877
|
+
}
|
|
878
|
+
else if (ch === '`') {
|
|
879
|
+
state = 'code';
|
|
880
|
+
code +=
|
|
881
|
+
backtickIdentifier.toUpperCase() === 'QUERY_VECTOR_INDEX' ? 'QUERY_VECTOR_INDEX' : ' ';
|
|
882
|
+
}
|
|
883
|
+
else if (ch === '\\' && next !== undefined) {
|
|
884
|
+
backtickIdentifier += next;
|
|
885
|
+
code += ' ';
|
|
886
|
+
i++;
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
backtickIdentifier += ch;
|
|
890
|
+
code += ch === '\n' || ch === '\r' ? ch : ' ';
|
|
891
|
+
}
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (ch === '\\') {
|
|
895
|
+
code += ' ';
|
|
896
|
+
if (next !== undefined) {
|
|
897
|
+
code += next === '\n' || next === '\r' ? next : ' ';
|
|
898
|
+
i++;
|
|
899
|
+
}
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const closesLiteral = (state === 'single' && ch === "'") || (state === 'double' && ch === '"');
|
|
903
|
+
if (closesLiteral)
|
|
904
|
+
state = 'code';
|
|
905
|
+
code += ch === '\n' || ch === '\r' ? ch : ' ';
|
|
906
|
+
}
|
|
907
|
+
return /\bCALL\s+QUERY_VECTOR_INDEX\s*\(/i.test(code);
|
|
908
|
+
}
|
|
783
909
|
/**
|
|
784
910
|
* Checkout a connection from the pool.
|
|
785
911
|
* Returns an available connection, or creates a new one if under the cap.
|
|
@@ -870,12 +996,26 @@ export const executeParameterized = async (repoId, cypher, params) => {
|
|
|
870
996
|
// a query built by splicing a caller-sized list names itself even when the
|
|
871
997
|
// repo is not initialized. Never throws (#2915).
|
|
872
998
|
warnIfQueryTextUnbounded(cypher, `pool executeParameterized (repo "${repoId}")`, (message) => poolSidecarLogger.warn(message));
|
|
873
|
-
|
|
999
|
+
let entry = pool.get(repoId);
|
|
874
1000
|
if (!entry) {
|
|
875
1001
|
throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
|
|
876
1002
|
}
|
|
877
|
-
|
|
1003
|
+
// Exact reads must not pay for VECTOR, but an explicit raw vector procedure
|
|
1004
|
+
// call is a semantic read. Preflight before taking the query connection:
|
|
1005
|
+
// ensureVectorExtension performs its own checkout, so holding one here could
|
|
1006
|
+
// make a saturated pool wait for a connection that every caller is holding.
|
|
1007
|
+
// A load rejection must not replace the query's own diagnostic.
|
|
1008
|
+
if (callsVectorIndex(cypher)) {
|
|
1009
|
+
await ensureVectorExtension(repoId).catch(() => false);
|
|
1010
|
+
// The preflight suspends, so close/re-init may replace the pool entry.
|
|
1011
|
+
// Re-read it before checkout to avoid querying through a stale handle.
|
|
1012
|
+
entry = pool.get(repoId);
|
|
1013
|
+
if (!entry) {
|
|
1014
|
+
throw new Error(`LadybugDB connection pool closed for repo "${repoId}" (re-init/teardown); retry the query.`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
878
1017
|
const conn = await checkout(entry);
|
|
1018
|
+
entry.lastUsed = Date.now();
|
|
879
1019
|
silenceStdout();
|
|
880
1020
|
activeQueryCount++;
|
|
881
1021
|
let queryResult;
|
|
@@ -256,12 +256,12 @@ export declare class LocalBackend {
|
|
|
256
256
|
*/
|
|
257
257
|
private warnedSiblingDrift;
|
|
258
258
|
/**
|
|
259
|
-
* One-shot stderr
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* stderr noisy per DoD §2.8.
|
|
259
|
+
* One-shot stderr guards for distinct VECTOR load and index-query failures.
|
|
260
|
+
* Keeping them separate preserves both diagnostics across semanticSearch calls
|
|
261
|
+
* without repeating either on hot paths.
|
|
263
262
|
*/
|
|
264
|
-
private
|
|
263
|
+
private warnedVectorLoadFailed;
|
|
264
|
+
private warnedVectorQueryFailed;
|
|
265
265
|
/**
|
|
266
266
|
* One-shot warning when a pruned or Node-unloadable optional embedding stack
|
|
267
267
|
* (#2370/#2372) forces semantic search to fall back to BM25 — so the
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
|
-
import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, statDbIdentity, dbIdentityChanged, } from '../../core/lbug/pool-adapter.js';
|
|
11
|
+
import { initLbug, executeQuery, executeParameterized, ensureVectorExtension, closeLbug, isLbugReady, statDbIdentity, dbIdentityChanged, } from '../../core/lbug/pool-adapter.js';
|
|
12
12
|
import { queryClassBeanMetadata } from './bean-metadata.js';
|
|
13
13
|
import { querySpringAopMetadata } from './aop-metadata.js';
|
|
14
14
|
import { queryConvexDispatchMetadata } from './convex-metadata.js';
|
|
@@ -783,12 +783,12 @@ export class LocalBackend {
|
|
|
783
783
|
*/
|
|
784
784
|
warnedSiblingDrift = new Set();
|
|
785
785
|
/**
|
|
786
|
-
* One-shot stderr
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
* stderr noisy per DoD §2.8.
|
|
786
|
+
* One-shot stderr guards for distinct VECTOR load and index-query failures.
|
|
787
|
+
* Keeping them separate preserves both diagnostics across semanticSearch calls
|
|
788
|
+
* without repeating either on hot paths.
|
|
790
789
|
*/
|
|
791
|
-
|
|
790
|
+
warnedVectorLoadFailed = false;
|
|
791
|
+
warnedVectorQueryFailed = false;
|
|
792
792
|
/**
|
|
793
793
|
* One-shot warning when a pruned or Node-unloadable optional embedding stack
|
|
794
794
|
* (#2370/#2372) forces semantic search to fall back to BM25 — so the
|
|
@@ -2518,17 +2518,25 @@ export class LocalBackend {
|
|
|
2518
2518
|
this.lastQueryEmbeddingDims.set(repo.lbugPath, dims);
|
|
2519
2519
|
const queryVecStr = `[${queryVec.join(',')}]`;
|
|
2520
2520
|
const maxDistance = getVectorMaxDistance(DEFAULT_MCP_VECTOR_MAX_DISTANCE);
|
|
2521
|
-
let
|
|
2522
|
-
// Always TRY the vector lane — no platform gate. LadybugDB ships the
|
|
2523
|
-
// VECTOR extension for every supported platform, Windows included
|
|
2524
|
-
// (#2623 follow-up; the old `platform !== 'win32'` gate was stale), so
|
|
2525
|
-
// whether the index is queryable is a per-machine runtime fact. The
|
|
2526
|
-
// catch below is the fallback: any failure (extension unloadable, index
|
|
2527
|
-
// absent, older DB) degrades to the exact scan with a once-per-backend
|
|
2528
|
-
// diagnostic instead of being silently swallowed.
|
|
2521
|
+
let vectorReady = false;
|
|
2529
2522
|
try {
|
|
2530
|
-
|
|
2531
|
-
|
|
2523
|
+
vectorReady = await ensureVectorExtension(repo.lbugPath);
|
|
2524
|
+
}
|
|
2525
|
+
catch (err) {
|
|
2526
|
+
if (!this.warnedVectorLoadFailed) {
|
|
2527
|
+
this.warnedVectorLoadFailed = true;
|
|
2528
|
+
logger.warn({ err }, 'GitNexus [query:vector]: vector extension load failed; using exact scan fallback');
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
let bestChunks = new Map();
|
|
2532
|
+
// Try the vector lane only after its lazy load succeeds. An unavailable
|
|
2533
|
+
// extension is already reported by ExtensionManager; an index/query
|
|
2534
|
+
// failure below gets its own once-per-backend diagnostic before the exact
|
|
2535
|
+
// scan fallback.
|
|
2536
|
+
if (vectorReady) {
|
|
2537
|
+
try {
|
|
2538
|
+
bestChunks = await collectBestChunks(limit, async (fetchLimit) => {
|
|
2539
|
+
const vectorQuery = `
|
|
2532
2540
|
CALL QUERY_VECTOR_INDEX('${EMBEDDING_TABLE_NAME}', '${EMBEDDING_INDEX_NAME}',
|
|
2533
2541
|
CAST(${queryVecStr} AS FLOAT[${dims}]), ${fetchLimit})
|
|
2534
2542
|
YIELD node AS emb, distance
|
|
@@ -2538,24 +2546,25 @@ export class LocalBackend {
|
|
|
2538
2546
|
emb.startLine AS startLine, emb.endLine AS endLine, distance
|
|
2539
2547
|
ORDER BY distance
|
|
2540
2548
|
`;
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2549
|
+
const embResults = await executeQuery(repo.lbugPath, vectorQuery);
|
|
2550
|
+
return embResults.map((row) => ({
|
|
2551
|
+
nodeId: row.nodeId ?? row[0],
|
|
2552
|
+
chunkIndex: row.chunkIndex ?? row[1] ?? 0,
|
|
2553
|
+
startLine: row.startLine ?? row[2] ?? 0,
|
|
2554
|
+
endLine: row.endLine ?? row[3] ?? 0,
|
|
2555
|
+
distance: row.distance ?? row[4],
|
|
2556
|
+
}));
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
catch (err) {
|
|
2560
|
+
bestChunks = new Map();
|
|
2561
|
+
if (!this.warnedVectorQueryFailed) {
|
|
2562
|
+
// Rare diagnostic: surface why semantic search fell back to the
|
|
2563
|
+
// exact scan. Emitted once per `LocalBackend` instance lifetime to
|
|
2564
|
+
// avoid noisy stderr on hot semantic-search paths (DoD §2.8).
|
|
2565
|
+
this.warnedVectorQueryFailed = true;
|
|
2566
|
+
logger.warn({ err }, 'GitNexus [query:vector]: vector index query failed; using exact scan fallback');
|
|
2567
|
+
}
|
|
2559
2568
|
}
|
|
2560
2569
|
}
|
|
2561
2570
|
if (bestChunks.size === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.10-rc.
|
|
3
|
+
"version": "1.6.10-rc.222",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|