ucn 3.7.44 → 3.7.46
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 +4 -4
- package/README.md +23 -24
- package/core/execute.js +27 -0
- package/core/project.js +46 -0
- package/core/verify.js +17 -1
- package/mcp/server.js +1 -1
- package/package.json +11 -10
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ucn
|
|
3
|
-
description: "Code
|
|
3
|
+
description: "Code intelligence toolkit — extract functions, trace callers, analyze impact, detect dead code without reading whole files. PREFER over grep+read when you need: who calls a function, what breaks if you change it, or the full call chain of a pipeline. One `ucn about` replaces 3-4 grep+read cycles. One `ucn trace` maps an entire execution flow without reading any files. Works on JS/TS, Python, Go, Rust, Java, HTML. Skip for plain text search or codebases under 500 LOC."
|
|
4
4
|
allowed-tools: Bash(ucn *), Bash(npx ucn *)
|
|
5
5
|
argument-hint: "[command] [symbol-name] [--flags]"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# UCN — Universal Code Navigator
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Extract functions, trace call chains, find callers, and detect dead code — without reading entire files. Works on JS/TS, Python, Go, Rust, Java, and HTML (inline scripts and event handlers).
|
|
11
11
|
|
|
12
12
|
## When to Reach for UCN Instead of Grep/Read
|
|
13
13
|
|
|
14
|
-
**Use UCN when
|
|
14
|
+
**Use UCN when the next action would be:**
|
|
15
15
|
|
|
16
16
|
- "Let me grep for all callers of this function" → `ucn impact <name>` — finds every call site, grouped by file, with args shown
|
|
17
17
|
- "Let me read this 800-line file to find one function" → `ucn fn <name> --file=<hint>` — extracts just that function
|
|
@@ -164,7 +164,7 @@ ucn trace problematic_function --depth=2 # See what it calls
|
|
|
164
164
|
```bash
|
|
165
165
|
ucn impact the_function # Who will break?
|
|
166
166
|
ucn smart the_function # See it + its helpers
|
|
167
|
-
# ... make
|
|
167
|
+
# ... make changes ...
|
|
168
168
|
ucn verify the_function # Did all call sites survive?
|
|
169
169
|
```
|
|
170
170
|
|
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# UCN
|
|
1
|
+
# UCN - Universal Code Navigator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Code intelligence for AI agents and developers - understand, extract, and navigate code without reading whole files.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Who calls this function?
|
|
7
|
-
- What breaks if I change this
|
|
8
|
-
- What
|
|
9
|
-
- What code is safe to delete?
|
|
5
|
+
Precise answers to structural code questions:
|
|
6
|
+
- Who calls this function? → without grepping the whole project
|
|
7
|
+
- What breaks if I change this? → every call site, with arguments
|
|
8
|
+
- What does this function do? → extracted with dependencies inline
|
|
9
|
+
- What code is safe to delete? → verified unused symbols
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
One command replaces 3-4 grep+read cycles. Powered by tree-sitter.
|
|
12
12
|
|
|
13
13
|
[](https://www.npmjs.com/package/ucn)
|
|
14
14
|
[](LICENSE)
|
|
@@ -20,22 +20,21 @@ Instead of reading full files, UCN gives precise, AST-verified answers.
|
|
|
20
20
|
```bash
|
|
21
21
|
npm install -g ucn
|
|
22
22
|
|
|
23
|
+
ucn toc # project overview
|
|
24
|
+
ucn fn handleRequest # extract a function without reading the file
|
|
23
25
|
ucn about handleRequest # full picture: definition, callers, callees, tests
|
|
24
26
|
ucn impact handleRequest # all call sites with arguments
|
|
25
27
|
ucn trace main --depth=3 # call tree, no file reads
|
|
26
28
|
ucn deadcode # unused functions, AST-verified
|
|
27
|
-
ucn fn handleRequest # extract a function without reading the file
|
|
28
|
-
ucn toc # project overview
|
|
29
|
-
ucn --interactive # REPL mode, index stays in memory
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
Supports JS/TS, Python, Go, Rust, Java, and HTML. Runs locally.
|
|
33
32
|
|
|
34
33
|
```
|
|
35
|
-
Terminal AI Agents
|
|
36
|
-
│ │
|
|
37
|
-
CLI MCP
|
|
38
|
-
|
|
34
|
+
Terminal AI Agents Agent Skills
|
|
35
|
+
│ │ │
|
|
36
|
+
CLI MCP Skill
|
|
37
|
+
└────────────────────┼────────────────────┘
|
|
39
38
|
│
|
|
40
39
|
┌──────┴──────┐
|
|
41
40
|
│ UCN Engine │
|
|
@@ -48,7 +47,7 @@ Parses JS/TS, Python, Go, Rust, Java, and HTML with tree-sitter. Runs locally.
|
|
|
48
47
|
|
|
49
48
|
## Why UCN
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
AI agents waste tokens reading entire files to find one function, or grep for callers and miss half of them. UCN builds a structural index of the codebase - it knows which functions call which, what depends on what, and what's unused. One command gives what would take 3-4 file reads and greps.
|
|
52
51
|
|
|
53
52
|
"What happens when `build()` runs?"
|
|
54
53
|
|
|
@@ -139,7 +138,7 @@ VS Code uses `.vscode/mcp.json`:
|
|
|
139
138
|
|
|
140
139
|
</details>
|
|
141
140
|
|
|
142
|
-
All 28 commands ship as a single MCP tool
|
|
141
|
+
All 28 commands ship as a single MCP tool - under 2KB of context.
|
|
143
142
|
|
|
144
143
|
### Agent Skill (no server needed)
|
|
145
144
|
|
|
@@ -294,13 +293,13 @@ ucn deadcode --exclude=test # what can be deleted?
|
|
|
294
293
|
|
|
295
294
|
## Limitations
|
|
296
295
|
|
|
297
|
-
UCN
|
|
296
|
+
UCN analyzes code structure statically - it doesn't run code.
|
|
298
297
|
|
|
299
|
-
- **5 languages + HTML**
|
|
300
|
-
- **Static analysis only**
|
|
301
|
-
- **Duck-typed methods**
|
|
302
|
-
- **Single project scope**
|
|
303
|
-
- **First-query index time**
|
|
298
|
+
- **5 languages + HTML** - JS/TS, Python, Go, Rust, Java. Falls back to text search for others.
|
|
299
|
+
- **Static analysis only** - Can't follow `eval()`, `getattr()`, reflection, or other dynamic dispatch.
|
|
300
|
+
- **Duck-typed methods** - `obj.method()` in JS/TS/Python is marked "uncertain" when the receiver type is ambiguous. Go/Rust/Java resolve with high confidence.
|
|
301
|
+
- **Single project scope** - Follows imports within the project but not into `node_modules` or `site-packages`.
|
|
302
|
+
- **First-query index time** - A few seconds on large projects. Cached incrementally after that.
|
|
304
303
|
|
|
305
304
|
---
|
|
306
305
|
|
package/core/execute.js
CHANGED
|
@@ -99,6 +99,24 @@ function checkFileError(result, file) {
|
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Validate that className filter actually matches a definition.
|
|
104
|
+
* Returns error string if className is invalid, null if OK.
|
|
105
|
+
*/
|
|
106
|
+
function validateClassName(index, name, className) {
|
|
107
|
+
if (!className) return null;
|
|
108
|
+
const allDefs = index.symbols.get(name);
|
|
109
|
+
if (!allDefs || allDefs.length === 0) return null; // no defs at all — let the command handle "not found"
|
|
110
|
+
const matching = allDefs.filter(d => d.className === className);
|
|
111
|
+
if (matching.length > 0) return null; // className matched
|
|
112
|
+
// className specified but no definitions match
|
|
113
|
+
const available = [...new Set(allDefs.filter(d => d.className).map(d => d.className))];
|
|
114
|
+
if (available.length > 0) {
|
|
115
|
+
return `Symbol "${name}" not found in class "${className}". Available in: ${available.join(', ')}.`;
|
|
116
|
+
}
|
|
117
|
+
return `Symbol "${name}" is not a method of any class. Defined in: ${allDefs[0].relativePath}:${allDefs[0].startLine}.`;
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
/** Parse a number param (handles string from CLI, number from MCP). */
|
|
103
121
|
function num(val, fallback) {
|
|
104
122
|
if (val == null) return fallback;
|
|
@@ -162,6 +180,8 @@ const HANDLERS = {
|
|
|
162
180
|
const err = requireName(p.name);
|
|
163
181
|
if (err) return { ok: false, error: err };
|
|
164
182
|
applyClassMethodSyntax(p);
|
|
183
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
184
|
+
if (classErr) return { ok: false, error: classErr };
|
|
165
185
|
const result = index.context(p.name, {
|
|
166
186
|
includeMethods: p.includeMethods,
|
|
167
187
|
includeUncertain: p.includeUncertain || false,
|
|
@@ -177,6 +197,8 @@ const HANDLERS = {
|
|
|
177
197
|
const err = requireName(p.name);
|
|
178
198
|
if (err) return { ok: false, error: err };
|
|
179
199
|
applyClassMethodSyntax(p);
|
|
200
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
201
|
+
if (classErr) return { ok: false, error: classErr };
|
|
180
202
|
const result = index.impact(p.name, {
|
|
181
203
|
file: p.file,
|
|
182
204
|
className: p.className,
|
|
@@ -291,6 +313,7 @@ const HANDLERS = {
|
|
|
291
313
|
topLevel: p.topLevel,
|
|
292
314
|
all: p.all,
|
|
293
315
|
top: num(p.top, undefined),
|
|
316
|
+
file: p.file,
|
|
294
317
|
});
|
|
295
318
|
return { ok: true, result };
|
|
296
319
|
},
|
|
@@ -562,6 +585,8 @@ const HANDLERS = {
|
|
|
562
585
|
const err = requireName(p.name);
|
|
563
586
|
if (err) return { ok: false, error: err };
|
|
564
587
|
applyClassMethodSyntax(p);
|
|
588
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
589
|
+
if (classErr) return { ok: false, error: classErr };
|
|
565
590
|
const result = index.verify(p.name, { file: p.file, className: p.className });
|
|
566
591
|
return { ok: true, result };
|
|
567
592
|
},
|
|
@@ -570,6 +595,8 @@ const HANDLERS = {
|
|
|
570
595
|
const err = requireName(p.name);
|
|
571
596
|
if (err) return { ok: false, error: err };
|
|
572
597
|
applyClassMethodSyntax(p);
|
|
598
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
599
|
+
if (classErr) return { ok: false, error: classErr };
|
|
573
600
|
if (!p.addParam && !p.removeParam && !p.renameTo) {
|
|
574
601
|
return { ok: false, error: 'Plan requires an operation: add_param, remove_param, or rename_to.' };
|
|
575
602
|
}
|
package/core/project.js
CHANGED
|
@@ -919,6 +919,21 @@ class ProjectIndex {
|
|
|
919
919
|
}
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
// For methods (symbols with className), objects can be passed as parameters
|
|
923
|
+
// to files with no import relationship to the definition file.
|
|
924
|
+
// Expand to all project files that mention the name (fast text pre-check).
|
|
925
|
+
if (symbol.className) {
|
|
926
|
+
for (const filePath of this.files.keys()) {
|
|
927
|
+
if (relevantFiles.has(filePath)) continue;
|
|
928
|
+
try {
|
|
929
|
+
const content = this._readFile(filePath);
|
|
930
|
+
if (content.includes(name)) {
|
|
931
|
+
relevantFiles.add(filePath);
|
|
932
|
+
}
|
|
933
|
+
} catch (e) { /* skip unreadable */ }
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
922
937
|
let calls = 0;
|
|
923
938
|
let definitions = 0;
|
|
924
939
|
let imports = 0;
|
|
@@ -3393,7 +3408,38 @@ class ProjectIndex {
|
|
|
3393
3408
|
let totalDynamic = 0;
|
|
3394
3409
|
let totalTests = 0;
|
|
3395
3410
|
|
|
3411
|
+
// When file= is specified, scope to matching files only
|
|
3412
|
+
let fileFilter = null;
|
|
3413
|
+
if (options.file) {
|
|
3414
|
+
const resolved = this.findFile(options.file);
|
|
3415
|
+
if (resolved) {
|
|
3416
|
+
fileFilter = new Set([resolved]);
|
|
3417
|
+
} else {
|
|
3418
|
+
// Try substring match for partial paths
|
|
3419
|
+
const matching = [];
|
|
3420
|
+
for (const fp of this.files.keys()) {
|
|
3421
|
+
const rp = path.relative(this.root, fp);
|
|
3422
|
+
if (rp.includes(options.file) || fp.includes(options.file)) {
|
|
3423
|
+
matching.push(fp);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
if (matching.length > 0) {
|
|
3427
|
+
fileFilter = new Set(matching);
|
|
3428
|
+
} else {
|
|
3429
|
+
return {
|
|
3430
|
+
meta: { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 },
|
|
3431
|
+
totals: { files: 0, lines: 0, functions: 0, classes: 0, state: 0, testFiles: 0 },
|
|
3432
|
+
summary: { topFunctionFiles: [], topLineFiles: [], entryFiles: [] },
|
|
3433
|
+
files: [],
|
|
3434
|
+
hiddenFiles: 0,
|
|
3435
|
+
error: `File not found in project: ${options.file}`
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3396
3441
|
for (const [filePath, fileEntry] of this.files) {
|
|
3442
|
+
if (fileFilter && !fileFilter.has(filePath)) continue;
|
|
3397
3443
|
let functions = fileEntry.symbols.filter(s =>
|
|
3398
3444
|
s.type === 'function' || s.type === 'method' || s.type === 'static' ||
|
|
3399
3445
|
s.type === 'constructor' || s.type === 'public' || s.type === 'abstract'
|
package/core/verify.js
CHANGED
|
@@ -482,7 +482,23 @@ function plan(index, name, options = {}) {
|
|
|
482
482
|
name: options.addParam,
|
|
483
483
|
...(options.defaultValue && { default: options.defaultValue })
|
|
484
484
|
};
|
|
485
|
-
|
|
485
|
+
|
|
486
|
+
// When adding a required param (no default), insert before the first
|
|
487
|
+
// optional param to avoid producing invalid signatures in Python/TS
|
|
488
|
+
// (required params must precede optional ones).
|
|
489
|
+
if (!options.defaultValue) {
|
|
490
|
+
const firstOptIdx = newParams.findIndex(p => p.optional || p.default !== undefined);
|
|
491
|
+
if (firstOptIdx !== -1) {
|
|
492
|
+
// Also skip self/cls/&self/&mut self at position 0
|
|
493
|
+
const insertIdx = Math.max(firstOptIdx,
|
|
494
|
+
(newParams.length > 0 && ['self', 'cls', '&self', '&mut self'].includes(newParams[0].name)) ? 1 : 0);
|
|
495
|
+
newParams.splice(insertIdx, 0, newParam);
|
|
496
|
+
} else {
|
|
497
|
+
newParams.push(newParam);
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
newParams.push(newParam);
|
|
501
|
+
}
|
|
486
502
|
|
|
487
503
|
// Generate new signature
|
|
488
504
|
const paramsList = newParams.map(p => {
|
package/mcp/server.js
CHANGED
|
@@ -158,7 +158,7 @@ function requireName(name) {
|
|
|
158
158
|
// CONSOLIDATED TOOL REGISTRATION
|
|
159
159
|
// ============================================================================
|
|
160
160
|
|
|
161
|
-
const TOOL_DESCRIPTION = `
|
|
161
|
+
const TOOL_DESCRIPTION = `Code intelligence toolkit for AI agents. Extract specific functions, trace call chains, find all callers, and detect dead code — without reading entire files or scanning full projects. Use instead of grep/read for code relationships. Supports JavaScript/TypeScript, Python, Go, Rust, Java, and HTML.
|
|
162
162
|
|
|
163
163
|
TOP 5 (covers 90% of tasks): about, impact, trace, find, deadcode
|
|
164
164
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.46",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ucn": "cli/index.js",
|
|
@@ -16,28 +16,29 @@
|
|
|
16
16
|
"mcp",
|
|
17
17
|
"mcp-server",
|
|
18
18
|
"model-context-protocol",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"ai-coding",
|
|
21
|
+
"code-intelligence",
|
|
19
22
|
"code-navigation",
|
|
20
23
|
"code-analysis",
|
|
21
|
-
"
|
|
24
|
+
"code-extraction",
|
|
22
25
|
"call-graph",
|
|
23
26
|
"callers",
|
|
24
27
|
"impact-analysis",
|
|
25
28
|
"dead-code",
|
|
26
29
|
"deadcode",
|
|
27
|
-
"ast",
|
|
28
|
-
"tree-sitter",
|
|
29
|
-
"parser",
|
|
30
|
-
"skill",
|
|
31
30
|
"agent-skill",
|
|
31
|
+
"skill",
|
|
32
32
|
"cli",
|
|
33
|
-
"
|
|
33
|
+
"tree-sitter",
|
|
34
|
+
"ast",
|
|
35
|
+
"static-analysis",
|
|
34
36
|
"javascript",
|
|
35
37
|
"typescript",
|
|
36
38
|
"python",
|
|
37
39
|
"go",
|
|
38
40
|
"rust",
|
|
39
|
-
"java"
|
|
40
|
-
"html"
|
|
41
|
+
"java"
|
|
41
42
|
],
|
|
42
43
|
"author": "Constantin-Mihail Leoca (https://github.com/mleoca)",
|
|
43
44
|
"repository": {
|