ucn 3.7.37 → 3.7.39

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/registry.js CHANGED
@@ -63,6 +63,7 @@ const PARAM_MAP = {
63
63
  include_exported: 'includeExported',
64
64
  include_decorated: 'includeDecorated',
65
65
  calls_only: 'callsOnly',
66
+ class_name: 'className',
66
67
  max_lines: 'maxLines',
67
68
  add_param: 'addParam',
68
69
  remove_param: 'removeParam',
package/mcp/server.js CHANGED
@@ -251,17 +251,18 @@ server.registerTool(
251
251
  staged: z.boolean().optional().describe('Analyze staged changes (diff_impact command)'),
252
252
  case_sensitive: z.boolean().optional().describe('Case-sensitive search (default: false, case-insensitive)'),
253
253
  all: z.boolean().optional().describe('Show all results (expand truncated sections). Applies to about, toc, related, trace, and others.'),
254
- top_level: z.boolean().optional().describe('Show only top-level functions in toc (exclude nested/indented)')
254
+ top_level: z.boolean().optional().describe('Show only top-level functions in toc (exclude nested/indented)'),
255
+ class_name: z.string().optional().describe('Class name to scope method analysis (e.g. "MarketDataFetcher" for close)')
256
+
255
257
  })
256
258
  },
257
259
  async (args) => {
258
- const { command, project_dir, name, file, exclude, include_tests,
259
- include_methods, include_uncertain, with_types, detailed,
260
- exact, in: inPath, top, depth, code_only, context: ctxLines,
261
- include_exported, include_decorated, calls_only, max_lines,
262
- direction, term, add_param, remove_param, rename_to,
263
- default_value, stack, item, range, base, staged,
264
- case_sensitive, regex, functions, all, top_level } = args;
260
+ const { command, project_dir } = args;
261
+
262
+ // Normalize ALL params once execute() handlers pick what they need.
263
+ // This eliminates per-case param selection and prevents CLI/MCP drift.
264
+ const { command: _c, project_dir: _p, ...rawParams } = args;
265
+ const ep = normalizeParams(rawParams);
265
266
 
266
267
  try {
267
268
  switch (command) {
@@ -274,7 +275,6 @@ server.registerTool(
274
275
 
275
276
  case 'about': {
276
277
  const index = getIndex(project_dir);
277
- const ep = normalizeParams({ name, file, exclude, with_types, all, include_methods, include_uncertain, top });
278
278
  const { ok, result, error } = execute(index, 'about', ep);
279
279
  if (!ok) return toolResult(error); // soft error — won't kill sibling calls
280
280
  return toolResult(output.formatAbout(result, {
@@ -285,19 +285,17 @@ server.registerTool(
285
285
 
286
286
  case 'context': {
287
287
  const index = getIndex(project_dir);
288
- const ep = normalizeParams({ name, file, exclude, include_methods, include_uncertain });
289
288
  const { ok, result: ctx, error } = execute(index, 'context', ep);
290
289
  if (!ok) return toolResult(error); // context uses soft error (not toolError)
291
290
  const { text, expandable } = output.formatContext(ctx, {
292
291
  expandHint: 'Use expand command with item number to see code for any item.'
293
292
  });
294
- expandCacheInstance.save(index.root, name, file, expandable);
293
+ expandCacheInstance.save(index.root, ep.name, ep.file, expandable);
295
294
  return toolResult(text);
296
295
  }
297
296
 
298
297
  case 'impact': {
299
298
  const index = getIndex(project_dir);
300
- const ep = normalizeParams({ name, file, exclude, top });
301
299
  const { ok, result, error } = execute(index, 'impact', ep);
302
300
  if (!ok) return toolResult(error); // soft error
303
301
  return toolResult(output.formatImpact(result));
@@ -305,7 +303,6 @@ server.registerTool(
305
303
 
306
304
  case 'smart': {
307
305
  const index = getIndex(project_dir);
308
- const ep = normalizeParams({ name, file, with_types, include_methods, include_uncertain });
309
306
  const { ok, result, error } = execute(index, 'smart', ep);
310
307
  if (!ok) return toolResult(error); // soft error
311
308
  return toolResult(output.formatSmart(result));
@@ -313,7 +310,6 @@ server.registerTool(
313
310
 
314
311
  case 'trace': {
315
312
  const index = getIndex(project_dir);
316
- const ep = normalizeParams({ name, file, depth, all, include_methods, include_uncertain });
317
313
  const { ok, result, error } = execute(index, 'trace', ep);
318
314
  if (!ok) return toolResult(error); // soft error
319
315
  return toolResult(output.formatTrace(result, {
@@ -324,19 +320,19 @@ server.registerTool(
324
320
 
325
321
  case 'example': {
326
322
  const index = getIndex(project_dir);
327
- const { ok, result, error } = execute(index, 'example', { name });
323
+ const { ok, result, error } = execute(index, 'example', ep);
328
324
  if (!ok) return toolResult(error);
329
- if (!result) return toolResult(`No usage examples found for "${name}".`);
330
- return toolResult(output.formatExample(result, name));
325
+ if (!result) return toolResult(`No usage examples found for "${ep.name}".`);
326
+ return toolResult(output.formatExample(result, ep.name));
331
327
  }
332
328
 
333
329
  case 'related': {
334
330
  const index = getIndex(project_dir);
335
- const { ok, result, error } = execute(index, 'related', { name, file, top, all });
331
+ const { ok, result, error } = execute(index, 'related', ep);
336
332
  if (!ok) return toolResult(error);
337
- if (!result) return toolResult(`Symbol "${name}" not found.`);
333
+ if (!result) return toolResult(`Symbol "${ep.name}" not found.`);
338
334
  return toolResult(output.formatRelated(result, {
339
- all: all || false, top,
335
+ all: ep.all || false, top: ep.top,
340
336
  allHint: 'Repeat with all=true to show all.'
341
337
  }));
342
338
  }
@@ -345,25 +341,22 @@ server.registerTool(
345
341
 
346
342
  case 'find': {
347
343
  const index = getIndex(project_dir);
348
- const ep = normalizeParams({ name, file, exclude, include_tests, exact, in: inPath });
349
344
  const { ok, result, error, note } = execute(index, 'find', ep);
350
345
  if (!ok) return toolResult(error); // soft error
351
- let text = output.formatFind(result, name, top);
346
+ let text = output.formatFind(result, ep.name, ep.top);
352
347
  if (note) text += '\n\n' + note;
353
348
  return toolResult(text);
354
349
  }
355
350
 
356
351
  case 'usages': {
357
352
  const index = getIndex(project_dir);
358
- const ep = normalizeParams({ name, exclude, include_tests, code_only, context: ctxLines, in: inPath });
359
353
  const { ok, result, error } = execute(index, 'usages', ep);
360
354
  if (!ok) return toolResult(error); // soft error
361
- return toolResult(output.formatUsages(result, name));
355
+ return toolResult(output.formatUsages(result, ep.name));
362
356
  }
363
357
 
364
358
  case 'toc': {
365
359
  const index = getIndex(project_dir);
366
- const ep = normalizeParams({ detailed, top_level, all, top });
367
360
  const { ok, result, error } = execute(index, 'toc', ep);
368
361
  if (!ok) return toolResult(error); // soft error
369
362
  return toolResult(output.formatToc(result, {
@@ -373,29 +366,26 @@ server.registerTool(
373
366
 
374
367
  case 'search': {
375
368
  const index = getIndex(project_dir);
376
- const ep = normalizeParams({ term, exclude, include_tests, code_only, context: ctxLines, case_sensitive, in: inPath, regex });
377
369
  const { ok, result, error } = execute(index, 'search', ep);
378
370
  if (!ok) return toolResult(error); // soft error
379
- return toolResult(output.formatSearch(result, term));
371
+ return toolResult(output.formatSearch(result, ep.term));
380
372
  }
381
373
 
382
374
  case 'tests': {
383
375
  const index = getIndex(project_dir);
384
- const ep = normalizeParams({ name, calls_only });
385
376
  const { ok, result, error } = execute(index, 'tests', ep);
386
377
  if (!ok) return toolResult(error); // soft error
387
- return toolResult(output.formatTests(result, name));
378
+ return toolResult(output.formatTests(result, ep.name));
388
379
  }
389
380
 
390
381
  case 'deadcode': {
391
382
  const index = getIndex(project_dir);
392
- const ep = normalizeParams({ exclude, in: inPath, include_exported, include_decorated, include_tests });
393
383
  const { ok, result, error } = execute(index, 'deadcode', ep);
394
384
  if (!ok) return toolResult(error); // soft error
395
385
  return toolResult(output.formatDeadcode(result, {
396
- top: top || 0,
397
- decoratedHint: !include_decorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
398
- exportedHint: !include_exported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use include_exported=true to audit them.` : undefined
386
+ top: ep.top || 0,
387
+ decoratedHint: !ep.includeDecorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
388
+ exportedHint: !ep.includeExported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use include_exported=true to audit them.` : undefined
399
389
  }));
400
390
  }
401
391
 
@@ -403,32 +393,32 @@ server.registerTool(
403
393
 
404
394
  case 'imports': {
405
395
  const index = getIndex(project_dir);
406
- const { ok, result, error } = execute(index, 'imports', { file });
396
+ const { ok, result, error } = execute(index, 'imports', ep);
407
397
  if (!ok) return toolResult(error); // soft error
408
- return toolResult(output.formatImports(result, file));
398
+ return toolResult(output.formatImports(result, ep.file));
409
399
  }
410
400
 
411
401
  case 'exporters': {
412
402
  const index = getIndex(project_dir);
413
- const { ok, result, error } = execute(index, 'exporters', { file });
403
+ const { ok, result, error } = execute(index, 'exporters', ep);
414
404
  if (!ok) return toolResult(error); // soft error
415
- return toolResult(output.formatExporters(result, file));
405
+ return toolResult(output.formatExporters(result, ep.file));
416
406
  }
417
407
 
418
408
  case 'file_exports': {
419
409
  const index = getIndex(project_dir);
420
- const { ok, result, error } = execute(index, 'fileExports', { file });
410
+ const { ok, result, error } = execute(index, 'fileExports', ep);
421
411
  if (!ok) return toolResult(error); // soft error
422
- return toolResult(output.formatFileExports(result, file));
412
+ return toolResult(output.formatFileExports(result, ep.file));
423
413
  }
424
414
 
425
415
  case 'graph': {
426
416
  const index = getIndex(project_dir);
427
- const { ok, result, error } = execute(index, 'graph', { file, direction, depth, all });
417
+ const { ok, result, error } = execute(index, 'graph', ep);
428
418
  if (!ok) return toolResult(error); // soft error
429
419
  return toolResult(output.formatGraph(result, {
430
- showAll: all || depth !== undefined,
431
- maxDepth: depth ?? 2, file,
420
+ showAll: ep.all || ep.depth !== undefined,
421
+ maxDepth: ep.depth ?? 2, file: ep.file,
432
422
  depthHint: 'Set depth parameter for deeper graph.',
433
423
  allHint: 'Set depth to expand all children.'
434
424
  }));
@@ -438,14 +428,13 @@ server.registerTool(
438
428
 
439
429
  case 'verify': {
440
430
  const index = getIndex(project_dir);
441
- const { ok, result, error } = execute(index, 'verify', { name, file });
431
+ const { ok, result, error } = execute(index, 'verify', ep);
442
432
  if (!ok) return toolResult(error); // soft error
443
433
  return toolResult(output.formatVerify(result));
444
434
  }
445
435
 
446
436
  case 'plan': {
447
437
  const index = getIndex(project_dir);
448
- const ep = normalizeParams({ name, add_param, remove_param, rename_to, default_value, file });
449
438
  const { ok, result, error } = execute(index, 'plan', ep);
450
439
  if (!ok) return toolResult(error); // soft error
451
440
  return toolResult(output.formatPlan(result));
@@ -453,7 +442,7 @@ server.registerTool(
453
442
 
454
443
  case 'diff_impact': {
455
444
  const index = getIndex(project_dir);
456
- const { ok, result, error } = execute(index, 'diffImpact', { base, staged, file });
445
+ const { ok, result, error } = execute(index, 'diffImpact', ep);
457
446
  if (!ok) return toolResult(error); // soft error — e.g. "not a git repo"
458
447
  return toolResult(output.formatDiffImpact(result));
459
448
  }
@@ -462,39 +451,38 @@ server.registerTool(
462
451
 
463
452
  case 'typedef': {
464
453
  const index = getIndex(project_dir);
465
- const { ok, result, error } = execute(index, 'typedef', { name, exact });
454
+ const { ok, result, error } = execute(index, 'typedef', ep);
466
455
  if (!ok) return toolResult(error); // soft error
467
- return toolResult(output.formatTypedef(result, name));
456
+ return toolResult(output.formatTypedef(result, ep.name));
468
457
  }
469
458
 
470
459
  case 'stacktrace': {
471
460
  const index = getIndex(project_dir);
472
- const { ok, result, error } = execute(index, 'stacktrace', { stack });
461
+ const { ok, result, error } = execute(index, 'stacktrace', ep);
473
462
  if (!ok) return toolResult(error); // soft error
474
463
  return toolResult(output.formatStackTrace(result));
475
464
  }
476
465
 
477
466
  case 'api': {
478
467
  const index = getIndex(project_dir);
479
- const { ok, result, error } = execute(index, 'api', { file });
468
+ const { ok, result, error } = execute(index, 'api', ep);
480
469
  if (!ok) return toolResult(error); // soft error
481
- return toolResult(output.formatApi(result, file || '.'));
470
+ return toolResult(output.formatApi(result, ep.file || '.'));
482
471
  }
483
472
 
484
473
  case 'stats': {
485
474
  const index = getIndex(project_dir);
486
- const { ok, result, error } = execute(index, 'stats', { functions });
475
+ const { ok, result, error } = execute(index, 'stats', ep);
487
476
  if (!ok) return toolResult(error); // soft error
488
- return toolResult(output.formatStats(result, { top: top || 0 }));
477
+ return toolResult(output.formatStats(result, { top: ep.top || 0 }));
489
478
  }
490
479
 
491
480
  // ── Extracting Code (via execute) ────────────────────────────
492
481
 
493
482
  case 'fn': {
494
- const err = requireName(name);
483
+ const err = requireName(ep.name);
495
484
  if (err) return err;
496
485
  const index = getIndex(project_dir);
497
- const ep = normalizeParams({ name, file, all });
498
486
  const { ok, result, error } = execute(index, 'fn', ep);
499
487
  if (!ok) return toolResult(error); // soft error
500
488
  // MCP path security: validate all result files are within project root
@@ -507,13 +495,12 @@ server.registerTool(
507
495
  }
508
496
 
509
497
  case 'class': {
510
- const err = requireName(name);
498
+ const err = requireName(ep.name);
511
499
  if (err) return err;
512
- if (max_lines !== undefined && (!Number.isInteger(max_lines) || max_lines < 1)) {
513
- return toolError(`Invalid max_lines: ${max_lines}. Must be a positive integer.`);
500
+ if (ep.maxLines !== undefined && (!Number.isInteger(ep.maxLines) || ep.maxLines < 1)) {
501
+ return toolError(`Invalid max_lines: ${ep.maxLines}. Must be a positive integer.`);
514
502
  }
515
503
  const index = getIndex(project_dir);
516
- const ep = normalizeParams({ name, file, all, max_lines });
517
504
  const { ok, result, error } = execute(index, 'class', ep);
518
505
  if (!ok) return toolResult(error); // soft error (class not found)
519
506
  // MCP path security: validate all result files are within project root
@@ -527,7 +514,6 @@ server.registerTool(
527
514
 
528
515
  case 'lines': {
529
516
  const index = getIndex(project_dir);
530
- const ep = normalizeParams({ file, range });
531
517
  const { ok, result, error } = execute(index, 'lines', ep);
532
518
  if (!ok) return toolResult(error); // soft error
533
519
  // MCP path security: validate file is within project root
@@ -537,13 +523,13 @@ server.registerTool(
537
523
  }
538
524
 
539
525
  case 'expand': {
540
- if (item === undefined || item === null) {
526
+ if (ep.item === undefined || ep.item === null) {
541
527
  return toolError('Item number is required (e.g. item=1).');
542
528
  }
543
529
  const index = getIndex(project_dir);
544
- const lookup = expandCacheInstance.lookup(index.root, item);
530
+ const lookup = expandCacheInstance.lookup(index.root, ep.item);
545
531
  const { ok, result, error } = execute(index, 'expand', {
546
- match: lookup.match, itemNum: item,
532
+ match: lookup.match, itemNum: ep.item,
547
533
  itemCount: lookup.itemCount, symbolName: lookup.symbolName,
548
534
  validateRoot: true
549
535
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.37",
3
+ "version": "3.7.39",
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",