ucn 3.7.11 → 3.7.12
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 +3 -0
- package/.mcpregistry_github_token +1 -1
- package/.mcpregistry_registry_token +1 -1
- package/cli/index.js +26 -14
- package/core/output.js +24 -2
- package/core/project.js +29 -4
- package/languages/utils.js +2 -3
- package/mcp/server.js +12 -8
- package/package.json +1 -1
|
@@ -92,6 +92,7 @@ ucn deadcode --exclude=test # Skip test files (most useful)
|
|
|
92
92
|
| Quick project overview | `ucn toc` | Every file with function/class counts and line counts |
|
|
93
93
|
| Find by glob pattern | `ucn find "handle*"` | Locate definitions matching a glob (supports * and ?) |
|
|
94
94
|
| Text search with context | `ucn search term --context=3` | Like grep -C 3, shows surrounding lines |
|
|
95
|
+
| Regex search | `ucn search '\d+' --regex` | Search with regex patterns (alternation, character classes, etc.) |
|
|
95
96
|
| Text search filtered | `ucn search term --exclude=test` | Search only in matching files |
|
|
96
97
|
| Finding all usages (not just calls) | `ucn usages <name>` | Groups into: definitions, calls, imports, type references |
|
|
97
98
|
| Finding sibling/related functions | `ucn related <name>` | Name-based + structural matching (same file, shared deps). Not semantic — best for parse/format pairs |
|
|
@@ -126,6 +127,8 @@ ucn [target] <command> [name] [--flags]
|
|
|
126
127
|
| `--staged` | Analyze staged changes (diff-impact) |
|
|
127
128
|
| `--no-cache` | Force re-index after editing files |
|
|
128
129
|
| `--context=N` | Lines of surrounding context in `usages`/`search` output |
|
|
130
|
+
| `--regex` | Use search term as a regex pattern |
|
|
131
|
+
| `--functions` | Show per-function line counts in `stats` (complexity audit) |
|
|
129
132
|
|
|
130
133
|
## Workflow Integration
|
|
131
134
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ghu_Jo1ldIUK4d6zLMSctdSV2Jx8UkoXa50x6epI
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.
|
|
1
|
+
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzE5NjQ2MjMsIm5iZiI6MTc3MTk2NDMyMywiaWF0IjoxNzcxOTY0MzIzLCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6Im1sZW9jYSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIubWxlb2NhLyoifV19.weq7KLw5cAAB7qZpxtU45meX0nLUU1vTiHlzFB5Ecf9OTFtKM63-JSwYBXrr410pdt-7CgW6pV6aGruoIpiIBw","expires_at":1771964623}
|
package/cli/index.js
CHANGED
|
@@ -82,7 +82,11 @@ const flags = {
|
|
|
82
82
|
followSymlinks: !args.includes('--no-follow-symlinks'),
|
|
83
83
|
// Diff-impact options
|
|
84
84
|
base: args.find(a => a.startsWith('--base='))?.split('=')[1] || null,
|
|
85
|
-
staged: args.includes('--staged')
|
|
85
|
+
staged: args.includes('--staged'),
|
|
86
|
+
// Regex search mode
|
|
87
|
+
regex: args.includes('--regex'),
|
|
88
|
+
// Stats: per-function line counts
|
|
89
|
+
functions: args.includes('--functions')
|
|
86
90
|
};
|
|
87
91
|
|
|
88
92
|
// Handle --file flag with space
|
|
@@ -101,7 +105,8 @@ const knownFlags = new Set([
|
|
|
101
105
|
'--file', '--context', '--exclude', '--not', '--in',
|
|
102
106
|
'--depth', '--direction', '--add-param', '--remove-param', '--rename-to',
|
|
103
107
|
'--default', '--top', '--no-follow-symlinks',
|
|
104
|
-
'--base', '--staged'
|
|
108
|
+
'--base', '--staged',
|
|
109
|
+
'--regex', '--functions'
|
|
105
110
|
]);
|
|
106
111
|
|
|
107
112
|
// Handle help flag
|
|
@@ -905,7 +910,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
905
910
|
if (arg.includes(',')) {
|
|
906
911
|
const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
|
|
907
912
|
for (let i = 0; i < fnNames.length; i++) {
|
|
908
|
-
if (i > 0) console.log('');
|
|
913
|
+
if (i > 0) console.log('\n' + '═'.repeat(60) + '\n');
|
|
909
914
|
extractFunctionFromProject(index, fnNames[i]);
|
|
910
915
|
}
|
|
911
916
|
} else {
|
|
@@ -1016,7 +1021,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
1016
1021
|
case 'search': {
|
|
1017
1022
|
requireArg(arg, 'Usage: ucn . search <term>');
|
|
1018
1023
|
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 });
|
|
1024
|
+
const searchResults = index.search(arg, { codeOnly: flags.codeOnly, context: flags.context, caseSensitive: flags.caseSensitive, exclude: searchExclude, in: flags.in, regex: flags.regex });
|
|
1020
1025
|
printOutput(searchResults,
|
|
1021
1026
|
r => output.formatSearchJson(r, arg),
|
|
1022
1027
|
r => output.formatSearch(r, arg)
|
|
@@ -1040,8 +1045,11 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
1040
1045
|
}
|
|
1041
1046
|
|
|
1042
1047
|
case 'stats': {
|
|
1043
|
-
const stats = index.getStats();
|
|
1044
|
-
printOutput(stats,
|
|
1048
|
+
const stats = index.getStats({ functions: flags.functions });
|
|
1049
|
+
printOutput(stats,
|
|
1050
|
+
output.formatStatsJson,
|
|
1051
|
+
r => output.formatStats(r, { top: flags.top })
|
|
1052
|
+
);
|
|
1045
1053
|
break;
|
|
1046
1054
|
}
|
|
1047
1055
|
|
|
@@ -1506,7 +1514,7 @@ function findInGlobFiles(files, name) {
|
|
|
1506
1514
|
|
|
1507
1515
|
function searchGlobFiles(files, term) {
|
|
1508
1516
|
const results = [];
|
|
1509
|
-
const regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1517
|
+
const regex = flags.regex ? new RegExp(term, flags.caseSensitive ? '' : 'i') : new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1510
1518
|
|
|
1511
1519
|
for (const file of files) {
|
|
1512
1520
|
try {
|
|
@@ -1557,7 +1565,7 @@ function searchGlobFiles(files, term) {
|
|
|
1557
1565
|
// ============================================================================
|
|
1558
1566
|
|
|
1559
1567
|
function searchFile(filePath, lines, term) {
|
|
1560
|
-
const regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1568
|
+
const regex = flags.regex ? new RegExp(term, flags.caseSensitive ? '' : 'i') : new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1561
1569
|
const matches = [];
|
|
1562
1570
|
|
|
1563
1571
|
lines.forEach((line, idx) => {
|
|
@@ -1686,7 +1694,7 @@ FIND CODE
|
|
|
1686
1694
|
find <name> Find symbol definitions (supports glob: find "handle*")
|
|
1687
1695
|
usages <name> All usages grouped: definitions, calls, imports, references
|
|
1688
1696
|
toc Table of contents (compact; --detailed lists all symbols)
|
|
1689
|
-
search <term> Text search (--context=N, --exclude=, --in
|
|
1697
|
+
search <term> Text search (--context=N, --exclude=, --in=, --regex)
|
|
1690
1698
|
tests <name> Find test files for a function
|
|
1691
1699
|
|
|
1692
1700
|
═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1719,7 +1727,7 @@ OTHER
|
|
|
1719
1727
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1720
1728
|
api Show exported/public symbols
|
|
1721
1729
|
typedef <name> Find type definitions
|
|
1722
|
-
stats Project statistics
|
|
1730
|
+
stats Project statistics (--functions for per-function line counts)
|
|
1723
1731
|
stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
|
|
1724
1732
|
example <name> Best usage example with context
|
|
1725
1733
|
|
|
@@ -1739,6 +1747,8 @@ Common Flags:
|
|
|
1739
1747
|
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
1740
1748
|
--include-uncertain Include ambiguous/uncertain matches
|
|
1741
1749
|
--include-exported Include exported symbols in deadcode
|
|
1750
|
+
--regex Use search term as a regex pattern
|
|
1751
|
+
--functions Show per-function line counts (stats command)
|
|
1742
1752
|
--include-decorated Include decorated/annotated symbols in deadcode
|
|
1743
1753
|
--exact Exact name match only (find)
|
|
1744
1754
|
--calls-only Only show call/test-case matches (tests)
|
|
@@ -1898,6 +1908,8 @@ function parseInteractiveFlags(tokens) {
|
|
|
1898
1908
|
base: tokens.find(a => a.startsWith('--base='))?.split('=')[1] || null,
|
|
1899
1909
|
staged: tokens.includes('--staged'),
|
|
1900
1910
|
maxLines: parseInt(tokens.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null,
|
|
1911
|
+
regex: tokens.includes('--regex'),
|
|
1912
|
+
functions: tokens.includes('--functions'),
|
|
1901
1913
|
};
|
|
1902
1914
|
}
|
|
1903
1915
|
|
|
@@ -2011,7 +2023,7 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
2011
2023
|
if (arg.includes(',')) {
|
|
2012
2024
|
const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
|
|
2013
2025
|
for (let i = 0; i < fnNames.length; i++) {
|
|
2014
|
-
if (i > 0) console.log('');
|
|
2026
|
+
if (i > 0) console.log('\n' + '═'.repeat(60) + '\n');
|
|
2015
2027
|
extractFunctionFromProject(index, fnNames[i], iflags);
|
|
2016
2028
|
}
|
|
2017
2029
|
} else {
|
|
@@ -2104,7 +2116,7 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
2104
2116
|
console.log('Usage: search <term>');
|
|
2105
2117
|
return;
|
|
2106
2118
|
}
|
|
2107
|
-
const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context, exclude: iflags.exclude, in: iflags.in });
|
|
2119
|
+
const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context, exclude: iflags.exclude, in: iflags.in, regex: iflags.regex });
|
|
2108
2120
|
console.log(output.formatSearch(results, arg));
|
|
2109
2121
|
break;
|
|
2110
2122
|
}
|
|
@@ -2136,8 +2148,8 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
2136
2148
|
}
|
|
2137
2149
|
|
|
2138
2150
|
case 'stats': {
|
|
2139
|
-
const stats = index.getStats();
|
|
2140
|
-
console.log(output.formatStats(stats));
|
|
2151
|
+
const stats = index.getStats({ functions: iflags.functions });
|
|
2152
|
+
console.log(output.formatStats(stats, { top: iflags.top }));
|
|
2141
2153
|
break;
|
|
2142
2154
|
}
|
|
2143
2155
|
|
package/core/output.js
CHANGED
|
@@ -1925,7 +1925,16 @@ function formatGraph(graph, options = {}) {
|
|
|
1925
1925
|
*/
|
|
1926
1926
|
function formatSearch(results, term) {
|
|
1927
1927
|
const totalMatches = results.reduce((sum, r) => sum + r.matches.length, 0);
|
|
1928
|
-
if (totalMatches === 0)
|
|
1928
|
+
if (totalMatches === 0) {
|
|
1929
|
+
const meta = results.meta;
|
|
1930
|
+
if (meta) {
|
|
1931
|
+
const scope = meta.filesSkipped > 0
|
|
1932
|
+
? `Searched ${meta.filesScanned} of ${meta.totalFiles} files (${meta.filesSkipped} excluded by filters).`
|
|
1933
|
+
: `Searched ${meta.filesScanned} files.`;
|
|
1934
|
+
return `No matches found for "${term}". ${scope}`;
|
|
1935
|
+
}
|
|
1936
|
+
return `No matches found for "${term}"`;
|
|
1937
|
+
}
|
|
1929
1938
|
|
|
1930
1939
|
const lines = [];
|
|
1931
1940
|
lines.push(`Found ${totalMatches} matches for "${term}" in ${results.length} files:`);
|
|
@@ -1969,7 +1978,7 @@ function formatFileExports(exports, filePath) {
|
|
|
1969
1978
|
/**
|
|
1970
1979
|
* Format stats command output
|
|
1971
1980
|
*/
|
|
1972
|
-
function formatStats(stats) {
|
|
1981
|
+
function formatStats(stats, options = {}) {
|
|
1973
1982
|
const lines = [];
|
|
1974
1983
|
lines.push('PROJECT STATISTICS');
|
|
1975
1984
|
lines.push('═'.repeat(60));
|
|
@@ -1988,6 +1997,19 @@ function formatStats(stats) {
|
|
|
1988
1997
|
lines.push(` ${type}: ${count}`);
|
|
1989
1998
|
}
|
|
1990
1999
|
|
|
2000
|
+
if (stats.functions) {
|
|
2001
|
+
const top = options.top || 30;
|
|
2002
|
+
const shown = stats.functions.slice(0, top);
|
|
2003
|
+
lines.push(`\nFunctions by line count (top ${shown.length} of ${stats.functions.length}):`);
|
|
2004
|
+
for (const fn of shown) {
|
|
2005
|
+
const loc = `${fn.file}:${fn.startLine}`;
|
|
2006
|
+
lines.push(` ${String(fn.lines).padStart(5)} lines ${fn.name} (${loc})`);
|
|
2007
|
+
}
|
|
2008
|
+
if (stats.functions.length > top) {
|
|
2009
|
+
lines.push(` ... ${stats.functions.length - top} more (use --top=N to show more)`);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1991
2013
|
return lines.join('\n');
|
|
1992
2014
|
}
|
|
1993
2015
|
|
package/core/project.js
CHANGED
|
@@ -4439,17 +4439,20 @@ class ProjectIndex {
|
|
|
4439
4439
|
this._beginOp();
|
|
4440
4440
|
try {
|
|
4441
4441
|
const results = [];
|
|
4442
|
-
|
|
4442
|
+
let filesScanned = 0;
|
|
4443
|
+
let filesSkipped = 0;
|
|
4443
4444
|
const regexFlags = options.caseSensitive ? 'g' : 'gi';
|
|
4444
|
-
const regex = new RegExp(escapeRegExp(term), regexFlags);
|
|
4445
|
+
const regex = options.regex ? new RegExp(term, regexFlags) : new RegExp(escapeRegExp(term), regexFlags);
|
|
4445
4446
|
|
|
4446
4447
|
for (const [filePath, fileEntry] of this.files) {
|
|
4447
4448
|
// Apply exclude/in filters
|
|
4448
4449
|
if ((options.exclude && options.exclude.length > 0) || options.in) {
|
|
4449
4450
|
if (!this.matchesFilters(fileEntry.relativePath, { exclude: options.exclude, in: options.in })) {
|
|
4451
|
+
filesSkipped++;
|
|
4450
4452
|
continue;
|
|
4451
4453
|
}
|
|
4452
4454
|
}
|
|
4455
|
+
filesScanned++;
|
|
4453
4456
|
try {
|
|
4454
4457
|
const content = this._readFile(filePath);
|
|
4455
4458
|
const lines = content.split('\n');
|
|
@@ -4462,7 +4465,7 @@ class ProjectIndex {
|
|
|
4462
4465
|
try {
|
|
4463
4466
|
const parser = getParser(language);
|
|
4464
4467
|
const { findMatchesWithASTFilter } = require('../languages/utils');
|
|
4465
|
-
const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true });
|
|
4468
|
+
const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true, regex: options.regex });
|
|
4466
4469
|
|
|
4467
4470
|
for (const m of astMatches) {
|
|
4468
4471
|
const match = {
|
|
@@ -4542,6 +4545,7 @@ class ProjectIndex {
|
|
|
4542
4545
|
}
|
|
4543
4546
|
}
|
|
4544
4547
|
|
|
4548
|
+
results.meta = { filesScanned, filesSkipped, totalFiles: this.files.size };
|
|
4545
4549
|
return results;
|
|
4546
4550
|
} finally { this._endOp(); }
|
|
4547
4551
|
}
|
|
@@ -4553,7 +4557,7 @@ class ProjectIndex {
|
|
|
4553
4557
|
/**
|
|
4554
4558
|
* Get project statistics
|
|
4555
4559
|
*/
|
|
4556
|
-
getStats() {
|
|
4560
|
+
getStats(options = {}) {
|
|
4557
4561
|
// Count total symbols (not just unique names)
|
|
4558
4562
|
let totalSymbols = 0;
|
|
4559
4563
|
for (const [name, symbols] of this.symbols) {
|
|
@@ -4588,6 +4592,27 @@ class ProjectIndex {
|
|
|
4588
4592
|
}
|
|
4589
4593
|
}
|
|
4590
4594
|
|
|
4595
|
+
// Per-function line counts for complexity audits
|
|
4596
|
+
if (options.functions) {
|
|
4597
|
+
const functions = [];
|
|
4598
|
+
for (const [name, symbols] of this.symbols) {
|
|
4599
|
+
for (const sym of symbols) {
|
|
4600
|
+
if (sym.type === 'function' || sym.params !== undefined) {
|
|
4601
|
+
const lineCount = sym.endLine - sym.startLine + 1;
|
|
4602
|
+
const relativePath = sym.relativePath || (sym.file ? path.relative(this.root, sym.file) : '');
|
|
4603
|
+
functions.push({
|
|
4604
|
+
name: sym.className ? `${sym.className}.${sym.name}` : sym.name,
|
|
4605
|
+
file: relativePath,
|
|
4606
|
+
startLine: sym.startLine,
|
|
4607
|
+
lines: lineCount
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
functions.sort((a, b) => b.lines - a.lines);
|
|
4613
|
+
stats.functions = functions;
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4591
4616
|
return stats;
|
|
4592
4617
|
}
|
|
4593
4618
|
|
package/languages/utils.js
CHANGED
|
@@ -417,9 +417,8 @@ function findMatchesWithASTFilter(content, term, parser, options = {}) {
|
|
|
417
417
|
const lines = content.split('\n');
|
|
418
418
|
const matches = [];
|
|
419
419
|
|
|
420
|
-
//
|
|
421
|
-
const
|
|
422
|
-
const regex = new RegExp(escapedTerm, 'gi');
|
|
420
|
+
// Create search pattern — use raw regex when regex mode is enabled
|
|
421
|
+
const regex = options.regex ? new RegExp(term, 'gi') : new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
423
422
|
|
|
424
423
|
lines.forEach((line, idx) => {
|
|
425
424
|
const lineNum = idx + 1;
|
package/mcp/server.js
CHANGED
|
@@ -196,7 +196,7 @@ FINDING CODE:
|
|
|
196
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>:
|
|
199
|
+
- search <term>: 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. Set regex=true to use the term as a regex pattern (e.g. "\\d+" or "foo|bar").
|
|
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
|
|
|
@@ -221,7 +221,7 @@ OTHER:
|
|
|
221
221
|
- typedef <name>: Find type definitions matching a name: interfaces, enums, structs, traits, type aliases. See field shapes, required methods, or enum values.
|
|
222
222
|
- stacktrace: Parse a stack trace, show source context per frame. Requires stack param. Handles JS, Python, Go, Rust, Java formats.
|
|
223
223
|
- api: Public API surface of project or file: all exported/public symbols with signatures. Use to understand what a library exposes. Pass file to scope to one file. Python needs __all__; use toc instead.
|
|
224
|
-
- stats: Quick project stats: file counts, symbol counts, lines of code by language and symbol type.`;
|
|
224
|
+
- stats: Quick project stats: file counts, symbol counts, lines of code by language and symbol type. Use functions=true for per-function line counts sorted by size (complexity audit).`;
|
|
225
225
|
|
|
226
226
|
server.registerTool(
|
|
227
227
|
'ucn',
|
|
@@ -256,7 +256,9 @@ server.registerTool(
|
|
|
256
256
|
calls_only: z.boolean().optional().describe('Only direct calls and test-case matches (tests command)'),
|
|
257
257
|
max_lines: z.number().optional().describe('Max source lines for class (large classes show summary by default)'),
|
|
258
258
|
direction: z.enum(['imports', 'importers', 'both']).optional().describe('Graph direction: imports (what this file uses), importers (who uses this file), both (default: both)'),
|
|
259
|
-
term: z.string().optional().describe('Search term (plain text
|
|
259
|
+
term: z.string().optional().describe('Search term (plain text by default; set regex=true to use as regex pattern)'),
|
|
260
|
+
regex: z.boolean().optional().describe('Treat search term as a regex pattern (default: false, plain text)'),
|
|
261
|
+
functions: z.boolean().optional().describe('Include per-function line counts in stats output, sorted by size (complexity audit)'),
|
|
260
262
|
add_param: z.string().optional().describe('Parameter name to add (plan command)'),
|
|
261
263
|
remove_param: z.string().optional().describe('Parameter name to remove (plan command)'),
|
|
262
264
|
rename_to: z.string().optional().describe('New function name (plan command)'),
|
|
@@ -276,7 +278,7 @@ server.registerTool(
|
|
|
276
278
|
include_exported, include_decorated, calls_only, max_lines,
|
|
277
279
|
direction, term, add_param, remove_param, rename_to,
|
|
278
280
|
default_value, stack, item, range, base, staged,
|
|
279
|
-
case_sensitive } = args;
|
|
281
|
+
case_sensitive, regex, functions } = args;
|
|
280
282
|
|
|
281
283
|
try {
|
|
282
284
|
switch (command) {
|
|
@@ -431,7 +433,8 @@ server.registerTool(
|
|
|
431
433
|
context: ctxLines || 0,
|
|
432
434
|
caseSensitive: case_sensitive || false,
|
|
433
435
|
exclude: searchExclude,
|
|
434
|
-
in: inPath || undefined
|
|
436
|
+
in: inPath || undefined,
|
|
437
|
+
regex: regex || false
|
|
435
438
|
});
|
|
436
439
|
return toolResult(output.formatSearch(result, term));
|
|
437
440
|
}
|
|
@@ -495,7 +498,8 @@ server.registerTool(
|
|
|
495
498
|
parts.push(note + output.formatFn(match, fnCode));
|
|
496
499
|
}
|
|
497
500
|
|
|
498
|
-
|
|
501
|
+
const separator = fnNames.length > 1 ? '\n\n' + '═'.repeat(60) + '\n\n' : '\n\n';
|
|
502
|
+
return toolResult(parts.join(separator));
|
|
499
503
|
}
|
|
500
504
|
|
|
501
505
|
case 'class': {
|
|
@@ -805,8 +809,8 @@ server.registerTool(
|
|
|
805
809
|
|
|
806
810
|
case 'stats': {
|
|
807
811
|
const index = getIndex(project_dir);
|
|
808
|
-
const stats = index.getStats();
|
|
809
|
-
return toolResult(output.formatStats(stats));
|
|
812
|
+
const stats = index.getStats({ functions: functions || false });
|
|
813
|
+
return toolResult(output.formatStats(stats, { top: top || 30 }));
|
|
810
814
|
}
|
|
811
815
|
|
|
812
816
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.12",
|
|
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",
|