gitnexus 1.6.4-rc.86 → 1.6.4-rc.88
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/ai-context.js +2 -1
- package/dist/cli/analyze.js +48 -31
- package/dist/cli/clean.js +4 -3
- package/dist/cli/cli-message.d.ts +15 -0
- package/dist/cli/cli-message.js +61 -0
- package/dist/cli/eval-server.js +37 -12
- package/dist/cli/group.js +4 -3
- package/dist/cli/mcp.js +13 -5
- package/dist/cli/optional-grammars.js +13 -4
- package/dist/cli/remove.js +7 -4
- package/dist/cli/serve.js +31 -13
- package/dist/cli/tool.js +6 -5
- package/dist/cli/wiki.js +2 -1
- package/dist/config/ignore-service.js +2 -1
- package/dist/core/embeddings/embedder.js +9 -8
- package/dist/core/embeddings/embedding-pipeline.js +15 -13
- package/dist/core/group/bridge-db.js +3 -4
- package/dist/core/group/extractors/elixir-workspace-extractor.js +2 -1
- package/dist/core/group/extractors/go-workspace-extractor.js +2 -1
- package/dist/core/group/extractors/grpc-extractor.js +2 -1
- package/dist/core/group/extractors/java-workspace-extractor.js +2 -1
- package/dist/core/group/extractors/manifest-extractor.js +2 -1
- package/dist/core/group/extractors/node-workspace-extractor.js +2 -1
- package/dist/core/group/extractors/python-workspace-extractor.js +2 -1
- package/dist/core/group/extractors/rust-workspace-extractor.js +2 -1
- package/dist/core/group/service.js +5 -4
- package/dist/core/group/sync.js +4 -3
- package/dist/core/ingestion/ast-cache.js +2 -1
- package/dist/core/ingestion/call-processor.js +3 -2
- package/dist/core/ingestion/cluster-enricher.js +3 -2
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +4 -20
- package/dist/core/ingestion/filesystem-walker.js +3 -2
- package/dist/core/ingestion/heritage-processor.js +3 -2
- package/dist/core/ingestion/import-processor.js +14 -12
- package/dist/core/ingestion/language-config.js +6 -5
- package/dist/core/ingestion/method-extractors/generic.js +2 -1
- package/dist/core/ingestion/parsing-processor.js +7 -6
- package/dist/core/ingestion/pipeline-phases/cobol.js +4 -3
- package/dist/core/ingestion/pipeline-phases/communities.js +2 -1
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +5 -4
- package/dist/core/ingestion/pipeline-phases/cross-file.js +3 -2
- package/dist/core/ingestion/pipeline-phases/markdown.js +2 -1
- package/dist/core/ingestion/pipeline-phases/mro.js +2 -1
- package/dist/core/ingestion/pipeline-phases/orm.js +2 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +9 -8
- package/dist/core/ingestion/pipeline-phases/processes.js +3 -2
- package/dist/core/ingestion/pipeline-phases/routes.js +4 -3
- package/dist/core/ingestion/pipeline-phases/runner.js +3 -2
- package/dist/core/ingestion/pipeline-phases/tools.js +2 -1
- package/dist/core/ingestion/process-processor.js +4 -3
- package/dist/core/ingestion/scope-extractor-bridge.js +2 -2
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +3 -2
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +2 -1
- package/dist/core/ingestion/type-env.js +2 -1
- package/dist/core/ingestion/utils/max-file-size.js +2 -1
- package/dist/core/ingestion/workers/parse-worker.js +5 -4
- package/dist/core/ingestion/workers/worker-pool.js +19 -8
- package/dist/core/lbug/extension-loader.js +2 -1
- package/dist/core/lbug/lbug-adapter.js +5 -4
- package/dist/core/logger.d.ts +125 -0
- package/dist/core/logger.js +323 -0
- package/dist/core/tree-sitter/parser-loader.js +10 -4
- package/dist/core/wiki/cursor-client.js +2 -1
- package/dist/core/wiki/llm-client.js +2 -9
- package/dist/mcp/core/embedder.js +3 -2
- package/dist/mcp/local/local-backend.js +17 -16
- package/dist/mcp/server.js +7 -1
- package/dist/server/api.js +22 -10
- package/dist/server/git-clone.js +2 -1
- package/dist/server/mcp-http.js +2 -1
- package/package.json +3 -1
package/dist/cli/ai-context.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { logger } from '../core/logger.js';
|
|
11
12
|
// ESM equivalent of __dirname
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -235,7 +236,7 @@ Use GitNexus tools to accomplish this task.
|
|
|
235
236
|
}
|
|
236
237
|
catch (err) {
|
|
237
238
|
// Skip on error, don't fail the whole process
|
|
238
|
-
|
|
239
|
+
logger.warn({ err }, `Warning: Could not install skill ${skill.name}:`);
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
return installedSkills;
|
package/dist/cli/analyze.js
CHANGED
|
@@ -19,6 +19,7 @@ import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-si
|
|
|
19
19
|
import { warnMissingOptionalGrammars } from './optional-grammars.js';
|
|
20
20
|
import { glob } from 'glob';
|
|
21
21
|
import fs from 'fs/promises';
|
|
22
|
+
import { cliError } from './cli-message.js';
|
|
22
23
|
// Capture stderr.write at module load BEFORE anything (LadybugDB native
|
|
23
24
|
// init, progress bar, console redirection) can monkey-patch it. The
|
|
24
25
|
// fatal handlers below MUST reach the user even when the analyze path
|
|
@@ -100,7 +101,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
100
101
|
if (options?.workerTimeout) {
|
|
101
102
|
const workerTimeoutSeconds = Number(options.workerTimeout);
|
|
102
103
|
if (!Number.isFinite(workerTimeoutSeconds) || workerTimeoutSeconds < 1) {
|
|
103
|
-
|
|
104
|
+
cliError(' --worker-timeout must be at least 1 second.\n');
|
|
104
105
|
process.exitCode = 1;
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
@@ -114,7 +115,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
114
115
|
if (typeof options?.embeddings === 'string') {
|
|
115
116
|
const parsed = Number(options.embeddings);
|
|
116
117
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
117
|
-
|
|
118
|
+
cliError(` --embeddings expects a non-negative integer (got "${options.embeddings}"). ` +
|
|
118
119
|
`Pass 0 to disable the safety cap, or omit the value to keep the default.\n`);
|
|
119
120
|
process.exitCode = 1;
|
|
120
121
|
return;
|
|
@@ -127,7 +128,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
127
128
|
return true;
|
|
128
129
|
const parsed = Number(value);
|
|
129
130
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
130
|
-
|
|
131
|
+
cliError(` ${optionName} must be a positive integer.\n`);
|
|
131
132
|
process.exitCode = 1;
|
|
132
133
|
return false;
|
|
133
134
|
}
|
|
@@ -142,7 +143,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
142
143
|
if (options?.embeddingDevice) {
|
|
143
144
|
const allowed = new Set(['auto', 'cpu', 'dml', 'cuda', 'wasm']);
|
|
144
145
|
if (!allowed.has(options.embeddingDevice)) {
|
|
145
|
-
|
|
146
|
+
cliError(' --embedding-device must be one of: auto, cpu, dml, cuda, wasm.\n');
|
|
146
147
|
process.exitCode = 1;
|
|
147
148
|
return;
|
|
148
149
|
}
|
|
@@ -221,7 +222,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
221
222
|
stopOnComplete: false,
|
|
222
223
|
}, cliProgress.Presets.shades_grey);
|
|
223
224
|
bar.start(100, 0, { phase: 'Initializing...' });
|
|
224
|
-
// Graceful SIGINT handling
|
|
225
|
+
// Graceful SIGINT handling. Pino's default destination is `sync: false`
|
|
226
|
+
// (buffered) — flush before exit so in-flight records reach stderr.
|
|
227
|
+
// See `gitnexus/src/core/logger.ts:flushLoggerSync`.
|
|
225
228
|
let aborted = false;
|
|
226
229
|
const sigintHandler = () => {
|
|
227
230
|
if (aborted)
|
|
@@ -231,12 +234,22 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
231
234
|
console.log('\n Interrupted — cleaning up...');
|
|
232
235
|
closeLbug()
|
|
233
236
|
.catch(() => { })
|
|
234
|
-
.finally(() =>
|
|
237
|
+
.finally(async () => {
|
|
238
|
+
const { flushLoggerSync } = await import('../core/logger.js');
|
|
239
|
+
flushLoggerSync();
|
|
240
|
+
process.exit(130);
|
|
241
|
+
});
|
|
235
242
|
};
|
|
236
243
|
process.on('SIGINT', sigintHandler);
|
|
237
|
-
// Route console output through bar.log() to prevent progress bar corruption
|
|
244
|
+
// Route console output through bar.log() to prevent progress bar corruption.
|
|
245
|
+
// This is a deliberate UI pattern (not a logging concern): analyze runs a
|
|
246
|
+
// long-lived progress bar on stdout; any concurrent console.* write would
|
|
247
|
+
// overwrite the bar mid-render. We capture originals, swap to barLog for
|
|
248
|
+
// the lifetime of the run, and restore on completion/error/SIGINT.
|
|
238
249
|
const origLog = console.log.bind(console);
|
|
250
|
+
// eslint-disable-next-line no-console -- intentional console-routing for progress bar UX
|
|
239
251
|
const origWarn = console.warn.bind(console);
|
|
252
|
+
// eslint-disable-next-line no-console -- intentional console-routing for progress bar UX
|
|
240
253
|
const origError = console.error.bind(console);
|
|
241
254
|
let barCurrentValue = 0;
|
|
242
255
|
const barLog = (...args) => {
|
|
@@ -245,7 +258,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
245
258
|
bar.update(barCurrentValue);
|
|
246
259
|
};
|
|
247
260
|
console.log = barLog;
|
|
261
|
+
// eslint-disable-next-line no-console -- intentional console-routing for progress bar UX
|
|
248
262
|
console.warn = barLog;
|
|
263
|
+
// eslint-disable-next-line no-console -- intentional console-routing for progress bar UX
|
|
249
264
|
console.error = barLog;
|
|
250
265
|
// Track elapsed time per phase
|
|
251
266
|
let lastPhaseLabel = 'Initializing...';
|
|
@@ -301,7 +316,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
301
316
|
clearInterval(elapsedTimer);
|
|
302
317
|
process.removeListener('SIGINT', sigintHandler);
|
|
303
318
|
console.log = origLog;
|
|
319
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
304
320
|
console.warn = origWarn;
|
|
321
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
305
322
|
console.error = origError;
|
|
306
323
|
bar.stop();
|
|
307
324
|
console.log(' Already up to date\n');
|
|
@@ -357,7 +374,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
357
374
|
clearInterval(elapsedTimer);
|
|
358
375
|
process.removeListener('SIGINT', sigintHandler);
|
|
359
376
|
console.log = origLog;
|
|
377
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
360
378
|
console.warn = origWarn;
|
|
379
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
361
380
|
console.error = origError;
|
|
362
381
|
bar.update(100, { phase: 'Done' });
|
|
363
382
|
bar.stop();
|
|
@@ -378,19 +397,20 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
378
397
|
clearInterval(elapsedTimer);
|
|
379
398
|
process.removeListener('SIGINT', sigintHandler);
|
|
380
399
|
console.log = origLog;
|
|
400
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
381
401
|
console.warn = origWarn;
|
|
402
|
+
// eslint-disable-next-line no-console -- restoring after intentional progress-bar routing
|
|
382
403
|
console.error = origError;
|
|
383
404
|
bar.stop();
|
|
384
405
|
const msg = err.message || String(err);
|
|
385
406
|
// Registry name-collision from --name (#829) — surface as an
|
|
386
407
|
// actionable error rather than a generic stack-trace.
|
|
387
408
|
if (err instanceof RegistryNameCollisionError) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
console.error('');
|
|
409
|
+
cliError(`\n Registry name collision:\n` +
|
|
410
|
+
` "${err.registryName}" is already used by "${err.existingPath}".\n\n` +
|
|
411
|
+
` Options:\n` +
|
|
412
|
+
` • Pick a different alias: gitnexus analyze --name <alias>\n` +
|
|
413
|
+
` • Allow the duplicate: gitnexus analyze --allow-duplicate-name (leaves "-r ${err.registryName}" ambiguous)\n`, { registryName: err.registryName, existingPath: err.existingPath });
|
|
394
414
|
process.exitCode = 1;
|
|
395
415
|
return;
|
|
396
416
|
}
|
|
@@ -423,34 +443,31 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
423
443
|
msg.includes('allocation failed') ||
|
|
424
444
|
msg.includes('heap out of memory') ||
|
|
425
445
|
msg.includes('JavaScript heap')) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
console.error('');
|
|
446
|
+
cliError(` This error typically occurs on very large repositories.\n` +
|
|
447
|
+
` Suggestions:\n` +
|
|
448
|
+
` 1. Add large vendored/generated directories to .gitnexusignore\n` +
|
|
449
|
+
` 2. Increase Node.js heap: NODE_OPTIONS="--max-old-space-size=16384"\n` +
|
|
450
|
+
` 3. Increase stack size: NODE_OPTIONS="--stack-size=4096"\n`, { recoveryHint: 'large-repo' });
|
|
432
451
|
}
|
|
433
452
|
else if (msg.includes('ERESOLVE') || msg.includes('Could not resolve dependency')) {
|
|
434
453
|
// Note: the original arborist "Cannot destructure property 'package' of
|
|
435
454
|
// 'node.target'" crash happens inside npm *before* gitnexus code runs,
|
|
436
455
|
// so it can't be caught here. This branch handles dependency-resolution
|
|
437
456
|
// errors that surface at runtime (e.g. dynamic require failures).
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
console.error('');
|
|
457
|
+
cliError(` This looks like an npm dependency resolution issue.\n` +
|
|
458
|
+
` Suggestions:\n` +
|
|
459
|
+
` 1. Clear the npm cache: npm cache clean --force\n` +
|
|
460
|
+
` 2. Update npm: npm install -g npm@latest\n` +
|
|
461
|
+
` 3. Reinstall gitnexus: npm install -g gitnexus@latest\n` +
|
|
462
|
+
` 4. Or try npx directly: npx gitnexus@latest analyze\n`, { recoveryHint: 'npm-resolution' });
|
|
445
463
|
}
|
|
446
464
|
else if (msg.includes('MODULE_NOT_FOUND') ||
|
|
447
465
|
msg.includes('Cannot find module') ||
|
|
448
466
|
msg.includes('ERR_MODULE_NOT_FOUND')) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
console.error('');
|
|
467
|
+
cliError(` A required module could not be loaded. The installation may be corrupt.\n` +
|
|
468
|
+
` Suggestions:\n` +
|
|
469
|
+
` 1. Reinstall: npm install -g gitnexus@latest\n` +
|
|
470
|
+
` 2. Clear cache: npm cache clean --force && npx gitnexus@latest analyze\n`, { recoveryHint: 'module-not-found' });
|
|
454
471
|
}
|
|
455
472
|
process.exitCode = 1;
|
|
456
473
|
return;
|
package/dist/cli/clean.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Also unregisters it from the global registry.
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'fs/promises';
|
|
8
|
+
import { logger } from '../core/logger.js';
|
|
8
9
|
import { findRepo, unregisterRepo, listRegisteredRepos, assertSafeStoragePath, UnsafeStoragePathError, } from '../storage/repo-manager.js';
|
|
9
10
|
export const cleanCommand = async (options) => {
|
|
10
11
|
// --all flag: clean all indexed repos
|
|
@@ -37,7 +38,7 @@ export const cleanCommand = async (options) => {
|
|
|
37
38
|
}
|
|
38
39
|
catch (err) {
|
|
39
40
|
if (err instanceof UnsafeStoragePathError) {
|
|
40
|
-
|
|
41
|
+
logger.error(`Refusing to clean ${entry.name}: ${err.message}`);
|
|
41
42
|
continue;
|
|
42
43
|
}
|
|
43
44
|
throw err;
|
|
@@ -48,7 +49,7 @@ export const cleanCommand = async (options) => {
|
|
|
48
49
|
console.log(`Deleted: ${entry.name} (${entry.storagePath})`);
|
|
49
50
|
}
|
|
50
51
|
catch (err) {
|
|
51
|
-
|
|
52
|
+
logger.error({ err }, `Failed to delete ${entry.name}:`);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
return;
|
|
@@ -73,6 +74,6 @@ export const cleanCommand = async (options) => {
|
|
|
73
74
|
console.log(`Deleted: ${repo.storagePath}`);
|
|
74
75
|
}
|
|
75
76
|
catch (err) {
|
|
76
|
-
|
|
77
|
+
logger.error({ err }, 'Failed to delete:');
|
|
77
78
|
}
|
|
78
79
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing informational message. Use for banners, listening URLs,
|
|
3
|
+
* and any message the user expects to read in plain text.
|
|
4
|
+
*/
|
|
5
|
+
export declare function cliInfo(msg: string, fields?: Record<string, unknown>): void;
|
|
6
|
+
/**
|
|
7
|
+
* User-facing warning. Operator-actionable but non-fatal — `cliWarn`
|
|
8
|
+
* indicates the command can still proceed in some form.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cliWarn(msg: string, fields?: Record<string, unknown>): void;
|
|
11
|
+
/**
|
|
12
|
+
* User-facing error. Indicates the command cannot proceed; usually
|
|
13
|
+
* paired with a non-zero exit code at the call site.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cliError(msg: string, fields?: Record<string, unknown>): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI message helpers — for user-facing banners, error guidance, and
|
|
3
|
+
* recovery hints emitted by `gitnexus` subcommands.
|
|
4
|
+
*
|
|
5
|
+
* These functions write **plain text** directly to `process.stderr` AND
|
|
6
|
+
* tee a structured pino record through the singleton `logger`. Plain text
|
|
7
|
+
* preserves the human-readable contract for users running `gitnexus`
|
|
8
|
+
* interactively, redirecting to a file, or piping to `cat`/`grep`. The
|
|
9
|
+
* structured tee keeps log aggregators happy.
|
|
10
|
+
*
|
|
11
|
+
* **Use these for:**
|
|
12
|
+
* - User-facing banners ("Server listening on http://...:N")
|
|
13
|
+
* - Validation errors ("--worker-timeout must be at least 1 second")
|
|
14
|
+
* - Recovery hints ("Suggestions: 1. Clear the npm cache, 2. ...")
|
|
15
|
+
* - One-line user notices ("No indexed repositories found.")
|
|
16
|
+
*
|
|
17
|
+
* **Do NOT use these for:**
|
|
18
|
+
* - Internal diagnostics (worker progress, retry counts, telemetry)
|
|
19
|
+
* — use `logger.info`/`warn`/`error` directly. Internal logs only
|
|
20
|
+
* need structured fields, not double-output to stderr.
|
|
21
|
+
* - High-volume hot paths — every `cliMessage` call writes twice (raw
|
|
22
|
+
* + structured). Acceptable for user-facing messages, wasteful for
|
|
23
|
+
* ingestion pipeline events.
|
|
24
|
+
*
|
|
25
|
+
* Design note: stderr is the right channel even for non-error messages
|
|
26
|
+
* because GitNexus CLI tools (`query`, `cypher`, `impact`) emit JSON
|
|
27
|
+
* data on stdout for piping (`gitnexus query | jq`). User banners on
|
|
28
|
+
* stdout would corrupt that pipeline.
|
|
29
|
+
*/
|
|
30
|
+
import { logger } from '../core/logger.js';
|
|
31
|
+
function writeStderr(msg) {
|
|
32
|
+
// Direct write — bypassing `console.*` so it cannot be intercepted by
|
|
33
|
+
// progress-bar redirection (see `cli/analyze.ts:barLog`) or other
|
|
34
|
+
// routing. The structured tee below still goes through the logger so
|
|
35
|
+
// log aggregation works either way.
|
|
36
|
+
process.stderr.write(msg.endsWith('\n') ? msg : msg + '\n');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* User-facing informational message. Use for banners, listening URLs,
|
|
40
|
+
* and any message the user expects to read in plain text.
|
|
41
|
+
*/
|
|
42
|
+
export function cliInfo(msg, fields) {
|
|
43
|
+
writeStderr(msg);
|
|
44
|
+
logger.info(fields ?? {}, msg);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* User-facing warning. Operator-actionable but non-fatal — `cliWarn`
|
|
48
|
+
* indicates the command can still proceed in some form.
|
|
49
|
+
*/
|
|
50
|
+
export function cliWarn(msg, fields) {
|
|
51
|
+
writeStderr(msg);
|
|
52
|
+
logger.warn(fields ?? {}, msg);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* User-facing error. Indicates the command cannot proceed; usually
|
|
56
|
+
* paired with a non-zero exit code at the call site.
|
|
57
|
+
*/
|
|
58
|
+
export function cliError(msg, fields) {
|
|
59
|
+
writeStderr(msg);
|
|
60
|
+
logger.error(fields ?? {}, msg);
|
|
61
|
+
}
|
package/dist/cli/eval-server.js
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
import http from 'http';
|
|
27
27
|
import { writeSync } from 'node:fs';
|
|
28
28
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
29
|
+
import { logger } from '../core/logger.js';
|
|
30
|
+
import { cliInfo, cliWarn } from './cli-message.js';
|
|
29
31
|
// ─── Text Formatters ──────────────────────────────────────────────────
|
|
30
32
|
// Convert structured JSON results into compact, LLM-friendly text.
|
|
31
33
|
// Design: minimize tokens, maximize actionability.
|
|
@@ -274,11 +276,16 @@ export async function evalServerCommand(options) {
|
|
|
274
276
|
const backend = new LocalBackend();
|
|
275
277
|
const ok = await backend.init();
|
|
276
278
|
if (!ok) {
|
|
277
|
-
|
|
279
|
+
// Operator-actionable but the server cannot start; warn-level so log
|
|
280
|
+
// aggregators don't trip error alerts on a configuration miss. Use
|
|
281
|
+
// cliWarn so the diagnostic reaches stderr synchronously before
|
|
282
|
+
// process.exit() — direct logger.warn would be lost to the buffered
|
|
283
|
+
// pino destination on hard exit (skips beforeExit flush).
|
|
284
|
+
cliWarn('GitNexus eval-server: No indexed repositories found. Run: gitnexus analyze');
|
|
278
285
|
process.exit(1);
|
|
279
286
|
}
|
|
280
287
|
const repos = await backend.listRepos();
|
|
281
|
-
|
|
288
|
+
logger.info({ repoCount: repos.length, repos: repos.map((r) => r.name) }, 'GitNexus eval-server: repos loaded');
|
|
282
289
|
let idleTimer = null;
|
|
283
290
|
function resetIdleTimer() {
|
|
284
291
|
if (idleTimeoutSec <= 0)
|
|
@@ -286,7 +293,7 @@ export async function evalServerCommand(options) {
|
|
|
286
293
|
if (idleTimer)
|
|
287
294
|
clearTimeout(idleTimer);
|
|
288
295
|
idleTimer = setTimeout(async () => {
|
|
289
|
-
|
|
296
|
+
logger.info({ idleTimeoutSec }, 'GitNexus eval-server: idle timeout reached, shutting down');
|
|
290
297
|
await backend.disconnect();
|
|
291
298
|
process.exit(0);
|
|
292
299
|
}, idleTimeoutSec * 1000);
|
|
@@ -351,16 +358,34 @@ export async function evalServerCommand(options) {
|
|
|
351
358
|
}
|
|
352
359
|
});
|
|
353
360
|
server.listen(port, '127.0.0.1', () => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
+
// Plain-text banner for the human watching stderr; structured record
|
|
362
|
+
// for log aggregation (split into two so the user sees a real banner
|
|
363
|
+
// not `{"level":30,"msg":"...","port":4747,"endpoints":[...]}`).
|
|
364
|
+
const bannerLines = [
|
|
365
|
+
`GitNexus eval-server: listening on http://127.0.0.1:${port}`,
|
|
366
|
+
` POST /tool/query — search execution flows`,
|
|
367
|
+
` POST /tool/context — 360-degree symbol view`,
|
|
368
|
+
` POST /tool/impact — blast radius analysis`,
|
|
369
|
+
` POST /tool/cypher — raw Cypher query`,
|
|
370
|
+
` GET /health — health check`,
|
|
371
|
+
` POST /shutdown — graceful shutdown`,
|
|
372
|
+
];
|
|
361
373
|
if (idleTimeoutSec > 0) {
|
|
362
|
-
|
|
374
|
+
bannerLines.push(` Auto-shutdown after ${idleTimeoutSec}s idle`);
|
|
363
375
|
}
|
|
376
|
+
cliInfo(bannerLines.join('\n'), {
|
|
377
|
+
port,
|
|
378
|
+
host: '127.0.0.1',
|
|
379
|
+
idleTimeoutSec: idleTimeoutSec > 0 ? idleTimeoutSec : undefined,
|
|
380
|
+
endpoints: [
|
|
381
|
+
'POST /tool/query',
|
|
382
|
+
'POST /tool/context',
|
|
383
|
+
'POST /tool/impact',
|
|
384
|
+
'POST /tool/cypher',
|
|
385
|
+
'GET /health',
|
|
386
|
+
'POST /shutdown',
|
|
387
|
+
],
|
|
388
|
+
});
|
|
364
389
|
try {
|
|
365
390
|
// Use fd 1 directly — LadybugDB captures process.stdout (#324)
|
|
366
391
|
writeSync(1, `GITNEXUS_EVAL_SERVER_READY:${port}\n`);
|
|
@@ -371,7 +396,7 @@ export async function evalServerCommand(options) {
|
|
|
371
396
|
});
|
|
372
397
|
resetIdleTimer();
|
|
373
398
|
const shutdown = async () => {
|
|
374
|
-
|
|
399
|
+
logger.info('GitNexus eval-server: shutting down...');
|
|
375
400
|
await backend.disconnect();
|
|
376
401
|
server.close();
|
|
377
402
|
process.exit(0);
|
package/dist/cli/group.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// gitnexus/src/cli/group.ts
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { logger } from '../core/logger.js';
|
|
3
4
|
const _require = createRequire(import.meta.url);
|
|
4
5
|
const yaml = _require('js-yaml');
|
|
5
6
|
export function registerGroupCommands(program) {
|
|
@@ -42,7 +43,7 @@ export function registerGroupCommands(program) {
|
|
|
42
43
|
const groupDir = getGroupDir(getDefaultGitnexusDir(), groupName);
|
|
43
44
|
const config = await loadGroupConfig(groupDir);
|
|
44
45
|
if (!(repoPath in config.repos)) {
|
|
45
|
-
|
|
46
|
+
logger.error(`Repo path "${repoPath}" not found in group "${groupName}"`);
|
|
46
47
|
process.exitCode = 1;
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
@@ -202,7 +203,7 @@ export function registerGroupCommands(program) {
|
|
|
202
203
|
payload.includeTests = true;
|
|
203
204
|
const raw = await backend.getGroupService().groupImpact(payload);
|
|
204
205
|
if (raw && typeof raw === 'object' && 'error' in raw) {
|
|
205
|
-
|
|
206
|
+
logger.error(String(raw.error));
|
|
206
207
|
process.exitCode = 1;
|
|
207
208
|
return;
|
|
208
209
|
}
|
|
@@ -280,7 +281,7 @@ export function registerGroupCommands(program) {
|
|
|
280
281
|
unmatchedOnly: Boolean(opts.unmatched),
|
|
281
282
|
});
|
|
282
283
|
if (raw && typeof raw === 'object' && 'error' in raw) {
|
|
283
|
-
|
|
284
|
+
logger.error(String(raw.error));
|
|
284
285
|
process.exitCode = 1;
|
|
285
286
|
return;
|
|
286
287
|
}
|
package/dist/cli/mcp.js
CHANGED
|
@@ -37,11 +37,17 @@ export const mcpCommand = async () => {
|
|
|
37
37
|
// startMCPServer (gitnexus/src/mcp/server.ts) so the server's shutdown
|
|
38
38
|
// path runs cleanly with full stack traces. Registering duplicates here
|
|
39
39
|
// would only produce noisy double-logging on the same exception.
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
40
|
+
// Dynamically import heavy backend modules AND the pino logger AFTER
|
|
41
|
+
// the sentinel installs. The logger is dynamic-imported (rather than
|
|
42
|
+
// static) to preserve the leaf-only static-import closure documented at
|
|
43
|
+
// the top of this file — `core/logger.js` itself doesn't write to
|
|
44
|
+
// stdout at module init, but transitive deps (pino, pino-pretty, the
|
|
45
|
+
// worker-thread transport) could in theory, and the import-closure
|
|
46
|
+
// regression test enforces the leaf invariant.
|
|
47
|
+
const [{ startMCPServer }, { LocalBackend }, { logger }] = await Promise.all([
|
|
43
48
|
import('../mcp/server.js'),
|
|
44
49
|
import('../mcp/local/local-backend.js'),
|
|
50
|
+
import('../core/logger.js'),
|
|
45
51
|
]);
|
|
46
52
|
// Missing-optional-grammar warnings are intentionally NOT emitted here.
|
|
47
53
|
// `gitnexus analyze` already warns at index time, filtered by the repo's
|
|
@@ -55,10 +61,12 @@ export const mcpCommand = async () => {
|
|
|
55
61
|
await backend.init();
|
|
56
62
|
const repos = await backend.listRepos();
|
|
57
63
|
if (repos.length === 0) {
|
|
58
|
-
|
|
64
|
+
// Operator-actionable but the server still starts and serves; warn-level,
|
|
65
|
+
// not error. Tools will discover newly-analyzed repos via lazy refresh.
|
|
66
|
+
logger.warn('GitNexus: No indexed repos yet. Run `gitnexus analyze` in a git repo — the server will pick it up automatically.');
|
|
59
67
|
}
|
|
60
68
|
else {
|
|
61
|
-
|
|
69
|
+
logger.info({ repoCount: repos.length, repos: repos.map((r) => r.name) }, 'GitNexus: MCP server starting');
|
|
62
70
|
}
|
|
63
71
|
// Start MCP server (serves all repos, discovers new ones lazily)
|
|
64
72
|
await startMCPServer(backend);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* is unavailable instead of silently getting a degraded index.
|
|
13
13
|
*/
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
|
+
import { cliWarn } from './cli-message.js';
|
|
15
16
|
const _require = createRequire(import.meta.url);
|
|
16
17
|
const OPTIONAL_GRAMMARS = [
|
|
17
18
|
{ name: 'tree-sitter-dart', pkg: 'tree-sitter-dart', extensions: ['.dart'] },
|
|
@@ -47,8 +48,12 @@ export function detectMissingOptionalGrammars() {
|
|
|
47
48
|
/could not find|no native build|prebuilds/i.test(msg);
|
|
48
49
|
if (!looksMissing) {
|
|
49
50
|
// Present but broken — surface so the user doesn't get a misleading
|
|
50
|
-
// "reinstall" recovery message that wouldn't actually help.
|
|
51
|
-
|
|
51
|
+
// "reinstall" recovery message that wouldn't actually help. cliWarn
|
|
52
|
+
// writes plain text to stderr AND tees a structured logger.warn
|
|
53
|
+
// record; the merged repo-wide ESLint pino-migration rule forbids
|
|
54
|
+
// direct `console.error` in CLI code (only `console.log` is allowed
|
|
55
|
+
// there for tool-data stdout output).
|
|
56
|
+
cliWarn(`GitNexus: optional grammar "${g.name}" is installed but failed to load (${msg.slice(0, 200)}). ${g.extensions.join('/')} files will not be parsed.`, { grammar: g.name, extensions: g.extensions, error: msg });
|
|
52
57
|
}
|
|
53
58
|
missing.push({ name: g.name, extensions: g.extensions });
|
|
54
59
|
}
|
|
@@ -69,10 +74,14 @@ export function warnMissingOptionalGrammars(opts) {
|
|
|
69
74
|
if (missing.length === 0)
|
|
70
75
|
return;
|
|
71
76
|
const ctx = opts?.context ? ` [${opts.context}]` : '';
|
|
77
|
+
// Hoist the optional set into a local so the closure below can narrow
|
|
78
|
+
// its type; references to `opts?.relevantExtensions` inside `.some()`
|
|
79
|
+
// lose the outer null-check narrowing and require a non-null assertion.
|
|
80
|
+
const relevantExtensions = opts?.relevantExtensions;
|
|
72
81
|
for (const g of missing) {
|
|
73
|
-
if (
|
|
82
|
+
if (relevantExtensions && !g.extensions.some((e) => relevantExtensions.has(e))) {
|
|
74
83
|
continue;
|
|
75
84
|
}
|
|
76
|
-
|
|
85
|
+
cliWarn(`GitNexus${ctx}: optional grammar "${g.name}" is unavailable — ${g.extensions.join('/')} files will not be parsed. Reinstall without GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (and ensure python3, make, g++) to enable.`, { grammar: g.name, extensions: g.extensions, context: opts?.context });
|
|
77
86
|
}
|
|
78
87
|
}
|
package/dist/cli/remove.js
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* here there is no pipeline, so no conflation.)
|
|
27
27
|
*/
|
|
28
28
|
import fs from 'fs/promises';
|
|
29
|
+
import { logger } from '../core/logger.js';
|
|
30
|
+
import { cliError } from './cli-message.js';
|
|
29
31
|
import { readRegistry, resolveRegistryEntry, assertSafeStoragePath, unregisterRepo, RegistryNotFoundError, RegistryAmbiguousTargetError, UnsafeStoragePathError, } from '../storage/repo-manager.js';
|
|
30
32
|
export const removeCommand = async (target, options) => {
|
|
31
33
|
// Read the registry snapshot once and pass it to the resolver — this
|
|
@@ -41,14 +43,14 @@ export const removeCommand = async (target, options) => {
|
|
|
41
43
|
// Idempotent: missing target is a no-op warning, not an error.
|
|
42
44
|
// The `availableNames` hint comes from the error itself so users
|
|
43
45
|
// can see what they might have meant.
|
|
44
|
-
|
|
46
|
+
logger.warn(`Nothing to remove: ${err.message}`);
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
if (err instanceof RegistryAmbiguousTargetError) {
|
|
48
50
|
// Duplicate aliases are allowed via --allow-duplicate-name (#829);
|
|
49
51
|
// refuse to guess which one the user meant — surface the full list
|
|
50
52
|
// and exit non-zero so scripts don't silently pick the wrong repo.
|
|
51
|
-
|
|
53
|
+
cliError(`Error: ${err.message}`);
|
|
52
54
|
process.exit(1);
|
|
53
55
|
}
|
|
54
56
|
throw err;
|
|
@@ -75,7 +77,7 @@ export const removeCommand = async (target, options) => {
|
|
|
75
77
|
}
|
|
76
78
|
catch (err) {
|
|
77
79
|
if (err instanceof UnsafeStoragePathError) {
|
|
78
|
-
|
|
80
|
+
cliError(`Error: ${err.message}`);
|
|
79
81
|
process.exit(1);
|
|
80
82
|
}
|
|
81
83
|
throw err;
|
|
@@ -93,7 +95,8 @@ export const removeCommand = async (target, options) => {
|
|
|
93
95
|
console.log(` Storage: ${entry.storagePath}`);
|
|
94
96
|
}
|
|
95
97
|
catch (err) {
|
|
96
|
-
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
cliError(`Failed to remove ${entry.name}: ${msg}`, { err });
|
|
97
100
|
process.exit(1);
|
|
98
101
|
}
|
|
99
102
|
};
|
package/dist/cli/serve.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { createServer } from '../server/api.js';
|
|
2
|
-
|
|
2
|
+
import { logger, flushLoggerSync } from '../core/logger.js';
|
|
3
|
+
import { cliError } from './cli-message.js';
|
|
4
|
+
// Catch anything that would cause a silent exit. Pino v10's default
|
|
5
|
+
// destination is `sync: false` (SonicBoom buffered) — call
|
|
6
|
+
// `flushLoggerSync()` between the log and `process.exit(1)` so the crash
|
|
7
|
+
// record is not lost to the unflushed buffer. Worker-thread transports
|
|
8
|
+
// (pino-pretty under TTY) handle their own flush on process exit in v10,
|
|
9
|
+
// so no separate `pino.final` integration is needed (the API was removed
|
|
10
|
+
// in v10 because the transport architecture made it unnecessary).
|
|
11
|
+
//
|
|
12
|
+
// We pass the Error itself in `{ err }` so pino's built-in err serializer
|
|
13
|
+
// captures `type`, `message`, and `stack` as structured fields.
|
|
3
14
|
process.on('uncaughtException', (err) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
console.error(err.stack);
|
|
15
|
+
logger.error({ err }, '[gitnexus serve] Uncaught exception');
|
|
16
|
+
flushLoggerSync();
|
|
7
17
|
process.exit(1);
|
|
8
18
|
});
|
|
9
19
|
process.on('unhandledRejection', (reason) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
21
|
+
logger.error({ err }, '[gitnexus serve] Unhandled rejection');
|
|
22
|
+
flushLoggerSync();
|
|
13
23
|
process.exit(1);
|
|
14
24
|
});
|
|
15
25
|
export const serveCommand = async (options) => {
|
|
@@ -22,16 +32,24 @@ export const serveCommand = async (options) => {
|
|
|
22
32
|
await createServer(port, host);
|
|
23
33
|
}
|
|
24
34
|
catch (err) {
|
|
25
|
-
console.error(`\nFailed to start GitNexus server:\n`);
|
|
26
|
-
console.error(` ${err.message || err}\n`);
|
|
27
35
|
if (err.code === 'EADDRINUSE') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
cliError(`\nFailed to start GitNexus server:\n` +
|
|
37
|
+
` ${err.message || err}\n\n` +
|
|
38
|
+
` Port ${port} is already in use. Either:\n` +
|
|
39
|
+
` 1. Stop the other process using port ${port}\n` +
|
|
40
|
+
` 2. Use a different port: gitnexus serve --port 4748\n`, { code: err.code, port, host });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
cliError(`\nFailed to start GitNexus server:\n ${err.message || err}\n`, {
|
|
44
|
+
code: err.code,
|
|
45
|
+
port,
|
|
46
|
+
host,
|
|
47
|
+
});
|
|
31
48
|
}
|
|
32
49
|
if (err.stack && process.env.DEBUG) {
|
|
33
|
-
|
|
50
|
+
logger.debug({ stack: err.stack }, 'serve start error stack');
|
|
34
51
|
}
|
|
52
|
+
flushLoggerSync();
|
|
35
53
|
process.exit(1);
|
|
36
54
|
}
|
|
37
55
|
};
|