ucn 3.8.23 → 3.8.26

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.
Files changed (44) hide show
  1. package/.claude/skills/ucn/SKILL.md +127 -12
  2. package/README.md +152 -156
  3. package/cli/index.js +363 -37
  4. package/core/analysis.js +936 -32
  5. package/core/bridge.js +1095 -0
  6. package/core/brief.js +408 -0
  7. package/core/cache.js +105 -5
  8. package/core/callers.js +72 -18
  9. package/core/check.js +200 -0
  10. package/core/discovery.js +57 -34
  11. package/core/entrypoints.js +638 -4
  12. package/core/execute.js +304 -5
  13. package/core/git-enrich.js +130 -0
  14. package/core/graph.js +24 -2
  15. package/core/output/analysis.js +157 -25
  16. package/core/output/brief.js +100 -0
  17. package/core/output/check.js +79 -0
  18. package/core/output/doctor.js +85 -0
  19. package/core/output/endpoints.js +239 -0
  20. package/core/output/extraction.js +2 -0
  21. package/core/output/find.js +126 -39
  22. package/core/output/graph.js +48 -15
  23. package/core/output/refactoring.js +103 -5
  24. package/core/output/reporting.js +63 -23
  25. package/core/output/search.js +110 -17
  26. package/core/output/shared.js +56 -2
  27. package/core/output.js +4 -0
  28. package/core/parser.js +8 -2
  29. package/core/project.js +39 -3
  30. package/core/registry.js +30 -14
  31. package/core/reporting.js +465 -2
  32. package/core/search.js +130 -52
  33. package/core/shared.js +101 -5
  34. package/core/tracing.js +16 -6
  35. package/core/verify.js +982 -95
  36. package/languages/go.js +91 -6
  37. package/languages/html.js +10 -0
  38. package/languages/java.js +151 -35
  39. package/languages/javascript.js +290 -33
  40. package/languages/python.js +78 -11
  41. package/languages/rust.js +267 -12
  42. package/languages/utils.js +315 -3
  43. package/mcp/server.js +91 -16
  44. package/package.json +9 -1
