ucn 3.8.4 → 3.8.6
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 +6 -1
- package/README.md +270 -201
- package/cli/index.js +4 -3
- package/mcp/server.js +54 -46
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ Extract functions, trace call chains, find callers, and detect dead code — wit
|
|
|
26
26
|
- Language not supported (only JS/TS, Python, Go, Rust, Java, HTML)
|
|
27
27
|
- Finding files by name — use glob
|
|
28
28
|
|
|
29
|
-
## The
|
|
29
|
+
## The Commands You'll Use Most
|
|
30
30
|
|
|
31
31
|
### 1. `about` — First stop for any investigation
|
|
32
32
|
|
|
@@ -157,6 +157,7 @@ ucn [target] <command> [name] [--flags]
|
|
|
157
157
|
|
|
158
158
|
| Flag | When to use it |
|
|
159
159
|
|------|---------------|
|
|
160
|
+
| `--class-name=X` | Scope to specific class (e.g., `--class-name=Repository` for method `save`) |
|
|
160
161
|
| `--file=<pattern>` | Disambiguate when a name exists in multiple files (e.g., `--file=api`) |
|
|
161
162
|
| `--exclude=test,mock` | Focus on production code only |
|
|
162
163
|
| `--in=src/core` | Limit search to a subdirectory |
|
|
@@ -185,6 +186,10 @@ ucn [target] <command> [name] [--flags]
|
|
|
185
186
|
| `--include-uncertain` | Include ambiguous/uncertain matches in `context`/`smart`/`about` |
|
|
186
187
|
| `--show-confidence` | Show confidence scores (0.0–1.0) per caller/callee edge in `context`/`about` |
|
|
187
188
|
| `--min-confidence=N` | Filter edges below confidence threshold (e.g., `--min-confidence=0.7` keeps only high-confidence edges) |
|
|
189
|
+
| `--calls-only` | Only show call/test-case matches in `tests` (skip file-level results) |
|
|
190
|
+
| `--add-param=<name>` | Add a parameter (`plan` command). Combine with `--default=<value>` |
|
|
191
|
+
| `--remove-param=<name>` | Remove a parameter (`plan` command) |
|
|
192
|
+
| `--rename-to=<name>` | Rename a function (`plan` command) |
|
|
188
193
|
| `--include-exported` | Include exported symbols in `deadcode` results |
|
|
189
194
|
| `--include-decorated` | Include decorated/annotated symbols in `deadcode` results |
|
|
190
195
|
| `--framework=X` | Filter `entrypoints` by framework (e.g., `express`, `spring`, `celery`) |
|
package/README.md
CHANGED
|
@@ -1,34 +1,13 @@
|
|
|
1
1
|
# UCN - Universal Code Navigator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
See what code does before you touch it.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
11
|
-
One command replaces 3-4 grep+read cycles. Powered by tree-sitter.
|
|
5
|
+
Find symbols, trace callers, check impact, pick the right tests, extract code and spot what's dead - from the terminal.
|
|
12
6
|
|
|
13
7
|
[](https://www.npmjs.com/package/ucn)
|
|
14
8
|
[](LICENSE)
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## 60-Second Quickstart
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install -g ucn
|
|
22
|
-
|
|
23
|
-
ucn toc # project overview
|
|
24
|
-
ucn fn handleRequest # extract a function without reading the file
|
|
25
|
-
ucn about handleRequest # full picture: definition, callers, callees, tests
|
|
26
|
-
ucn impact handleRequest # all call sites with arguments
|
|
27
|
-
ucn trace main --depth=3 # call tree, no file reads
|
|
28
|
-
ucn deadcode # unused functions, AST-verified
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Supports JS/TS, Python, Go, Rust, Java, and HTML. Runs locally.
|
|
10
|
+
All commands, one engine, three surfaces:
|
|
32
11
|
|
|
33
12
|
```
|
|
34
13
|
Terminal AI Agents Agent Skills
|
|
@@ -43,11 +22,27 @@ Supports JS/TS, Python, Go, Rust, Java, and HTML. Runs locally.
|
|
|
43
22
|
└─────────────┘
|
|
44
23
|
```
|
|
45
24
|
|
|
25
|
+
Supports JavaScript, TypeScript, Python, Go, Rust, Java, and HTML inline scripts.
|
|
26
|
+
|
|
27
|
+
If you work with AI, add UCN as a [Skill or MCP](#ai-setup) and let the agent ask better code questions instead of reading whole files.
|
|
28
|
+
All commands ship as a single tool.
|
|
29
|
+
|
|
30
|
+
UCN is deliberately lightweight:
|
|
31
|
+
|
|
32
|
+
- **No background processes** - parses on demand, answers, exits
|
|
33
|
+
- **No language servers** - tree-sitter does the parsing, no compilation needed
|
|
34
|
+
- **MCP is optional** - only needed if you connect UCN to an AI agent, the CLI and Skill work on their own
|
|
35
|
+
|
|
46
36
|
---
|
|
47
37
|
|
|
48
|
-
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g ucn
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
ucn trace main --depth=3 # full execution flow
|
|
42
|
+
ucn about handleRequest # definition + callers + callees + tests
|
|
43
|
+
ucn impact handleRequest # every call site with arguments
|
|
44
|
+
ucn deadcode --exclude=test # unused code, AST-verified
|
|
45
|
+
```
|
|
51
46
|
|
|
52
47
|
"What happens when `build()` runs?"
|
|
53
48
|
|
|
@@ -55,47 +50,155 @@ AI agents waste tokens reading entire files to find one function, or grep for ca
|
|
|
55
50
|
$ ucn trace build --depth=2
|
|
56
51
|
|
|
57
52
|
build
|
|
58
|
-
├── detectProjectPattern (discovery.js:
|
|
59
|
-
│ ├── checkDir (discovery.js:
|
|
60
|
-
│ └── shouldIgnore (discovery.js:
|
|
61
|
-
├── parseGitignore (discovery.js:
|
|
62
|
-
├── expandGlob (discovery.js:
|
|
63
|
-
│ ├── parseGlobPattern (discovery.js:
|
|
64
|
-
│ ├── walkDir (discovery.js:
|
|
65
|
-
│ └── compareNames (discovery.js:
|
|
66
|
-
├── indexFile (project.js:
|
|
67
|
-
│ ├── addSymbol (project.js:
|
|
53
|
+
├── detectProjectPattern (core/discovery.js:399) 1x
|
|
54
|
+
│ ├── checkDir (core/discovery.js:403) 2x
|
|
55
|
+
│ └── shouldIgnore (core/discovery.js:347) 1x
|
|
56
|
+
├── parseGitignore (core/discovery.js:130) 1x
|
|
57
|
+
├── expandGlob (core/discovery.js:190) 1x
|
|
58
|
+
│ ├── parseGlobPattern (core/discovery.js:226) 1x
|
|
59
|
+
│ ├── walkDir (core/discovery.js:283) 1x
|
|
60
|
+
│ └── compareNames (core/discovery.js:169) 1x
|
|
61
|
+
├── indexFile (core/project.js:273) 1x
|
|
62
|
+
│ ├── addSymbol (core/project.js:343) 4x
|
|
68
63
|
│ ├── detectLanguage (languages/index.js:157) 1x
|
|
69
|
-
│ ├──
|
|
70
|
-
│ └── extractImports (imports.js:19) 1x
|
|
71
|
-
├── buildImportGraph (project.js:
|
|
72
|
-
└── buildInheritanceGraph (project.js:
|
|
64
|
+
│ ├── parse (core/parser.js:69) 1x
|
|
65
|
+
│ └── extractImports (core/imports.js:19) 1x
|
|
66
|
+
├── buildImportGraph (core/project.js:549) 1x
|
|
67
|
+
└── buildInheritanceGraph (core/project.js:627) 1x
|
|
73
68
|
```
|
|
74
69
|
|
|
75
|
-
One command. No files opened.
|
|
70
|
+
One command. No files opened. Every function located by file and line.
|
|
76
71
|
|
|
77
72
|
---
|
|
78
73
|
|
|
79
|
-
##
|
|
74
|
+
## Understand code you didn't write
|
|
75
|
+
|
|
76
|
+
`ucn about` gives you everything about a function in one shot - who calls it, what it calls, which tests cover it, and the source code.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
$ ucn about expandGlob
|
|
80
|
+
|
|
81
|
+
expandGlob (function)
|
|
82
|
+
════════════════════════════════════════════════════════════
|
|
83
|
+
core/discovery.js:190-221
|
|
84
|
+
expandGlob (pattern, options = {})
|
|
85
|
+
|
|
86
|
+
CALLERS (3):
|
|
87
|
+
cli/index.js:859 [runGlobCommand]
|
|
88
|
+
const files = expandGlob(pattern);
|
|
89
|
+
core/cache.js:274 [isCacheStale]
|
|
90
|
+
const currentFiles = expandGlob(pattern, globOpts);
|
|
91
|
+
core/project.js:195 [build]
|
|
92
|
+
const files = expandGlob(pattern, globOpts);
|
|
93
|
+
|
|
94
|
+
CALLEES (3):
|
|
95
|
+
parseGlobPattern [utility] - core/discovery.js:226
|
|
96
|
+
walkDir [utility] - core/discovery.js:283
|
|
97
|
+
compareNames [utility] - core/discovery.js:169
|
|
98
|
+
|
|
99
|
+
TESTS: 6 matches in 2 file(s)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Need to trace execution upward instead? `ucn reverse-trace fn` walks the caller chain back to entry points.
|
|
103
|
+
|
|
104
|
+
## Change code without breaking things
|
|
105
|
+
|
|
106
|
+
Before touching a function, check if all existing call sites match its signature:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
$ ucn verify expandGlob
|
|
110
|
+
|
|
111
|
+
expandGlob (pattern, options = {})
|
|
112
|
+
Expected arguments: 1-2
|
|
113
|
+
|
|
114
|
+
STATUS: ✓ All calls valid (7 calls, 0 mismatches)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then preview the refactoring. UCN shows exactly what needs to change and where:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
$ ucn plan expandGlob --rename-to=expandGlobPattern
|
|
121
|
+
|
|
122
|
+
SIGNATURE CHANGE:
|
|
123
|
+
Before: expandGlob (pattern, options = {})
|
|
124
|
+
After: expandGlobPattern (pattern, options = {})
|
|
125
|
+
|
|
126
|
+
CHANGES NEEDED: 7 across 4 files
|
|
127
|
+
|
|
128
|
+
cli/index.js :859
|
|
129
|
+
const files = expandGlob(pattern);
|
|
130
|
+
→ const files = expandGlobPattern(pattern);
|
|
131
|
+
|
|
132
|
+
core/cache.js :274
|
|
133
|
+
const currentFiles = expandGlob(pattern, globOpts);
|
|
134
|
+
→ const currentFiles = expandGlobPattern(pattern, globOpts);
|
|
135
|
+
|
|
136
|
+
core/project.js :195
|
|
137
|
+
const files = expandGlob(pattern, globOpts);
|
|
138
|
+
→ const files = expandGlobPattern(pattern, globOpts);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Run `ucn diff-impact --staged` before committing to see what you changed and who calls it.
|
|
142
|
+
|
|
143
|
+
## Find what to clean up
|
|
144
|
+
|
|
145
|
+
Which tests should you run after a change? `affected-tests` walks the blast radius and finds every test that touches the affected functions:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
$ ucn affected-tests expandGlob
|
|
149
|
+
|
|
150
|
+
1 function changed → 18 functions affected (depth 3)
|
|
151
|
+
Summary: 18 affected → 12 test files, 11/18 functions covered (61%)
|
|
152
|
+
|
|
153
|
+
Uncovered (7): runGlobCommand, runProjectCommand, ...
|
|
154
|
+
⚠ These affected functions have no test references
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Find unused code
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
$ ucn deadcode --exclude=test
|
|
161
|
+
|
|
162
|
+
Dead code: 1 unused symbol(s)
|
|
163
|
+
|
|
164
|
+
core/discovery.js
|
|
165
|
+
[ 162- 166] legacyResolve (function)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Extract without reading the whole file
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
$ ucn fn compareNames
|
|
80
172
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
173
|
+
core/discovery.js:169
|
|
174
|
+
[ 169- 177] compareNames(a, b)
|
|
175
|
+
────────────────────────────────────────────────────────────
|
|
176
|
+
function compareNames(a, b) {
|
|
177
|
+
const aLower = a.toLowerCase();
|
|
178
|
+
const bLower = b.toLowerCase();
|
|
179
|
+
if (aLower < bLower) return -1;
|
|
180
|
+
if (aLower > bLower) return 1;
|
|
181
|
+
if (a < b) return -1;
|
|
182
|
+
if (a > b) return 1;
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
91
186
|
|
|
92
187
|
---
|
|
93
188
|
|
|
94
|
-
##
|
|
189
|
+
## Testing and reliability
|
|
190
|
+
|
|
191
|
+
- **Fast** - indexes its own ~25K-line codebase in under 100ms, cached after first run
|
|
192
|
+
- **Discipline** - every bug fix gets a regression test, test code is ~3x the source
|
|
193
|
+
- **Coverage** - every command, every supported language, every surface (CLI, MCP, interactive)
|
|
194
|
+
- **Systematic** - a harness exercises all command and flag combinations against real multi-language fixtures
|
|
195
|
+
- **Test types** - unit, integration, per-language regression, formatter, cache, MCP edge cases, architecture parity guards
|
|
95
196
|
|
|
96
|
-
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## AI Setup
|
|
97
200
|
|
|
98
|
-
|
|
201
|
+
### MCP
|
|
99
202
|
|
|
100
203
|
```bash
|
|
101
204
|
# Claude Code
|
|
@@ -138,12 +241,8 @@ VS Code uses `.vscode/mcp.json`:
|
|
|
138
241
|
|
|
139
242
|
</details>
|
|
140
243
|
|
|
141
|
-
All commands ship as a single MCP tool - under 2KB of context.
|
|
142
|
-
|
|
143
244
|
### Agent Skill (no server needed)
|
|
144
245
|
|
|
145
|
-
Drop-in for Claude Code or Codex CLI:
|
|
146
|
-
|
|
147
246
|
```bash
|
|
148
247
|
# Claude Code
|
|
149
248
|
mkdir -p ~/.claude/skills
|
|
@@ -156,156 +255,126 @@ cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.agents/skills/
|
|
|
156
255
|
|
|
157
256
|
---
|
|
158
257
|
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
[
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
## All commands
|
|
269
|
-
|
|
270
|
-
```
|
|
271
|
-
UNDERSTAND MODIFY SAFELY
|
|
272
|
-
───────────────────── ─────────────────────
|
|
273
|
-
about full picture impact all call sites
|
|
274
|
-
context callers + callees blast transitive impact
|
|
275
|
-
smart function + helpers diff-impact git diff + callers
|
|
276
|
-
trace call tree verify signature check
|
|
277
|
-
reverse-trace callers → root plan refactor preview
|
|
278
|
-
|
|
279
|
-
FIND & EXTRACT ARCHITECTURE
|
|
280
|
-
───────────────────── ─────────────────────
|
|
281
|
-
find locate definitions imports file dependencies
|
|
282
|
-
usages all occurrences exporters reverse dependencies
|
|
283
|
-
fn extract function graph dependency tree
|
|
284
|
-
class extract class circular-deps import cycles
|
|
285
|
-
toc project overview related sibling functions
|
|
286
|
-
deadcode unused code tests find test coverage
|
|
287
|
-
search text search affected-tests tests for changes
|
|
288
|
-
example best usage example stacktrace error trace context
|
|
289
|
-
lines extract line range api public API surface
|
|
290
|
-
expand drill into context typedef type definitions
|
|
291
|
-
file-exports file's exports
|
|
292
|
-
stats project stats
|
|
258
|
+
## Full help
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
UCN - Universal Code Navigator
|
|
262
|
+
|
|
263
|
+
Supported: JavaScript, TypeScript, Python, Go, Rust, Java, HTML
|
|
264
|
+
|
|
265
|
+
Usage:
|
|
266
|
+
ucn [command] [args] Project mode (current directory)
|
|
267
|
+
ucn <file> [command] [args] Single file mode
|
|
268
|
+
ucn <dir> [command] [args] Project mode (specific directory)
|
|
269
|
+
ucn "pattern" [command] [args] Glob pattern mode
|
|
270
|
+
(Default output is text; add --json for machine-readable JSON)
|
|
271
|
+
|
|
272
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
273
|
+
UNDERSTAND CODE
|
|
274
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
275
|
+
about <name> Full picture (definition, callers, callees, tests, code)
|
|
276
|
+
context <name> Who calls this + what it calls (numbered for expand)
|
|
277
|
+
smart <name> Function + all dependencies inline
|
|
278
|
+
impact <name> What breaks if changed (call sites grouped by file)
|
|
279
|
+
blast <name> Transitive blast radius (callers of callers, --depth=N)
|
|
280
|
+
trace <name> Call tree visualization (--depth=N expands all children)
|
|
281
|
+
reverse-trace <name> Upward call chain to entry points (--depth=N, default 5)
|
|
282
|
+
related <name> Find similar functions (same file, shared deps)
|
|
283
|
+
example <name> Best usage example with context
|
|
284
|
+
|
|
285
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
286
|
+
FIND CODE
|
|
287
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
288
|
+
find <name> Find symbol definitions (supports glob: find "handle*")
|
|
289
|
+
usages <name> All usages grouped: definitions, calls, imports, references
|
|
290
|
+
toc Table of contents (compact; --detailed lists all symbols)
|
|
291
|
+
search <term> Text search (regex default, --context=N, --exclude=, --in=)
|
|
292
|
+
Structural: --type=function|class|call --param= --returns= --decorator= --exported --unused
|
|
293
|
+
tests <name> Find test files for a function
|
|
294
|
+
affected-tests <n> Tests affected by a change (blast + test detection, --depth=N)
|
|
295
|
+
|
|
296
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
297
|
+
EXTRACT CODE
|
|
298
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
299
|
+
fn <name>[,n2,...] Extract function(s) (comma-separated for bulk, --file)
|
|
300
|
+
class <name> Extract class
|
|
301
|
+
lines <range> Extract line range (e.g., lines 50-100)
|
|
302
|
+
expand <N> Show code for item N from context output
|
|
303
|
+
|
|
304
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
305
|
+
FILE DEPENDENCIES
|
|
306
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
307
|
+
imports <file> What does file import
|
|
308
|
+
exporters <file> Who imports this file
|
|
309
|
+
file-exports <file> What does file export
|
|
310
|
+
graph <file> Full dependency tree (--depth=N, --direction=imports|importers|both)
|
|
311
|
+
circular-deps Detect circular import chains (--file=, --exclude=)
|
|
312
|
+
|
|
313
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
314
|
+
REFACTORING HELPERS
|
|
315
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
316
|
+
plan <name> Preview refactoring (--add-param, --remove-param, --rename-to)
|
|
317
|
+
verify <name> Check all call sites match signature
|
|
318
|
+
diff-impact What changed in git diff and who calls it (--base, --staged)
|
|
319
|
+
deadcode Find unused functions/classes
|
|
320
|
+
entrypoints Detect framework entry points (routes, DI, tasks)
|
|
321
|
+
|
|
322
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
323
|
+
OTHER
|
|
324
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
325
|
+
api Show exported/public symbols
|
|
326
|
+
typedef <name> Find type definitions
|
|
327
|
+
stats Project statistics (--functions for per-function line counts)
|
|
328
|
+
stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
|
|
329
|
+
|
|
330
|
+
Common Flags:
|
|
331
|
+
--file <pattern> Filter by file path (e.g., --file=routes)
|
|
332
|
+
--exclude=a,b Exclude patterns (e.g., --exclude=test,mock)
|
|
333
|
+
--in=<path> Only in path (e.g., --in=src/core)
|
|
334
|
+
--depth=N Trace/graph depth (default: 3, also expands all children)
|
|
335
|
+
--direction=X Graph direction: imports, importers, or both (default: both)
|
|
336
|
+
--all Expand truncated sections (about, trace, graph, related)
|
|
337
|
+
--top=N Limit results (find, deadcode)
|
|
338
|
+
--limit=N Limit result count (find, usages, search, deadcode, api, toc)
|
|
339
|
+
--max-files=N Max files to index (large projects)
|
|
340
|
+
--context=N Lines of context around matches
|
|
341
|
+
--json Machine-readable output
|
|
342
|
+
--code-only Filter out comments and strings
|
|
343
|
+
--with-types Include type definitions
|
|
344
|
+
--include-tests Include test files
|
|
345
|
+
--class-name=X Scope to specific class (e.g., --class-name=Repository)
|
|
346
|
+
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
347
|
+
--include-uncertain Include ambiguous/uncertain matches
|
|
348
|
+
--show-confidence Show confidence scores per caller/callee edge
|
|
349
|
+
--min-confidence=N Filter edges below confidence threshold (0.0-1.0)
|
|
350
|
+
--include-exported Include exported symbols in deadcode
|
|
351
|
+
--no-regex Force plain text search (regex is default)
|
|
352
|
+
--functions Show per-function line counts (stats command)
|
|
353
|
+
--include-decorated Include decorated/annotated symbols in deadcode
|
|
354
|
+
--framework=X Filter entrypoints by framework (e.g., --framework=express,spring)
|
|
355
|
+
--exact Exact name match only (find)
|
|
356
|
+
--calls-only Only show call/test-case matches (tests)
|
|
357
|
+
--case-sensitive Case-sensitive text search (search)
|
|
358
|
+
--detailed List all symbols in toc (compact by default)
|
|
359
|
+
--top-level Show only top-level functions in toc
|
|
360
|
+
--max-lines=N Max source lines for class (large classes show summary)
|
|
361
|
+
--no-cache Disable caching
|
|
362
|
+
--clear-cache Clear cache before running
|
|
363
|
+
--base=<ref> Git ref for diff-impact (default: HEAD)
|
|
293
364
|
```
|
|
294
365
|
|
|
295
366
|
---
|
|
296
367
|
|
|
297
368
|
## Limitations
|
|
298
369
|
|
|
299
|
-
|
|
370
|
+
- Single-project scope - follows imports within the project, not into `node_modules` or `site-packages`
|
|
371
|
+
- No runtime execution - static analysis only
|
|
372
|
+
- Dynamic dispatch and reflection are only partially visible or invisible
|
|
373
|
+
- JS, TS, and Python method calls can be uncertain when receiver type is unknown
|
|
374
|
+
- Large repos take a few seconds on the first query, then use cache
|
|
300
375
|
|
|
301
|
-
|
|
302
|
-
- **Static analysis only** - Can't follow `eval()`, `getattr()`, reflection, or other dynamic dispatch.
|
|
303
|
-
- **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.
|
|
304
|
-
- **Single project scope** - Follows imports within the project but not into `node_modules` or `site-packages`.
|
|
305
|
-
- **First-query index time** - A few seconds on large projects. Cached incrementally after that.
|
|
376
|
+
If you need compiler diagnostics, taint analysis, or runtime semantics, those are different tools for different jobs. UCN trades that depth for speed, portability, and zero setup.
|
|
306
377
|
|
|
307
378
|
---
|
|
308
379
|
|
|
309
|
-
## License
|
|
310
|
-
|
|
311
380
|
MIT
|
package/cli/index.js
CHANGED
|
@@ -74,7 +74,7 @@ function parseFlags(tokens) {
|
|
|
74
74
|
file: getValueFlag('--file'),
|
|
75
75
|
exclude: parseExclude(),
|
|
76
76
|
in: getValueFlag('--in'),
|
|
77
|
-
includeTests: tokens.includes('--include-tests'),
|
|
77
|
+
includeTests: tokens.includes('--include-tests') ? true : undefined,
|
|
78
78
|
includeExported: tokens.includes('--include-exported'),
|
|
79
79
|
includeDecorated: tokens.includes('--include-decorated'),
|
|
80
80
|
includeUncertain: tokens.includes('--include-uncertain'),
|
|
@@ -171,7 +171,7 @@ const VALUE_FLAGS = new Set([
|
|
|
171
171
|
'--add-param', '--remove-param', '--rename-to', '--default',
|
|
172
172
|
'--base', '--exclude', '--not', '--in', '--max-lines', '--class-name',
|
|
173
173
|
'--type', '--param', '--receiver', '--returns', '--decorator',
|
|
174
|
-
'--limit', '--max-files', '--min-confidence', '--stack'
|
|
174
|
+
'--limit', '--max-files', '--min-confidence', '--stack', '--framework'
|
|
175
175
|
]);
|
|
176
176
|
|
|
177
177
|
// Remove flags from args, then add args after -- (which are all positional)
|
|
@@ -1124,6 +1124,7 @@ Common Flags:
|
|
|
1124
1124
|
--code-only Filter out comments and strings
|
|
1125
1125
|
--with-types Include type definitions
|
|
1126
1126
|
--include-tests Include test files
|
|
1127
|
+
--class-name=X Scope to specific class (e.g., --class-name=Repository)
|
|
1127
1128
|
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
1128
1129
|
--include-uncertain Include ambiguous/uncertain matches
|
|
1129
1130
|
--show-confidence Show confidence scores per caller/callee edge
|
|
@@ -1248,7 +1249,7 @@ Flags can be added per-command: context myFunc --include-methods
|
|
|
1248
1249
|
const tokens = input.split(/\s+/);
|
|
1249
1250
|
const command = tokens[0];
|
|
1250
1251
|
// Flags that take a space-separated value (--flag value)
|
|
1251
|
-
const valueFlagNames = new Set(['--file', '--in', '--base', '--add-param', '--remove-param', '--rename-to', '--default', '--depth', '--top', '--context', '--max-lines', '--direction', '--exclude', '--not', '--stack', '--type', '--param', '--receiver', '--returns', '--decorator', '--limit', '--max-files', '--min-confidence', '--class-name']);
|
|
1252
|
+
const valueFlagNames = new Set(['--file', '--in', '--base', '--add-param', '--remove-param', '--rename-to', '--default', '--depth', '--top', '--context', '--max-lines', '--direction', '--exclude', '--not', '--stack', '--type', '--param', '--receiver', '--returns', '--decorator', '--limit', '--max-files', '--min-confidence', '--class-name', '--framework']);
|
|
1252
1253
|
const flagTokens = [];
|
|
1253
1254
|
const argTokens = [];
|
|
1254
1255
|
const skipNext = new Set();
|
package/mcp/server.js
CHANGED
|
@@ -45,20 +45,18 @@ const indexCache = new Map(); // projectDir → { index, checkedAt }
|
|
|
45
45
|
const MAX_CACHE_SIZE = 10;
|
|
46
46
|
const expandCacheInstance = new ExpandCache();
|
|
47
47
|
|
|
48
|
-
function getIndex(projectDir) {
|
|
48
|
+
function getIndex(projectDir, options) {
|
|
49
|
+
const maxFiles = options && options.maxFiles;
|
|
49
50
|
const absDir = path.resolve(projectDir);
|
|
50
51
|
if (!fs.existsSync(absDir) || !fs.statSync(absDir).isDirectory()) {
|
|
51
52
|
throw new Error(`Project directory not found: ${absDir}`);
|
|
52
53
|
}
|
|
53
54
|
const root = findProjectRoot(absDir);
|
|
54
55
|
const cached = indexCache.get(root);
|
|
55
|
-
const STALE_CHECK_INTERVAL_MS = 2000;
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return cached.index; // Recently verified fresh
|
|
61
|
-
}
|
|
57
|
+
// Always check staleness — MCP is used in iterative agent loops where
|
|
58
|
+
// files change between requests, so a throttle causes stale results.
|
|
59
|
+
if (cached && !maxFiles) {
|
|
62
60
|
if (!cached.index.isCacheStale()) {
|
|
63
61
|
cached.checkedAt = Date.now();
|
|
64
62
|
return cached.index;
|
|
@@ -67,12 +65,15 @@ function getIndex(projectDir) {
|
|
|
67
65
|
|
|
68
66
|
// Build new index (or rebuild stale one)
|
|
69
67
|
const index = new ProjectIndex(root);
|
|
68
|
+
const buildOpts = { quiet: true, forceRebuild: false };
|
|
69
|
+
if (maxFiles) buildOpts.maxFiles = maxFiles;
|
|
70
70
|
const loaded = index.loadCache();
|
|
71
|
-
if (loaded && !index.isCacheStale()) {
|
|
72
|
-
// Disk cache is fresh
|
|
71
|
+
if (loaded && !maxFiles && !index.isCacheStale()) {
|
|
72
|
+
// Disk cache is fresh (skip when maxFiles is set — cached index may have different file count)
|
|
73
73
|
} else {
|
|
74
|
-
|
|
75
|
-
index.
|
|
74
|
+
buildOpts.forceRebuild = !!loaded;
|
|
75
|
+
index.build(null, buildOpts);
|
|
76
|
+
if (!maxFiles) index.saveCache(); // Don't pollute disk cache with partial indexes
|
|
76
77
|
// Clear expand cache entries for this project — stale after rebuild
|
|
77
78
|
expandCacheInstance.clearForRoot(root);
|
|
78
79
|
}
|
|
@@ -93,7 +94,10 @@ function getIndex(projectDir) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
// Don't cache partial indexes (maxFiles) — they'd serve wrong results for full queries
|
|
98
|
+
if (!maxFiles) {
|
|
99
|
+
indexCache.set(root, { index, checkedAt: Date.now() });
|
|
100
|
+
}
|
|
97
101
|
return index;
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -185,6 +189,7 @@ UNDERSTANDING CODE:
|
|
|
185
189
|
- smart <name>: Get a function's source with all called functions expanded inline (not constants/variables). Use to understand or modify a function and its dependencies in one read.
|
|
186
190
|
- trace <name>: Call tree from a function downward. Use to understand "what happens when X runs" — maps which modules a pipeline touches without reading files. Set depth (default: 3); setting depth expands all children.
|
|
187
191
|
- example <name>: Best real-world usage example. Automatically scores call sites by quality and returns the top one with context. Use to understand expected calling patterns.
|
|
192
|
+
- reverse_trace <name>: Upward call chain to entry points — who calls this, who calls those callers, etc. Use to find all paths that lead to a function. Set depth (default: 5) to control how far up. Complement to trace (which goes downward).
|
|
188
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.
|
|
189
194
|
|
|
190
195
|
FINDING CODE:
|
|
@@ -195,6 +200,7 @@ FINDING CODE:
|
|
|
195
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.
|
|
196
201
|
- affected_tests <name>: Which tests to run after changing a function. Combines blast (transitive callers) with test detection. Shows test files, coverage %, and uncovered functions. Use depth= to control depth.
|
|
197
202
|
- 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.
|
|
203
|
+
- entrypoints: Detect framework entry points: routes, handlers, DI providers, tasks. Auto-detects Express, Flask, Spring, Gin, Actix, and more. Use framework= to filter by specific framework.
|
|
198
204
|
|
|
199
205
|
EXTRACTING CODE (use instead of reading entire files):
|
|
200
206
|
- fn <name>: Extract one or more functions. Comma-separated for bulk extraction (e.g. "parse,format,validate"). Use file to disambiguate.
|
|
@@ -207,6 +213,7 @@ FILE DEPENDENCIES (require file param):
|
|
|
207
213
|
- exporters: Every file that imports/depends on this file — shows dependents rather than dependencies. Use before moving, renaming, or deleting.
|
|
208
214
|
- file_exports: File's public API: all exported functions, classes, variables with signatures. Use to understand what a module offers before importing. Requires explicit export markers; use toc --detailed as fallback.
|
|
209
215
|
- graph: File-level dependency tree. Use to understand module architecture — which files form a cluster, what the dependency chain looks like. Set direction ("imports"/"importers"/"both"). Can be noisy — use depth=1 for large codebases.
|
|
216
|
+
- circular_deps: Detect circular import chains. Shows cycle paths and involved files. Use file= to check a specific file, exclude= to ignore paths.
|
|
210
217
|
|
|
211
218
|
REFACTORING:
|
|
212
219
|
- verify <name>: Check all call sites match function signature (argument count). Run before adding/removing parameters to catch breakage early.
|
|
@@ -272,7 +279,8 @@ server.registerTool(
|
|
|
272
279
|
returns: z.string().optional().describe('Filter by return type (structural search). E.g. "Promise", "error".'),
|
|
273
280
|
decorator: z.string().optional().describe('Filter by decorator/annotation (structural search). E.g. "Route", "Test".'),
|
|
274
281
|
exported: z.boolean().optional().describe('Only exported/public symbols (structural search).'),
|
|
275
|
-
unused: z.boolean().optional().describe('Only symbols with zero callers (structural search).')
|
|
282
|
+
unused: z.boolean().optional().describe('Only symbols with zero callers (structural search).'),
|
|
283
|
+
framework: z.string().optional().describe('Filter entrypoints by framework (e.g. "express", "spring", "flask"). Comma-separated for multiple.')
|
|
276
284
|
|
|
277
285
|
})
|
|
278
286
|
},
|
|
@@ -294,7 +302,7 @@ server.registerTool(
|
|
|
294
302
|
// ── Commands using shared executor ─────────────────────────
|
|
295
303
|
|
|
296
304
|
case 'about': {
|
|
297
|
-
const index = getIndex(project_dir);
|
|
305
|
+
const index = getIndex(project_dir, ep);
|
|
298
306
|
const { ok, result, error } = execute(index, 'about', ep);
|
|
299
307
|
if (!ok) return toolResult(error); // soft error — won't kill sibling calls
|
|
300
308
|
return toolResult(output.formatAbout(result, {
|
|
@@ -305,7 +313,7 @@ server.registerTool(
|
|
|
305
313
|
}
|
|
306
314
|
|
|
307
315
|
case 'context': {
|
|
308
|
-
const index = getIndex(project_dir);
|
|
316
|
+
const index = getIndex(project_dir, ep);
|
|
309
317
|
const { ok, result: ctx, error } = execute(index, 'context', ep);
|
|
310
318
|
if (!ok) return toolResult(error); // context uses soft error (not toolError)
|
|
311
319
|
const { text, expandable } = output.formatContext(ctx, {
|
|
@@ -317,14 +325,14 @@ server.registerTool(
|
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
case 'impact': {
|
|
320
|
-
const index = getIndex(project_dir);
|
|
328
|
+
const index = getIndex(project_dir, ep);
|
|
321
329
|
const { ok, result, error } = execute(index, 'impact', ep);
|
|
322
330
|
if (!ok) return toolResult(error); // soft error
|
|
323
331
|
return toolResult(output.formatImpact(result));
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
case 'blast': {
|
|
327
|
-
const index = getIndex(project_dir);
|
|
335
|
+
const index = getIndex(project_dir, ep);
|
|
328
336
|
const { ok, result, error } = execute(index, 'blast', ep);
|
|
329
337
|
if (!ok) return toolResult(error); // soft error
|
|
330
338
|
return toolResult(output.formatBlast(result, {
|
|
@@ -333,14 +341,14 @@ server.registerTool(
|
|
|
333
341
|
}
|
|
334
342
|
|
|
335
343
|
case 'smart': {
|
|
336
|
-
const index = getIndex(project_dir);
|
|
344
|
+
const index = getIndex(project_dir, ep);
|
|
337
345
|
const { ok, result, error } = execute(index, 'smart', ep);
|
|
338
346
|
if (!ok) return toolResult(error); // soft error
|
|
339
347
|
return toolResult(output.formatSmart(result));
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
case 'trace': {
|
|
343
|
-
const index = getIndex(project_dir);
|
|
351
|
+
const index = getIndex(project_dir, ep);
|
|
344
352
|
const { ok, result, error } = execute(index, 'trace', ep);
|
|
345
353
|
if (!ok) return toolResult(error); // soft error
|
|
346
354
|
return toolResult(output.formatTrace(result, {
|
|
@@ -350,7 +358,7 @@ server.registerTool(
|
|
|
350
358
|
}
|
|
351
359
|
|
|
352
360
|
case 'reverse_trace': {
|
|
353
|
-
const index = getIndex(project_dir);
|
|
361
|
+
const index = getIndex(project_dir, ep);
|
|
354
362
|
const { ok, result, error } = execute(index, 'reverseTrace', ep);
|
|
355
363
|
if (!ok) return toolResult(error);
|
|
356
364
|
return toolResult(output.formatReverseTrace(result, {
|
|
@@ -359,7 +367,7 @@ server.registerTool(
|
|
|
359
367
|
}
|
|
360
368
|
|
|
361
369
|
case 'example': {
|
|
362
|
-
const index = getIndex(project_dir);
|
|
370
|
+
const index = getIndex(project_dir, ep);
|
|
363
371
|
const { ok, result, error } = execute(index, 'example', ep);
|
|
364
372
|
if (!ok) return toolResult(error);
|
|
365
373
|
if (!result) return toolResult(`No usage examples found for "${ep.name}".`);
|
|
@@ -367,7 +375,7 @@ server.registerTool(
|
|
|
367
375
|
}
|
|
368
376
|
|
|
369
377
|
case 'related': {
|
|
370
|
-
const index = getIndex(project_dir);
|
|
378
|
+
const index = getIndex(project_dir, ep);
|
|
371
379
|
const { ok, result, error } = execute(index, 'related', ep);
|
|
372
380
|
if (!ok) return toolResult(error);
|
|
373
381
|
if (!result) return toolResult(`Symbol "${ep.name}" not found.`);
|
|
@@ -380,7 +388,7 @@ server.registerTool(
|
|
|
380
388
|
// ── Finding Code ────────────────────────────────────────────
|
|
381
389
|
|
|
382
390
|
case 'find': {
|
|
383
|
-
const index = getIndex(project_dir);
|
|
391
|
+
const index = getIndex(project_dir, ep);
|
|
384
392
|
const { ok, result, error, note } = execute(index, 'find', ep);
|
|
385
393
|
if (!ok) return toolResult(error); // soft error
|
|
386
394
|
let text = output.formatFind(result, ep.name, ep.top);
|
|
@@ -389,7 +397,7 @@ server.registerTool(
|
|
|
389
397
|
}
|
|
390
398
|
|
|
391
399
|
case 'usages': {
|
|
392
|
-
const index = getIndex(project_dir);
|
|
400
|
+
const index = getIndex(project_dir, ep);
|
|
393
401
|
const { ok, result, error, note } = execute(index, 'usages', ep);
|
|
394
402
|
if (!ok) return toolResult(error); // soft error
|
|
395
403
|
let text = output.formatUsages(result, ep.name);
|
|
@@ -398,7 +406,7 @@ server.registerTool(
|
|
|
398
406
|
}
|
|
399
407
|
|
|
400
408
|
case 'toc': {
|
|
401
|
-
const index = getIndex(project_dir);
|
|
409
|
+
const index = getIndex(project_dir, ep);
|
|
402
410
|
const { ok, result, error, note } = execute(index, 'toc', ep);
|
|
403
411
|
if (!ok) return toolResult(error); // soft error
|
|
404
412
|
let text = output.formatToc(result, {
|
|
@@ -409,7 +417,7 @@ server.registerTool(
|
|
|
409
417
|
}
|
|
410
418
|
|
|
411
419
|
case 'search': {
|
|
412
|
-
const index = getIndex(project_dir);
|
|
420
|
+
const index = getIndex(project_dir, ep);
|
|
413
421
|
const { ok, result, error, structural } = execute(index, 'search', ep);
|
|
414
422
|
if (!ok) return toolResult(error); // soft error
|
|
415
423
|
if (structural) {
|
|
@@ -419,21 +427,21 @@ server.registerTool(
|
|
|
419
427
|
}
|
|
420
428
|
|
|
421
429
|
case 'tests': {
|
|
422
|
-
const index = getIndex(project_dir);
|
|
430
|
+
const index = getIndex(project_dir, ep);
|
|
423
431
|
const { ok, result, error } = execute(index, 'tests', ep);
|
|
424
432
|
if (!ok) return toolResult(error); // soft error
|
|
425
433
|
return toolResult(output.formatTests(result, ep.name));
|
|
426
434
|
}
|
|
427
435
|
|
|
428
436
|
case 'affected_tests': {
|
|
429
|
-
const index = getIndex(project_dir);
|
|
437
|
+
const index = getIndex(project_dir, ep);
|
|
430
438
|
const { ok, result, error } = execute(index, 'affectedTests', ep);
|
|
431
439
|
if (!ok) return toolResult(error);
|
|
432
440
|
return toolResult(output.formatAffectedTests(result));
|
|
433
441
|
}
|
|
434
442
|
|
|
435
443
|
case 'deadcode': {
|
|
436
|
-
const index = getIndex(project_dir);
|
|
444
|
+
const index = getIndex(project_dir, ep);
|
|
437
445
|
const { ok, result, error, note } = execute(index, 'deadcode', ep);
|
|
438
446
|
if (!ok) return toolResult(error); // soft error
|
|
439
447
|
const dcNote = note;
|
|
@@ -447,7 +455,7 @@ server.registerTool(
|
|
|
447
455
|
}
|
|
448
456
|
|
|
449
457
|
case 'entrypoints': {
|
|
450
|
-
const index = getIndex(project_dir);
|
|
458
|
+
const index = getIndex(project_dir, ep);
|
|
451
459
|
const { ok, result, error } = execute(index, 'entrypoints', ep);
|
|
452
460
|
if (!ok) return toolResult(error);
|
|
453
461
|
return toolResult(output.formatEntrypoints(result));
|
|
@@ -456,28 +464,28 @@ server.registerTool(
|
|
|
456
464
|
// ── File Dependencies ───────────────────────────────────────
|
|
457
465
|
|
|
458
466
|
case 'imports': {
|
|
459
|
-
const index = getIndex(project_dir);
|
|
467
|
+
const index = getIndex(project_dir, ep);
|
|
460
468
|
const { ok, result, error } = execute(index, 'imports', ep);
|
|
461
469
|
if (!ok) return toolResult(error); // soft error
|
|
462
470
|
return toolResult(output.formatImports(result, ep.file));
|
|
463
471
|
}
|
|
464
472
|
|
|
465
473
|
case 'exporters': {
|
|
466
|
-
const index = getIndex(project_dir);
|
|
474
|
+
const index = getIndex(project_dir, ep);
|
|
467
475
|
const { ok, result, error } = execute(index, 'exporters', ep);
|
|
468
476
|
if (!ok) return toolResult(error); // soft error
|
|
469
477
|
return toolResult(output.formatExporters(result, ep.file));
|
|
470
478
|
}
|
|
471
479
|
|
|
472
480
|
case 'file_exports': {
|
|
473
|
-
const index = getIndex(project_dir);
|
|
481
|
+
const index = getIndex(project_dir, ep);
|
|
474
482
|
const { ok, result, error } = execute(index, 'fileExports', ep);
|
|
475
483
|
if (!ok) return toolResult(error); // soft error
|
|
476
484
|
return toolResult(output.formatFileExports(result, ep.file));
|
|
477
485
|
}
|
|
478
486
|
|
|
479
487
|
case 'graph': {
|
|
480
|
-
const index = getIndex(project_dir);
|
|
488
|
+
const index = getIndex(project_dir, ep);
|
|
481
489
|
const { ok, result, error } = execute(index, 'graph', ep);
|
|
482
490
|
if (!ok) return toolResult(error); // soft error
|
|
483
491
|
return toolResult(output.formatGraph(result, {
|
|
@@ -489,7 +497,7 @@ server.registerTool(
|
|
|
489
497
|
}
|
|
490
498
|
|
|
491
499
|
case 'circular_deps': {
|
|
492
|
-
const index = getIndex(project_dir);
|
|
500
|
+
const index = getIndex(project_dir, ep);
|
|
493
501
|
const { ok, result, error } = execute(index, 'circularDeps', ep);
|
|
494
502
|
if (!ok) return toolResult(error);
|
|
495
503
|
return toolResult(output.formatCircularDeps(result));
|
|
@@ -498,21 +506,21 @@ server.registerTool(
|
|
|
498
506
|
// ── Refactoring ─────────────────────────────────────────────
|
|
499
507
|
|
|
500
508
|
case 'verify': {
|
|
501
|
-
const index = getIndex(project_dir);
|
|
509
|
+
const index = getIndex(project_dir, ep);
|
|
502
510
|
const { ok, result, error } = execute(index, 'verify', ep);
|
|
503
511
|
if (!ok) return toolResult(error); // soft error
|
|
504
512
|
return toolResult(output.formatVerify(result));
|
|
505
513
|
}
|
|
506
514
|
|
|
507
515
|
case 'plan': {
|
|
508
|
-
const index = getIndex(project_dir);
|
|
516
|
+
const index = getIndex(project_dir, ep);
|
|
509
517
|
const { ok, result, error } = execute(index, 'plan', ep);
|
|
510
518
|
if (!ok) return toolResult(error); // soft error
|
|
511
519
|
return toolResult(output.formatPlan(result));
|
|
512
520
|
}
|
|
513
521
|
|
|
514
522
|
case 'diff_impact': {
|
|
515
|
-
const index = getIndex(project_dir);
|
|
523
|
+
const index = getIndex(project_dir, ep);
|
|
516
524
|
const { ok, result, error } = execute(index, 'diffImpact', ep);
|
|
517
525
|
if (!ok) return toolResult(error); // soft error — e.g. "not a git repo"
|
|
518
526
|
return toolResult(output.formatDiffImpact(result));
|
|
@@ -521,21 +529,21 @@ server.registerTool(
|
|
|
521
529
|
// ── Other ───────────────────────────────────────────────────
|
|
522
530
|
|
|
523
531
|
case 'typedef': {
|
|
524
|
-
const index = getIndex(project_dir);
|
|
532
|
+
const index = getIndex(project_dir, ep);
|
|
525
533
|
const { ok, result, error } = execute(index, 'typedef', ep);
|
|
526
534
|
if (!ok) return toolResult(error); // soft error
|
|
527
535
|
return toolResult(output.formatTypedef(result, ep.name));
|
|
528
536
|
}
|
|
529
537
|
|
|
530
538
|
case 'stacktrace': {
|
|
531
|
-
const index = getIndex(project_dir);
|
|
539
|
+
const index = getIndex(project_dir, ep);
|
|
532
540
|
const { ok, result, error } = execute(index, 'stacktrace', ep);
|
|
533
541
|
if (!ok) return toolResult(error); // soft error
|
|
534
542
|
return toolResult(output.formatStackTrace(result));
|
|
535
543
|
}
|
|
536
544
|
|
|
537
545
|
case 'api': {
|
|
538
|
-
const index = getIndex(project_dir);
|
|
546
|
+
const index = getIndex(project_dir, ep);
|
|
539
547
|
const { ok, result, error, note } = execute(index, 'api', ep);
|
|
540
548
|
if (!ok) return toolResult(error); // soft error
|
|
541
549
|
let apiText = output.formatApi(result, ep.file || '.');
|
|
@@ -544,7 +552,7 @@ server.registerTool(
|
|
|
544
552
|
}
|
|
545
553
|
|
|
546
554
|
case 'stats': {
|
|
547
|
-
const index = getIndex(project_dir);
|
|
555
|
+
const index = getIndex(project_dir, ep);
|
|
548
556
|
const { ok, result, error } = execute(index, 'stats', ep);
|
|
549
557
|
if (!ok) return toolResult(error); // soft error
|
|
550
558
|
return toolResult(output.formatStats(result, { top: ep.top || 0 }));
|
|
@@ -555,7 +563,7 @@ server.registerTool(
|
|
|
555
563
|
case 'fn': {
|
|
556
564
|
const err = requireName(ep.name);
|
|
557
565
|
if (err) return err;
|
|
558
|
-
const index = getIndex(project_dir);
|
|
566
|
+
const index = getIndex(project_dir, ep);
|
|
559
567
|
const { ok, result, error } = execute(index, 'fn', ep);
|
|
560
568
|
if (!ok) return toolResult(error); // soft error
|
|
561
569
|
// MCP path security: validate all result files are within project root
|
|
@@ -573,7 +581,7 @@ server.registerTool(
|
|
|
573
581
|
if (ep.maxLines !== undefined && (!Number.isInteger(ep.maxLines) || ep.maxLines < 1)) {
|
|
574
582
|
return toolError(`Invalid max_lines: ${ep.maxLines}. Must be a positive integer.`);
|
|
575
583
|
}
|
|
576
|
-
const index = getIndex(project_dir);
|
|
584
|
+
const index = getIndex(project_dir, ep);
|
|
577
585
|
const { ok, result, error } = execute(index, 'class', ep);
|
|
578
586
|
if (!ok) return toolResult(error); // soft error (class not found)
|
|
579
587
|
// MCP path security: validate all result files are within project root
|
|
@@ -586,7 +594,7 @@ server.registerTool(
|
|
|
586
594
|
}
|
|
587
595
|
|
|
588
596
|
case 'lines': {
|
|
589
|
-
const index = getIndex(project_dir);
|
|
597
|
+
const index = getIndex(project_dir, ep);
|
|
590
598
|
const { ok, result, error } = execute(index, 'lines', ep);
|
|
591
599
|
if (!ok) return toolResult(error); // soft error
|
|
592
600
|
// MCP path security: validate file is within project root
|
|
@@ -599,7 +607,7 @@ server.registerTool(
|
|
|
599
607
|
if (ep.item === undefined || ep.item === null) {
|
|
600
608
|
return toolError('Item number is required (e.g. item=1).');
|
|
601
609
|
}
|
|
602
|
-
const index = getIndex(project_dir);
|
|
610
|
+
const index = getIndex(project_dir, ep);
|
|
603
611
|
const lookup = expandCacheInstance.lookup(index.root, ep.item);
|
|
604
612
|
const { ok, result, error } = execute(index, 'expand', {
|
|
605
613
|
match: lookup.match, itemNum: ep.item,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.6",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
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",
|