ucn 3.7.19 → 3.7.21
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/core/output.js +18 -7
- package/core/parser.js +3 -0
- package/core/project.js +6 -0
- package/languages/go.js +2 -1
- package/languages/javascript.js +5 -3
- package/languages/rust.js +11 -8
- package/mcp/server.js +1 -0
- package/package.json +1 -1
package/core/output.js
CHANGED
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
|
|
11
|
+
const FILE_ERROR_MESSAGES = {
|
|
12
|
+
'file-not-found': 'File not found in project',
|
|
13
|
+
'file-ambiguous': 'Ambiguous file match'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function formatFileError(errorObj, fallbackPath) {
|
|
17
|
+
const msg = FILE_ERROR_MESSAGES[errorObj.error] || errorObj.error;
|
|
18
|
+
const file = errorObj.filePath || fallbackPath || '';
|
|
19
|
+
return `Error: ${msg}: ${file}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
/**
|
|
12
23
|
* Normalize parameters for display
|
|
13
24
|
* Collapses multiline params to single line
|
|
@@ -381,7 +392,7 @@ function formatSearchJson(results, term) {
|
|
|
381
392
|
* Format imports as JSON
|
|
382
393
|
*/
|
|
383
394
|
function formatImportsJson(imports, filePath) {
|
|
384
|
-
if (imports?.error
|
|
395
|
+
if (imports?.error) return JSON.stringify({ found: false, error: imports.error, file: imports.filePath || filePath }, null, 2);
|
|
385
396
|
return JSON.stringify({
|
|
386
397
|
file: filePath,
|
|
387
398
|
importCount: imports.length,
|
|
@@ -406,7 +417,7 @@ function formatStatsJson(stats) {
|
|
|
406
417
|
* Format dependency graph as JSON
|
|
407
418
|
*/
|
|
408
419
|
function formatGraphJson(graph) {
|
|
409
|
-
if (graph?.error
|
|
420
|
+
if (graph?.error) return JSON.stringify({ found: false, error: graph.error, file: graph.filePath }, null, 2);
|
|
410
421
|
return JSON.stringify({
|
|
411
422
|
file: graph.file,
|
|
412
423
|
depth: graph.depth,
|
|
@@ -456,7 +467,7 @@ function formatSmartJson(result) {
|
|
|
456
467
|
* Format imports command output - text
|
|
457
468
|
*/
|
|
458
469
|
function formatImports(imports, filePath) {
|
|
459
|
-
if (imports?.error
|
|
470
|
+
if (imports?.error) return formatFileError(imports, filePath);
|
|
460
471
|
const lines = [`Imports in ${filePath}:\n`];
|
|
461
472
|
|
|
462
473
|
const internal = imports.filter(i => !i.isExternal && !i.isDynamic);
|
|
@@ -505,7 +516,7 @@ function formatImports(imports, filePath) {
|
|
|
505
516
|
* Format exporters command output - text
|
|
506
517
|
*/
|
|
507
518
|
function formatExporters(exporters, filePath) {
|
|
508
|
-
if (exporters?.error
|
|
519
|
+
if (exporters?.error) return formatFileError(exporters, filePath);
|
|
509
520
|
const lines = [`Files that import ${filePath}:\n`];
|
|
510
521
|
|
|
511
522
|
if (exporters.length === 0) {
|
|
@@ -641,7 +652,7 @@ function formatDisambiguation(matches, name, command) {
|
|
|
641
652
|
* Format exporters as JSON
|
|
642
653
|
*/
|
|
643
654
|
function formatExportersJson(exporters, filePath) {
|
|
644
|
-
if (exporters?.error
|
|
655
|
+
if (exporters?.error) return JSON.stringify({ found: false, error: exporters.error, file: exporters.filePath || filePath }, null, 2);
|
|
645
656
|
return JSON.stringify({
|
|
646
657
|
file: filePath,
|
|
647
658
|
importerCount: exporters.length,
|
|
@@ -1815,7 +1826,7 @@ function formatGraph(graph, options = {}) {
|
|
|
1815
1826
|
if (typeof options === 'boolean') {
|
|
1816
1827
|
options = { showAll: options };
|
|
1817
1828
|
}
|
|
1818
|
-
if (graph?.error
|
|
1829
|
+
if (graph?.error) return formatFileError(graph);
|
|
1819
1830
|
if (graph.nodes.length === 0) {
|
|
1820
1831
|
const file = options.file || graph.root || '';
|
|
1821
1832
|
return file ? `File not found: ${file}` : 'File not found.';
|
|
@@ -1985,7 +1996,7 @@ function formatSearch(results, term) {
|
|
|
1985
1996
|
* Format file-exports command output
|
|
1986
1997
|
*/
|
|
1987
1998
|
function formatFileExports(exports, filePath) {
|
|
1988
|
-
if (exports?.error
|
|
1999
|
+
if (exports?.error) return formatFileError(exports, filePath);
|
|
1989
2000
|
if (exports.length === 0) return `No exports found in ${filePath}`;
|
|
1990
2001
|
|
|
1991
2002
|
const lines = [];
|
package/core/parser.js
CHANGED
|
@@ -67,6 +67,9 @@ const { detectLanguage, getParser, getLanguageModule, isSupported, PARSE_OPTIONS
|
|
|
67
67
|
* @returns {ParseResult}
|
|
68
68
|
*/
|
|
69
69
|
function parse(code, language) {
|
|
70
|
+
if (!language) {
|
|
71
|
+
throw new Error('Language parameter is required');
|
|
72
|
+
}
|
|
70
73
|
// Detect language if file path provided
|
|
71
74
|
if (language.includes('.') || language.includes('/')) {
|
|
72
75
|
language = detectLanguage(language);
|
package/core/project.js
CHANGED
|
@@ -364,6 +364,9 @@ class ProjectIndex {
|
|
|
364
364
|
const seenModules = new Set();
|
|
365
365
|
|
|
366
366
|
for (const importModule of fileEntry.imports) {
|
|
367
|
+
// Skip null modules (e.g., dynamic include! macros in Rust)
|
|
368
|
+
if (!importModule) continue;
|
|
369
|
+
|
|
367
370
|
// Deduplicate: same module imported multiple times in one file
|
|
368
371
|
// (e.g., lazy imports inside different functions)
|
|
369
372
|
if (seenModules.has(importModule)) continue;
|
|
@@ -1511,6 +1514,7 @@ class ProjectIndex {
|
|
|
1511
1514
|
}
|
|
1512
1515
|
|
|
1513
1516
|
const tree = safeParse(parser, content);
|
|
1517
|
+
if (!tree) return false;
|
|
1514
1518
|
|
|
1515
1519
|
// Find all occurrences of name in the line
|
|
1516
1520
|
const nameRegex = new RegExp('(?<![a-zA-Z0-9_$])' + escapeRegExp(name) + '(?![a-zA-Z0-9_$])', 'g');
|
|
@@ -2117,6 +2121,7 @@ class ProjectIndex {
|
|
|
2117
2121
|
}
|
|
2118
2122
|
|
|
2119
2123
|
const tree = safeParse(parser, content);
|
|
2124
|
+
if (!tree) return false;
|
|
2120
2125
|
const tokenType = getTokenTypeAtPosition(tree.rootNode, lineNum, column);
|
|
2121
2126
|
return tokenType === 'comment' || tokenType === 'string';
|
|
2122
2127
|
} catch (e) {
|
|
@@ -5006,6 +5011,7 @@ class ProjectIndex {
|
|
|
5006
5011
|
const parser = getParser(language);
|
|
5007
5012
|
const content = this._readFile(filePath);
|
|
5008
5013
|
const tree = safeParse(parser, content);
|
|
5014
|
+
if (!tree) return result;
|
|
5009
5015
|
|
|
5010
5016
|
const row = lineNum - 1;
|
|
5011
5017
|
const node = tree.rootNode.descendantForPosition({ row, column: 0 });
|
package/languages/go.js
CHANGED
|
@@ -448,6 +448,7 @@ function findCallsInCode(code, parser) {
|
|
|
448
448
|
if (node.type === 'short_var_declaration' || node.type === 'var_declaration') {
|
|
449
449
|
// Check if RHS contains a func_literal
|
|
450
450
|
const hasFunc = (n) => {
|
|
451
|
+
if (!n) return false;
|
|
451
452
|
if (n.type === 'func_literal') return true;
|
|
452
453
|
for (let i = 0; i < n.childCount; i++) {
|
|
453
454
|
if (hasFunc(n.child(i))) return true;
|
|
@@ -518,7 +519,7 @@ function findCallsInCode(code, parser) {
|
|
|
518
519
|
onLeave: (node) => {
|
|
519
520
|
if (isFunctionNode(node)) {
|
|
520
521
|
const leaving = functionStack.pop();
|
|
521
|
-
closureScopes.delete(leaving.startLine);
|
|
522
|
+
if (leaving) closureScopes.delete(leaving.startLine);
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
});
|
package/languages/javascript.js
CHANGED
|
@@ -1453,7 +1453,9 @@ function findImportsInCode(code, parser) {
|
|
|
1453
1453
|
}
|
|
1454
1454
|
}
|
|
1455
1455
|
|
|
1456
|
-
|
|
1456
|
+
if (modulePath) {
|
|
1457
|
+
imports.push({ module: modulePath, names, type: 'require', line, dynamic });
|
|
1458
|
+
}
|
|
1457
1459
|
}
|
|
1458
1460
|
}
|
|
1459
1461
|
|
|
@@ -1466,8 +1468,8 @@ function findImportsInCode(code, parser) {
|
|
|
1466
1468
|
if (firstArg && firstArg.type === 'string') {
|
|
1467
1469
|
const modulePath = firstArg.text.slice(1, -1);
|
|
1468
1470
|
imports.push({ module: modulePath, names: [], type: 'dynamic', line, dynamic: false });
|
|
1469
|
-
} else {
|
|
1470
|
-
imports.push({ module: firstArg
|
|
1471
|
+
} else if (firstArg) {
|
|
1472
|
+
imports.push({ module: firstArg.text, names: [], type: 'dynamic', line, dynamic: true });
|
|
1471
1473
|
}
|
|
1472
1474
|
}
|
|
1473
1475
|
}
|
package/languages/rust.js
CHANGED
|
@@ -845,16 +845,19 @@ function findImportsInCode(code, parser) {
|
|
|
845
845
|
if (node.type === 'macro_invocation') {
|
|
846
846
|
const nameNode = node.childForFieldName('macro');
|
|
847
847
|
if (nameNode && /^include(_str|_bytes)?$/.test(nameNode.text)) {
|
|
848
|
-
const argsNode = node.
|
|
848
|
+
const argsNode = node.namedChildren.find(c => c.type === 'token_tree');
|
|
849
849
|
const arg = argsNode?.namedChild(0);
|
|
850
850
|
const dynamic = !arg || arg.type !== 'string_literal';
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
851
|
+
const modulePath = arg ? arg.text.replace(/^["']|["']$/g, '') : null;
|
|
852
|
+
if (modulePath) {
|
|
853
|
+
imports.push({
|
|
854
|
+
module: modulePath,
|
|
855
|
+
names: [],
|
|
856
|
+
type: 'include',
|
|
857
|
+
dynamic,
|
|
858
|
+
line: node.startPosition.row + 1
|
|
859
|
+
});
|
|
860
|
+
}
|
|
858
861
|
}
|
|
859
862
|
}
|
|
860
863
|
return true;
|
package/mcp/server.js
CHANGED
|
@@ -108,6 +108,7 @@ const server = new McpServer({
|
|
|
108
108
|
const MAX_OUTPUT_CHARS = 100000; // ~100KB, safe for all MCP clients
|
|
109
109
|
|
|
110
110
|
function toolResult(text) {
|
|
111
|
+
if (!text) return { content: [{ type: 'text', text: '(no output)' }] };
|
|
111
112
|
if (text.length > MAX_OUTPUT_CHARS) {
|
|
112
113
|
const truncated = text.substring(0, MAX_OUTPUT_CHARS);
|
|
113
114
|
// Cut at last newline to avoid breaking mid-line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.21",
|
|
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",
|