ucn 3.8.12 → 3.8.14
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 +3 -1
- package/.github/workflows/ci.yml +15 -3
- package/.github/workflows/publish.yml +20 -8
- package/README.md +1 -0
- package/cli/index.js +165 -246
- package/core/analysis.js +1400 -0
- package/core/build-worker.js +194 -0
- package/core/cache.js +105 -7
- package/core/callers.js +194 -64
- package/core/deadcode.js +22 -66
- package/core/discovery.js +9 -54
- package/core/execute.js +139 -54
- package/core/graph.js +615 -0
- package/core/output/analysis-ext.js +271 -0
- package/core/output/analysis.js +491 -0
- package/core/output/extraction.js +188 -0
- package/core/output/find.js +355 -0
- package/core/output/graph.js +399 -0
- package/core/output/refactoring.js +293 -0
- package/core/output/reporting.js +331 -0
- package/core/output/search.js +307 -0
- package/core/output/shared.js +271 -0
- package/core/output/tracing.js +416 -0
- package/core/output.js +15 -3293
- package/core/parallel-build.js +165 -0
- package/core/project.js +299 -3633
- package/core/registry.js +59 -0
- package/core/reporting.js +258 -0
- package/core/search.js +890 -0
- package/core/stacktrace.js +1 -1
- package/core/tracing.js +631 -0
- package/core/verify.js +10 -13
- package/eslint.config.js +43 -0
- package/jsconfig.json +10 -0
- package/languages/go.js +21 -2
- package/languages/html.js +8 -0
- package/languages/index.js +102 -40
- package/languages/java.js +13 -0
- package/languages/javascript.js +17 -1
- package/languages/python.js +14 -0
- package/languages/rust.js +13 -0
- package/languages/utils.js +1 -1
- package/mcp/server.js +45 -28
- package/package.json +8 -3
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/output/find.js - Find/usages/symbol lookup formatters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const {
|
|
7
|
+
formatFunctionSignature,
|
|
8
|
+
formatClassSignature,
|
|
9
|
+
computeConfidence,
|
|
10
|
+
} = require('./shared');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Format find command output
|
|
14
|
+
*/
|
|
15
|
+
function formatFind(symbols, query, top) {
|
|
16
|
+
if (symbols.length === 0) {
|
|
17
|
+
return `No symbols found for "${query}"`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lines = [];
|
|
21
|
+
const limit = (top && top > 0) ? Math.min(symbols.length, top) : Math.min(symbols.length, 10);
|
|
22
|
+
const hidden = symbols.length - limit;
|
|
23
|
+
|
|
24
|
+
if (hidden > 0) {
|
|
25
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}" (showing top ${limit}):`);
|
|
26
|
+
} else {
|
|
27
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}":`);
|
|
28
|
+
}
|
|
29
|
+
lines.push('─'.repeat(60));
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < limit; i++) {
|
|
32
|
+
const s = symbols[i];
|
|
33
|
+
const sig = s.params !== undefined
|
|
34
|
+
? formatFunctionSignature(s)
|
|
35
|
+
: formatClassSignature(s);
|
|
36
|
+
lines.push(`${s.relativePath}:${s.startLine} ${sig}`);
|
|
37
|
+
if (s.usageCounts !== undefined) {
|
|
38
|
+
const c = s.usageCounts;
|
|
39
|
+
const parts = [];
|
|
40
|
+
if (c.calls > 0) parts.push(`${c.calls} calls`);
|
|
41
|
+
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
42
|
+
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
43
|
+
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
44
|
+
lines.push(parts.length > 0
|
|
45
|
+
? ` (${c.total} usages: ${parts.join(', ')})`
|
|
46
|
+
: ` (${c.total} usages)`);
|
|
47
|
+
} else if (s.usageCount !== undefined) {
|
|
48
|
+
lines.push(` (${s.usageCount} usages)`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (hidden > 0) {
|
|
53
|
+
lines.push(`... ${hidden} more result(s).`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatFindJson(items) {
|
|
60
|
+
return JSON.stringify({
|
|
61
|
+
meta: { command: 'find', count: items.length },
|
|
62
|
+
data: items.map(m => ({
|
|
63
|
+
name: m.name,
|
|
64
|
+
type: m.type,
|
|
65
|
+
file: m.relativePath || m.file,
|
|
66
|
+
line: m.startLine,
|
|
67
|
+
endLine: m.endLine,
|
|
68
|
+
...(m.className && { className: m.className }),
|
|
69
|
+
...(m.receiver && { receiver: m.receiver }),
|
|
70
|
+
})),
|
|
71
|
+
}, null, 2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Format find results with depth/confidence features (detailed view).
|
|
76
|
+
* Returns a string. Used by CLI and interactive mode.
|
|
77
|
+
*
|
|
78
|
+
* @param {Array} symbols - Find result array
|
|
79
|
+
* @param {string} query - Original search query
|
|
80
|
+
* @param {object} options - { depth, top, all }
|
|
81
|
+
*/
|
|
82
|
+
function formatFindDetailed(symbols, query, options = {}) {
|
|
83
|
+
const { depth, top, all } = options;
|
|
84
|
+
const DEFAULT_LIMIT = 5;
|
|
85
|
+
|
|
86
|
+
if (symbols.length === 0) {
|
|
87
|
+
return `No symbols found for "${query}"`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lines = [];
|
|
91
|
+
const limit = all ? symbols.length : (top > 0 ? top : DEFAULT_LIMIT);
|
|
92
|
+
const showing = Math.min(limit, symbols.length);
|
|
93
|
+
const hidden = symbols.length - showing;
|
|
94
|
+
|
|
95
|
+
if (hidden > 0) {
|
|
96
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}" (showing top ${showing}):`);
|
|
97
|
+
} else {
|
|
98
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}":`);
|
|
99
|
+
}
|
|
100
|
+
lines.push('─'.repeat(60));
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < showing; i++) {
|
|
103
|
+
const s = symbols[i];
|
|
104
|
+
// Depth 0: just location
|
|
105
|
+
if (depth === '0') {
|
|
106
|
+
lines.push(`${s.relativePath}:${s.startLine}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Depth 1 (default): location + signature
|
|
111
|
+
const sig = s.params !== undefined
|
|
112
|
+
? formatFunctionSignature(s)
|
|
113
|
+
: formatClassSignature(s);
|
|
114
|
+
|
|
115
|
+
const confidence = computeConfidence(s);
|
|
116
|
+
const confStr = confidence.level !== 'high' ? ` [${confidence.level}]` : '';
|
|
117
|
+
|
|
118
|
+
lines.push(`${s.relativePath}:${s.startLine} ${sig}${confStr}`);
|
|
119
|
+
if (s.usageCounts !== undefined) {
|
|
120
|
+
const c = s.usageCounts;
|
|
121
|
+
const parts = [];
|
|
122
|
+
if (c.calls > 0) parts.push(`${c.calls} calls`);
|
|
123
|
+
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
124
|
+
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
125
|
+
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
126
|
+
lines.push(` (${c.total} usages: ${parts.join(', ')})`);
|
|
127
|
+
} else if (s.usageCount !== undefined) {
|
|
128
|
+
lines.push(` (${s.usageCount} usages)`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (confidence.level !== 'high' && confidence.reasons.length > 0) {
|
|
132
|
+
lines.push(` ⚠ ${confidence.reasons.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Depth 2: + first 10 lines of code
|
|
136
|
+
if (depth === '2' || depth === 'full') {
|
|
137
|
+
try {
|
|
138
|
+
const content = fs.readFileSync(s.file, 'utf-8');
|
|
139
|
+
const fileLines = content.split('\n');
|
|
140
|
+
const maxLines = depth === 'full' ? (s.endLine - s.startLine + 1) : 10;
|
|
141
|
+
const endLine = Math.min(s.startLine + maxLines - 1, s.endLine);
|
|
142
|
+
lines.push(' ───');
|
|
143
|
+
for (let j = s.startLine - 1; j < endLine; j++) {
|
|
144
|
+
lines.push(` ${fileLines[j]}`);
|
|
145
|
+
}
|
|
146
|
+
if (depth === '2' && s.endLine > endLine) {
|
|
147
|
+
lines.push(` ... (${s.endLine - endLine} more lines)`);
|
|
148
|
+
}
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// Skip code extraction on error
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (hidden > 0) {
|
|
157
|
+
lines.push(`... ${hidden} more result(s). Use --all to see all, or --top=N to see more.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return lines.join('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format symbol search results as JSON
|
|
165
|
+
*/
|
|
166
|
+
function formatSymbolJson(symbols, query) {
|
|
167
|
+
return JSON.stringify({
|
|
168
|
+
meta: { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 },
|
|
169
|
+
data: {
|
|
170
|
+
query,
|
|
171
|
+
count: symbols.length,
|
|
172
|
+
results: symbols.map(s => ({
|
|
173
|
+
name: s.name,
|
|
174
|
+
type: s.type,
|
|
175
|
+
file: s.relativePath || s.file,
|
|
176
|
+
startLine: s.startLine,
|
|
177
|
+
endLine: s.endLine,
|
|
178
|
+
...(s.params && { params: s.params }), // FULL params
|
|
179
|
+
...(s.paramsStructured && { paramsStructured: s.paramsStructured }),
|
|
180
|
+
...(s.returnType && { returnType: s.returnType }),
|
|
181
|
+
...(s.modifiers && { modifiers: s.modifiers }),
|
|
182
|
+
...(s.usageCount !== undefined && { usageCount: s.usageCount }),
|
|
183
|
+
...(s.usageCounts !== undefined && { usageCounts: s.usageCounts })
|
|
184
|
+
}))
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Format usages as JSON - FULL expressions, never truncated
|
|
191
|
+
*/
|
|
192
|
+
function formatUsagesJson(usages, name) {
|
|
193
|
+
const definitions = usages.filter(u => u.isDefinition);
|
|
194
|
+
const refs = usages.filter(u => !u.isDefinition);
|
|
195
|
+
|
|
196
|
+
const calls = refs.filter(u => u.usageType === 'call');
|
|
197
|
+
const imports = refs.filter(u => u.usageType === 'import');
|
|
198
|
+
const references = refs.filter(u => u.usageType === 'reference');
|
|
199
|
+
|
|
200
|
+
const formatUsage = (u) => ({
|
|
201
|
+
file: u.relativePath || u.file,
|
|
202
|
+
line: u.line,
|
|
203
|
+
expression: u.content, // FULL expression - key improvement
|
|
204
|
+
...(u.args && { args: u.args }), // Parsed arguments
|
|
205
|
+
...(u.before && u.before.length > 0 && { before: u.before }),
|
|
206
|
+
...(u.after && u.after.length > 0 && { after: u.after })
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return JSON.stringify({
|
|
210
|
+
meta: { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 },
|
|
211
|
+
data: {
|
|
212
|
+
symbol: name,
|
|
213
|
+
definitionCount: definitions.length,
|
|
214
|
+
callCount: calls.length,
|
|
215
|
+
importCount: imports.length,
|
|
216
|
+
referenceCount: references.length,
|
|
217
|
+
totalUsages: refs.length,
|
|
218
|
+
definitions: definitions.map(d => ({
|
|
219
|
+
file: d.relativePath || d.file,
|
|
220
|
+
line: d.line,
|
|
221
|
+
signature: d.signature || null, // FULL signature
|
|
222
|
+
type: d.type || null,
|
|
223
|
+
...(d.returnType && { returnType: d.returnType }),
|
|
224
|
+
...(d.before && d.before.length > 0 && { before: d.before }),
|
|
225
|
+
...(d.after && d.after.length > 0 && { after: d.after })
|
|
226
|
+
})),
|
|
227
|
+
calls: calls.map(formatUsage),
|
|
228
|
+
imports: imports.map(formatUsage),
|
|
229
|
+
references: references.map(formatUsage)
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Format usages command output
|
|
236
|
+
*/
|
|
237
|
+
function formatUsages(usages, name) {
|
|
238
|
+
const defs = usages.filter(u => u.isDefinition);
|
|
239
|
+
const calls = usages.filter(u => u.usageType === 'call');
|
|
240
|
+
const imports = usages.filter(u => u.usageType === 'import');
|
|
241
|
+
const refs = usages.filter(u => !u.isDefinition && u.usageType === 'reference');
|
|
242
|
+
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push(`Usages of "${name}": ${defs.length} definitions, ${calls.length} calls, ${imports.length} imports, ${refs.length} references`);
|
|
245
|
+
lines.push('═'.repeat(60));
|
|
246
|
+
|
|
247
|
+
function renderContextLines(usage) {
|
|
248
|
+
if (usage.before && usage.before.length > 0) {
|
|
249
|
+
for (const line of usage.before) {
|
|
250
|
+
lines.push(` ${line}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderAfterLines(usage) {
|
|
256
|
+
if (usage.after && usage.after.length > 0) {
|
|
257
|
+
for (const line of usage.after) {
|
|
258
|
+
lines.push(` ${line}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (defs.length > 0) {
|
|
264
|
+
lines.push('\nDEFINITIONS:');
|
|
265
|
+
for (const d of defs) {
|
|
266
|
+
lines.push(` ${d.relativePath}:${d.line || d.startLine}`);
|
|
267
|
+
if (d.signature) lines.push(` ${d.signature}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (calls.length > 0) {
|
|
272
|
+
lines.push('\nCALLS:');
|
|
273
|
+
for (const c of calls) {
|
|
274
|
+
lines.push(` ${c.relativePath}:${c.line}`);
|
|
275
|
+
renderContextLines(c);
|
|
276
|
+
lines.push(` ${c.content.trim()}`);
|
|
277
|
+
renderAfterLines(c);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (imports.length > 0) {
|
|
282
|
+
lines.push('\nIMPORTS:');
|
|
283
|
+
for (const i of imports) {
|
|
284
|
+
lines.push(` ${i.relativePath}:${i.line}`);
|
|
285
|
+
lines.push(` ${i.content.trim()}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (refs.length > 0) {
|
|
290
|
+
lines.push('\nREFERENCES:');
|
|
291
|
+
for (const r of refs) {
|
|
292
|
+
lines.push(` ${r.relativePath}:${r.line}`);
|
|
293
|
+
renderContextLines(r);
|
|
294
|
+
lines.push(` ${r.content.trim()}`);
|
|
295
|
+
renderAfterLines(r);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return lines.join('\n');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format disambiguation prompt - text
|
|
304
|
+
*/
|
|
305
|
+
function formatDisambiguation(matches, name, command) {
|
|
306
|
+
const lines = [`Multiple matches for "${name}":\n`];
|
|
307
|
+
|
|
308
|
+
for (const m of matches) {
|
|
309
|
+
const sig = m.params !== undefined
|
|
310
|
+
? formatFunctionSignature(m)
|
|
311
|
+
: formatClassSignature(m);
|
|
312
|
+
lines.push(` ${m.relativePath}:${m.startLine} ${sig}`);
|
|
313
|
+
if (m.usageCount !== undefined) {
|
|
314
|
+
lines.push(` (${m.usageCount} usages)`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push(`Use: ucn . ${command} ${name} --file <pattern>`);
|
|
320
|
+
|
|
321
|
+
return lines.join('\n');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Format disambiguation prompt - JSON
|
|
326
|
+
*/
|
|
327
|
+
function formatDisambiguationJson(matches, name, command) {
|
|
328
|
+
return JSON.stringify({
|
|
329
|
+
query: name,
|
|
330
|
+
command,
|
|
331
|
+
count: matches.length,
|
|
332
|
+
matches: matches.map(m => ({
|
|
333
|
+
name: m.name,
|
|
334
|
+
type: m.type,
|
|
335
|
+
file: m.relativePath,
|
|
336
|
+
startLine: m.startLine,
|
|
337
|
+
endLine: m.endLine,
|
|
338
|
+
...(m.params !== undefined && { params: m.params }),
|
|
339
|
+
...(m.className && { className: m.className }),
|
|
340
|
+
...(m.usageCount !== undefined && { usageCount: m.usageCount }),
|
|
341
|
+
})),
|
|
342
|
+
hint: `Use: ucn . ${command} ${name} --file <pattern>`
|
|
343
|
+
}, null, 2);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
module.exports = {
|
|
347
|
+
formatFind,
|
|
348
|
+
formatFindJson,
|
|
349
|
+
formatFindDetailed,
|
|
350
|
+
formatSymbolJson,
|
|
351
|
+
formatUsages,
|
|
352
|
+
formatUsagesJson,
|
|
353
|
+
formatDisambiguation,
|
|
354
|
+
formatDisambiguationJson,
|
|
355
|
+
};
|