ucn 3.7.27 → 3.7.28

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/README.md CHANGED
@@ -20,13 +20,13 @@ Instead of reading full files, UCN gives precise, AST-verified answers.
20
20
  ```bash
21
21
  npm install -g ucn
22
22
 
23
- ucn about handleRequest # full picture: definition, callers, callees, tests
24
- ucn impact handleRequest # all call sites with arguments
25
- ucn trace main --depth=3 # call tree, no file reads
26
- ucn deadcode # unused functions, AST-verified
27
- ucn fn handleRequest # extract a function without reading the file
28
- ucn toc # project overview
29
- ucn --interactive # REPL mode, index stays in memory
23
+ ucn about handleRequest # full picture: definition, callers, callees, tests
24
+ ucn impact handleRequest # all call sites with arguments
25
+ ucn trace main --depth=3 # call tree, no file reads
26
+ ucn deadcode # unused functions, AST-verified
27
+ ucn fn handleRequest # extract a function without reading the file
28
+ ucn toc # project overview
29
+ ucn --interactive # REPL mode, index stays in memory
30
30
  ```
31
31
 
32
32
  Parses JS/TS, Python, Go, Rust, Java, and HTML with tree-sitter. Runs locally.
package/core/callers.js CHANGED
@@ -704,7 +704,18 @@ function findCallees(index, def, options = {}) {
704
704
  }
705
705
  }
706
706
  }
