gitnexus 1.6.6-rc.2 → 1.6.6-rc.4
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.
- package/dist/cli/analyze.js +11 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/serve.js +6 -1
- package/dist/cli/wiki.js +31 -8
- package/dist/core/lbug/lbug-adapter.js +17 -1
- package/dist/core/lbug/lbug-config.d.ts +1 -1
- package/dist/core/lbug/lbug-config.js +1 -1
- package/dist/core/lbug/pool-adapter.js +2 -3
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +23 -6
- package/package.json +1 -1
package/dist/cli/analyze.js
CHANGED
|
@@ -12,6 +12,7 @@ import { execFileSync } from 'child_process';
|
|
|
12
12
|
import v8 from 'v8';
|
|
13
13
|
import cliProgress from 'cli-progress';
|
|
14
14
|
import { closeLbug } from '../core/lbug/lbug-adapter.js';
|
|
15
|
+
import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../core/lbug/lbug-config.js';
|
|
15
16
|
import { getStoragePaths, getGlobalRegistryPath, RegistryNameCollisionError, AnalysisNotFinalizedError, assertAnalysisFinalized, } from '../storage/repo-manager.js';
|
|
16
17
|
import { getGitRoot, hasGitDir } from '../storage/git.js';
|
|
17
18
|
import { runFullAnalysis } from '../core/run-analyze.js';
|
|
@@ -468,6 +469,16 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
468
469
|
process.exitCode = 1;
|
|
469
470
|
return;
|
|
470
471
|
}
|
|
472
|
+
// WAL corruption — the index file is unreadable. Give a clear recovery
|
|
473
|
+
// path without a confusing stack trace (the native error message alone
|
|
474
|
+
// is enough signal).
|
|
475
|
+
if (isWalCorruptionError(err) || msg.includes('LadybugDB WAL corruption')) {
|
|
476
|
+
cliError(` The GitNexus index has a corrupted WAL file.\n` +
|
|
477
|
+
` This usually happens when a previous analysis was interrupted mid-write.\n` +
|
|
478
|
+
` ${WAL_RECOVERY_SUGGESTION}\n`, { recoveryHint: 'wal-corruption' });
|
|
479
|
+
process.exitCode = 1;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
471
482
|
// HF download failure — show clean guidance without the raw stack trace.
|
|
472
483
|
// Checked before writeFatalToStderr so the user sees one focused message
|
|
473
484
|
// rather than a stack-trace dump followed by a second remediation block.
|
package/dist/cli/index.js
CHANGED
|
@@ -103,7 +103,7 @@ program
|
|
|
103
103
|
.option('--reasoning-model', 'Mark deployment as reasoning model (o1/o3/o4-mini) — strips temperature, uses max_completion_tokens')
|
|
104
104
|
.option('--no-reasoning-model', 'Disable reasoning model mode (overrides saved config)')
|
|
105
105
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
106
|
-
.option('--timeout <seconds>', '
|
|
106
|
+
.option('--timeout <seconds>', 'LLM request timeout in seconds (default: disabled)')
|
|
107
107
|
.option('--retries <n>', 'Max LLM retry attempts per request (default: 3)')
|
|
108
108
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
109
109
|
.option('-v, --verbose', 'Enable verbose output (show LLM commands and responses)')
|
package/dist/cli/serve.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer } from '../server/api.js';
|
|
2
2
|
import { logger, flushLoggerSync } from '../core/logger.js';
|
|
3
3
|
import { cliError } from './cli-message.js';
|
|
4
|
+
import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../core/lbug/lbug-config.js';
|
|
4
5
|
// Catch anything that would cause a silent exit. Pino v10's default
|
|
5
6
|
// destination is `sync: false` (SonicBoom buffered) — call
|
|
6
7
|
// `flushLoggerSync()` between the log and `process.exit(1)` so the crash
|
|
@@ -32,7 +33,11 @@ export const serveCommand = async (options) => {
|
|
|
32
33
|
await createServer(port, host);
|
|
33
34
|
}
|
|
34
35
|
catch (err) {
|
|
35
|
-
if (err
|
|
36
|
+
if (isWalCorruptionError(err)) {
|
|
37
|
+
cliError(`\nGitNexus server could not start: the index has a corrupted WAL file.\n` +
|
|
38
|
+
` ${WAL_RECOVERY_SUGGESTION}\n`, { recoveryHint: 'wal-corruption' });
|
|
39
|
+
}
|
|
40
|
+
else if (err.code === 'EADDRINUSE') {
|
|
36
41
|
cliError(`\nFailed to start GitNexus server:\n` +
|
|
37
42
|
` ${err.message || err}\n\n` +
|
|
38
43
|
` Port ${port} is already in use. Either:\n` +
|
package/dist/cli/wiki.js
CHANGED
|
@@ -14,6 +14,19 @@ import { WikiGenerator } from '../core/wiki/generator.js';
|
|
|
14
14
|
import { resolveLLMConfig } from '../core/wiki/llm-client.js';
|
|
15
15
|
import { detectCursorCLI } from '../core/wiki/cursor-client.js';
|
|
16
16
|
import { logger } from '../core/logger.js';
|
|
17
|
+
function parsePositiveIntegerOption(value, flag, multiplier = 1) {
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (!/^[1-9]\d*$/.test(trimmed)) {
|
|
22
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
23
|
+
}
|
|
24
|
+
const parsed = parseInt(trimmed, 10);
|
|
25
|
+
if (parsed > Math.floor(Number.MAX_SAFE_INTEGER / multiplier)) {
|
|
26
|
+
throw new Error(`${flag} is too large`);
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
17
30
|
/**
|
|
18
31
|
* Prompt the user for input via stdin.
|
|
19
32
|
*/
|
|
@@ -100,6 +113,17 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
100
113
|
process.exitCode = 1;
|
|
101
114
|
return;
|
|
102
115
|
}
|
|
116
|
+
let timeoutSeconds;
|
|
117
|
+
let retries;
|
|
118
|
+
try {
|
|
119
|
+
timeoutSeconds = parsePositiveIntegerOption(options?.timeout, '--timeout', 1000);
|
|
120
|
+
retries = parsePositiveIntegerOption(options?.retries, '--retries');
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.log(` Error: ${error.message}\n`);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
103
127
|
// ── Resolve LLM config (with interactive fallback) ─────────────────
|
|
104
128
|
// Save any CLI overrides immediately
|
|
105
129
|
if (options?.apiKey ||
|
|
@@ -305,15 +329,11 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
305
329
|
}
|
|
306
330
|
}
|
|
307
331
|
// ── Apply per-run overrides not saved to config ────────────────────
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
if (!isNaN(secs) && secs > 0)
|
|
311
|
-
llmConfig.requestTimeoutMs = secs * 1000;
|
|
332
|
+
if (timeoutSeconds !== undefined) {
|
|
333
|
+
llmConfig.requestTimeoutMs = timeoutSeconds * 1000;
|
|
312
334
|
}
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
if (!isNaN(n) && n > 0)
|
|
316
|
-
llmConfig.maxAttempts = n;
|
|
335
|
+
if (retries !== undefined) {
|
|
336
|
+
llmConfig.maxAttempts = retries;
|
|
317
337
|
}
|
|
318
338
|
// ── Setup progress bar with elapsed timer ──────────────────────────
|
|
319
339
|
const bar = new cliProgress.SingleBar({
|
|
@@ -473,6 +493,9 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
473
493
|
if (err.message?.includes('No source files')) {
|
|
474
494
|
console.log(`\n ${err.message}\n`);
|
|
475
495
|
}
|
|
496
|
+
else if (err.message?.includes('LLM request timed out after')) {
|
|
497
|
+
console.log(`\n Timeout: ${err.message}\n`);
|
|
498
|
+
}
|
|
476
499
|
else if (err.message?.includes('content filter')) {
|
|
477
500
|
// Content filter block — actionable message
|
|
478
501
|
console.log(`\n Content Filter: ${err.message}\n`);
|
|
@@ -8,7 +8,7 @@ import lbug from '@ladybugdb/core';
|
|
|
8
8
|
import { NODE_TABLES, REL_TABLE_NAME, SCHEMA_QUERIES, EMBEDDING_TABLE_NAME, STALE_HASH_SENTINEL, } from './schema.js';
|
|
9
9
|
import { streamAllCSVsToDisk } from './csv-generator.js';
|
|
10
10
|
import { extensionManager } from './extension-loader.js';
|
|
11
|
-
import { closeLbugConnection, isDbBusyError, isOpenRetryExhausted, openLbugConnection, waitForWindowsHandleRelease, } from './lbug-config.js';
|
|
11
|
+
import { closeLbugConnection, isDbBusyError, isOpenRetryExhausted, isWalCorruptionError, openLbugConnection, WAL_RECOVERY_SUGGESTION, waitForWindowsHandleRelease, } from './lbug-config.js';
|
|
12
12
|
import { isVectorExtensionSupportedByPlatform } from '../platform/capabilities.js';
|
|
13
13
|
import { logger } from '../logger.js';
|
|
14
14
|
/**
|
|
@@ -508,6 +508,22 @@ const doInitLbug = async (dbPath) => {
|
|
|
508
508
|
// anyway and any genuine cross-process lock contention surfaces
|
|
509
509
|
// on the next operation via withLbugDb's retry. Logging it here
|
|
510
510
|
// would just be noise in CI.
|
|
511
|
+
//
|
|
512
|
+
// WAL corruption: the first DDL write after DB open triggers WAL
|
|
513
|
+
// replay — if the WAL file was left in a corrupt state by an
|
|
514
|
+
// interrupted previous run, the native engine throws here. Rather
|
|
515
|
+
// than logging a WARN and continuing in a broken state, close the
|
|
516
|
+
// DB cleanly and surface an actionable error so the caller (serve,
|
|
517
|
+
// MCP, analyze) can exit with a clear recovery message.
|
|
518
|
+
if (isWalCorruptionError(err)) {
|
|
519
|
+
await safeClose();
|
|
520
|
+
currentDbPath = null;
|
|
521
|
+
ftsLoaded = false;
|
|
522
|
+
vectorExtensionLoaded = false;
|
|
523
|
+
ensuredFTSIndexes.clear();
|
|
524
|
+
throw new Error(`LadybugDB WAL corruption detected at ${dbPath}. ${WAL_RECOVERY_SUGGESTION}\n` +
|
|
525
|
+
` Original error: ${msg.slice(0, 200)}`);
|
|
526
|
+
}
|
|
511
527
|
if (!msg.includes('already exists') && !isDbBusyError(err)) {
|
|
512
528
|
logger.warn(`⚠️ Schema creation warning: ${msg.slice(0, 120)}`);
|
|
513
529
|
}
|
|
@@ -32,7 +32,7 @@ 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.";
|
|
35
|
+
export declare const WAL_RECOVERY_SUGGESTION = "WAL corruption detected. Run `gitnexus analyze --force` to rebuild the index.";
|
|
36
36
|
export declare function isWalCorruptionError(err: unknown): boolean;
|
|
37
37
|
type LbugModule = typeof lbug;
|
|
38
38
|
export interface LbugDatabaseOptions {
|
|
@@ -44,7 +44,7 @@ export const LBUG_MAX_DB_SIZE = (() => {
|
|
|
44
44
|
})();
|
|
45
45
|
/** Matches WAL corruption errors from the LadybugDB engine. */
|
|
46
46
|
const WAL_CORRUPTION_RE = /corrupt(ed)?\s+wal|invalid\s+wal\s+record|wal.*corrupt|checksum.*wal/i;
|
|
47
|
-
export const WAL_RECOVERY_SUGGESTION = 'WAL corruption detected. Run `gitnexus analyze` to rebuild the index.';
|
|
47
|
+
export const WAL_RECOVERY_SUGGESTION = 'WAL corruption detected. Run `gitnexus analyze --force` to rebuild the index.';
|
|
48
48
|
export function isWalCorruptionError(err) {
|
|
49
49
|
if (!err)
|
|
50
50
|
return false;
|
|
@@ -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, isWalCorruptionError } from './lbug-config.js';
|
|
20
|
+
import { createLbugDatabase, isWalCorruptionError, WAL_RECOVERY_SUGGESTION, } from './lbug-config.js';
|
|
21
21
|
const pool = new Map();
|
|
22
22
|
const poolCloseListeners = new Set();
|
|
23
23
|
/**
|
|
@@ -310,8 +310,7 @@ async function doInitLbug(repoId, dbPath) {
|
|
|
310
310
|
break;
|
|
311
311
|
}
|
|
312
312
|
catch (retryErr) {
|
|
313
|
-
throw new Error(`LadybugDB WAL corruption detected for ${repoId}. ` +
|
|
314
|
-
`Run \`gitnexus analyze\` to rebuild the index. ` +
|
|
313
|
+
throw new Error(`LadybugDB WAL corruption detected for ${repoId}. ${WAL_RECOVERY_SUGGESTION} ` +
|
|
315
314
|
`(${retryErr instanceof Error ? retryErr.message : String(retryErr)})`);
|
|
316
315
|
}
|
|
317
316
|
}
|
|
@@ -19,7 +19,7 @@ export interface LLMConfig {
|
|
|
19
19
|
apiVersion?: string;
|
|
20
20
|
/** When true, strips sampling params and uses max_completion_tokens instead of max_tokens */
|
|
21
21
|
isReasoningModel?: boolean;
|
|
22
|
-
/** Per-attempt fetch timeout in ms
|
|
22
|
+
/** Per-attempt fetch timeout in ms. Omit to disable request timeouts. */
|
|
23
23
|
requestTimeoutMs?: number;
|
|
24
24
|
/** Max fetch attempts before giving up (default: 3). */
|
|
25
25
|
maxAttempts?: number;
|
|
@@ -38,6 +38,19 @@ export async function resolveLLMConfig(overrides) {
|
|
|
38
38
|
export function estimateTokens(text) {
|
|
39
39
|
return Math.ceil(text.length / 4);
|
|
40
40
|
}
|
|
41
|
+
function formatTimeoutDuration(timeoutMs) {
|
|
42
|
+
if (timeoutMs >= 1000 && timeoutMs % 1000 === 0) {
|
|
43
|
+
return `${timeoutMs / 1000}s`;
|
|
44
|
+
}
|
|
45
|
+
return `${timeoutMs}ms`;
|
|
46
|
+
}
|
|
47
|
+
function isTimeoutLikeError(err) {
|
|
48
|
+
if (!(err instanceof Error))
|
|
49
|
+
return false;
|
|
50
|
+
if (err.name === 'TimeoutError' || err.name === 'AbortError')
|
|
51
|
+
return true;
|
|
52
|
+
return /time(d)?\s*out|timeout/i.test(err.message);
|
|
53
|
+
}
|
|
41
54
|
/**
|
|
42
55
|
* Validate that a base URL supplied for LLM API calls is a safe HTTP/HTTPS
|
|
43
56
|
* endpoint (CWE-918 / CodeQL js/http-to-file-access).
|
|
@@ -166,12 +179,12 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
166
179
|
...authHeaders,
|
|
167
180
|
},
|
|
168
181
|
body: JSON.stringify(body),
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
// Request timeout is opt-in for wiki generation. Large local
|
|
183
|
+
// model runs can legitimately take well over a minute, so the
|
|
184
|
+
// default runtime path must not impose a hidden 60s ceiling.
|
|
185
|
+
signal: config.requestTimeoutMs !== undefined
|
|
186
|
+
? AbortSignal.timeout(config.requestTimeoutMs)
|
|
187
|
+
: undefined,
|
|
175
188
|
}, {
|
|
176
189
|
breakerKey: `wiki-llm-${new URL(url).host}`,
|
|
177
190
|
retry: { maxAttempts: config.maxAttempts ?? 3, baseDelayMs: 2_000, capDelayMs: 30_000 },
|
|
@@ -185,6 +198,10 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
185
198
|
const errorText = await err.response.text().catch(() => 'unknown error');
|
|
186
199
|
throw new Error(`LLM API error (${err.response.status} after retries): ${errorText.slice(0, 500)}`);
|
|
187
200
|
}
|
|
201
|
+
if (config.requestTimeoutMs !== undefined && isTimeoutLikeError(err)) {
|
|
202
|
+
throw new Error(`LLM request timed out after ${formatTimeoutDuration(config.requestTimeoutMs)}. ` +
|
|
203
|
+
'Increase --timeout or omit it to disable the request timeout.');
|
|
204
|
+
}
|
|
188
205
|
throw err;
|
|
189
206
|
}
|
|
190
207
|
if (!response.ok) {
|
package/package.json
CHANGED