gitnexus 1.6.4-rc.91 → 1.6.4-rc.92
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.
|
@@ -32,9 +32,12 @@ import type lbug from '@ladybugdb/core';
|
|
|
32
32
|
* integer; anything invalid falls back to the default.
|
|
33
33
|
*/
|
|
34
34
|
export declare const LBUG_MAX_DB_SIZE: number;
|
|
35
|
+
export declare const WAL_RECOVERY_SUGGESTION = "WAL corruption detected. Run `gitnexus analyze` to rebuild the index.";
|
|
36
|
+
export declare function isWalCorruptionError(err: unknown): boolean;
|
|
35
37
|
type LbugModule = typeof lbug;
|
|
36
38
|
export interface LbugDatabaseOptions {
|
|
37
39
|
readOnly?: boolean;
|
|
40
|
+
throwOnWalReplayFailure?: boolean;
|
|
38
41
|
}
|
|
39
42
|
export interface LbugConnectionHandle {
|
|
40
43
|
db: lbug.Database;
|
|
@@ -39,8 +39,22 @@ export const LBUG_MAX_DB_SIZE = (() => {
|
|
|
39
39
|
}
|
|
40
40
|
return 16 * 1024 * 1024 * 1024;
|
|
41
41
|
})();
|
|
42
|
+
/** Matches WAL corruption errors from the LadybugDB engine. */
|
|
43
|
+
const WAL_CORRUPTION_RE = /corrupt(ed)?\s+wal|invalid\s+wal\s+record|wal.*corrupt|checksum.*wal/i;
|
|
44
|
+
export const WAL_RECOVERY_SUGGESTION = 'WAL corruption detected. Run `gitnexus analyze` to rebuild the index.';
|
|
45
|
+
export function isWalCorruptionError(err) {
|
|
46
|
+
if (!err)
|
|
47
|
+
return false;
|
|
48
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
49
|
+
return WAL_CORRUPTION_RE.test(msg);
|
|
50
|
+
}
|
|
42
51
|
export function createLbugDatabase(lbugModule, databasePath, options = {}) {
|
|
43
|
-
|
|
52
|
+
// .d.ts declares fewer args than the native constructor accepts.
|
|
53
|
+
return new lbugModule.Database(databasePath, 0, // bufferManagerSize
|
|
54
|
+
false, // enableCompression (pinned for v0.16.0)
|
|
55
|
+
options.readOnly ?? false, LBUG_MAX_DB_SIZE, true, // autoCheckpoint
|
|
56
|
+
-1, // checkpointThreshold
|
|
57
|
+
options.throwOnWalReplayFailure ?? true, true);
|
|
44
58
|
}
|
|
45
59
|
export async function openLbugConnection(lbugModule, databasePath, options = {}) {
|
|
46
60
|
let db;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import fs from 'fs/promises';
|
|
18
18
|
import lbug from '@ladybugdb/core';
|
|
19
19
|
import { loadFTSExtension } from './lbug-adapter.js';
|
|
20
|
-
import { createLbugDatabase } from './lbug-config.js';
|
|
20
|
+
import { createLbugDatabase, isWalCorruptionError } from './lbug-config.js';
|
|
21
21
|
const pool = new Map();
|
|
22
22
|
const poolCloseListeners = new Set();
|
|
23
23
|
/**
|
|
@@ -51,7 +51,7 @@ let idleTimer = null;
|
|
|
51
51
|
// @ladybugdb/core), corrupting stdout in the pre-sentinel window. Routing
|
|
52
52
|
// through the leaf breaks that chain.
|
|
53
53
|
export { realStdoutWrite, realStderrWrite, setActiveStdoutWrite } from '../../mcp/stdio-capture.js';
|
|
54
|
-
import { getActiveStdoutWrite } from '../../mcp/stdio-capture.js';
|
|
54
|
+
import { getActiveStdoutWrite, realStderrWrite } from '../../mcp/stdio-capture.js';
|
|
55
55
|
let stdoutSilenceCount = 0;
|
|
56
56
|
/** True while pre-warming connections — prevents watchdog from prematurely restoring stdout */
|
|
57
57
|
let preWarmActive = false;
|
|
@@ -203,6 +203,44 @@ const QUERY_TIMEOUT_MS = 30_000;
|
|
|
203
203
|
const WAITER_TIMEOUT_MS = 15_000;
|
|
204
204
|
const LOCK_RETRY_ATTEMPTS = 3;
|
|
205
205
|
const LOCK_RETRY_DELAY_MS = 2000;
|
|
206
|
+
async function openReadOnlyDatabase(dbPath) {
|
|
207
|
+
let db;
|
|
208
|
+
silenceStdout();
|
|
209
|
+
try {
|
|
210
|
+
db = createLbugDatabase(lbug, dbPath, {
|
|
211
|
+
readOnly: true,
|
|
212
|
+
throwOnWalReplayFailure: false,
|
|
213
|
+
});
|
|
214
|
+
await db.init();
|
|
215
|
+
return db;
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (db)
|
|
219
|
+
await db.close().catch(() => { });
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
restoreStdout();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Quarantine the .wal file and retry opening the database.
|
|
228
|
+
* Used when the initial open fails with a WAL corruption error.
|
|
229
|
+
*/
|
|
230
|
+
async function tryQuarantineAndReopen(dbPath, repoId) {
|
|
231
|
+
const walPath = dbPath + '.wal';
|
|
232
|
+
const quarantineName = `${walPath}.corrupt.${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
233
|
+
try {
|
|
234
|
+
await fs.rename(walPath, quarantineName);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
throw new Error(`LadybugDB WAL corruption detected for ${repoId}. ` +
|
|
238
|
+
`Run \`gitnexus analyze\` to rebuild the index. (quarantine failed)`);
|
|
239
|
+
}
|
|
240
|
+
realStderrWrite(`GitNexus: LadybugDB WAL quarantined for ${repoId}; graph may be stale. ` +
|
|
241
|
+
`Run \`gitnexus analyze\` to rebuild the index.\n`);
|
|
242
|
+
return await openReadOnlyDatabase(dbPath);
|
|
243
|
+
}
|
|
206
244
|
/** Deduplicates concurrent initLbug calls for the same repoId */
|
|
207
245
|
const initPromises = new Map();
|
|
208
246
|
/**
|
|
@@ -256,17 +294,27 @@ async function doInitLbug(repoId, dbPath) {
|
|
|
256
294
|
// avoids lock conflicts when `gitnexus analyze` is writing.
|
|
257
295
|
let lastError = null;
|
|
258
296
|
for (let attempt = 1; attempt <= LOCK_RETRY_ATTEMPTS; attempt++) {
|
|
259
|
-
silenceStdout();
|
|
260
297
|
try {
|
|
261
|
-
const db =
|
|
262
|
-
restoreStdout();
|
|
298
|
+
const db = await openReadOnlyDatabase(dbPath);
|
|
263
299
|
shared = { db, refCount: 0, ftsLoaded: false };
|
|
264
300
|
dbCache.set(dbPath, shared);
|
|
265
301
|
break;
|
|
266
302
|
}
|
|
267
303
|
catch (err) {
|
|
268
|
-
restoreStdout();
|
|
269
304
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
305
|
+
if (isWalCorruptionError(lastError)) {
|
|
306
|
+
try {
|
|
307
|
+
const db = await tryQuarantineAndReopen(dbPath, repoId);
|
|
308
|
+
shared = { db, refCount: 0, ftsLoaded: false };
|
|
309
|
+
dbCache.set(dbPath, shared);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
catch (retryErr) {
|
|
313
|
+
throw new Error(`LadybugDB WAL corruption detected for ${repoId}. ` +
|
|
314
|
+
`Run \`gitnexus analyze\` to rebuild the index. ` +
|
|
315
|
+
`(${retryErr instanceof Error ? retryErr.message : String(retryErr)})`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
270
318
|
const isLockError = lastError.message.includes('Could not set lock') || lastError.message.includes('lock');
|
|
271
319
|
if (!isLockError || attempt === LOCK_RETRY_ATTEMPTS)
|
|
272
320
|
break;
|
|
@@ -259,6 +259,7 @@ export declare class LocalBackend {
|
|
|
259
259
|
* UID-based direct lookup. No cluster in output.
|
|
260
260
|
*/
|
|
261
261
|
private context;
|
|
262
|
+
private _contextImpl;
|
|
262
263
|
/**
|
|
263
264
|
* Legacy explore — kept for backwards compatibility with resources.ts.
|
|
264
265
|
* Routes cluster/process types to direct graph queries.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, isWriteQuery, } from '../../core/lbug/pool-adapter.js';
|
|
11
|
+
import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../../core/lbug/lbug-config.js';
|
|
11
12
|
export { isWriteQuery };
|
|
12
13
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
13
14
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
@@ -1022,7 +1023,14 @@ export class LocalBackend {
|
|
|
1022
1023
|
return result;
|
|
1023
1024
|
}
|
|
1024
1025
|
catch (err) {
|
|
1025
|
-
|
|
1026
|
+
const msg = err.message || 'Query failed';
|
|
1027
|
+
if (isWalCorruptionError(err)) {
|
|
1028
|
+
return {
|
|
1029
|
+
error: msg,
|
|
1030
|
+
recoverySuggestion: WAL_RECOVERY_SUGGESTION,
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
return { error: msg };
|
|
1026
1034
|
}
|
|
1027
1035
|
}
|
|
1028
1036
|
/**
|
|
@@ -1389,6 +1397,21 @@ export class LocalBackend {
|
|
|
1389
1397
|
* UID-based direct lookup. No cluster in output.
|
|
1390
1398
|
*/
|
|
1391
1399
|
async context(repo, params) {
|
|
1400
|
+
try {
|
|
1401
|
+
return await this._contextImpl(repo, params);
|
|
1402
|
+
}
|
|
1403
|
+
catch (err) {
|
|
1404
|
+
const msg = (err instanceof Error ? err.message : String(err)) || 'Context query failed';
|
|
1405
|
+
if (isWalCorruptionError(err)) {
|
|
1406
|
+
return {
|
|
1407
|
+
error: msg,
|
|
1408
|
+
recoverySuggestion: WAL_RECOVERY_SUGGESTION,
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
throw err;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async _contextImpl(repo, params) {
|
|
1392
1415
|
await this.ensureInitialized(repo.id);
|
|
1393
1416
|
const { name, uid, file_path, kind, include_content } = params;
|
|
1394
1417
|
if (!name && !uid) {
|
|
@@ -1990,6 +2013,7 @@ export class LocalBackend {
|
|
|
1990
2013
|
impactedCount: 0,
|
|
1991
2014
|
risk: 'UNKNOWN',
|
|
1992
2015
|
suggestion: 'The graph query failed — try gitnexus context <symbol> as a fallback',
|
|
2016
|
+
...(isWalCorruptionError(err) ? { recoverySuggestion: WAL_RECOVERY_SUGGESTION } : {}),
|
|
1993
2017
|
};
|
|
1994
2018
|
}
|
|
1995
2019
|
}
|
package/package.json
CHANGED