ucn 3.7.12 → 3.7.14
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 +2 -2
- package/.mcpregistry_github_token +1 -1
- package/.mcpregistry_registry_token +1 -1
- package/cli/index.js +20 -8
- package/core/output.js +8 -3
- package/core/project.js +16 -3
- package/languages/utils.js +7 -2
- package/mcp/server.js +4 -4
- package/package.json +1 -1
|
@@ -92,7 +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+'
|
|
95
|
+
| Regex search (default) | `ucn search '\d+'` | Search supports regex by default (alternation, character classes, etc.) |
|
|
96
96
|
| Text search filtered | `ucn search term --exclude=test` | Search only in matching files |
|
|
97
97
|
| Finding all usages (not just calls) | `ucn usages <name>` | Groups into: definitions, calls, imports, type references |
|
|
98
98
|
| Finding sibling/related functions | `ucn related <name>` | Name-based + structural matching (same file, shared deps). Not semantic — best for parse/format pairs |
|
|
@@ -127,7 +127,7 @@ ucn [target] <command> [name] [--flags]
|
|
|
127
127
|
| `--staged` | Analyze staged changes (diff-impact) |
|
|
128
128
|
| `--no-cache` | Force re-index after editing files |
|
|
129
129
|
| `--context=N` | Lines of surrounding context in `usages`/`search` output |
|
|
130
|
-
| `--regex` |
|
|
130
|
+
| `--no-regex` | Force plain text search (regex is default) |
|
|
131
131
|
| `--functions` | Show per-function line counts in `stats` (complexity audit) |
|
|
132
132
|
|
|
133
133
|
## Workflow Integration
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ghu_k0bpQ4IVOHqywvBi3Q437S5YCpLbSA0Laxm6
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.
|
|
1
|
+
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzIwMTAwNjAsIm5iZiI6MTc3MjAwOTc2MCwiaWF0IjoxNzcyMDA5NzYwLCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6Im1sZW9jYSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIubWxlb2NhLyoifV19.hVf316b7GiborTKy9ZVvcnNEJyNHjIEE7zAC876XjNuyLXCUeV7OGHI8i9ZTPP9U2v8yEk9_awczj0Ziy6xrBQ","expires_at":1772010060}
|
package/cli/index.js
CHANGED
|
@@ -83,8 +83,8 @@ const flags = {
|
|
|
83
83
|
// Diff-impact options
|
|
84
84
|
base: args.find(a => a.startsWith('--base='))?.split('=')[1] || null,
|
|
85
85
|
staged: args.includes('--staged'),
|
|
86
|
-
// Regex search mode
|
|
87
|
-
regex: args.includes('--regex'),
|
|
86
|
+
// Regex search mode (default: ON; --no-regex to force plain text)
|
|
87
|
+
regex: args.includes('--no-regex') ? false : undefined,
|
|
88
88
|
// Stats: per-function line counts
|
|
89
89
|
functions: args.includes('--functions')
|
|
90
90
|
};
|
|
@@ -106,7 +106,7 @@ const knownFlags = new Set([
|
|
|
106
106
|
'--depth', '--direction', '--add-param', '--remove-param', '--rename-to',
|
|
107
107
|
'--default', '--top', '--no-follow-symlinks',
|
|
108
108
|
'--base', '--staged',
|
|
109
|
-
'--regex', '--functions'
|
|
109
|
+
'--regex', '--no-regex', '--functions'
|
|
110
110
|
]);
|
|
111
111
|
|
|
112
112
|
// Handle help flag
|
|
@@ -1514,7 +1514,13 @@ function findInGlobFiles(files, name) {
|
|
|
1514
1514
|
|
|
1515
1515
|
function searchGlobFiles(files, term) {
|
|
1516
1516
|
const results = [];
|
|
1517
|
-
const
|
|
1517
|
+
const useRegex = flags.regex !== false; // Default: regex ON
|
|
1518
|
+
let regex;
|
|
1519
|
+
if (useRegex) {
|
|
1520
|
+
try { regex = new RegExp(term, flags.caseSensitive ? '' : 'i'); } catch (e) { regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i'); }
|
|
1521
|
+
} else {
|
|
1522
|
+
regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1523
|
+
}
|
|
1518
1524
|
|
|
1519
1525
|
for (const file of files) {
|
|
1520
1526
|
try {
|
|
@@ -1565,7 +1571,13 @@ function searchGlobFiles(files, term) {
|
|
|
1565
1571
|
// ============================================================================
|
|
1566
1572
|
|
|
1567
1573
|
function searchFile(filePath, lines, term) {
|
|
1568
|
-
const
|
|
1574
|
+
const useRegex = flags.regex !== false;
|
|
1575
|
+
let regex;
|
|
1576
|
+
if (useRegex) {
|
|
1577
|
+
try { regex = new RegExp(term, flags.caseSensitive ? '' : 'i'); } catch (e) { regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i'); }
|
|
1578
|
+
} else {
|
|
1579
|
+
regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
|
|
1580
|
+
}
|
|
1569
1581
|
const matches = [];
|
|
1570
1582
|
|
|
1571
1583
|
lines.forEach((line, idx) => {
|
|
@@ -1694,7 +1706,7 @@ FIND CODE
|
|
|
1694
1706
|
find <name> Find symbol definitions (supports glob: find "handle*")
|
|
1695
1707
|
usages <name> All usages grouped: definitions, calls, imports, references
|
|
1696
1708
|
toc Table of contents (compact; --detailed lists all symbols)
|
|
1697
|
-
search <term> Text search (--context=N, --exclude=, --in
|
|
1709
|
+
search <term> Text search (regex default, --context=N, --exclude=, --in=)
|
|
1698
1710
|
tests <name> Find test files for a function
|
|
1699
1711
|
|
|
1700
1712
|
═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1747,7 +1759,7 @@ Common Flags:
|
|
|
1747
1759
|
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
1748
1760
|
--include-uncertain Include ambiguous/uncertain matches
|
|
1749
1761
|
--include-exported Include exported symbols in deadcode
|
|
1750
|
-
--regex
|
|
1762
|
+
--no-regex Force plain text search (regex is default)
|
|
1751
1763
|
--functions Show per-function line counts (stats command)
|
|
1752
1764
|
--include-decorated Include decorated/annotated symbols in deadcode
|
|
1753
1765
|
--exact Exact name match only (find)
|
|
@@ -1908,7 +1920,7 @@ function parseInteractiveFlags(tokens) {
|
|
|
1908
1920
|
base: tokens.find(a => a.startsWith('--base='))?.split('=')[1] || null,
|
|
1909
1921
|
staged: tokens.includes('--staged'),
|
|
1910
1922
|
maxLines: parseInt(tokens.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null,
|
|
1911
|
-
regex: tokens.includes('--regex'),
|
|
1923
|
+
regex: tokens.includes('--no-regex') ? false : undefined,
|
|
1912
1924
|
functions: tokens.includes('--functions'),
|
|
1913
1925
|
};
|
|
1914
1926
|
}
|
package/core/output.js
CHANGED
|
@@ -1924,20 +1924,25 @@ function formatGraph(graph, options = {}) {
|
|
|
1924
1924
|
* Format search command output
|
|
1925
1925
|
*/
|
|
1926
1926
|
function formatSearch(results, term) {
|
|
1927
|
+
const meta = results.meta;
|
|
1928
|
+
const fallbackNote = meta && meta.regexFallback
|
|
1929
|
+
? `\nNote: Invalid regex (${meta.regexFallback}). Fell back to plain text search.`
|
|
1930
|
+
: '';
|
|
1931
|
+
|
|
1927
1932
|
const totalMatches = results.reduce((sum, r) => sum + r.matches.length, 0);
|
|
1928
1933
|
if (totalMatches === 0) {
|
|
1929
|
-
const meta = results.meta;
|
|
1930
1934
|
if (meta) {
|
|
1931
1935
|
const scope = meta.filesSkipped > 0
|
|
1932
1936
|
? `Searched ${meta.filesScanned} of ${meta.totalFiles} files (${meta.filesSkipped} excluded by filters).`
|
|
1933
1937
|
: `Searched ${meta.filesScanned} files.`;
|
|
1934
|
-
return `No matches found for "${term}". ${scope}`;
|
|
1938
|
+
return `No matches found for "${term}". ${scope}${fallbackNote}`;
|
|
1935
1939
|
}
|
|
1936
|
-
return `No matches found for "${term}"`;
|
|
1940
|
+
return `No matches found for "${term}"${fallbackNote}`;
|
|
1937
1941
|
}
|
|
1938
1942
|
|
|
1939
1943
|
const lines = [];
|
|
1940
1944
|
lines.push(`Found ${totalMatches} matches for "${term}" in ${results.length} files:`);
|
|
1945
|
+
if (fallbackNote) lines.push(fallbackNote.trim());
|
|
1941
1946
|
lines.push('═'.repeat(60));
|
|
1942
1947
|
|
|
1943
1948
|
for (const result of results) {
|
package/core/project.js
CHANGED
|
@@ -4442,7 +4442,20 @@ class ProjectIndex {
|
|
|
4442
4442
|
let filesScanned = 0;
|
|
4443
4443
|
let filesSkipped = 0;
|
|
4444
4444
|
const regexFlags = options.caseSensitive ? 'g' : 'gi';
|
|
4445
|
-
const
|
|
4445
|
+
const useRegex = options.regex !== false; // Default: regex ON
|
|
4446
|
+
let regex;
|
|
4447
|
+
let regexFallback = false;
|
|
4448
|
+
if (useRegex) {
|
|
4449
|
+
try {
|
|
4450
|
+
regex = new RegExp(term, regexFlags);
|
|
4451
|
+
} catch (e) {
|
|
4452
|
+
// Invalid regex — fall back to plain text
|
|
4453
|
+
regex = new RegExp(escapeRegExp(term), regexFlags);
|
|
4454
|
+
regexFallback = e.message;
|
|
4455
|
+
}
|
|
4456
|
+
} else {
|
|
4457
|
+
regex = new RegExp(escapeRegExp(term), regexFlags);
|
|
4458
|
+
}
|
|
4446
4459
|
|
|
4447
4460
|
for (const [filePath, fileEntry] of this.files) {
|
|
4448
4461
|
// Apply exclude/in filters
|
|
@@ -4465,7 +4478,7 @@ class ProjectIndex {
|
|
|
4465
4478
|
try {
|
|
4466
4479
|
const parser = getParser(language);
|
|
4467
4480
|
const { findMatchesWithASTFilter } = require('../languages/utils');
|
|
4468
|
-
const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true, regex:
|
|
4481
|
+
const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true, regex: useRegex });
|
|
4469
4482
|
|
|
4470
4483
|
for (const m of astMatches) {
|
|
4471
4484
|
const match = {
|
|
@@ -4545,7 +4558,7 @@ class ProjectIndex {
|
|
|
4545
4558
|
}
|
|
4546
4559
|
}
|
|
4547
4560
|
|
|
4548
|
-
results.meta = { filesScanned, filesSkipped, totalFiles: this.files.size };
|
|
4561
|
+
results.meta = { filesScanned, filesSkipped, totalFiles: this.files.size, regexFallback };
|
|
4549
4562
|
return results;
|
|
4550
4563
|
} finally { this._endOp(); }
|
|
4551
4564
|
}
|
package/languages/utils.js
CHANGED
|
@@ -417,8 +417,13 @@ function findMatchesWithASTFilter(content, term, parser, options = {}) {
|
|
|
417
417
|
const lines = content.split('\n');
|
|
418
418
|
const matches = [];
|
|
419
419
|
|
|
420
|
-
//
|
|
421
|
-
|
|
420
|
+
// Default: regex mode ON. Use raw pattern unless regex=false.
|
|
421
|
+
let regex;
|
|
422
|
+
if (options.regex !== false) {
|
|
423
|
+
try { regex = new RegExp(term, 'gi'); } catch (e) { regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }
|
|
424
|
+
} else {
|
|
425
|
+
regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
426
|
+
}
|
|
422
427
|
|
|
423
428
|
lines.forEach((line, idx) => {
|
|
424
429
|
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>: 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.
|
|
199
|
+
- search <term>: Text search (like grep, respects .gitignore). Supports regex by default (e.g. "\\d+" or "foo|bar"). Supports context=N for surrounding lines, exclude/in for file filtering. Case-insensitive by default; set case_sensitive=true for exact case. Invalid regex auto-falls back to plain text.
|
|
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
|
|
|
@@ -256,8 +256,8 @@ 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 (
|
|
260
|
-
regex: z.boolean().optional().describe('Treat search term as a regex pattern (default: false
|
|
259
|
+
term: z.string().optional().describe('Search term (regex by default; set regex=false to force plain text)'),
|
|
260
|
+
regex: z.boolean().optional().describe('Treat search term as a regex pattern (default: true). Set false to force plain text escaping.'),
|
|
261
261
|
functions: z.boolean().optional().describe('Include per-function line counts in stats output, sorted by size (complexity audit)'),
|
|
262
262
|
add_param: z.string().optional().describe('Parameter name to add (plan command)'),
|
|
263
263
|
remove_param: z.string().optional().describe('Parameter name to remove (plan command)'),
|
|
@@ -434,7 +434,7 @@ server.registerTool(
|
|
|
434
434
|
caseSensitive: case_sensitive || false,
|
|
435
435
|
exclude: searchExclude,
|
|
436
436
|
in: inPath || undefined,
|
|
437
|
-
regex: regex
|
|
437
|
+
regex: regex
|
|
438
438
|
});
|
|
439
439
|
return toolResult(output.formatSearch(result, term));
|
|
440
440
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.14",
|
|
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",
|