ucn 3.7.31 → 3.7.33

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/core/callers.js CHANGED
@@ -227,47 +227,54 @@ function findCallers(index, name, options = {}) {
227
227
  if (call.selfAttribute && fileEntry.language === 'python') {
228
228
  // self.attr.method() — resolve via attribute type inference
229
229
  const callerSymbol = index.findEnclosingFunction(filePath, call.line, true);
230
- if (!callerSymbol?.className) continue;
231
- const attrTypes = getInstanceAttributeTypes(index, filePath, callerSymbol.className);
232
- if (!attrTypes) continue;
233
- const targetClass = attrTypes.get(call.selfAttribute);
234
- if (!targetClass) continue;
235
- // Check if any definition of searched function belongs to targetClass
236
- const matchesDef = definitions.some(d => d.className === targetClass);
237
- if (!matchesDef) continue;
238
- resolvedBySameClass = true;
239
- // Falls through to add as caller
230
+ if (!callerSymbol?.className) {
231
+ // Can't resolve include only if includeMethods requested
232
+ if (!options.includeMethods) continue;
233
+ } else {
234
+ const attrTypes = getInstanceAttributeTypes(index, filePath, callerSymbol.className);
235
+ const targetClass = attrTypes?.get(call.selfAttribute);
236
+ if (targetClass && definitions.some(d => d.className === targetClass)) {
237
+ resolvedBySameClass = true;
238
+ } else if (!options.includeMethods) {
239
+ continue;
240
+ }
241
+ }
240
242
  } else if (['self', 'cls', 'this', 'super'].includes(call.receiver)) {
241
243
  // self/this/super.method() — resolve to same-class or parent method
242
244
  const callerSymbol = index.findEnclosingFunction(filePath, call.line, true);
243
- if (!callerSymbol?.className) continue;
244
- // For super(), skip same-class — only check parent chain
245
- let matchesDef = call.receiver === 'super'
246
- ? false
247
- : definitions.some(d => d.className === callerSymbol.className);
248
- // Walk inheritance chain using BFS if not found in same class
249
- if (!matchesDef) {
250
- const visited = new Set([callerSymbol.className]);
251
- const callerFile = callerSymbol.file || filePath;
252
- const startParents = index._getInheritanceParents(callerSymbol.className, callerFile) || [];
253
- const queue = startParents.map(p => ({ name: p, contextFile: callerFile }));
254
- while (queue.length > 0 && !matchesDef) {
255
- const { name: current, contextFile } = queue.shift();
256
- if (visited.has(current)) continue;
257
- visited.add(current);
258
- matchesDef = definitions.some(d => d.className === current);
259
- if (!matchesDef) {
260
- const resolvedFile = index._resolveClassFile(current, contextFile);
261
- const grandparents = index._getInheritanceParents(current, resolvedFile) || [];
262
- for (const gp of grandparents) {
263
- if (!visited.has(gp)) queue.push({ name: gp, contextFile: resolvedFile });
245
+ if (!callerSymbol?.className) {
246
+ if (!options.includeMethods) continue;
247
+ } else {
248
+ // For super(), skip same-class — only check parent chain
249
+ let matchesDef = call.receiver === 'super'
250
+ ? false
251
+ : definitions.some(d => d.className === callerSymbol.className);
252
+ // Walk inheritance chain using BFS if not found in same class
253
+ if (!matchesDef) {
254
+ const visited = new Set([callerSymbol.className]);
255
+ const callerFile = callerSymbol.file || filePath;
256
+ const startParents = index._getInheritanceParents(callerSymbol.className, callerFile) || [];
257
+ const queue = startParents.map(p => ({ name: p, contextFile: callerFile }));
258
+ while (queue.length > 0 && !matchesDef) {
259
+ const { name: current, contextFile } = queue.shift();
260
+ if (visited.has(current)) continue;
261
+ visited.add(current);
262
+ matchesDef = definitions.some(d => d.className === current);
263
+ if (!matchesDef) {
264
+ const resolvedFile = index._resolveClassFile(current, contextFile);
265
+ const grandparents = index._getInheritanceParents(current, resolvedFile) || [];
266
+ for (const gp of grandparents) {
267
+ if (!visited.has(gp)) queue.push({ name: gp, contextFile: resolvedFile });
268
+ }
264
269
  }
265
270
  }
266
271
  }
272
+ if (matchesDef) {
273
+ resolvedBySameClass = true;
274
+ } else if (!options.includeMethods) {
275
+ continue;
276
+ }
267
277
  }
268
- if (!matchesDef) continue;
269
- resolvedBySameClass = true;
270
- // Falls through to add as caller
271
278
  } else {
272
279
  // Go doesn't use this/self/cls - always include Go method calls
273
280
  // Java method calls are always obj.method() - include by default
package/core/project.js CHANGED
@@ -2420,7 +2420,7 @@ class ProjectIndex {
2420
2420
  const symParts = symName.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase().split('_');
2421
2421
 
2422
2422
  // Check for shared parts (require ≥50% of the longer name to match)
2423
- const sharedParts = nameParts.filter(p => symParts.includes(p) && p.length > 2);
2423
+ const sharedParts = nameParts.filter(p => symParts.includes(p) && p.length > 3);
2424
2424
  const maxParts = Math.max(nameParts.length, symParts.length);
2425
2425
  if (sharedParts.length > 0 && sharedParts.length / maxParts >= 0.5) {
2426
2426
  const sym = symbols[0];
@@ -2786,7 +2786,7 @@ class ProjectIndex {
2786
2786
  try {
2787
2787
  const maxCallers = options.all ? Infinity : (options.maxCallers || 10);
2788
2788
  const maxCallees = options.all ? Infinity : (options.maxCallees || 10);
2789
- const includeMethods = options.includeMethods ?? true;
2789
+ const includeMethods = options.includeMethods ?? false;
2790
2790
 
2791
2791
  // Find symbol definition(s) — skip counts since about() computes its own via usages()
2792
2792
  const definitions = this.find(name, { exact: true, file: options.file, className: options.className, skipCounts: true });
package/core/verify.js CHANGED
@@ -327,6 +327,14 @@ function plan(index, name, options = {}) {
327
327
  let changes = [];
328
328
 
329
329
  if (options.addParam) {
330
+ // Check if parameter already exists
331
+ if (currentParams.some(p => p.name === options.addParam)) {
332
+ return {
333
+ found: true,
334
+ error: `Parameter "${options.addParam}" already exists in ${name}`,
335
+ currentParams: currentParams.map(p => p.name)
336
+ };
337
+ }
330
338
  operation = 'add-param';
331
339
  const newParam = {
332
340
  name: options.addParam,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.31",
3
+ "version": "3.7.33",
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",