ucn 3.8.5 → 3.8.6
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/cli/index.js +1 -1
- package/mcp/server.js +49 -45
- package/package.json +1 -1
package/cli/index.js
CHANGED
|
@@ -74,7 +74,7 @@ function parseFlags(tokens) {
|
|
|
74
74
|
file: getValueFlag('--file'),
|
|
75
75
|
exclude: parseExclude(),
|
|
76
76
|
in: getValueFlag('--in'),
|
|
77
|
-
includeTests: tokens.includes('--include-tests'),
|
|
77
|
+
includeTests: tokens.includes('--include-tests') ? true : undefined,
|
|
78
78
|
includeExported: tokens.includes('--include-exported'),
|
|
79
79
|
includeDecorated: tokens.includes('--include-decorated'),
|
|
80
80
|
includeUncertain: tokens.includes('--include-uncertain'),
|
package/mcp/server.js
CHANGED
|
@@ -45,20 +45,18 @@ const indexCache = new Map(); // projectDir → { index, checkedAt }
|
|
|
45
45
|
const MAX_CACHE_SIZE = 10;
|
|
46
46
|
const expandCacheInstance = new ExpandCache();
|
|
47
47
|
|
|
48
|
-
function getIndex(projectDir) {
|
|
48
|
+
function getIndex(projectDir, options) {
|
|
49
|
+
const maxFiles = options && options.maxFiles;
|
|
49
50
|
const absDir = path.resolve(projectDir);
|
|
50
51
|
if (!fs.existsSync(absDir) || !fs.statSync(absDir).isDirectory()) {
|
|
51
52
|
throw new Error(`Project directory not found: ${absDir}`);
|
|
52
53
|
}
|
|
53
54
|
const root = findProjectRoot(absDir);
|
|
54
55
|
const cached = indexCache.get(root);
|
|
55
|
-
const STALE_CHECK_INTERVAL_MS = 2000;
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return cached.index; // Recently verified fresh
|
|
61
|
-
}
|
|
57
|
+
// Always check staleness — MCP is used in iterative agent loops where
|
|
58
|
+
// files change between requests, so a throttle causes stale results.
|
|
59
|
+
if (cached && !maxFiles) {
|
|
62
60
|
if (!cached.index.isCacheStale()) {
|
|
63
61
|
cached.checkedAt = Date.now();
|
|
64
62
|
return cached.index;
|
|
@@ -67,12 +65,15 @@ function getIndex(projectDir) {
|
|
|
67
65
|
|
|
68
66
|
// Build new index (or rebuild stale one)
|
|
69
67
|
const index = new ProjectIndex(root);
|
|
68
|
+
const buildOpts = { quiet: true, forceRebuild: false };
|
|
69
|
+
if (maxFiles) buildOpts.maxFiles = maxFiles;
|
|
70
70
|
const loaded = index.loadCache();
|
|
71
|
-
if (loaded && !index.isCacheStale()) {
|
|
72
|
-
// Disk cache is fresh
|
|
71
|
+
if (loaded && !maxFiles && !index.isCacheStale()) {
|
|
72
|
+
// Disk cache is fresh (skip when maxFiles is set — cached index may have different file count)
|
|
73
73
|
} else {
|
|
74
|
-
|
|
75
|
-
index.
|
|
74
|
+
buildOpts.forceRebuild = !!loaded;
|
|
75
|
+
index.build(null, buildOpts);
|
|
76
|
+
if (!maxFiles) index.saveCache(); // Don't pollute disk cache with partial indexes
|
|
76
77
|
// Clear expand cache entries for this project — stale after rebuild
|
|
77
78
|
expandCacheInstance.clearForRoot(root);
|
|
78
79
|
}
|
|
@@ -93,7 +94,10 @@ function getIndex(projectDir) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
// Don't cache partial indexes (maxFiles) — they'd serve wrong results for full queries
|
|
98
|
+
if (!maxFiles) {
|
|
99
|
+
indexCache.set(root, { index, checkedAt: Date.now() });
|
|
100
|
+
}
|
|
97
101
|
return index;
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -298,7 +302,7 @@ server.registerTool(
|
|
|
298
302
|
// ── Commands using shared executor ─────────────────────────
|
|
299
303
|
|
|
300
304
|
case 'about': {
|
|
301
|
-
const index = getIndex(project_dir);
|
|
305
|
+
const index = getIndex(project_dir, ep);
|
|
302
306
|
const { ok, result, error } = execute(index, 'about', ep);
|
|
303
307
|
if (!ok) return toolResult(error); // soft error — won't kill sibling calls
|
|
304
308
|
return toolResult(output.formatAbout(result, {
|
|
@@ -309,7 +313,7 @@ server.registerTool(
|
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
case 'context': {
|
|
312
|
-
const index = getIndex(project_dir);
|
|
316
|
+
const index = getIndex(project_dir, ep);
|
|
313
317
|
const { ok, result: ctx, error } = execute(index, 'context', ep);
|
|
314
318
|
if (!ok) return toolResult(error); // context uses soft error (not toolError)
|
|
315
319
|
const { text, expandable } = output.formatContext(ctx, {
|
|
@@ -321,14 +325,14 @@ server.registerTool(
|
|
|
321
325
|
}
|
|
322
326
|
|
|
323
327
|
case 'impact': {
|
|
324
|
-
const index = getIndex(project_dir);
|
|
328
|
+
const index = getIndex(project_dir, ep);
|
|
325
329
|
const { ok, result, error } = execute(index, 'impact', ep);
|
|
326
330
|
if (!ok) return toolResult(error); // soft error
|
|
327
331
|
return toolResult(output.formatImpact(result));
|
|
328
332
|
}
|
|
329
333
|
|
|
330
334
|
case 'blast': {
|
|
331
|
-
const index = getIndex(project_dir);
|
|
335
|
+
const index = getIndex(project_dir, ep);
|
|
332
336
|
const { ok, result, error } = execute(index, 'blast', ep);
|
|
333
337
|
if (!ok) return toolResult(error); // soft error
|
|
334
338
|
return toolResult(output.formatBlast(result, {
|
|
@@ -337,14 +341,14 @@ server.registerTool(
|
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
case 'smart': {
|
|
340
|
-
const index = getIndex(project_dir);
|
|
344
|
+
const index = getIndex(project_dir, ep);
|
|
341
345
|
const { ok, result, error } = execute(index, 'smart', ep);
|
|
342
346
|
if (!ok) return toolResult(error); // soft error
|
|
343
347
|
return toolResult(output.formatSmart(result));
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
case 'trace': {
|
|
347
|
-
const index = getIndex(project_dir);
|
|
351
|
+
const index = getIndex(project_dir, ep);
|
|
348
352
|
const { ok, result, error } = execute(index, 'trace', ep);
|
|
349
353
|
if (!ok) return toolResult(error); // soft error
|
|
350
354
|
return toolResult(output.formatTrace(result, {
|
|
@@ -354,7 +358,7 @@ server.registerTool(
|
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
case 'reverse_trace': {
|
|
357
|
-
const index = getIndex(project_dir);
|
|
361
|
+
const index = getIndex(project_dir, ep);
|
|
358
362
|
const { ok, result, error } = execute(index, 'reverseTrace', ep);
|
|
359
363
|
if (!ok) return toolResult(error);
|
|
360
364
|
return toolResult(output.formatReverseTrace(result, {
|
|
@@ -363,7 +367,7 @@ server.registerTool(
|
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
case 'example': {
|
|
366
|
-
const index = getIndex(project_dir);
|
|
370
|
+
const index = getIndex(project_dir, ep);
|
|
367
371
|
const { ok, result, error } = execute(index, 'example', ep);
|
|
368
372
|
if (!ok) return toolResult(error);
|
|
369
373
|
if (!result) return toolResult(`No usage examples found for "${ep.name}".`);
|
|
@@ -371,7 +375,7 @@ server.registerTool(
|
|
|
371
375
|
}
|
|
372
376
|
|
|
373
377
|
case 'related': {
|
|
374
|
-
const index = getIndex(project_dir);
|
|
378
|
+
const index = getIndex(project_dir, ep);
|
|
375
379
|
const { ok, result, error } = execute(index, 'related', ep);
|
|
376
380
|
if (!ok) return toolResult(error);
|
|
377
381
|
if (!result) return toolResult(`Symbol "${ep.name}" not found.`);
|
|
@@ -384,7 +388,7 @@ server.registerTool(
|
|
|
384
388
|
// ── Finding Code ────────────────────────────────────────────
|
|
385
389
|
|
|
386
390
|
case 'find': {
|
|
387
|
-
const index = getIndex(project_dir);
|
|
391
|
+
const index = getIndex(project_dir, ep);
|
|
388
392
|
const { ok, result, error, note } = execute(index, 'find', ep);
|
|
389
393
|
if (!ok) return toolResult(error); // soft error
|
|
390
394
|
let text = output.formatFind(result, ep.name, ep.top);
|
|
@@ -393,7 +397,7 @@ server.registerTool(
|
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
case 'usages': {
|
|
396
|
-
const index = getIndex(project_dir);
|
|
400
|
+
const index = getIndex(project_dir, ep);
|
|
397
401
|
const { ok, result, error, note } = execute(index, 'usages', ep);
|
|
398
402
|
if (!ok) return toolResult(error); // soft error
|
|
399
403
|
let text = output.formatUsages(result, ep.name);
|
|
@@ -402,7 +406,7 @@ server.registerTool(
|
|
|
402
406
|
}
|
|
403
407
|
|
|
404
408
|
case 'toc': {
|
|
405
|
-
const index = getIndex(project_dir);
|
|
409
|
+
const index = getIndex(project_dir, ep);
|
|
406
410
|
const { ok, result, error, note } = execute(index, 'toc', ep);
|
|
407
411
|
if (!ok) return toolResult(error); // soft error
|
|
408
412
|
let text = output.formatToc(result, {
|
|
@@ -413,7 +417,7 @@ server.registerTool(
|
|
|
413
417
|
}
|
|
414
418
|
|
|
415
419
|
case 'search': {
|
|
416
|
-
const index = getIndex(project_dir);
|
|
420
|
+
const index = getIndex(project_dir, ep);
|
|
417
421
|
const { ok, result, error, structural } = execute(index, 'search', ep);
|
|
418
422
|
if (!ok) return toolResult(error); // soft error
|
|
419
423
|
if (structural) {
|
|
@@ -423,21 +427,21 @@ server.registerTool(
|
|
|
423
427
|
}
|
|
424
428
|
|
|
425
429
|
case 'tests': {
|
|
426
|
-
const index = getIndex(project_dir);
|
|
430
|
+
const index = getIndex(project_dir, ep);
|
|
427
431
|
const { ok, result, error } = execute(index, 'tests', ep);
|
|
428
432
|
if (!ok) return toolResult(error); // soft error
|
|
429
433
|
return toolResult(output.formatTests(result, ep.name));
|
|
430
434
|
}
|
|
431
435
|
|
|
432
436
|
case 'affected_tests': {
|
|
433
|
-
const index = getIndex(project_dir);
|
|
437
|
+
const index = getIndex(project_dir, ep);
|
|
434
438
|
const { ok, result, error } = execute(index, 'affectedTests', ep);
|
|
435
439
|
if (!ok) return toolResult(error);
|
|
436
440
|
return toolResult(output.formatAffectedTests(result));
|
|
437
441
|
}
|
|
438
442
|
|
|
439
443
|
case 'deadcode': {
|
|
440
|
-
const index = getIndex(project_dir);
|
|
444
|
+
const index = getIndex(project_dir, ep);
|
|
441
445
|
const { ok, result, error, note } = execute(index, 'deadcode', ep);
|
|
442
446
|
if (!ok) return toolResult(error); // soft error
|
|
443
447
|
const dcNote = note;
|
|
@@ -451,7 +455,7 @@ server.registerTool(
|
|
|
451
455
|
}
|
|
452
456
|
|
|
453
457
|
case 'entrypoints': {
|
|
454
|
-
const index = getIndex(project_dir);
|
|
458
|
+
const index = getIndex(project_dir, ep);
|
|
455
459
|
const { ok, result, error } = execute(index, 'entrypoints', ep);
|
|
456
460
|
if (!ok) return toolResult(error);
|
|
457
461
|
return toolResult(output.formatEntrypoints(result));
|
|
@@ -460,28 +464,28 @@ server.registerTool(
|
|
|
460
464
|
// ── File Dependencies ───────────────────────────────────────
|
|
461
465
|
|
|
462
466
|
case 'imports': {
|
|
463
|
-
const index = getIndex(project_dir);
|
|
467
|
+
const index = getIndex(project_dir, ep);
|
|
464
468
|
const { ok, result, error } = execute(index, 'imports', ep);
|
|
465
469
|
if (!ok) return toolResult(error); // soft error
|
|
466
470
|
return toolResult(output.formatImports(result, ep.file));
|
|
467
471
|
}
|
|
468
472
|
|
|
469
473
|
case 'exporters': {
|
|
470
|
-
const index = getIndex(project_dir);
|
|
474
|
+
const index = getIndex(project_dir, ep);
|
|
471
475
|
const { ok, result, error } = execute(index, 'exporters', ep);
|
|
472
476
|
if (!ok) return toolResult(error); // soft error
|
|
473
477
|
return toolResult(output.formatExporters(result, ep.file));
|
|
474
478
|
}
|
|
475
479
|
|
|
476
480
|
case 'file_exports': {
|
|
477
|
-
const index = getIndex(project_dir);
|
|
481
|
+
const index = getIndex(project_dir, ep);
|
|
478
482
|
const { ok, result, error } = execute(index, 'fileExports', ep);
|
|
479
483
|
if (!ok) return toolResult(error); // soft error
|
|
480
484
|
return toolResult(output.formatFileExports(result, ep.file));
|
|
481
485
|
}
|
|
482
486
|
|
|
483
487
|
case 'graph': {
|
|
484
|
-
const index = getIndex(project_dir);
|
|
488
|
+
const index = getIndex(project_dir, ep);
|
|
485
489
|
const { ok, result, error } = execute(index, 'graph', ep);
|
|
486
490
|
if (!ok) return toolResult(error); // soft error
|
|
487
491
|
return toolResult(output.formatGraph(result, {
|
|
@@ -493,7 +497,7 @@ server.registerTool(
|
|
|
493
497
|
}
|
|
494
498
|
|
|
495
499
|
case 'circular_deps': {
|
|
496
|
-
const index = getIndex(project_dir);
|
|
500
|
+
const index = getIndex(project_dir, ep);
|
|
497
501
|
const { ok, result, error } = execute(index, 'circularDeps', ep);
|
|
498
502
|
if (!ok) return toolResult(error);
|
|
499
503
|
return toolResult(output.formatCircularDeps(result));
|
|
@@ -502,21 +506,21 @@ server.registerTool(
|
|
|
502
506
|
// ── Refactoring ─────────────────────────────────────────────
|
|
503
507
|
|
|
504
508
|
case 'verify': {
|
|
505
|
-
const index = getIndex(project_dir);
|
|
509
|
+
const index = getIndex(project_dir, ep);
|
|
506
510
|
const { ok, result, error } = execute(index, 'verify', ep);
|
|
507
511
|
if (!ok) return toolResult(error); // soft error
|
|
508
512
|
return toolResult(output.formatVerify(result));
|
|
509
513
|
}
|
|
510
514
|
|
|
511
515
|
case 'plan': {
|
|
512
|
-
const index = getIndex(project_dir);
|
|
516
|
+
const index = getIndex(project_dir, ep);
|
|
513
517
|
const { ok, result, error } = execute(index, 'plan', ep);
|
|
514
518
|
if (!ok) return toolResult(error); // soft error
|
|
515
519
|
return toolResult(output.formatPlan(result));
|
|
516
520
|
}
|
|
517
521
|
|
|
518
522
|
case 'diff_impact': {
|
|
519
|
-
const index = getIndex(project_dir);
|
|
523
|
+
const index = getIndex(project_dir, ep);
|
|
520
524
|
const { ok, result, error } = execute(index, 'diffImpact', ep);
|
|
521
525
|
if (!ok) return toolResult(error); // soft error — e.g. "not a git repo"
|
|
522
526
|
return toolResult(output.formatDiffImpact(result));
|
|
@@ -525,21 +529,21 @@ server.registerTool(
|
|
|
525
529
|
// ── Other ───────────────────────────────────────────────────
|
|
526
530
|
|
|
527
531
|
case 'typedef': {
|
|
528
|
-
const index = getIndex(project_dir);
|
|
532
|
+
const index = getIndex(project_dir, ep);
|
|
529
533
|
const { ok, result, error } = execute(index, 'typedef', ep);
|
|
530
534
|
if (!ok) return toolResult(error); // soft error
|
|
531
535
|
return toolResult(output.formatTypedef(result, ep.name));
|
|
532
536
|
}
|
|
533
537
|
|
|
534
538
|
case 'stacktrace': {
|
|
535
|
-
const index = getIndex(project_dir);
|
|
539
|
+
const index = getIndex(project_dir, ep);
|
|
536
540
|
const { ok, result, error } = execute(index, 'stacktrace', ep);
|
|
537
541
|
if (!ok) return toolResult(error); // soft error
|
|
538
542
|
return toolResult(output.formatStackTrace(result));
|
|
539
543
|
}
|
|
540
544
|
|
|
541
545
|
case 'api': {
|
|
542
|
-
const index = getIndex(project_dir);
|
|
546
|
+
const index = getIndex(project_dir, ep);
|
|
543
547
|
const { ok, result, error, note } = execute(index, 'api', ep);
|
|
544
548
|
if (!ok) return toolResult(error); // soft error
|
|
545
549
|
let apiText = output.formatApi(result, ep.file || '.');
|
|
@@ -548,7 +552,7 @@ server.registerTool(
|
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
case 'stats': {
|
|
551
|
-
const index = getIndex(project_dir);
|
|
555
|
+
const index = getIndex(project_dir, ep);
|
|
552
556
|
const { ok, result, error } = execute(index, 'stats', ep);
|
|
553
557
|
if (!ok) return toolResult(error); // soft error
|
|
554
558
|
return toolResult(output.formatStats(result, { top: ep.top || 0 }));
|
|
@@ -559,7 +563,7 @@ server.registerTool(
|
|
|
559
563
|
case 'fn': {
|
|
560
564
|
const err = requireName(ep.name);
|
|
561
565
|
if (err) return err;
|
|
562
|
-
const index = getIndex(project_dir);
|
|
566
|
+
const index = getIndex(project_dir, ep);
|
|
563
567
|
const { ok, result, error } = execute(index, 'fn', ep);
|
|
564
568
|
if (!ok) return toolResult(error); // soft error
|
|
565
569
|
// MCP path security: validate all result files are within project root
|
|
@@ -577,7 +581,7 @@ server.registerTool(
|
|
|
577
581
|
if (ep.maxLines !== undefined && (!Number.isInteger(ep.maxLines) || ep.maxLines < 1)) {
|
|
578
582
|
return toolError(`Invalid max_lines: ${ep.maxLines}. Must be a positive integer.`);
|
|
579
583
|
}
|
|
580
|
-
const index = getIndex(project_dir);
|
|
584
|
+
const index = getIndex(project_dir, ep);
|
|
581
585
|
const { ok, result, error } = execute(index, 'class', ep);
|
|
582
586
|
if (!ok) return toolResult(error); // soft error (class not found)
|
|
583
587
|
// MCP path security: validate all result files are within project root
|
|
@@ -590,7 +594,7 @@ server.registerTool(
|
|
|
590
594
|
}
|
|
591
595
|
|
|
592
596
|
case 'lines': {
|
|
593
|
-
const index = getIndex(project_dir);
|
|
597
|
+
const index = getIndex(project_dir, ep);
|
|
594
598
|
const { ok, result, error } = execute(index, 'lines', ep);
|
|
595
599
|
if (!ok) return toolResult(error); // soft error
|
|
596
600
|
// MCP path security: validate file is within project root
|
|
@@ -603,7 +607,7 @@ server.registerTool(
|
|
|
603
607
|
if (ep.item === undefined || ep.item === null) {
|
|
604
608
|
return toolError('Item number is required (e.g. item=1).');
|
|
605
609
|
}
|
|
606
|
-
const index = getIndex(project_dir);
|
|
610
|
+
const index = getIndex(project_dir, ep);
|
|
607
611
|
const lookup = expandCacheInstance.lookup(index.root, ep.item);
|
|
608
612
|
const { ok, result, error } = execute(index, 'expand', {
|
|
609
613
|
match: lookup.match, itemNum: ep.item,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.6",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
|
|
6
6
|
"main": "index.js",
|