ucn 3.8.23 → 3.8.25
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/.claude/skills/ucn/SKILL.md +114 -11
- package/README.md +152 -156
- package/cli/index.js +363 -37
- package/core/analysis.js +936 -32
- package/core/bridge.js +1111 -0
- package/core/brief.js +408 -0
- package/core/cache.js +105 -5
- package/core/callers.js +72 -18
- package/core/check.js +200 -0
- package/core/discovery.js +57 -34
- package/core/entrypoints.js +638 -4
- package/core/execute.js +304 -5
- package/core/git-enrich.js +130 -0
- package/core/graph.js +24 -2
- package/core/output/analysis.js +157 -25
- package/core/output/brief.js +100 -0
- package/core/output/check.js +79 -0
- package/core/output/doctor.js +85 -0
- package/core/output/endpoints.js +239 -0
- package/core/output/extraction.js +2 -0
- package/core/output/find.js +126 -39
- package/core/output/graph.js +48 -15
- package/core/output/refactoring.js +103 -5
- package/core/output/reporting.js +63 -23
- package/core/output/search.js +110 -17
- package/core/output/shared.js +56 -2
- package/core/output.js +4 -0
- package/core/parser.js +8 -2
- package/core/project.js +39 -3
- package/core/registry.js +30 -14
- package/core/reporting.js +465 -2
- package/core/search.js +130 -10
- package/core/shared.js +101 -5
- package/core/tracing.js +16 -6
- package/core/verify.js +982 -95
- package/languages/go.js +91 -6
- package/languages/html.js +10 -0
- package/languages/java.js +151 -35
- package/languages/javascript.js +290 -33
- package/languages/python.js +78 -11
- package/languages/rust.js +267 -12
- package/languages/utils.js +315 -3
- package/mcp/server.js +91 -16
- 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
|
-
//
|
|
839
|
-
|
|
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
|
-
|
|
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.
|