ucn 3.4.4 → 3.4.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/README.md CHANGED
@@ -134,45 +134,31 @@ EXTERNAL:
134
134
  fs, path, crypto
135
135
  ```
136
136
 
137
- ## Supported Languages
138
-
139
- JavaScript, TypeScript, Python, Go, Rust, Java
140
-
141
- ## Install
137
+ ## Workflows
142
138
 
139
+ **Investigating a bug:**
143
140
  ```bash
144
- npm install -g ucn
141
+ ucn about problematic_function # Understand it fully
142
+ ucn trace problematic_function --depth=2 # See what it calls
145
143
  ```
146
144
 
147
- ### Claude Code (optional)
148
-
149
- To use UCN as a skill in Claude Code:
150
-
145
+ **Before modifying a function:**
151
146
  ```bash
152
- mkdir -p ~/.claude/skills
153
-
154
- # If installed via npm:
155
- cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.claude/skills/
156
-
157
- # If cloned from git:
158
- git clone https://github.com/mleoca/ucn.git
159
- cp -r ucn/.claude/skills/ucn ~/.claude/skills/
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?
160
151
  ```
161
152
 
162
- ### Codex (optional)
163
-
164
- To use UCN as a skill in OpenAI Codex:
165
-
153
+ **Periodic cleanup:**
166
154
  ```bash
167
- mkdir -p ~/.agents/skills
155
+ ucn deadcode --exclude=test # What can be deleted?
156
+ ucn toc # Project overview
157
+ ```
168
158
 
169
- # If installed via npm:
170
- cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.agents/skills/
159
+ ## Supported Languages
171
160
 
172
- # If cloned from git:
173
- git clone https://github.com/mleoca/ucn.git
174
- cp -r ucn/.claude/skills/ucn ~/.agents/skills/
175
- ```
161
+ JavaScript, TypeScript, Python, Go, Rust, Java
176
162
 
177
163
  ## Usage
178
164
 
@@ -257,6 +243,7 @@ Common Flags:
257
243
  --clear-cache Clear cache before running
258
244
  --no-follow-symlinks Don't follow symbolic links
259
245
  -i, --interactive Keep index in memory for multiple queries
246
+ --mcp Start as MCP server (stdio transport)
260
247
 
261
248
  Quick Start:
262
249
  ucn toc # See project structure (compact)
@@ -267,26 +254,85 @@ Quick Start:
267
254
  ucn --interactive # Multiple queries
268
255
  ```
269
256
 
270
- ## Workflows
257
+ ## Install
271
258
 
272
- **Investigating a bug:**
273
259
  ```bash
274
- ucn about problematic_function # Understand it fully
275
- ucn trace problematic_function --depth=2 # See what it calls
260
+ npm install -g ucn
276
261
  ```
277
262
 
278
- **Before modifying a function:**
263
+ ### MCP Server
264
+
265
+ UCN includes a built-in [MCP](https://modelcontextprotocol.io) server, so any MCP-compatible AI client can use it as a tool. It 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`).
266
+
267
+ **One-line setup** (for clients that support it):
268
+
269
+ | Client | Command |
270
+ |--------|---------|
271
+ | Claude Code | `claude mcp add ucn -- npx -y ucn --mcp` |
272
+ | OpenAI Codex CLI | `codex mcp add ucn -- npx -y ucn --mcp` |
273
+ | VS Code Copilot | `code --add-mcp '{"name":"ucn","command":"npx","args":["-y","ucn","--mcp"]}'` |
274
+
275
+ **Manual config** — add to the appropriate config file for your client:
276
+
277
+ | Client | Config file |
278
+ |--------|-------------|
279
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
280
+ | Cursor | `~/.cursor/mcp.json` or `.cursor/mcp.json` in project |
281
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
282
+ | Cline | VS Code sidebar > MCP Servers > Configure |
283
+ | Claude Code | `~/.claude/mcp-config.json` |
284
+
285
+ ```json
286
+ {
287
+ "mcpServers": {
288
+ "ucn": {
289
+ "command": "npx",
290
+ "args": ["-y", "ucn", "--mcp"]
291
+ }
292
+ }
293
+ }
294
+ ```
295
+
296
+ <details>
297
+ <summary>VS Code Copilot uses a slightly different format (<code>.vscode/mcp.json</code>)</summary>
298
+
299
+ ```json
300
+ {
301
+ "servers": {
302
+ "ucn": {
303
+ "type": "stdio",
304
+ "command": "npx",
305
+ "args": ["-y", "ucn", "--mcp"]
306
+ }
307
+ }
308
+ }
309
+ ```
310
+ </details>
311
+
312
+ ### Claude Code Skill (alternative to MCP)
313
+
279
314
  ```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?
315
+ mkdir -p ~/.claude/skills
316
+
317
+ # If installed via npm:
318
+ cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.claude/skills/
319
+
320
+ # If cloned from git:
321
+ git clone https://github.com/mleoca/ucn.git
322
+ cp -r ucn/.claude/skills/ucn ~/.claude/skills/
284
323
  ```
285
324
 
286
- **Periodic cleanup:**
325
+ ### Codex Skill (alternative to MCP)
326
+
287
327
  ```bash
288
- ucn deadcode --exclude=test # What can be deleted?
289
- ucn toc # Project overview
328
+ mkdir -p ~/.agents/skills
329
+
330
+ # If installed via npm:
331
+ cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.agents/skills/
332
+
333
+ # If cloned from git:
334
+ git clone https://github.com/mleoca/ucn.git
335
+ cp -r ucn/.claude/skills/ucn ~/.agents/skills/
290
336
  ```
291
337
 
292
338
  ## License
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
@@ -1803,7 +1803,7 @@ class ProjectIndex {
1803
1803
  // Find all test files
1804
1804
  const testFiles = [];
1805
1805
  for (const [filePath, fileEntry] of this.files) {
1806
- if (isTestFile(filePath, fileEntry.language)) {
1806
+ if (isTestFile(fileEntry.relativePath, fileEntry.language)) {
1807
1807
  testFiles.push({ path: filePath, entry: fileEntry });
1808
1808
  }
1809
1809
  }
@@ -2108,14 +2108,13 @@ class ProjectIndex {
2108
2108
  continue;
2109
2109
  }
2110
2110
 
2111
+ const fileEntry = this.files.get(symbol.file);
2112
+ const lang = fileEntry?.language;
2113
+
2111
2114
  // Skip test files unless requested
2112
- if (!options.includeTests && isTestFile(symbol.file, symbol.language)) {
2115
+ if (!options.includeTests && isTestFile(symbol.relativePath, lang)) {
2113
2116
  continue;
2114
2117
  }
2115
-
2116
- // Check if exported
2117
- const fileEntry = this.files.get(symbol.file);
2118
- const lang = fileEntry?.language;
2119
2118
  const mods = symbol.modifiers || [];
2120
2119
 
2121
2120
  // Language-specific entry points (called by runtime, no AST-visible callers)
@@ -3653,7 +3652,7 @@ class ProjectIndex {
3653
3652
  totalState += state.length;
3654
3653
  totalLines += fileEntry.lines;
3655
3654
  totalDynamic += fileEntry.dynamicImports || 0;
3656
- if (isTestFile(filePath)) totalTests += 1;
3655
+ if (isTestFile(fileEntry.relativePath, fileEntry.language)) totalTests += 1;
3657
3656
 
3658
3657
  const entry = {
3659
3658
  file: fileEntry.relativePath,