ucn 3.7.15 → 3.7.17
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 +17 -0
- package/core/output.js +21 -4
- package/mcp/server.js +11 -9
- package/package.json +1 -1
|
@@ -99,6 +99,12 @@ ucn deadcode --exclude=test # Skip test files (most useful)
|
|
|
99
99
|
| Preview a rename or param change | `ucn plan <name> --rename-to=new_name` | Shows what would change without doing it |
|
|
100
100
|
| File-level dependency tree | `ucn graph <file> --depth=1` | Visual import tree. Setting `--depth=N` expands all children. Can be noisy — use depth=1 for large projects. For function-level flow, use `trace` instead |
|
|
101
101
|
| Find which tests cover a function | `ucn tests <name>` | Test files and test function names |
|
|
102
|
+
| Extract specific lines from a file | `ucn lines --file=<file> --range=10-20` | Pull a line range without reading the whole file |
|
|
103
|
+
| Find type definitions | `ucn typedef <name>` | Interfaces, enums, structs, traits, type aliases |
|
|
104
|
+
| See a project's public API | `ucn api` or `ucn api --file=<file>` | All exported/public symbols with signatures |
|
|
105
|
+
| Drill into context results | `ucn expand <N>` | Show source code for item N from a previous `context` call |
|
|
106
|
+
| Best usage example of a function | `ucn example <name>` | Finds and scores the best call site with surrounding context |
|
|
107
|
+
| Debug a stack trace | `ucn stacktrace --stack="<trace>"` | Parses stack frames and shows source context per frame |
|
|
102
108
|
|
|
103
109
|
## Command Format
|
|
104
110
|
|
|
@@ -126,9 +132,20 @@ ucn [target] <command> [name] [--flags]
|
|
|
126
132
|
| `--base=<ref>` | Git ref for diff-impact (default: HEAD) |
|
|
127
133
|
| `--staged` | Analyze staged changes (diff-impact) |
|
|
128
134
|
| `--no-cache` | Force re-index after editing files |
|
|
135
|
+
| `--clear-cache` | Delete cached index entirely before running |
|
|
129
136
|
| `--context=N` | Lines of surrounding context in `usages`/`search` output |
|
|
130
137
|
| `--no-regex` | Force plain text search (regex is default) |
|
|
131
138
|
| `--functions` | Show per-function line counts in `stats` (complexity audit) |
|
|
139
|
+
| `--json` | Machine-readable JSON output (wrapped in `{meta, data}`) |
|
|
140
|
+
| `--code-only` | Exclude matches in comments and strings (`search`/`usages`) |
|
|
141
|
+
| `--with-types` | Include related type definitions in `smart`/`about` output |
|
|
142
|
+
| `--detailed` | Show full symbol listing per file in `toc` |
|
|
143
|
+
| `--top=N` | Limit result count (default: 10 for most commands) |
|
|
144
|
+
| `--case-sensitive` | Case-sensitive text search (default: case-insensitive) |
|
|
145
|
+
| `--exact` | Exact name match only in `find`/`typedef` (no substring) |
|
|
146
|
+
| `--include-uncertain` | Include ambiguous/uncertain matches in `context`/`smart`/`about` |
|
|
147
|
+
| `--include-exported` | Include exported symbols in `deadcode` results |
|
|
148
|
+
| `--include-decorated` | Include decorated/annotated symbols in `deadcode` results |
|
|
132
149
|
|
|
133
150
|
## Workflow Integration
|
|
134
151
|
|
package/core/output.js
CHANGED
|
@@ -1920,6 +1920,20 @@ function formatGraph(graph, options = {}) {
|
|
|
1920
1920
|
return lines.join('\n');
|
|
1921
1921
|
}
|
|
1922
1922
|
|
|
1923
|
+
/**
|
|
1924
|
+
* Detect common double-escaping patterns in regex search terms.
|
|
1925
|
+
* When MCP/JSON transport is involved, agents often write \\. when they mean \. (literal dot).
|
|
1926
|
+
* Returns a hint string if double-escaping is suspected, empty string otherwise.
|
|
1927
|
+
*/
|
|
1928
|
+
function detectDoubleEscaping(term) {
|
|
1929
|
+
// Look for \\. \\d \\w \\s \\b \\D \\W \\S \\B \\( \\) \\[ \\] — common double-escaped sequences
|
|
1930
|
+
const doubleEscaped = term.match(/\\\\[.dDwWsSbB()\[\]*+?^${}|]/g);
|
|
1931
|
+
if (!doubleEscaped) return '';
|
|
1932
|
+
const examples = [...new Set(doubleEscaped)].slice(0, 3);
|
|
1933
|
+
const fixed = examples.map(e => e.slice(1)); // remove one backslash
|
|
1934
|
+
return `\nHint: Pattern contains ${examples.join(', ')} which matches literal backslash(es). If you meant ${fixed.join(', ')}, use a single backslash (MCP/JSON parameters are already raw strings).`;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1923
1937
|
/**
|
|
1924
1938
|
* Format search command output
|
|
1925
1939
|
*/
|
|
@@ -1933,15 +1947,17 @@ function formatSearch(results, term) {
|
|
|
1933
1947
|
if (totalMatches === 0) {
|
|
1934
1948
|
if (meta) {
|
|
1935
1949
|
const scope = meta.filesSkipped > 0
|
|
1936
|
-
? `Searched ${meta.filesScanned} of ${meta.totalFiles}
|
|
1937
|
-
: `Searched ${meta.filesScanned}
|
|
1938
|
-
|
|
1950
|
+
? `Searched ${meta.filesScanned} of ${meta.totalFiles} file${meta.totalFiles === 1 ? '' : 's'} (${meta.filesSkipped} excluded by filters).`
|
|
1951
|
+
: `Searched ${meta.filesScanned} file${meta.filesScanned === 1 ? '' : 's'}.`;
|
|
1952
|
+
const escapingHint = detectDoubleEscaping(term);
|
|
1953
|
+
return `No matches found for "${term}". ${scope}${fallbackNote}${escapingHint}`;
|
|
1939
1954
|
}
|
|
1940
1955
|
return `No matches found for "${term}"${fallbackNote}`;
|
|
1941
1956
|
}
|
|
1942
1957
|
|
|
1943
1958
|
const lines = [];
|
|
1944
|
-
|
|
1959
|
+
const fileWord = results.length === 1 ? 'file' : 'files';
|
|
1960
|
+
lines.push(`Found ${totalMatches} match${totalMatches === 1 ? '' : 'es'} for "${term}" in ${results.length} ${fileWord}:`);
|
|
1945
1961
|
if (fallbackNote) lines.push(fallbackNote.trim());
|
|
1946
1962
|
lines.push('═'.repeat(60));
|
|
1947
1963
|
|
|
@@ -2358,6 +2374,7 @@ module.exports = {
|
|
|
2358
2374
|
formatClass,
|
|
2359
2375
|
formatGraph,
|
|
2360
2376
|
formatSearch,
|
|
2377
|
+
detectDoubleEscaping,
|
|
2361
2378
|
formatFileExports,
|
|
2362
2379
|
formatStats,
|
|
2363
2380
|
|
package/mcp/server.js
CHANGED
|
@@ -268,7 +268,9 @@ server.registerTool(
|
|
|
268
268
|
range: z.string().optional().describe('Line range to extract, e.g. "10-20" or "15" (lines command)'),
|
|
269
269
|
base: z.string().optional().describe('Git ref to diff against (default: HEAD). E.g. "HEAD~3", "main", a commit SHA'),
|
|
270
270
|
staged: z.boolean().optional().describe('Analyze staged changes (diff_impact command)'),
|
|
271
|
-
case_sensitive: z.boolean().optional().describe('Case-sensitive search (default: false, case-insensitive)')
|
|
271
|
+
case_sensitive: z.boolean().optional().describe('Case-sensitive search (default: false, case-insensitive)'),
|
|
272
|
+
all: z.boolean().optional().describe('Show all results (expand truncated sections). Applies to about, toc, related, trace, and others.'),
|
|
273
|
+
top_level: z.boolean().optional().describe('Show only top-level functions in toc (exclude nested/indented)')
|
|
272
274
|
})
|
|
273
275
|
},
|
|
274
276
|
async (args) => {
|
|
@@ -278,7 +280,7 @@ server.registerTool(
|
|
|
278
280
|
include_exported, include_decorated, calls_only, max_lines,
|
|
279
281
|
direction, term, add_param, remove_param, rename_to,
|
|
280
282
|
default_value, stack, item, range, base, staged,
|
|
281
|
-
case_sensitive, regex, functions } = args;
|
|
283
|
+
case_sensitive, regex, functions, all, top_level } = args;
|
|
282
284
|
|
|
283
285
|
try {
|
|
284
286
|
switch (command) {
|
|
@@ -291,9 +293,9 @@ server.registerTool(
|
|
|
291
293
|
const err = requireName(name);
|
|
292
294
|
if (err) return err;
|
|
293
295
|
const index = getIndex(project_dir);
|
|
294
|
-
const result = index.about(name, { file, exclude: parseExclude(exclude), withTypes: with_types || false, includeMethods: include_methods ?? undefined, maxCallers: top, maxCallees: top });
|
|
296
|
+
const result = index.about(name, { file, exclude: parseExclude(exclude), withTypes: with_types || false, includeMethods: include_methods ?? undefined, includeUncertain: include_uncertain || false, all: all || false, maxCallers: top, maxCallees: top });
|
|
295
297
|
return toolResult(output.formatAbout(result, {
|
|
296
|
-
allHint: 'Repeat with
|
|
298
|
+
allHint: 'Repeat with all=true to show all.',
|
|
297
299
|
methodsHint: 'Note: obj.method() callers/callees excluded. Use include_methods=true to include them.'
|
|
298
300
|
}));
|
|
299
301
|
}
|
|
@@ -378,12 +380,12 @@ server.registerTool(
|
|
|
378
380
|
const err = requireName(name);
|
|
379
381
|
if (err) return err;
|
|
380
382
|
const index = getIndex(project_dir);
|
|
381
|
-
const result = index.related(name, { file, top, all:
|
|
383
|
+
const result = index.related(name, { file, top, all: all || false });
|
|
382
384
|
if (!result) return toolResult(`Symbol "${name}" not found.`);
|
|
383
385
|
return toolResult(output.formatRelated(result, {
|
|
384
|
-
showAll:
|
|
386
|
+
showAll: all || false,
|
|
385
387
|
top,
|
|
386
|
-
allHint: 'Repeat with
|
|
388
|
+
allHint: 'Repeat with all=true to show all.'
|
|
387
389
|
}));
|
|
388
390
|
}
|
|
389
391
|
|
|
@@ -416,7 +418,7 @@ server.registerTool(
|
|
|
416
418
|
|
|
417
419
|
case 'toc': {
|
|
418
420
|
const index = getIndex(project_dir);
|
|
419
|
-
const toc = index.getToc({ detailed: detailed || false, top });
|
|
421
|
+
const toc = index.getToc({ detailed: detailed || false, topLevel: top_level || false, all: all || false, top });
|
|
420
422
|
return toolResult(output.formatToc(toc, {
|
|
421
423
|
topHint: 'Set top=N or use detailed=false for compact view.'
|
|
422
424
|
}));
|
|
@@ -728,7 +730,7 @@ server.registerTool(
|
|
|
728
730
|
if (result?.error === 'file-not-found') return toolError(`File not found in project: ${file}`);
|
|
729
731
|
if (result?.error === 'file-ambiguous') return toolError(`Ambiguous file "${file}". Candidates:\n${result.candidates.map(c => ' ' + c).join('\n')}`);
|
|
730
732
|
return toolResult(output.formatGraph(result, {
|
|
731
|
-
showAll: depth !== undefined,
|
|
733
|
+
showAll: all || depth !== undefined,
|
|
732
734
|
file,
|
|
733
735
|
depthHint: 'Set depth parameter for deeper graph.',
|
|
734
736
|
allHint: 'Set depth to expand all children.'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.17",
|
|
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",
|