package/core/project.js CHANGED
@@ -223,6 +223,9 @@ class ProjectIndex {
223
223
  // Always invalidate caches on rebuild
224
224
  this._completenessCache = null;
225
225
  this._attrTypeCache = null;
226
+ // Endpoints cache (server routes / client requests / bridges) becomes
227
+ // stale when files change; clear on every rebuild.
228
+ this._endpointsCache = null;
226
229
 
227
230
  let indexed = 0;
228
231
  let changed = 0;
@@ -403,6 +406,9 @@ class ProjectIndex {
403
406
  params: item.params,
404
407
  paramsStructured: item.paramsStructured,
405
408
  returnType: item.returnType,
409
+ ...(item.paramTypes && { paramTypes: item.paramTypes }),
410
+ ...(item.isAsync && { isAsync: true }),
411
+ ...(item.isGenerator && { isGenerator: true }),
406
412
  modifiers: item.modifiers,
407
413
  docstring: item.docstring,
408
414
  bindingId: `${fileEntry.relativePath}:${type}:${item.startLine}`,
@@ -417,6 +423,13 @@ class ProjectIndex {
417
423
  ...(item.memberType && { memberType: item.memberType }),
418
424
  ...(item.fieldType && { fieldType: item.fieldType }),
419
425
  ...(item.decorators && item.decorators.length > 0 && { decorators: item.decorators }),
426
+ // Decorator/annotation/attribute argument capture for endpoints command:
427
+ // these fields hold the parsed first-string-arg of each route-style annotation.
428
+ // Only populated when at least one entry has a string-literal arg, keeping memory
429
+ // overhead minimal for non-route code.
430
+ ...(item.decoratorsWithArgs && item.decoratorsWithArgs.length > 0 && { decoratorsWithArgs: item.decoratorsWithArgs }),
431
+ ...(item.annotationsWithArgs && item.annotationsWithArgs.length > 0 && { annotationsWithArgs: item.annotationsWithArgs }),
432
+ ...(item.attributesWithArgs && item.attributesWithArgs.length > 0 && { attributesWithArgs: item.attributesWithArgs }),
420
433
  ...(item.nameLine && { nameLine: item.nameLine }),
421
434
  ...(item.traitImpl && { traitImpl: true }),
422
435
  ...(item.isSignature && { isSignature: true })
@@ -494,6 +507,8 @@ class ProjectIndex {
494
507
 
495
508
  // Invalidate lazy Java file index (will be rebuilt on next use)
496
509
  this._javaFileIndex = null;
510
+ // Endpoints cache is project-wide; safest to clear on any file removal.
511
+ this._endpointsCache = null;
497
512
  }
498
513
 
499
514
  /**
@@ -828,15 +843,27 @@ class ProjectIndex {
828
843
  }
829
844
  }
830
845
 
846
+ // Filter by exact startLine when a handle was supplied. This pins
847
+ // the resolution to one specific definition — no ambiguity allowed.
848
+ if (options.line && Number.isFinite(options.line)) {
849
+ const filtered = definitions.filter(d => d.startLine === options.line);
850
+ if (filtered.length > 0) {
851
+ definitions = filtered;
852
+ }
853
+ }
854
+
831
855
  // Score each definition for selection
832
856
  const typeOrder = new Set(['class', 'struct', 'interface', 'type', 'impl']);
857
+ const { isTestPath } = require('./shared');
833
858
  const scored = definitions.map(d => {
834
859
  let score = 0;
835
860
  const rp = d.relativePath || '';
836
861
  // Prefer class/struct/interface types (+1000)
837
862
  if (typeOrder.has(d.type)) score += 1000;
838
- // Deprioritize test files (-500)
839
- if (isTestFile(rp, detectLanguage(d.file))) {
863
+ // BUG-M4: deprioritize test files (-500). Combine the filename-pattern
864
+ // detector with the path-segment detector so `about` agrees with `find`'s
865
+ // exclusion logic (e.g. `test/agent-benchmark.js` is treated as a test).
866
+ if (isTestFile(rp, detectLanguage(d.file)) || isTestPath(rp)) {
840
867
  score -= 500;
841
868
  }
842
869
  // Deprioritize examples/docs/vendor directories (-300)
@@ -1364,7 +1391,10 @@ class ProjectIndex {
1364
1391
  }
1365
1392
  parts.push(def.name);
1366
1393
  if (def.params !== undefined) {
1367
- parts.push(`(${def.params})`);
1394
+ // Prefer typed rendering when paramTypes/paramsStructured carry annotations
1395
+ const { renderTypedParams } = require('./output/shared');
1396
+ const typed = renderTypedParams(def);
1397
+ parts.push(`(${typed != null ? typed : def.params})`);
1368
1398
  }
1369
1399
  if (def.returnType) {
1370
1400
  parts.push(`: ${def.returnType}`);
@@ -1938,8 +1968,14 @@ class ProjectIndex {
1938
1968
  /** Analyze a call site using AST for example scoring */
1939
1969
  _analyzeCallSiteAST(filePath, lineNum, funcName) { return verifyModule.analyzeCallSiteAST(this, filePath, lineNum, funcName); }
1940
1970
 
1971
+ /** Analyze a call site's argument shape (used by `example --diverse`) */
1972
+ _analyzeCallShape(filePath, lineNum, funcName) { return verifyModule.analyzeCallShape(this, filePath, lineNum, funcName); }
1973
+
1941
1974
  /** Diff-based impact analysis: find which functions changed and who calls them */
1942
1975
  diffImpact(options) { return analysisModule.diffImpact(this, options); }
1976
+
1977
+ /** Audit async/await: find calls that are likely missing an `await`. */
1978
+ auditAsync(options) { return analysisModule.auditAsync(this, options); }
1943
1979
  }
1944
1980
 
1945
1981
  const { parseDiff } = require('./analysis');
package/core/registry.js CHANGED
@@ -15,17 +15,17 @@
15
15
  // Order: understanding, finding, extracting, file-deps, refactoring, other.
16
16
  const CANONICAL_COMMANDS = [
17
17
  // Understanding code
18
- 'about', 'context', 'impact', 'blast', 'smart', 'trace', 'reverseTrace', 'example', 'related',
18
+ 'about', 'context', 'impact', 'blast', 'smart', 'trace', 'reverseTrace', 'example', 'related', 'brief',
19
19
  // Finding code
20
- 'find', 'usages', 'toc', 'search', 'tests', 'affectedTests', 'deadcode', 'entrypoints',
20
+ 'find', 'usages', 'toc', 'search', 'tests', 'affectedTests', 'deadcode', 'entrypoints', 'endpoints',
21
21
  // Extracting code
22
22
  'fn', 'class', 'lines', 'expand',
23
23
  // File dependencies
24
24
  'imports', 'exporters', 'fileExports', 'graph', 'circularDeps',
25
25
  // Refactoring
26
- 'verify', 'plan', 'diffImpact',
26
+ 'verify', 'plan', 'diffImpact', 'check',
27
27
  // Other
28
- 'typedef', 'stacktrace', 'api', 'stats',
28
+ 'typedef', 'stacktrace', 'api', 'stats', 'doctor', 'auditAsync',
29
29
  ];
30
30
 
31
31
  // ============================================================================
@@ -47,6 +47,9 @@ const CLI_ALIASES = {
47
47
  'circular-deps': 'circularDeps',
48
48
  'circular': 'circularDeps',
49
49
  'cycles': 'circularDeps',
50
+ 'audit-async': 'auditAsync',
51
+ // BUG-3: parity with other multi-word commands (circular-deps, reverse-trace, ...)
52
+ 'entry-points': 'entrypoints',
50
53
  };
51
54
 
52
55
  // MCP uses snake_case for multi-word names.
@@ -56,6 +59,7 @@ const MCP_ALIASES = {
56
59
  'affected_tests': 'affectedTests',
57
60
  'reverse_trace': 'reverseTrace',
58
61
  'circular_deps': 'circularDeps',
62
+ 'audit_async': 'auditAsync',
59
63
  };
60
64
 
61
65
  // ============================================================================
@@ -65,6 +69,7 @@ const MCP_ALIASES = {
65
69
  const PARAM_MAP = {
66
70
  project_dir: 'projectDir',
67
71
  include_tests: 'includeTests',
72
+ exclude_tests: 'excludeTests',
68
73
  include_methods: 'includeMethods',
69
74
  include_uncertain: 'includeUncertain',
70
75
  with_types: 'withTypes',
@@ -74,6 +79,7 @@ const PARAM_MAP = {
74
79
  include_decorated: 'includeDecorated',
75
80
  min_confidence: 'minConfidence',
76
81
  show_confidence: 'showConfidence',
82
+ hide_confidence: 'hideConfidence',
77
83
  calls_only: 'callsOnly',
78
84
  class_name: 'className',
79
85
  max_lines: 'maxLines',
@@ -85,6 +91,10 @@ const PARAM_MAP = {
85
91
  max_files: 'maxFiles',
86
92
  max_chars: 'maxChars',
87
93
  follow_symlinks: 'followSymlinks',
94
+ unreachable_only: 'unreachableOnly',
95
+ server_only: 'serverOnly',
96
+ client_only: 'clientOnly',
97
+ hide_uncertain: 'hideUncertain',
88
98
  };
89
99
 
90
100
  // ============================================================================
@@ -96,24 +106,26 @@ const PARAM_MAP = {
96
106
  // file* = file is the command subject (required), not a filter pattern.
97
107
  const FLAG_APPLICABILITY = {
98
108
  // Understanding code
99
- about: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'includeTests', 'top', 'all', 'withTypes', 'minConfidence', 'showConfidence'],
100
- context: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'minConfidence', 'showConfidence'],
101
- impact: ['name', 'file', 'exclude', 'className', 'top'],
109
+ about: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'includeTests', 'top', 'all', 'withTypes', 'minConfidence', 'showConfidence', 'unreachableOnly', 'compact', 'git'],
110
+ context: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'minConfidence', 'showConfidence', 'unreachableOnly', 'compact'],
111
+ impact: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'top', 'unreachableOnly', 'compact'],
102
112
  blast: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
103
113
  reverseTrace: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
104
114
  smart: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'withTypes', 'minConfidence'],
105
115
  trace: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
106
- example: ['name', 'file', 'className'],
116
+ example: ['name', 'file', 'className', 'diverse', 'top', 'includeTests'],
107
117
  related: ['name', 'file', 'className', 'top', 'all'],
118
+ brief: ['name', 'file', 'className', 'git'],
108
119
  // Finding code
109
- find: ['name', 'file', 'exclude', 'className', 'includeTests', 'top', 'limit', 'exact', 'in', 'all', 'depth'],
110
- usages: ['name', 'file', 'exclude', 'className', 'includeTests', 'limit', 'codeOnly', 'context', 'in'],
120
+ find: ['name', 'file', 'exclude', 'className', 'includeTests', 'top', 'limit', 'exact', 'in', 'all', 'depth', 'compact'],
121
+ usages: ['name', 'file', 'exclude', 'className', 'includeTests', 'limit', 'codeOnly', 'context', 'in', 'compact'],
111
122
  toc: ['file', 'exclude', 'top', 'limit', 'all', 'detailed', 'topLevel', 'in'],
112
123
  search: ['term', 'file', 'exclude', 'includeTests', 'top', 'limit', 'codeOnly', 'caseSensitive', 'context', 'regex', 'in', 'type', 'param', 'receiver', 'returns', 'decorator', 'exported', 'unused'],
113
124
  tests: ['name', 'file', 'exclude', 'className', 'callsOnly'],
114
125
  affectedTests:['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'minConfidence'],
115
126
  deadcode: ['file', 'exclude', 'includeTests', 'includeExported', 'includeDecorated', 'limit', 'in'],
116
- entrypoints: ['file', 'exclude', 'includeTests', 'limit', 'type', 'framework'],
127
+ entrypoints: ['file', 'exclude', 'includeTests', 'excludeTests', 'limit', 'type', 'framework'],
128
+ endpoints: ['file', 'exclude', 'limit', 'framework', 'bridge', 'serverOnly', 'clientOnly', 'unmatched', 'method', 'prefix', 'hideUncertain'],
117
129
  // Extracting code
118
130
  fn: ['name', 'file', 'className', 'all'],
119
131
  class: ['name', 'file', 'all', 'maxLines'],
@@ -126,21 +138,25 @@ const FLAG_APPLICABILITY = {
126
138
  graph: ['file', 'depth', 'direction', 'all'],
127
139
  circularDeps: ['file', 'exclude'],
128
140
  // Refactoring
129
- verify: ['name', 'file', 'className'],
141
+ verify: ['name', 'file', 'className', 'includeMethods', 'includeUncertain'],
130
142
  plan: ['name', 'file', 'className', 'addParam', 'removeParam', 'renameTo', 'defaultValue'],
131
143
  diffImpact: ['file', 'limit', 'base', 'staged', 'all'],
144
+ check: ['file', 'base', 'staged', 'limit'],
132
145
  // Other
133
146
  typedef: ['name', 'file', 'className', 'exact'],
134
147
  stacktrace: ['stack'],
135
148
  api: ['file', 'limit'],
136
- stats: ['functions', 'top'],
149
+ stats: ['functions', 'hot', 'top'],
150
+ doctor: ['file', 'in', 'limit', 'deep'],
151
+ auditAsync: ['file', 'exclude', 'limit'],
137
152
  };
138
153
 
139
154
  // Commands whose output is project-wide — truncation means you need a filter, not more text.
140
155
  // Used by MCP server for tighter default output limits.
141
156
  const BROAD_COMMANDS = new Set([
142
- 'toc', 'entrypoints', 'diffImpact', 'affectedTests',
157
+ 'toc', 'entrypoints', 'endpoints', 'diffImpact', 'affectedTests',
143
158
  'deadcode', 'usages', 'reverseTrace', 'circularDeps',
159
+ 'doctor', 'check', 'auditAsync',
144
160
  ]);
145
161
 
146
162
  // Commands that can operate on a single file without a project index.