ucn 3.4.3 → 3.4.5

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/README.md CHANGED
@@ -134,6 +134,27 @@ EXTERNAL:
134
134
  fs, path, crypto
135
135
  ```
136
136
 
137
+ ## Workflows
138
+
139
+ **Investigating a bug:**
140
+ ```bash
141
+ ucn about problematic_function # Understand it fully
142
+ ucn trace problematic_function --depth=2 # See what it calls
143
+ ```
144
+
145
+ **Before modifying a function:**
146
+ ```bash
147
+ ucn impact the_function # Who will break?
148
+ ucn smart the_function # See it + its helpers
149
+ # ... make your changes ...
150
+ ucn verify the_function # Did all call sites survive?
151
+ ```
152
+
153
+ **Periodic cleanup:**
154
+ ```bash
155
+ ucn deadcode --exclude=test # What can be deleted?
156
+ ucn toc # Project overview
157
+ ```
137
158
  ## Supported Languages
138
159
 
139
160
  JavaScript, TypeScript, Python, Go, Rust, Java
@@ -144,9 +165,88 @@ JavaScript, TypeScript, Python, Go, Rust, Java
144
165
  npm install -g ucn
145
166
  ```
146
167
 
147
- ### Claude Code (optional)
168
+ ### MCP Server
169
+
170
+ UCN includes a built-in [MCP](https://modelcontextprotocol.io) server, so any MCP-compatible AI client can use it as a tool.
171
+
172
+ **Claude Code** (`~/.claude/mcp-config.json`):
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "ucn": {
177
+ "command": "npx",
178
+ "args": ["-y", "ucn", "--mcp"]
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ **Claude Desktop** (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
185
+ ```json
186
+ {
187
+ "mcpServers": {
188
+ "ucn": {
189
+ "command": "npx",
190
+ "args": ["-y", "ucn", "--mcp"]
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ **Cursor** (`~/.cursor/mcp.json` or `.cursor/mcp.json` in project):
197
+ ```json
198
+ {
199
+ "mcpServers": {
200
+ "ucn": {
201
+ "command": "npx",
202
+ "args": ["-y", "ucn", "--mcp"]
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ **Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
209
+ ```json
210
+ {
211
+ "mcpServers": {
212
+ "ucn": {
213
+ "command": "npx",
214
+ "args": ["-y", "ucn", "--mcp"]
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ **VS Code Copilot** (`.vscode/mcp.json`):
221
+ ```json
222
+ {
223
+ "servers": {
224
+ "ucn": {
225
+ "type": "stdio",
226
+ "command": "npx",
227
+ "args": ["-y", "ucn", "--mcp"]
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ **Zed** (Settings > `settings.json`):
234
+ ```json
235
+ {
236
+ "context_servers": {
237
+ "ucn": {
238
+ "command": "npx",
239
+ "args": ["-y", "ucn", "--mcp"]
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ The MCP server exposes 27 tools: `ucn_about`, `ucn_context`, `ucn_impact`, `ucn_smart`, `ucn_trace`, `ucn_find`, `ucn_usages`, `ucn_toc`, `ucn_deadcode`, `ucn_fn`, `ucn_class`, `ucn_verify`, `ucn_imports`, `ucn_exporters`, `ucn_tests`, `ucn_related`, `ucn_graph`, `ucn_file_exports`, `ucn_search`, `ucn_plan`, `ucn_typedef`, `ucn_stacktrace`, `ucn_example`, `ucn_expand`, `ucn_lines`, `ucn_api`, `ucn_stats`.
148
246
 
149
- To use UCN as a skill in Claude Code:
247
+ ### Claude Code Skill (alternative)
248
+
249
+ To use UCN as a skill in Claude Code (alternative to MCP):
150
250
 
151
251
  ```bash
152
252
  mkdir -p ~/.claude/skills
@@ -257,6 +357,7 @@ Common Flags:
257
357
  --clear-cache Clear cache before running
258
358
  --no-follow-symlinks Don't follow symbolic links
259
359
  -i, --interactive Keep index in memory for multiple queries
360
+ --mcp Start as MCP server (stdio transport)
260
361
 
261
362
  Quick Start:
262
363
  ucn toc # See project structure (compact)
@@ -267,28 +368,6 @@ Quick Start:
267
368
  ucn --interactive # Multiple queries
268
369
  ```
269
370
 
270
- ## Workflows
271
-
272
- **Investigating a bug:**
273
- ```bash
274
- ucn about problematic_function # Understand it fully
275
- ucn trace problematic_function --depth=2 # See what it calls
276
- ```
277
-
278
- **Before modifying a function:**
279
- ```bash
280
- ucn impact the_function # Who will break?
281
- ucn smart the_function # See it + its helpers
282
- # ... make your changes ...
283
- ucn verify the_function # Did all call sites survive?
284
- ```
285
-
286
- **Periodic cleanup:**
287
- ```bash
288
- ucn deadcode --exclude=test # What can be deleted?
289
- ucn toc # Project overview
290
- ```
291
-
292
371
  ## License
293
372
 
294
373
  MIT
package/cli/index.js CHANGED
@@ -22,6 +22,10 @@ const output = require('../core/output');
22
22
 
23
23
  const rawArgs = process.argv.slice(2);
24
24
 
25
+ // MCP server mode — launch server and skip CLI
26
+ if (rawArgs.includes('--mcp')) {
27
+ require('../mcp/server.js');
28
+ } else {
25
29
  // Support -- to separate flags from positional arguments
26
30
  const doubleDashIdx = rawArgs.indexOf('--');
27
31
  const args = doubleDashIdx === -1 ? rawArgs : rawArgs.slice(0, doubleDashIdx);
@@ -78,7 +82,7 @@ if (fileArgIdx !== -1 && args[fileArgIdx + 1]) {
78
82
 
79
83
  // Known flags for validation
80
84
  const knownFlags = new Set([
81
- '--help', '-h',
85
+ '--help', '-h', '--mcp',
82
86
  '--json', '--verbose', '--no-quiet', '--quiet',
83
87
  '--code-only', '--with-types', '--top-level', '--exact',
84
88
  '--no-cache', '--clear-cache', '--include-tests',
@@ -2542,3 +2546,5 @@ if (flags.interactive) {
2542
2546
  } else {
2543
2547
  main();
2544
2548
  }
2549
+
2550
+ } // end of --mcp else block
package/core/discovery.js CHANGED
@@ -298,7 +298,8 @@ function shouldIgnore(name, ignores, parentDir) {
298
298
  }
299
299
 
300
300
  // Check conditional ignores (only if parentDir provided)
301
- if (parentDir && CONDITIONAL_IGNORES[name]) {
301
+ // Use Array.isArray to avoid matching Object.prototype properties (e.g. dir named "constructor")
302
+ if (parentDir && Array.isArray(CONDITIONAL_IGNORES[name])) {
302
303
  const markers = CONDITIONAL_IGNORES[name];
303
304
  for (const marker of markers) {
304
305
  if (fs.existsSync(path.join(parentDir, marker))) {
package/core/project.js CHANGED
@@ -1021,8 +1021,8 @@ class ProjectIndex {
1021
1021
  const matchesDef = definitions.some(d => d.className === targetClass);
1022
1022
  if (!matchesDef) continue;
1023
1023
  // Falls through to add as caller
1024
- } else if (fileEntry.language === 'python' && ['self', 'cls'].includes(call.receiver)) {
1025
- // self.method() / cls.method() — resolve to same-class method
1024
+ } else if (['self', 'cls', 'this'].includes(call.receiver)) {
1025
+ // self.method() / cls.method() / this.method() — resolve to same-class method
1026
1026
  const callerSymbol = this.findEnclosingFunction(filePath, call.line, true);
1027
1027
  if (!callerSymbol?.className) continue;
1028
1028
  // Check if any definition of searched function belongs to caller's class
@@ -1030,8 +1030,6 @@ class ProjectIndex {
1030
1030
  if (!matchesDef) continue;
1031
1031
  // Falls through to add as caller
1032
1032
  } else {
1033
- // Always skip this/self/cls calls (internal state access, not function calls)
1034
- if (['this', 'self', 'cls'].includes(call.receiver)) continue;
1035
1033
  // Go doesn't use this/self/cls - always include Go method calls
1036
1034
  // For other languages, skip method calls unless explicitly requested
1037
1035
  if (fileEntry.language !== 'go' && !options.includeMethods) continue;
@@ -1143,14 +1141,14 @@ class ProjectIndex {
1143
1141
 
1144
1142
  // Smart method call handling:
1145
1143
  // - Go: include all method calls (Go doesn't use this/self/cls)
1146
- // - Python self.method(): resolve to same-class method (handled below)
1144
+ // - self/this.method(): resolve to same-class method (handled below)
1147
1145
  // - Python self.attr.method(): resolve via selfAttribute (handled below)
1148
1146
  // - Other languages: skip method calls unless explicitly requested
1149
1147
  if (call.isMethod) {
1150
1148
  if (call.selfAttribute && language === 'python') {
1151
1149
  // Will be resolved in second pass below
1152
- } else if (language === 'python' && ['self', 'cls'].includes(call.receiver)) {
1153
- // self.method() / cls.method() — resolve to same-class method below
1150
+ } else if (['self', 'cls', 'this'].includes(call.receiver)) {
1151
+ // self.method() / cls.method() / this.method() — resolve to same-class method below
1154
1152
  } else if (language !== 'go' && !options.includeMethods) {
1155
1153
  continue;
1156
1154
  }
@@ -1166,8 +1164,8 @@ class ProjectIndex {
1166
1164
  continue;
1167
1165
  }
1168
1166
 
1169
- // Collect Python self.method() calls for same-class resolution
1170
- if (language === 'python' && call.isMethod && ['self', 'cls'].includes(call.receiver)) {
1167
+ // Collect self/this.method() calls for same-class resolution
1168
+ if (call.isMethod && ['self', 'cls', 'this'].includes(call.receiver)) {
1171
1169
  if (!selfMethodCalls) selfMethodCalls = [];
1172
1170
  selfMethodCalls.push(call);
1173
1171
  continue;
@@ -1255,7 +1253,7 @@ class ProjectIndex {
1255
1253
  }
1256
1254
  }
1257
1255
 
1258
- // Third pass: resolve Python self.method() calls to same-class methods
1256
+ // Third pass: resolve self/this.method() calls to same-class methods
1259
1257
  if (selfMethodCalls && def.className) {
1260
1258
  for (const call of selfMethodCalls) {
1261
1259
  const symbols = this.symbols.get(call.name);
@@ -1590,7 +1588,7 @@ class ProjectIndex {
1590
1588
  const fileEntry = this.files.get(filePath);
1591
1589
  if (!fileEntry) return null;
1592
1590
 
1593
- const nonCallableTypes = new Set(['class', 'struct', 'interface', 'type', 'state']);
1591
+ const nonCallableTypes = new Set(['class', 'struct', 'interface', 'type', 'state', 'impl']);
1594
1592
  for (const symbol of fileEntry.symbols) {
1595
1593
  if (!nonCallableTypes.has(symbol.type) &&
1596
1594
  symbol.startLine <= lineNum &&
package/languages/java.js CHANGED
@@ -567,7 +567,7 @@ function findCallsInCode(code, parser) {
567
567
  name: nameNode.text,
568
568
  line: node.startPosition.row + 1,
569
569
  isMethod: !!objNode,
570
- receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
570
+ receiver: (objNode?.type === 'identifier' || objNode?.type === 'this') ? objNode.text : undefined,
571
571
  enclosingFunction
572
572
  });
573
573
  }
package/languages/rust.js CHANGED
@@ -622,7 +622,7 @@ function findCallsInCode(code, parser) {
622
622
  name: fieldNode.text,
623
623
  line: node.startPosition.row + 1,
624
624
  isMethod: true,
625
- receiver: valueNode?.type === 'identifier' ? valueNode.text : undefined,
625
+ receiver: (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined,
626
626
  enclosingFunction
627
627
  });
628
628
  }