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 +7 -7
- package/core/callers.js +12 -1
- package/core/discovery.js +7 -0
- package/core/output.js +4 -0
- package/core/project.js +109 -6
- package/mcp/server.js +23 -23
- package/package.json +3 -2
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
|
|
24
|
-
ucn impact handleRequest
|
|
25
|
-
ucn trace main --depth=3
|
|
26
|
-
ucn deadcode
|
|
27
|
-
ucn fn handleRequest
|
|
28
|
-
ucn toc
|
|
29
|
-
ucn --interactive
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|