ucn 3.7.9 → 3.7.11
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/.claude/skills/ucn/SKILL.md +5 -1
- package/.mcpregistry_github_token +1 -0
- package/.mcpregistry_registry_token +1 -0
- package/cli/index.js +30 -11
- package/core/project.js +30 -2
- package/mcp/server.js +31 -20
- package/package.json +2 -1
- package/test/accuracy.test.js +0 -1863
- package/test/fixtures/go/go.mod +0 -3
- package/test/fixtures/go/main.go +0 -257
- package/test/fixtures/go/service.go +0 -187
- package/test/fixtures/java/DataService.java +0 -279
- package/test/fixtures/java/Main.java +0 -287
- package/test/fixtures/java/Utils.java +0 -199
- package/test/fixtures/java/pom.xml +0 -6
- package/test/fixtures/javascript/main.js +0 -109
- package/test/fixtures/javascript/package.json +0 -1
- package/test/fixtures/javascript/service.js +0 -88
- package/test/fixtures/javascript/utils.js +0 -67
- package/test/fixtures/python/main.py +0 -198
- package/test/fixtures/python/pyproject.toml +0 -3
- package/test/fixtures/python/service.py +0 -166
- package/test/fixtures/python/utils.py +0 -118
- package/test/fixtures/rust/Cargo.toml +0 -3
- package/test/fixtures/rust/main.rs +0 -253
- package/test/fixtures/rust/service.rs +0 -210
- package/test/fixtures/rust/utils.rs +0 -154
- package/test/fixtures/typescript/main.ts +0 -154
- package/test/fixtures/typescript/package.json +0 -1
- package/test/fixtures/typescript/repository.ts +0 -149
- package/test/fixtures/typescript/types.ts +0 -114
- package/test/mcp-edge-cases.js +0 -634
- package/test/parser.test.js +0 -13634
- package/test/public-repos-bugs.json +0 -32
- package/test/public-repos-test.js +0 -477
- package/test/systematic-test.js +0 -619
|
@@ -63,10 +63,11 @@ Shows the entire pipeline — what `generate_report` calls, what those functions
|
|
|
63
63
|
|
|
64
64
|
### 4. `fn` / `class` — Extract without reading the whole file
|
|
65
65
|
|
|
66
|
-
Pull one
|
|
66
|
+
Pull one or more functions out of a large file. Supports comma-separated names for bulk extraction.
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
ucn fn handle_request --file=api # --file disambiguates when name exists in multiple files
|
|
70
|
+
ucn fn parse,format,validate # Extract multiple functions in one call
|
|
70
71
|
ucn class MarketDataFetcher
|
|
71
72
|
```
|
|
72
73
|
|
|
@@ -89,6 +90,9 @@ ucn deadcode --exclude=test # Skip test files (most useful)
|
|
|
89
90
|
| Understanding a file's role in the project | `ucn imports <file>` | What it depends on |
|
|
90
91
|
| Understanding who depends on a file | `ucn exporters <file>` | Which files import it |
|
|
91
92
|
| Quick project overview | `ucn toc` | Every file with function/class counts and line counts |
|
|
93
|
+
| Find by glob pattern | `ucn find "handle*"` | Locate definitions matching a glob (supports * and ?) |
|
|
94
|
+
| Text search with context | `ucn search term --context=3` | Like grep -C 3, shows surrounding lines |
|
|
95
|
+
| Text search filtered | `ucn search term --exclude=test` | Search only in matching files |
|
|
92
96
|
| Finding all usages (not just calls) | `ucn usages <name>` | Groups into: definitions, calls, imports, type references |
|
|
93
97
|
| Finding sibling/related functions | `ucn related <name>` | Name-based + structural matching (same file, shared deps). Not semantic — best for parse/format pairs |
|
|
94
98
|
| Preview a rename or param change | `ucn plan <name> --rename-to=new_name` | Shows what would change without doing it |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ghu_gwrBjpCa3Gl8WuJxABQ8XOgRdbPfFf36TmIA
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzE3ODU4MTAsIm5iZiI6MTc3MTc4NTUxMCwiaWF0IjoxNzcxNzg1NTEwLCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6Im1sZW9jYSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIubWxlb2NhLyoifV19.DW2gbu6Lnoc43CqRsxlhrIy3CQaMxAlLykOfYP3lY08d0FHVmXhg9gUt6qt2G6ihn-G99tA6oOf8tGvxUteeBQ","expires_at":1771785810}
|
package/cli/index.js
CHANGED
|
@@ -901,7 +901,16 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
901
901
|
|
|
902
902
|
case 'fn': {
|
|
903
903
|
requireArg(arg, 'Usage: ucn . fn <name>');
|
|
904
|
-
|
|
904
|
+
// Support comma-separated names for bulk extraction
|
|
905
|
+
if (arg.includes(',')) {
|
|
906
|
+
const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
|
|
907
|
+
for (let i = 0; i < fnNames.length; i++) {
|
|
908
|
+
if (i > 0) console.log('');
|
|
909
|
+
extractFunctionFromProject(index, fnNames[i]);
|
|
910
|
+
}
|
|
911
|
+
} else {
|
|
912
|
+
extractFunctionFromProject(index, arg);
|
|
913
|
+
}
|
|
905
914
|
break;
|
|
906
915
|
}
|
|
907
916
|
|
|
@@ -1006,7 +1015,8 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
1006
1015
|
|
|
1007
1016
|
case 'search': {
|
|
1008
1017
|
requireArg(arg, 'Usage: ucn . search <term>');
|
|
1009
|
-
const
|
|
1018
|
+
const searchExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
|
|
1019
|
+
const searchResults = index.search(arg, { codeOnly: flags.codeOnly, context: flags.context, caseSensitive: flags.caseSensitive, exclude: searchExclude, in: flags.in });
|
|
1010
1020
|
printOutput(searchResults,
|
|
1011
1021
|
r => output.formatSearchJson(r, arg),
|
|
1012
1022
|
r => output.formatSearch(r, arg)
|
|
@@ -1673,16 +1683,16 @@ UNDERSTAND CODE (UCN's strength - semantic analysis)
|
|
|
1673
1683
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1674
1684
|
FIND CODE
|
|
1675
1685
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1676
|
-
find <name> Find symbol definitions (
|
|
1686
|
+
find <name> Find symbol definitions (supports glob: find "handle*")
|
|
1677
1687
|
usages <name> All usages grouped: definitions, calls, imports, references
|
|
1678
1688
|
toc Table of contents (compact; --detailed lists all symbols)
|
|
1679
|
-
search <term> Text search (
|
|
1689
|
+
search <term> Text search (--context=N, --exclude=, --in=)
|
|
1680
1690
|
tests <name> Find test files for a function
|
|
1681
1691
|
|
|
1682
1692
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1683
1693
|
EXTRACT CODE
|
|
1684
1694
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1685
|
-
fn <name>
|
|
1695
|
+
fn <name>[,n2,...] Extract function(s) (comma-separated for bulk, --file)
|
|
1686
1696
|
class <name> Extract class
|
|
1687
1697
|
lines <range> Extract line range (e.g., lines 50-100)
|
|
1688
1698
|
expand <N> Show code for item N from context output
|
|
@@ -1789,7 +1799,7 @@ function runInteractive(rootDir) {
|
|
|
1789
1799
|
console.log(`
|
|
1790
1800
|
Commands:
|
|
1791
1801
|
toc Project overview (--detailed)
|
|
1792
|
-
find <name> Find symbol (--exact,
|
|
1802
|
+
find <name> Find symbol (--exact, glob: "handle*")
|
|
1793
1803
|
about <name> Everything about a symbol
|
|
1794
1804
|
usages <name> All usages grouped by type
|
|
1795
1805
|
context <name> Callers + callees
|
|
@@ -1799,7 +1809,7 @@ Commands:
|
|
|
1799
1809
|
trace <name> Call tree (--depth=N)
|
|
1800
1810
|
example <name> Best usage example
|
|
1801
1811
|
related <name> Sibling functions
|
|
1802
|
-
fn <name>
|
|
1812
|
+
fn <name>[,n2,...] Extract function(s) (--file=)
|
|
1803
1813
|
class <name> Extract class code (--file=)
|
|
1804
1814
|
lines <range> Extract lines (--file= required)
|
|
1805
1815
|
graph <file> File dependency tree (--direction=, --depth=)
|
|
@@ -1807,7 +1817,7 @@ Commands:
|
|
|
1807
1817
|
imports <file> What file imports
|
|
1808
1818
|
exporters <file> Who imports file
|
|
1809
1819
|
tests <name> Find tests (--calls-only)
|
|
1810
|
-
search <term> Text search (--
|
|
1820
|
+
search <term> Text search (--context=N, --exclude=, --in=)
|
|
1811
1821
|
typedef <name> Find type definitions
|
|
1812
1822
|
deadcode Find unused functions/classes
|
|
1813
1823
|
verify <name> Check call sites match signature
|
|
@@ -1994,10 +2004,19 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
1994
2004
|
|
|
1995
2005
|
case 'fn': {
|
|
1996
2006
|
if (!arg) {
|
|
1997
|
-
console.log('Usage: fn <name> [--file=<pattern>]');
|
|
2007
|
+
console.log('Usage: fn <name>[,name2,...] [--file=<pattern>]');
|
|
1998
2008
|
return;
|
|
1999
2009
|
}
|
|
2000
|
-
|
|
2010
|
+
// Support comma-separated names for bulk extraction
|
|
2011
|
+
if (arg.includes(',')) {
|
|
2012
|
+
const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
|
|
2013
|
+
for (let i = 0; i < fnNames.length; i++) {
|
|
2014
|
+
if (i > 0) console.log('');
|
|
2015
|
+
extractFunctionFromProject(index, fnNames[i], iflags);
|
|
2016
|
+
}
|
|
2017
|
+
} else {
|
|
2018
|
+
extractFunctionFromProject(index, arg, iflags);
|
|
2019
|
+
}
|
|
2001
2020
|
break;
|
|
2002
2021
|
}
|
|
2003
2022
|
|
|
@@ -2085,7 +2104,7 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
2085
2104
|
console.log('Usage: search <term>');
|
|
2086
2105
|
return;
|
|
2087
2106
|
}
|
|
2088
|
-
const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context });
|
|
2107
|
+
const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context, exclude: iflags.exclude, in: iflags.in });
|
|
2089
2108
|
console.log(output.formatSearch(results, arg));
|
|
2090
2109
|
break;
|
|
2091
2110
|
}
|
package/core/project.js
CHANGED
|
@@ -687,6 +687,22 @@ class ProjectIndex {
|
|
|
687
687
|
}
|
|
688
688
|
|
|
689
689
|
find(name, options = {}) {
|
|
690
|
+
// Glob pattern matching (e.g., _update*, handle*Request, get?ata)
|
|
691
|
+
const isGlob = name.includes('*') || name.includes('?');
|
|
692
|
+
if (isGlob && !options.exact) {
|
|
693
|
+
const globRegex = new RegExp('^' + name.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i');
|
|
694
|
+
const matches = [];
|
|
695
|
+
for (const [symName, symbols] of this.symbols) {
|
|
696
|
+
if (globRegex.test(symName)) {
|
|
697
|
+
for (const sym of symbols) {
|
|
698
|
+
matches.push({ ...sym, _fuzzyScore: 800 });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
matches.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
703
|
+
return this._applyFindFilters(matches, options);
|
|
704
|
+
}
|
|
705
|
+
|
|
690
706
|
const matches = this.symbols.get(name) || [];
|
|
691
707
|
|
|
692
708
|
if (matches.length === 0 && !options.exact) {
|
|
@@ -705,7 +721,13 @@ class ProjectIndex {
|
|
|
705
721
|
matches.push(...candidates);
|
|
706
722
|
}
|
|
707
723
|
|
|
708
|
-
|
|
724
|
+
return this._applyFindFilters(matches, options);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Apply file/exclude/in filters and usage counts to find results
|
|
729
|
+
*/
|
|
730
|
+
_applyFindFilters(matches, options) {
|
|
709
731
|
let filtered = matches;
|
|
710
732
|
|
|
711
733
|
// Filter by file pattern
|
|
@@ -4411,7 +4433,7 @@ class ProjectIndex {
|
|
|
4411
4433
|
/**
|
|
4412
4434
|
* Search for text across the project
|
|
4413
4435
|
* @param {string} term - Search term
|
|
4414
|
-
* @param {object} options - { codeOnly, context }
|
|
4436
|
+
* @param {object} options - { codeOnly, context, caseSensitive, exclude, in }
|
|
4415
4437
|
*/
|
|
4416
4438
|
search(term, options = {}) {
|
|
4417
4439
|
this._beginOp();
|
|
@@ -4422,6 +4444,12 @@ class ProjectIndex {
|
|
|
4422
4444
|
const regex = new RegExp(escapeRegExp(term), regexFlags);
|
|
4423
4445
|
|
|
4424
4446
|
for (const [filePath, fileEntry] of this.files) {
|
|
4447
|
+
// Apply exclude/in filters
|
|
4448
|
+
if ((options.exclude && options.exclude.length > 0) || options.in) {
|
|
4449
|
+
if (!this.matchesFilters(fileEntry.relativePath, { exclude: options.exclude, in: options.in })) {
|
|
4450
|
+
continue;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4425
4453
|
try {
|
|
4426
4454
|
const content = this._readFile(filePath);
|
|
4427
4455
|
const lines = content.split('\n');
|
package/mcp/server.js
CHANGED
|
@@ -193,15 +193,15 @@ UNDERSTANDING CODE:
|
|
|
193
193
|
- related <name>: Sibling functions: same file, similar names, or shared callers/callees. Find companions to update together (e.g., serialize when you're changing deserialize). Name-based, not semantic.
|
|
194
194
|
|
|
195
195
|
FINDING CODE:
|
|
196
|
-
- find <name>: Locate definitions ranked by usage count. Use when you know the name but not the file.
|
|
196
|
+
- find <name>: Locate definitions ranked by usage count. Supports glob patterns (e.g. find "handle*" or "_update*"). Use when you know the name but not the file.
|
|
197
197
|
- usages <name>: See every usage organized by type: definitions, calls, imports, references. Complete picture of how something is used. Use code_only=true to skip comments/strings.
|
|
198
198
|
- toc: Get a quick overview of a project you haven't seen before — file counts, line counts, function/class counts, entry points. Use detailed=true for full symbol listing.
|
|
199
|
-
- search <term>: Plain text search (like grep, respects .gitignore).
|
|
199
|
+
- search <term>: Plain text search (like grep, respects .gitignore). Supports context=N for surrounding lines, exclude/in for file filtering. Case-insensitive by default; set case_sensitive=true for exact case.
|
|
200
200
|
- tests <name>: Find test files covering a function, test case names, and how it's called in tests. Use before modifying or to find test patterns to follow.
|
|
201
201
|
- deadcode: Find dead code: functions/classes with zero callers. Use during cleanup to identify safely deletable code. Excludes exported, decorated, and test symbols by default — use include_exported/include_decorated/include_tests to expand.
|
|
202
202
|
|
|
203
203
|
EXTRACTING CODE (use instead of reading entire files):
|
|
204
|
-
- fn <name>: Extract one
|
|
204
|
+
- fn <name>: Extract one or more functions. Comma-separated for bulk extraction (e.g. "parse,format,validate"). Use file to disambiguate.
|
|
205
205
|
- class <name>: Extract a class/struct/interface with all its methods. Handles all supported types: JS/TS, Python, Go, Rust, Java. Large classes (>200 lines) show summary; use max_lines for truncated source.
|
|
206
206
|
- lines: Extract specific lines (e.g. range="10-20" or just "15"). Requires file and range. Use when you know the exact line range you need.
|
|
207
207
|
- expand <item>: Drill into a numbered item from the last context result (requires running context first in the same session). Context returns numbered callers/callees — use this to see their full source code.
|
|
@@ -237,7 +237,7 @@ server.registerTool(
|
|
|
237
237
|
'api', 'stats', 'diff_impact', 'stacktrace'
|
|
238
238
|
]),
|
|
239
239
|
project_dir: z.string().describe('Absolute or relative path to the project root directory'),
|
|
240
|
-
name: z.string().optional().describe('Symbol name to analyze (
|
|
240
|
+
name: z.string().optional().describe('Symbol name to analyze. For fn: comma-separated for bulk (e.g. "parse,format"). For find: supports glob patterns (e.g. "handle*").'),
|
|
241
241
|
file: z.string().optional().describe('File path (imports/exporters/graph/file_exports/lines/api/diff_impact) or filter pattern for disambiguation (e.g. "parser", "src/core")'),
|
|
242
242
|
exclude: z.string().optional().describe('Comma-separated patterns to exclude (e.g. "test,mock,vendor")'),
|
|
243
243
|
include_tests: z.boolean().optional().describe('Include test files in results (excluded by default)'),
|
|
@@ -425,10 +425,13 @@ server.registerTool(
|
|
|
425
425
|
return toolError('Search term is required.');
|
|
426
426
|
}
|
|
427
427
|
const index = getIndex(project_dir);
|
|
428
|
+
const searchExclude = include_tests ? parseExclude(exclude) : addTestExclusions(parseExclude(exclude));
|
|
428
429
|
const result = index.search(term, {
|
|
429
430
|
codeOnly: code_only || false,
|
|
430
431
|
context: ctxLines || 0,
|
|
431
|
-
caseSensitive: case_sensitive || false
|
|
432
|
+
caseSensitive: case_sensitive || false,
|
|
433
|
+
exclude: searchExclude,
|
|
434
|
+
in: inPath || undefined
|
|
432
435
|
});
|
|
433
436
|
return toolResult(output.formatSearch(result, term));
|
|
434
437
|
}
|
|
@@ -465,26 +468,34 @@ server.registerTool(
|
|
|
465
468
|
const err = requireName(name);
|
|
466
469
|
if (err) return err;
|
|
467
470
|
const index = getIndex(project_dir);
|
|
468
|
-
const matches = index.find(name, { file }).filter(m => m.type === 'function' || m.params !== undefined);
|
|
469
471
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
472
|
+
// Support comma-separated names for bulk extraction
|
|
473
|
+
const fnNames = name.includes(',') ? name.split(',').map(n => n.trim()).filter(Boolean) : [name];
|
|
474
|
+
const parts = [];
|
|
473
475
|
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
const fnPathCheck = resolveAndValidatePath(index, match.relativePath || path.relative(index.root, match.file));
|
|
477
|
-
if (typeof fnPathCheck !== 'string') return fnPathCheck;
|
|
478
|
-
const code = fs.readFileSync(match.file, 'utf-8');
|
|
479
|
-
const codeLines = code.split('\n');
|
|
480
|
-
const fnCode = codeLines.slice(match.startLine - 1, match.endLine).join('\n');
|
|
476
|
+
for (const fnName of fnNames) {
|
|
477
|
+
const matches = index.find(fnName, { file }).filter(m => m.type === 'function' || m.params !== undefined);
|
|
481
478
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
479
|
+
if (matches.length === 0) {
|
|
480
|
+
parts.push(`Function "${fnName}" not found.`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const match = matches.length > 1 ? pickBestDefinition(matches) : matches[0];
|
|
485
|
+
const fnPathCheck = resolveAndValidatePath(index, match.relativePath || path.relative(index.root, match.file));
|
|
486
|
+
if (typeof fnPathCheck !== 'string') return fnPathCheck;
|
|
487
|
+
const code = fs.readFileSync(match.file, 'utf-8');
|
|
488
|
+
const codeLines = code.split('\n');
|
|
489
|
+
const fnCode = codeLines.slice(match.startLine - 1, match.endLine).join('\n');
|
|
490
|
+
|
|
491
|
+
let note = '';
|
|
492
|
+
if (matches.length > 1 && !file) {
|
|
493
|
+
note = `Note: Found ${matches.length} definitions for "${fnName}". Showing ${match.relativePath}:${match.startLine}. Use file parameter to disambiguate.\n`;
|
|
494
|
+
}
|
|
495
|
+
parts.push(note + output.formatFn(match, fnCode));
|
|
485
496
|
}
|
|
486
497
|
|
|
487
|
-
return toolResult(
|
|
498
|
+
return toolResult(parts.join('\n\n'));
|
|
488
499
|
}
|
|
489
500
|
|
|
490
501
|
case 'class': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.11",
|
|
4
|
+
"mcpName": "io.github.mleoca/ucn",
|
|
4
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.",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"bin": {
|