ucn 3.8.7 → 3.8.8

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.
@@ -209,15 +209,15 @@ const FRAMEWORK_PATTERNS = [
209
209
  // ── Go Framework Patterns ─────────────────────────────────────────
210
210
 
211
211
  // Cobra CLI framework — RunE, Run, PreRunE etc. assigned to cobra.Command struct fields
212
- // Detected via call-pattern: cobra.Command composite literals with function value fields
212
+ // Detected via composite literal: &cobra.Command{RunE: handler}
213
213
  {
214
214
  id: 'cobra-command',
215
215
  languages: new Set(['go']),
216
216
  type: 'cli',
217
217
  framework: 'cobra',
218
- detection: 'callPattern',
219
- receiverPattern: /^cobra$/i,
220
- methodPattern: /^Command$/,
218
+ detection: 'compositePattern',
219
+ typePattern: /^cobra\.Command$/,
220
+ fieldPattern: /^(Run|RunE|PreRun|PreRunE|PostRun|PostRunE|PersistentPreRun|PersistentPreRunE|PersistentPostRun|PersistentPostRunE)$/,
221
221
  },
222
222
 
223
223
  // Go goroutine launch — go func() or go handler()
@@ -289,50 +289,77 @@ function matchDecoratorOrModifier(symbol, language) {
289
289
  */
290
290
  function buildCallbackEntrypointMap(index) {
291
291
  const callPatterns = FRAMEWORK_PATTERNS.filter(p => p.detection === 'callPattern');
292
- if (callPatterns.length === 0) return new Map();
292
+ const compositePatterns = FRAMEWORK_PATTERNS.filter(p => p.detection === 'compositePattern');
293
+ if (callPatterns.length === 0 && compositePatterns.length === 0) return new Map();
293
294
 
294
295
  const result = new Map(); // name -> info
295
296
 
296
297
  for (const [filePath, fileEntry] of index.files) {
297
298
  const lang = fileEntry.language;
298
- const relevantPatterns = callPatterns.filter(p => p.languages.has(lang));
299
- if (relevantPatterns.length === 0) continue;
300
299
 
301
300
  const calls = getCachedCalls(index, filePath);
302
301
  if (!calls) continue;
303
302
 
304
- // Pass 1: find route-registration calls, index by line
305
- // Match both method calls (obj.method) and package-qualified calls (pkg.Func)
306
- const routeLines = new Map(); // line -> { pattern, call }
307
- for (const call of calls) {
308
- if (!call.receiver) continue;
309
- for (const pattern of relevantPatterns) {
310
- if (pattern.receiverPattern.test(call.receiver) &&
311
- pattern.methodPattern.test(call.name)) {
312
- routeLines.set(call.line, { pattern, call });
313
- break;
303
+ // Pass 1+2: call-pattern detection (e.g., app.GET("/", handler))
304
+ const relevantCallPatterns = callPatterns.filter(p => p.languages.has(lang));
305
+ if (relevantCallPatterns.length > 0) {
306
+ // Pass 1: find route-registration calls, index by line
307
+ const routeLines = new Map(); // line -> { pattern, call }
308
+ for (const call of calls) {
309
+ if (!call.receiver) continue;
310
+ for (const pattern of relevantCallPatterns) {
311
+ if (pattern.receiverPattern.test(call.receiver) &&
312
+ pattern.methodPattern.test(call.name)) {
313
+ routeLines.set(call.line, { pattern, call });
314
+ break;
315
+ }
316
+ }
317
+ }
318
+
319
+ if (routeLines.size > 0) {
320
+ // Pass 2: find callbacks on route-registration lines
321
+ for (const call of calls) {
322
+ if (!call.isFunctionReference && !call.isPotentialCallback) continue;
323
+ const route = routeLines.get(call.line);
324
+ if (!route) continue;
325
+
326
+ if (!result.has(call.name)) {
327
+ result.set(call.name, {
328
+ framework: route.pattern.framework,
329
+ type: route.pattern.type,
330
+ patternId: route.pattern.id,
331
+ method: route.call.name.toUpperCase(),
332
+ file: filePath,
333
+ line: call.line,
334
+ });
335
+ }
314
336
  }
315
337
  }
316
338
  }
317
339
 
318
- if (routeLines.size === 0) continue;
319
-
320
- // Pass 2: find callbacks on route-registration lines
321
- for (const call of calls) {
322
- if (!call.isFunctionReference && !call.isPotentialCallback) continue;
323
- const route = routeLines.get(call.line);
324
- if (!route) continue;
325
-
326
- // This callback is registered as a framework handler
327
- if (!result.has(call.name)) {
328
- result.set(call.name, {
329
- framework: route.pattern.framework,
330
- type: route.pattern.type,
331
- patternId: route.pattern.id,
332
- method: route.call.name.toUpperCase(),
333
- file: filePath,
334
- line: call.line,
335
- });
340
+ // Pass 3: composite literal patterns (e.g., &cobra.Command{RunE: handler})
341
+ const relevantCompositePatterns = compositePatterns.filter(p => p.languages.has(lang));
342
+ if (relevantCompositePatterns.length > 0) {
343
+ for (const call of calls) {
344
+ if (!call.compositeType) continue;
345
+ if (!call.isPotentialCallback && !call.isFunctionReference) continue;
346
+
347
+ for (const pattern of relevantCompositePatterns) {
348
+ if (pattern.typePattern.test(call.compositeType) &&
349
+ pattern.fieldPattern.test(call.fieldName)) {
350
+ if (!result.has(call.name)) {
351
+ result.set(call.name, {
352
+ framework: pattern.framework,
353
+ type: pattern.type,
354
+ patternId: pattern.id,
355
+ method: call.fieldName,
356
+ file: filePath,
357
+ line: call.line,
358
+ });
359
+ }
360
+ break;
361
+ }
362
+ }
336
363
  }
337
364
  }
338
365
  }
package/core/output.js CHANGED
@@ -3122,6 +3122,7 @@ function formatEntrypoints(results, options = {}) {
3122
3122
 
3123
3123
  const typeLabels = {
3124
3124
  http: 'HTTP Routes',
3125
+ cli: 'CLI Handlers',
3125
3126
  di: 'Dependency Injection',
3126
3127
  jobs: 'Job Schedulers',
3127
3128
  test: 'Test Fixtures',
package/languages/go.js CHANGED
@@ -954,6 +954,31 @@ function findCallsInCode(code, parser, options = {}) {
954
954
  valueNode = valueNode.namedChildCount > 0 ? valueNode.namedChild(0) : null;
955
955
  }
956
956
  if (valueNode) {
957
+ // Extract field name (the key) and parent composite literal type
958
+ let keyNode = node.namedChildCount >= 1 ? node.namedChild(0) : null;
959
+ if (keyNode && keyNode.type === 'literal_element') {
960
+ keyNode = keyNode.namedChildCount > 0 ? keyNode.namedChild(0) : null;
961
+ }
962
+ const fieldName = keyNode ? keyNode.text : undefined;
963
+
964
+ let compositeType;
965
+ let compositeLit = node.parent; // literal_value
966
+ if (compositeLit && compositeLit.type === 'literal_value') {
967
+ compositeLit = compositeLit.parent; // composite_literal
968
+ }
969
+ if (compositeLit && compositeLit.type === 'composite_literal') {
970
+ const typeNode = compositeLit.childForFieldName('type');
971
+ if (typeNode) {
972
+ if (typeNode.type === 'qualified_type') {
973
+ const pkg = typeNode.childForFieldName('package')?.text;
974
+ const typeName = typeNode.childForFieldName('name')?.text;
975
+ compositeType = pkg && typeName ? `${pkg}.${typeName}` : typeNode.text;
976
+ } else if (typeNode.type === 'type_identifier') {
977
+ compositeType = typeNode.text;
978
+ }
979
+ }
980
+ }
981
+
957
982
  if (valueNode.type === 'identifier') {
958
983
  const name = valueNode.text;
959
984
  if (!GO_SKIP_IDENTS.has(name) && !GO_BUILTINS.has(name) && !importAliases.has(name) && /^[a-zA-Z]/.test(name)) {
@@ -965,7 +990,9 @@ function findCallsInCode(code, parser, options = {}) {
965
990
  isFunctionReference: true,
966
991
  isPotentialCallback: true,
967
992
  enclosingFunction,
968
- uncertain: false
993
+ uncertain: false,
994
+ ...(compositeType && { compositeType }),
995
+ ...(fieldName && { fieldName }),
969
996
  });
970
997
  }
971
998
  }
@@ -984,7 +1011,9 @@ function findCallsInCode(code, parser, options = {}) {
984
1011
  ...(receiverType && { receiverType }),
985
1012
  enclosingFunction,
986
1013
  isPotentialCallback: true,
987
- uncertain: false
1014
+ uncertain: false,
1015
+ ...(compositeType && { compositeType }),
1016
+ ...(fieldName && { fieldName }),
988
1017
  });
989
1018
  }
990
1019
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.8.7",
3
+ "version": "3.8.8",
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",