707
- // Priority 4: If default (symbols[0]) is a test file, prefer non-test
707
+ // Priority 4: If default is from a bundled/minified file, prefer non-bundled
708
+ if (!bindingId) {
709
+ const calleeFileEntry = index.files.get(callee.file);
710
+ if (calleeFileEntry && calleeFileEntry.isBundled) {
711
+ const nonBundled = symbols.find(s => {
712
+ const fe = index.files.get(s.file);
713
+ return fe && !fe.isBundled;
714
+ });
715
+ if (nonBundled) callee = nonBundled;
716
+ }
717
+ }
718
+ // Priority 5: If default is a test file, prefer non-test
708
719
  if (!bindingId) {
709
720
  const calleeFileEntry = index.files.get(callee.file);
710
721
  if (calleeFileEntry && isTestFile(calleeFileEntry.relativePath, calleeFileEntry.language)) {
package/core/discovery.js CHANGED
@@ -30,6 +30,7 @@ const DEFAULT_IGNORES = [
30
30
 
31
31
  // Build outputs
32
32
  'dist',
33
+ '*-dist',
33
34
  'build',
34
35
  'out',
35
36
  '.next',
@@ -38,6 +39,12 @@ const DEFAULT_IGNORES = [
38
39
  '.output',
39
40
  '.vercel',
40
41
  '.netlify',
42
+ '.turbo',
43
+ '.parcel-cache',
44
+ '.svelte-kit',
45
+ '.docusaurus',
46
+ 'storybook-static',
47
+ '_site',
41
48
 
42
49
  // Test/coverage
43
50
  'coverage',
package/core/output.js CHANGED
@@ -613,6 +613,10 @@ function formatApi(symbols, filePath) {
613
613
 
614
614
  if (symbols.length === 0) {
615
615
  lines.push(' (none found)');
616
+ if (filePath && filePath.endsWith('.py')) {
617
+ lines.push('');
618
+ lines.push('Note: Python requires __all__ for export detection. Use \'toc\' command to see all functions/classes.');
619
+ }
616
620
  } else {
617
621
  // Group by file
618
622
  const byFile = new Map();
package/core/project.js CHANGED
@@ -1477,42 +1477,145 @@ class ProjectIndex {
1477
1477
  */
1478
1478
  isKeyword(name, language) {
1479
1479
  if (!LANGUAGE_KEYWORDS) {
1480
- // Initialize on first use
1480
+ // Initialize on first use — includes both keywords AND builtins
1481
+ // to prevent cross-language false positives (e.g. Python set() → JS bundle)
1481
1482
  LANGUAGE_KEYWORDS = {
1482
1483
  javascript: new Set([
1484
+ // Keywords
1483
1485
  'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break',
1484
1486
  'continue', 'return', 'function', 'class', 'const', 'let', 'var',
1485
1487
  'new', 'this', 'super', 'import', 'export', 'default', 'from',
1486
1488
  'try', 'catch', 'finally', 'throw', 'async', 'await', 'yield',
1487
- 'typeof', 'instanceof', 'in', 'of', 'delete', 'void', 'with'
1489
+ 'typeof', 'instanceof', 'in', 'of', 'delete', 'void', 'with',
1490
+ // Global builtins
1491
+ 'undefined', 'NaN', 'Infinity', 'globalThis',
1492
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
1493
+ 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
1494
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
1495
+ 'console', 'JSON', 'Math', 'Date', 'RegExp',
1496
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
1497
+ 'Map', 'Set', 'WeakMap', 'WeakSet', 'WeakRef',
1498
+ 'Promise', 'Proxy', 'Reflect',
1499
+ 'Error', 'TypeError', 'RangeError', 'ReferenceError', 'SyntaxError',
1500
+ 'URIError', 'EvalError', 'AggregateError',
1501
+ 'ArrayBuffer', 'SharedArrayBuffer', 'DataView',
1502
+ 'Int8Array', 'Uint8Array', 'Int16Array', 'Uint16Array',
1503
+ 'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array',
1504
+ 'BigInt64Array', 'BigUint64Array',
1505
+ 'TextEncoder', 'TextDecoder', 'URL', 'URLSearchParams',
1506
+ 'fetch', 'Request', 'Response', 'Headers',
1507
+ 'atob', 'btoa', 'structuredClone', 'queueMicrotask',
1508
+ 'require'
1488
1509
  ]),
1489
1510
  python: new Set([
1511
+ // Keywords
1490
1512
  'if', 'else', 'elif', 'for', 'while', 'def', 'class', 'return',
1491
1513
  'import', 'from', 'try', 'except', 'finally', 'raise', 'async',
1492
1514
  'await', 'yield', 'with', 'as', 'lambda', 'pass', 'break',
1493
1515
  'continue', 'del', 'global', 'nonlocal', 'assert', 'is', 'not',
1494
- 'and', 'or', 'in', 'True', 'False', 'None', 'self', 'cls'
1516
+ 'and', 'or', 'in', 'True', 'False', 'None', 'self', 'cls',
1517
+ // Type constructors
1518
+ 'int', 'float', 'str', 'bool', 'list', 'dict', 'set', 'tuple',
1519
+ 'bytes', 'bytearray', 'frozenset', 'complex', 'memoryview',
1520
+ 'object', 'type',
1521
+ // Common builtins
1522
+ 'print', 'len', 'range', 'abs', 'round', 'min', 'max', 'sum',
1523
+ 'sorted', 'reversed', 'enumerate', 'zip', 'map', 'filter',
1524
+ 'any', 'all', 'iter', 'next', 'hash', 'id', 'repr', 'format',
1525
+ 'chr', 'ord', 'hex', 'oct', 'bin', 'pow', 'divmod',
1526
+ 'input', 'open', 'super',
1527
+ // Introspection
1528
+ 'isinstance', 'issubclass', 'hasattr', 'getattr', 'setattr',
1529
+ 'delattr', 'callable', 'dir', 'vars', 'globals', 'locals', 'help',
1530
+ // Decorators / descriptors
1531
+ 'property', 'staticmethod', 'classmethod',
1532
+ // Exception types
1533
+ 'Exception', 'BaseException', 'ValueError', 'TypeError',
1534
+ 'KeyError', 'IndexError', 'AttributeError', 'RuntimeError',
1535
+ 'NotImplementedError', 'StopIteration', 'StopAsyncIteration',
1536
+ 'GeneratorExit', 'OSError', 'IOError', 'FileNotFoundError',
1537
+ 'FileExistsError', 'PermissionError', 'IsADirectoryError',
1538
+ 'ImportError', 'ModuleNotFoundError', 'NameError',
1539
+ 'UnboundLocalError', 'ZeroDivisionError', 'OverflowError',
1540
+ 'FloatingPointError', 'ArithmeticError', 'LookupError',
1541
+ 'RecursionError', 'MemoryError', 'SystemExit',
1542
+ 'KeyboardInterrupt', 'AssertionError',
1543
+ 'UnicodeError', 'UnicodeDecodeError', 'UnicodeEncodeError',
1544
+ 'Warning', 'DeprecationWarning', 'FutureWarning',
1545
+ 'UserWarning', 'SyntaxWarning', 'RuntimeWarning',
1546
+ 'ConnectionError', 'TimeoutError', 'BrokenPipeError',
1547
+ // Other builtins
1548
+ 'NotImplemented', 'Ellipsis', '__import__', '__name__',
1549
+ '__file__', '__doc__', '__all__', '__init__', '__new__',
1550
+ '__del__', '__repr__', '__str__', '__len__', '__iter__'
1495
1551
  ]),
1496
1552
  go: new Set([
1553
+ // Keywords
1497
1554
  'if', 'else', 'for', 'switch', 'case', 'break', 'continue',
1498
1555
  'return', 'func', 'type', 'struct', 'interface', 'package',
1499
1556
  'import', 'go', 'defer', 'select', 'chan', 'map', 'range',
1500
- 'fallthrough', 'goto', 'var', 'const', 'default'
1557
+ 'fallthrough', 'goto', 'var', 'const', 'default',
1558
+ // Builtins
1559
+ 'append', 'cap', 'close', 'copy', 'delete', 'len', 'make',
1560
+ 'new', 'panic', 'recover', 'print', 'println', 'complex',
1561
+ 'real', 'imag', 'clear', 'min', 'max',
1562
+ // Builtin types (prevent cross-language matches)
1563
+ 'error', 'string', 'bool', 'byte', 'rune',
1564
+ 'int', 'int8', 'int16', 'int32', 'int64',
1565
+ 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr',
1566
+ 'float32', 'float64', 'complex64', 'complex128',
1567
+ 'nil', 'true', 'false', 'iota'
1501
1568
  ]),
1502
1569
  rust: new Set([
1570
+ // Keywords
1503
1571
  'if', 'else', 'for', 'while', 'loop', 'fn', 'impl', 'pub',
1504
1572
  'mod', 'use', 'crate', 'self', 'super', 'match', 'unsafe',
1505
1573
  'move', 'ref', 'mut', 'where', 'let', 'const', 'struct',
1506
1574
  'enum', 'trait', 'async', 'await', 'return', 'break',
1507
- 'continue', 'type', 'as', 'in', 'dyn', 'static'
1575
+ 'continue', 'type', 'as', 'in', 'dyn', 'static',
1576
+ // Macros (common calls that aren't project functions)
1577
+ 'println', 'print', 'eprintln', 'eprint', 'format',
1578
+ 'vec', 'panic', 'assert', 'assert_eq', 'assert_ne',
1579
+ 'debug_assert', 'debug_assert_eq', 'debug_assert_ne',
1580
+ 'todo', 'unimplemented', 'unreachable',
1581
+ 'cfg', 'derive', 'include', 'include_str', 'include_bytes',
1582
+ 'env', 'concat', 'stringify', 'file', 'line', 'column',
1583
+ // Std prelude types/traits
1584
+ 'Some', 'None', 'Ok', 'Err', 'Box', 'Vec', 'String',
1585
+ 'Option', 'Result', 'Clone', 'Copy', 'Drop',
1586
+ 'Default', 'Debug', 'Display', 'Iterator',
1587
+ 'From', 'Into', 'TryFrom', 'TryInto',
1588
+ 'AsRef', 'AsMut', 'Deref', 'DerefMut',
1589
+ 'Send', 'Sync', 'Sized', 'Unpin',
1590
+ 'Fn', 'FnMut', 'FnOnce',
1591
+ 'PartialEq', 'Eq', 'PartialOrd', 'Ord', 'Hash'
1508
1592
  ]),
1509
1593
  java: new Set([
1594
+ // Keywords
1510
1595
  'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break',
1511
1596
  'continue', 'return', 'class', 'interface', 'enum', 'extends',
1512
1597
  'implements', 'new', 'this', 'super', 'import', 'package',
1513
1598
  'try', 'catch', 'finally', 'throw', 'throws', 'abstract',
1514
1599
  'static', 'final', 'synchronized', 'volatile', 'transient',
1515
- 'native', 'void', 'instanceof', 'default'
1600
+ 'native', 'void', 'instanceof', 'default',
1601
+ // Primitive types
1602
+ 'boolean', 'byte', 'char', 'short', 'int', 'long',
1603
+ 'float', 'double', 'null', 'true', 'false',
1604
+ // java.lang builtins (auto-imported)
1605
+ 'System', 'String', 'Object', 'Class', 'Integer', 'Long',
1606
+ 'Double', 'Float', 'Boolean', 'Character', 'Byte', 'Short',
1607
+ 'Math', 'StringBuilder', 'StringBuffer',
1608
+ 'Thread', 'Runnable', 'Throwable',
1609
+ 'Exception', 'RuntimeException', 'Error',
1610
+ 'NullPointerException', 'IllegalArgumentException',
1611
+ 'IllegalStateException', 'IndexOutOfBoundsException',
1612
+ 'ClassCastException', 'UnsupportedOperationException',
1613
+ 'ArithmeticException', 'SecurityException',
1614
+ 'StackOverflowError', 'OutOfMemoryError',
1615
+ 'Override', 'Deprecated', 'SuppressWarnings',
1616
+ 'FunctionalInterface', 'SafeVarargs',
1617
+ 'Iterable', 'Comparable', 'AutoCloseable', 'Cloneable',
1618
+ 'Enum', 'Record', 'Void'
1516
1619
  ])
1517
1620
  };
1518
1621
  // TypeScript/TSX share JavaScript keywords
package/mcp/server.js CHANGED
@@ -276,7 +276,7 @@ server.registerTool(
276
276
  const index = getIndex(project_dir);
277
277
  const ep = normalizeParams({ name, file, exclude, with_types, all, include_methods, include_uncertain, top });
278
278
  const { ok, result, error } = execute(index, 'about', ep);
279
- if (!ok) return toolError(error);
279
+ if (!ok) return toolResult(error); // soft error — won't kill sibling calls
280
280
  return toolResult(output.formatAbout(result, {
281
281
  allHint: 'Repeat with all=true to show all.',
282
282
  methodsHint: 'Note: obj.method() callers/callees excluded. Use include_methods=true to include them.'
@@ -299,7 +299,7 @@ server.registerTool(
299
299
  const index = getIndex(project_dir);
300
300
  const ep = normalizeParams({ name, file, exclude });
301
301
  const { ok, result, error } = execute(index, 'impact', ep);
302
- if (!ok) return toolError(error);
302
+ if (!ok) return toolResult(error); // soft error
303
303
  return toolResult(output.formatImpact(result));
304
304
  }
305
305
 
@@ -315,7 +315,7 @@ server.registerTool(
315
315
  const index = getIndex(project_dir);
316
316
  const ep = normalizeParams({ name, file, depth, all, include_methods, include_uncertain });
317
317
  const { ok, result, error } = execute(index, 'trace', ep);
318
- if (!ok) return toolError(error);
318
+ if (!ok) return toolResult(error); // soft error
319
319
  return toolResult(output.formatTrace(result, {
320
320
  allHint: 'Set depth to expand all children.',
321
321
  methodsHint: 'Note: obj.method() calls excluded. Use include_methods=true to include them.'
@@ -347,7 +347,7 @@ server.registerTool(
347
347
  const index = getIndex(project_dir);
348
348
  const ep = normalizeParams({ name, file, exclude, include_tests, exact, in: inPath });
349
349
  const { ok, result, error } = execute(index, 'find', ep);
350
- if (!ok) return toolError(error);
350
+ if (!ok) return toolResult(error); // soft error
351
351
  return toolResult(output.formatFind(result, name, top));
352
352
  }
353
353
 
@@ -355,7 +355,7 @@ server.registerTool(
355
355
  const index = getIndex(project_dir);
356
356
  const ep = normalizeParams({ name, exclude, include_tests, code_only, context: ctxLines, in: inPath });
357
357
  const { ok, result, error } = execute(index, 'usages', ep);
358
- if (!ok) return toolError(error);
358
+ if (!ok) return toolResult(error); // soft error
359
359
  return toolResult(output.formatUsages(result, name));
360
360
  }
361
361
 
@@ -363,7 +363,7 @@ server.registerTool(
363
363
  const index = getIndex(project_dir);
364
364
  const ep = normalizeParams({ detailed, top_level, all, top });
365
365
  const { ok, result, error } = execute(index, 'toc', ep);
366
- if (!ok) return toolError(error);
366
+ if (!ok) return toolResult(error); // soft error
367
367
  return toolResult(output.formatToc(result, {
368
368
  topHint: 'Set top=N or use detailed=false for compact view.'
369
369
  }));
@@ -373,7 +373,7 @@ server.registerTool(
373
373
  const index = getIndex(project_dir);
374
374
  const ep = normalizeParams({ term, exclude, include_tests, code_only, context: ctxLines, case_sensitive, in: inPath, regex });
375
375
  const { ok, result, error } = execute(index, 'search', ep);
376
- if (!ok) return toolError(error);
376
+ if (!ok) return toolResult(error); // soft error
377
377
  return toolResult(output.formatSearch(result, term));
378
378
  }
379
379
 
@@ -381,7 +381,7 @@ server.registerTool(
381
381
  const index = getIndex(project_dir);
382
382
  const ep = normalizeParams({ name, calls_only });
383
383
  const { ok, result, error } = execute(index, 'tests', ep);
384
- if (!ok) return toolError(error);
384
+ if (!ok) return toolResult(error); // soft error
385
385
  return toolResult(output.formatTests(result, name));
386
386
  }
387
387
 
@@ -389,7 +389,7 @@ server.registerTool(
389
389
  const index = getIndex(project_dir);
390
390
  const ep = normalizeParams({ exclude, in: inPath, include_exported, include_decorated, include_tests });
391
391
  const { ok, result, error } = execute(index, 'deadcode', ep);
392
- if (!ok) return toolError(error);
392
+ if (!ok) return toolResult(error); // soft error
393
393
  return toolResult(output.formatDeadcode(result, {
394
394
  top: top || 0,
395
395
  decoratedHint: !include_decorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
@@ -402,28 +402,28 @@ server.registerTool(
402
402
  case 'imports': {
403
403
  const index = getIndex(project_dir);
404
404
  const { ok, result, error } = execute(index, 'imports', { file });
405
- if (!ok) return toolError(error);
405
+ if (!ok) return toolResult(error); // soft error
406
406
  return toolResult(output.formatImports(result, file));
407
407
  }
408
408
 
409
409
  case 'exporters': {
410
410
  const index = getIndex(project_dir);
411
411
  const { ok, result, error } = execute(index, 'exporters', { file });
412
- if (!ok) return toolError(error);
412
+ if (!ok) return toolResult(error); // soft error
413
413
  return toolResult(output.formatExporters(result, file));
414
414
  }
415
415
 
416
416
  case 'file_exports': {
417
417
  const index = getIndex(project_dir);
418
418
  const { ok, result, error } = execute(index, 'fileExports', { file });
419
- if (!ok) return toolError(error);
419
+ if (!ok) return toolResult(error); // soft error
420
420
  return toolResult(output.formatFileExports(result, file));
421
421
  }
422
422
 
423
423
  case 'graph': {
424
424
  const index = getIndex(project_dir);
425
425
  const { ok, result, error } = execute(index, 'graph', { file, direction, depth, all });
426
- if (!ok) return toolError(error);
426
+ if (!ok) return toolResult(error); // soft error
427
427
  return toolResult(output.formatGraph(result, {
428
428
  showAll: all || depth !== undefined,
429
429
  maxDepth: depth ?? 2, file,
@@ -437,7 +437,7 @@ server.registerTool(
437
437
  case 'verify': {
438
438
  const index = getIndex(project_dir);
439
439
  const { ok, result, error } = execute(index, 'verify', { name, file });
440
- if (!ok) return toolError(error);
440
+ if (!ok) return toolResult(error); // soft error
441
441
  return toolResult(output.formatVerify(result));
442
442
  }
443
443
 
@@ -445,14 +445,14 @@ server.registerTool(
445
445
  const index = getIndex(project_dir);
446
446
  const ep = normalizeParams({ name, add_param, remove_param, rename_to, default_value, file });
447
447
  const { ok, result, error } = execute(index, 'plan', ep);
448
- if (!ok) return toolError(error);
448
+ if (!ok) return toolResult(error); // soft error
449
449
  return toolResult(output.formatPlan(result));
450
450
  }
451
451
 
452
452
  case 'diff_impact': {
453
453
  const index = getIndex(project_dir);
454
454
  const { ok, result, error } = execute(index, 'diffImpact', { base, staged, file });
455
- if (!ok) return toolError(error);
455
+ if (!ok) return toolResult(error); // soft error — e.g. "not a git repo"
456
456
  return toolResult(output.formatDiffImpact(result));
457
457
  }
458
458
 
@@ -461,28 +461,28 @@ server.registerTool(
461
461
  case 'typedef': {
462
462
  const index = getIndex(project_dir);
463
463
  const { ok, result, error } = execute(index, 'typedef', { name, exact });
464
- if (!ok) return toolError(error);
464
+ if (!ok) return toolResult(error); // soft error
465
465
  return toolResult(output.formatTypedef(result, name));
466
466
  }
467
467
 
468
468
  case 'stacktrace': {
469
469
  const index = getIndex(project_dir);
470
470
  const { ok, result, error } = execute(index, 'stacktrace', { stack });
471
- if (!ok) return toolError(error);
471
+ if (!ok) return toolResult(error); // soft error
472
472
  return toolResult(output.formatStackTrace(result));
473
473
  }
474
474
 
475
475
  case 'api': {
476
476
  const index = getIndex(project_dir);
477
477
  const { ok, result, error } = execute(index, 'api', { file });
478
- if (!ok) return toolError(error);
478
+ if (!ok) return toolResult(error); // soft error
479
479
  return toolResult(output.formatApi(result, file || '.'));
480
480
  }
481
481
 
482
482
  case 'stats': {
483
483
  const index = getIndex(project_dir);
484
484
  const { ok, result, error } = execute(index, 'stats', { functions });
485
- if (!ok) return toolError(error);
485
+ if (!ok) return toolResult(error); // soft error
486
486
  return toolResult(output.formatStats(result, { top: top || 0 }));
487
487
  }
488
488
 
@@ -494,7 +494,7 @@ server.registerTool(
494
494
  const index = getIndex(project_dir);
495
495
  const ep = normalizeParams({ name, file, all });
496
496
  const { ok, result, error } = execute(index, 'fn', ep);
497
- if (!ok) return toolError(error);
497
+ if (!ok) return toolResult(error); // soft error
498
498
  // MCP path security: validate all result files are within project root
499
499
  for (const entry of result.entries) {
500
500
  const check = resolveAndValidatePath(index, entry.match.relativePath || path.relative(index.root, entry.match.file));
@@ -527,7 +527,7 @@ server.registerTool(
527
527
  const index = getIndex(project_dir);
528
528
  const ep = normalizeParams({ file, range });
529
529
  const { ok, result, error } = execute(index, 'lines', ep);
530
- if (!ok) return toolError(error);
530
+ if (!ok) return toolResult(error); // soft error
531
531
  // MCP path security: validate file is within project root
532
532
  const check = resolveAndValidatePath(index, result.relativePath);
533
533
  if (typeof check !== 'string') return check;
@@ -545,7 +545,7 @@ server.registerTool(
545
545
  itemCount: lookup.itemCount, symbolName: lookup.symbolName,
546
546
  validateRoot: true
547
547
  });
548
- if (!ok) return toolError(error);
548
+ if (!ok) return toolResult(error); // soft error
549
549
  return toolResult(result.text);
550
550
  }
551
551
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.27",
3
+ "version": "3.7.28",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
6
6
  "main": "index.js",
@@ -9,7 +9,8 @@
9
9
  "ucn-mcp": "mcp/server.js"
10
10
  },
11
11
  "scripts": {
12
- "test": "node --test test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-cross.test.js test/accuracy.test.js test/systematic-test.js test/mcp-edge-cases.js test/parity-test.js"
12
+ "test": "node --test test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-cross.test.js test/accuracy.test.js test/systematic-test.js test/mcp-edge-cases.js test/parity-test.js",
13
+ "benchmark:agent": "node test/agent-understanding-benchmark.js"
13
14
  },
14
15
  "keywords": [
15
16
  "mcp",