ucn 3.0.0
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.
Potentially problematic release.
This version of ucn might be problematic. Click here for more details.
- package/.claude/skills/ucn/SKILL.md +77 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/cli/index.js +2437 -0
- package/core/discovery.js +513 -0
- package/core/imports.js +558 -0
- package/core/output.js +1274 -0
- package/core/parser.js +279 -0
- package/core/project.js +3261 -0
- package/index.js +52 -0
- package/languages/go.js +653 -0
- package/languages/index.js +267 -0
- package/languages/java.js +826 -0
- package/languages/javascript.js +1346 -0
- package/languages/python.js +667 -0
- package/languages/rust.js +950 -0
- package/languages/utils.js +457 -0
- package/package.json +42 -0
- package/test/fixtures/go/go.mod +3 -0
- package/test/fixtures/go/main.go +257 -0
- package/test/fixtures/go/service.go +187 -0
- package/test/fixtures/java/DataService.java +279 -0
- package/test/fixtures/java/Main.java +287 -0
- package/test/fixtures/java/Utils.java +199 -0
- package/test/fixtures/java/pom.xml +6 -0
- package/test/fixtures/javascript/main.js +109 -0
- package/test/fixtures/javascript/package.json +1 -0
- package/test/fixtures/javascript/service.js +88 -0
- package/test/fixtures/javascript/utils.js +67 -0
- package/test/fixtures/python/main.py +198 -0
- package/test/fixtures/python/pyproject.toml +3 -0
- package/test/fixtures/python/service.py +166 -0
- package/test/fixtures/python/utils.py +118 -0
- package/test/fixtures/rust/Cargo.toml +3 -0
- package/test/fixtures/rust/main.rs +253 -0
- package/test/fixtures/rust/service.rs +210 -0
- package/test/fixtures/rust/utils.rs +154 -0
- package/test/fixtures/typescript/main.ts +154 -0
- package/test/fixtures/typescript/package.json +1 -0
- package/test/fixtures/typescript/repository.ts +149 -0
- package/test/fixtures/typescript/types.ts +114 -0
- package/test/parser.test.js +3661 -0
- package/test/public-repos-test.js +477 -0
- package/test/systematic-test.js +619 -0
- package/ucn.js +8 -0
package/core/output.js
ADDED
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/output.js - Output formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* KEY PRINCIPLE: Never truncate critical information.
|
|
5
|
+
* Full expressions, full signatures, full context.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalize parameters for display
|
|
13
|
+
* Collapses multiline params to single line
|
|
14
|
+
* @param {string} params - Raw params string
|
|
15
|
+
* @returns {string} - Normalized params (NO truncation)
|
|
16
|
+
*/
|
|
17
|
+
function normalizeParams(params) {
|
|
18
|
+
if (!params || params === '...') return params || '...';
|
|
19
|
+
// Collapse whitespace (newlines, tabs, multiple spaces) to single space
|
|
20
|
+
return params.replace(/\s+/g, ' ').trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format a line number for display
|
|
25
|
+
* @param {number} line - 1-indexed line number
|
|
26
|
+
* @param {number} width - Padding width
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function lineNum(line, width = 4) {
|
|
30
|
+
return String(line).padStart(width);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format a line range
|
|
35
|
+
* @param {number} start - 1-indexed start line
|
|
36
|
+
* @param {number} end - 1-indexed end line
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function lineRange(start, end) {
|
|
40
|
+
return `[${lineNum(start)}-${lineNum(end)}]`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format a single line location
|
|
45
|
+
* @param {number} line - 1-indexed line number
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function lineLoc(line) {
|
|
49
|
+
return `[${lineNum(line)}]`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// TEXT FORMATTERS
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format function signature for TOC display
|
|
58
|
+
* @param {object} fn - Function definition
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function formatFunctionSignature(fn) {
|
|
62
|
+
const parts = [];
|
|
63
|
+
|
|
64
|
+
// Modifiers
|
|
65
|
+
if (fn.modifiers && fn.modifiers.length > 0) {
|
|
66
|
+
parts.push(fn.modifiers.join(' '));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Generator marker
|
|
70
|
+
if (fn.isGenerator) parts.push('*');
|
|
71
|
+
|
|
72
|
+
// Name
|
|
73
|
+
parts.push(fn.name);
|
|
74
|
+
|
|
75
|
+
// Generics
|
|
76
|
+
if (fn.generics) parts.push(fn.generics);
|
|
77
|
+
|
|
78
|
+
// Parameters - FULL, never truncated
|
|
79
|
+
const params = normalizeParams(fn.params);
|
|
80
|
+
parts.push(`(${params})`);
|
|
81
|
+
|
|
82
|
+
// Return type
|
|
83
|
+
if (fn.returnType) parts.push(`: ${fn.returnType}`);
|
|
84
|
+
|
|
85
|
+
// Arrow indicator
|
|
86
|
+
if (fn.isArrow) parts.push(' =>');
|
|
87
|
+
|
|
88
|
+
return parts.filter(p => p).join('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format class/type signature for TOC display
|
|
93
|
+
*/
|
|
94
|
+
function formatClassSignature(cls) {
|
|
95
|
+
const parts = [cls.type, cls.name];
|
|
96
|
+
|
|
97
|
+
if (cls.generics) parts.push(cls.generics);
|
|
98
|
+
if (cls.extends) parts.push(`extends ${cls.extends}`);
|
|
99
|
+
if (cls.implements && cls.implements.length > 0) {
|
|
100
|
+
parts.push(`implements ${cls.implements.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return parts.join(' ');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format class member for TOC display
|
|
108
|
+
*/
|
|
109
|
+
function formatMemberSignature(member) {
|
|
110
|
+
const parts = [];
|
|
111
|
+
|
|
112
|
+
// Member type (static, get, set, private, etc.)
|
|
113
|
+
if (member.memberType && member.memberType !== 'method') {
|
|
114
|
+
parts.push(member.memberType);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Async
|
|
118
|
+
if (member.isAsync) parts.push('async');
|
|
119
|
+
|
|
120
|
+
// Generator
|
|
121
|
+
if (member.isGenerator) parts.push('*');
|
|
122
|
+
|
|
123
|
+
// Name
|
|
124
|
+
parts.push(member.name);
|
|
125
|
+
|
|
126
|
+
// Parameters
|
|
127
|
+
if (member.params !== undefined) {
|
|
128
|
+
const params = normalizeParams(member.params);
|
|
129
|
+
parts.push(`(${params})`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Return type
|
|
133
|
+
if (member.returnType) parts.push(`: ${member.returnType}`);
|
|
134
|
+
|
|
135
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Print section header
|
|
140
|
+
*/
|
|
141
|
+
function header(title, char = '═') {
|
|
142
|
+
console.log(title);
|
|
143
|
+
console.log(char.repeat(60));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Print subheader
|
|
148
|
+
*/
|
|
149
|
+
function subheader(title) {
|
|
150
|
+
console.log(title);
|
|
151
|
+
console.log('─'.repeat(40));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Print a usage/call site - FULL expression, never truncated
|
|
156
|
+
* @param {object} usage - Usage object
|
|
157
|
+
* @param {string} [relativePath] - Relative file path
|
|
158
|
+
*/
|
|
159
|
+
function printUsage(usage, relativePath) {
|
|
160
|
+
const file = relativePath || usage.file;
|
|
161
|
+
// FULL content - this is the key improvement
|
|
162
|
+
console.log(` ${file}:${usage.line}`);
|
|
163
|
+
console.log(` ${usage.content.trim()}`);
|
|
164
|
+
|
|
165
|
+
// Context lines if provided
|
|
166
|
+
if (usage.before && usage.before.length > 0) {
|
|
167
|
+
for (const line of usage.before) {
|
|
168
|
+
console.log(` ... ${line.trim()}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (usage.after && usage.after.length > 0) {
|
|
172
|
+
for (const line of usage.after) {
|
|
173
|
+
console.log(` ... ${line.trim()}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Print definition with full signature
|
|
180
|
+
*/
|
|
181
|
+
function printDefinition(def, relativePath) {
|
|
182
|
+
const file = relativePath || def.file;
|
|
183
|
+
console.log(` ${file}:${def.line}`);
|
|
184
|
+
if (def.signature) {
|
|
185
|
+
console.log(` ${def.signature}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// JSON FORMATTERS
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Format TOC data as JSON
|
|
195
|
+
*/
|
|
196
|
+
function formatTocJson(data) {
|
|
197
|
+
return JSON.stringify({
|
|
198
|
+
files: data.totalFiles,
|
|
199
|
+
lines: data.totalLines,
|
|
200
|
+
functions: data.totalFunctions,
|
|
201
|
+
classes: data.totalClasses,
|
|
202
|
+
state: data.totalState,
|
|
203
|
+
byFile: data.byFile.map(f => ({
|
|
204
|
+
file: f.file,
|
|
205
|
+
language: f.language,
|
|
206
|
+
lines: f.lines,
|
|
207
|
+
functions: f.functions.map(fn => ({
|
|
208
|
+
name: fn.name,
|
|
209
|
+
params: fn.params, // FULL params
|
|
210
|
+
paramsStructured: fn.paramsStructured || [],
|
|
211
|
+
startLine: fn.startLine,
|
|
212
|
+
endLine: fn.endLine,
|
|
213
|
+
modifiers: fn.modifiers || [],
|
|
214
|
+
...(fn.returnType && { returnType: fn.returnType }),
|
|
215
|
+
...(fn.generics && { generics: fn.generics }),
|
|
216
|
+
...(fn.docstring && { docstring: fn.docstring }),
|
|
217
|
+
...(fn.isArrow && { isArrow: true }),
|
|
218
|
+
...(fn.isGenerator && { isGenerator: true })
|
|
219
|
+
})),
|
|
220
|
+
classes: f.classes.map(c => ({
|
|
221
|
+
name: c.name,
|
|
222
|
+
type: c.type,
|
|
223
|
+
startLine: c.startLine,
|
|
224
|
+
endLine: c.endLine,
|
|
225
|
+
modifiers: c.modifiers || [],
|
|
226
|
+
members: c.members || [],
|
|
227
|
+
...(c.extends && { extends: c.extends }),
|
|
228
|
+
...(c.implements && { implements: c.implements }),
|
|
229
|
+
...(c.generics && { generics: c.generics }),
|
|
230
|
+
...(c.docstring && { docstring: c.docstring })
|
|
231
|
+
})),
|
|
232
|
+
state: f.state.map(s => ({
|
|
233
|
+
name: s.name,
|
|
234
|
+
startLine: s.startLine,
|
|
235
|
+
endLine: s.endLine
|
|
236
|
+
}))
|
|
237
|
+
}))
|
|
238
|
+
}, null, 2);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Format symbol search results as JSON
|
|
243
|
+
*/
|
|
244
|
+
function formatSymbolJson(symbols, query) {
|
|
245
|
+
return JSON.stringify({
|
|
246
|
+
query,
|
|
247
|
+
count: symbols.length,
|
|
248
|
+
results: symbols.map(s => ({
|
|
249
|
+
name: s.name,
|
|
250
|
+
type: s.type,
|
|
251
|
+
file: s.relativePath || s.file,
|
|
252
|
+
startLine: s.startLine,
|
|
253
|
+
endLine: s.endLine,
|
|
254
|
+
...(s.params && { params: s.params }), // FULL params
|
|
255
|
+
...(s.paramsStructured && { paramsStructured: s.paramsStructured }),
|
|
256
|
+
...(s.returnType && { returnType: s.returnType }),
|
|
257
|
+
...(s.modifiers && { modifiers: s.modifiers }),
|
|
258
|
+
...(s.usageCount !== undefined && { usageCount: s.usageCount }),
|
|
259
|
+
...(s.usageCounts !== undefined && { usageCounts: s.usageCounts })
|
|
260
|
+
}))
|
|
261
|
+
}, null, 2);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Format usages as JSON - FULL expressions, never truncated
|
|
266
|
+
*/
|
|
267
|
+
function formatUsagesJson(usages, name) {
|
|
268
|
+
const definitions = usages.filter(u => u.isDefinition);
|
|
269
|
+
const refs = usages.filter(u => !u.isDefinition);
|
|
270
|
+
|
|
271
|
+
const calls = refs.filter(u => u.usageType === 'call');
|
|
272
|
+
const imports = refs.filter(u => u.usageType === 'import');
|
|
273
|
+
const references = refs.filter(u => u.usageType === 'reference');
|
|
274
|
+
|
|
275
|
+
const formatUsage = (u) => ({
|
|
276
|
+
file: u.relativePath || u.file,
|
|
277
|
+
line: u.line,
|
|
278
|
+
expression: u.content, // FULL expression - key improvement
|
|
279
|
+
...(u.args && { args: u.args }), // Parsed arguments
|
|
280
|
+
...(u.before && u.before.length > 0 && { before: u.before }),
|
|
281
|
+
...(u.after && u.after.length > 0 && { after: u.after })
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return JSON.stringify({
|
|
285
|
+
symbol: name,
|
|
286
|
+
definitionCount: definitions.length,
|
|
287
|
+
callCount: calls.length,
|
|
288
|
+
importCount: imports.length,
|
|
289
|
+
referenceCount: references.length,
|
|
290
|
+
totalUsages: refs.length,
|
|
291
|
+
definitions: definitions.map(d => ({
|
|
292
|
+
file: d.relativePath || d.file,
|
|
293
|
+
line: d.line,
|
|
294
|
+
signature: d.signature || null, // FULL signature
|
|
295
|
+
type: d.type || null,
|
|
296
|
+
...(d.returnType && { returnType: d.returnType }),
|
|
297
|
+
...(d.before && d.before.length > 0 && { before: d.before }),
|
|
298
|
+
...(d.after && d.after.length > 0 && { after: d.after })
|
|
299
|
+
})),
|
|
300
|
+
calls: calls.map(formatUsage),
|
|
301
|
+
imports: imports.map(formatUsage),
|
|
302
|
+
references: references.map(formatUsage)
|
|
303
|
+
}, null, 2);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Format context (callers + callees) as JSON
|
|
308
|
+
*/
|
|
309
|
+
function formatContextJson(context) {
|
|
310
|
+
return JSON.stringify({
|
|
311
|
+
function: context.function,
|
|
312
|
+
file: context.file,
|
|
313
|
+
callerCount: context.callers.length,
|
|
314
|
+
calleeCount: context.callees.length,
|
|
315
|
+
callers: context.callers.map(c => ({
|
|
316
|
+
file: c.relativePath || c.file,
|
|
317
|
+
line: c.line,
|
|
318
|
+
expression: c.content, // FULL expression
|
|
319
|
+
callerName: c.callerName
|
|
320
|
+
})),
|
|
321
|
+
callees: context.callees.map(c => ({
|
|
322
|
+
name: c.name,
|
|
323
|
+
type: c.type,
|
|
324
|
+
file: c.relativePath || c.file,
|
|
325
|
+
line: c.startLine,
|
|
326
|
+
params: c.params, // FULL params
|
|
327
|
+
weight: c.weight || 'normal' // Dependency weight: core, setup, utility
|
|
328
|
+
}))
|
|
329
|
+
}, null, 2);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Format extracted function as JSON
|
|
334
|
+
*/
|
|
335
|
+
function formatFunctionJson(fn, code) {
|
|
336
|
+
return JSON.stringify({
|
|
337
|
+
name: fn.name,
|
|
338
|
+
params: fn.params, // FULL params
|
|
339
|
+
paramsStructured: fn.paramsStructured || [],
|
|
340
|
+
startLine: fn.startLine,
|
|
341
|
+
endLine: fn.endLine,
|
|
342
|
+
modifiers: fn.modifiers || [],
|
|
343
|
+
...(fn.returnType && { returnType: fn.returnType }),
|
|
344
|
+
...(fn.generics && { generics: fn.generics }),
|
|
345
|
+
...(fn.docstring && { docstring: fn.docstring }),
|
|
346
|
+
...(fn.isArrow && { isArrow: true }),
|
|
347
|
+
...(fn.isGenerator && { isGenerator: true }),
|
|
348
|
+
code // FULL code
|
|
349
|
+
}, null, 2);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Format search results as JSON
|
|
354
|
+
*/
|
|
355
|
+
function formatSearchJson(results, term) {
|
|
356
|
+
return JSON.stringify({
|
|
357
|
+
term,
|
|
358
|
+
totalMatches: results.reduce((sum, r) => sum + r.matches.length, 0),
|
|
359
|
+
files: results.map(r => ({
|
|
360
|
+
file: r.file,
|
|
361
|
+
matchCount: r.matches.length,
|
|
362
|
+
matches: r.matches.map(m => ({
|
|
363
|
+
line: m.line,
|
|
364
|
+
content: m.content // FULL content
|
|
365
|
+
}))
|
|
366
|
+
}))
|
|
367
|
+
}, null, 2);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Format imports as JSON
|
|
372
|
+
*/
|
|
373
|
+
function formatImportsJson(imports, filePath) {
|
|
374
|
+
return JSON.stringify({
|
|
375
|
+
file: filePath,
|
|
376
|
+
importCount: imports.length,
|
|
377
|
+
imports: imports.map(i => ({
|
|
378
|
+
module: i.module,
|
|
379
|
+
names: i.names,
|
|
380
|
+
type: i.type,
|
|
381
|
+
resolved: i.resolved || null
|
|
382
|
+
}))
|
|
383
|
+
}, null, 2);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Format project stats as JSON
|
|
388
|
+
*/
|
|
389
|
+
function formatStatsJson(stats) {
|
|
390
|
+
return JSON.stringify(stats, null, 2);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Format dependency graph as JSON
|
|
395
|
+
*/
|
|
396
|
+
function formatGraphJson(graph) {
|
|
397
|
+
return JSON.stringify({
|
|
398
|
+
file: graph.file,
|
|
399
|
+
depth: graph.depth,
|
|
400
|
+
dependencies: graph.dependencies
|
|
401
|
+
}, null, 2);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Format smart extraction result as JSON
|
|
406
|
+
* Includes function + all dependencies
|
|
407
|
+
*/
|
|
408
|
+
function formatSmartJson(result) {
|
|
409
|
+
return JSON.stringify({
|
|
410
|
+
target: {
|
|
411
|
+
name: result.target.name,
|
|
412
|
+
file: result.target.file,
|
|
413
|
+
startLine: result.target.startLine,
|
|
414
|
+
endLine: result.target.endLine,
|
|
415
|
+
params: result.target.params,
|
|
416
|
+
returnType: result.target.returnType,
|
|
417
|
+
code: result.target.code
|
|
418
|
+
},
|
|
419
|
+
dependencies: result.dependencies.map(d => ({
|
|
420
|
+
name: d.name,
|
|
421
|
+
type: d.type,
|
|
422
|
+
file: d.file,
|
|
423
|
+
startLine: d.startLine,
|
|
424
|
+
endLine: d.endLine,
|
|
425
|
+
params: d.params,
|
|
426
|
+
weight: d.weight, // core, setup, utility
|
|
427
|
+
callCount: d.callCount,
|
|
428
|
+
code: d.code
|
|
429
|
+
})),
|
|
430
|
+
types: result.types || []
|
|
431
|
+
}, null, 2);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// NEW FORMATTERS (v2 Migration)
|
|
436
|
+
// ============================================================================
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Format imports command output - text
|
|
440
|
+
*/
|
|
441
|
+
function formatImports(imports, filePath) {
|
|
442
|
+
const lines = [`Imports in ${filePath}:\n`];
|
|
443
|
+
|
|
444
|
+
const internal = imports.filter(i => !i.isExternal);
|
|
445
|
+
const external = imports.filter(i => i.isExternal);
|
|
446
|
+
|
|
447
|
+
if (internal.length > 0) {
|
|
448
|
+
lines.push('INTERNAL:');
|
|
449
|
+
for (const imp of internal) {
|
|
450
|
+
lines.push(` ${imp.module}`);
|
|
451
|
+
if (imp.resolved) {
|
|
452
|
+
lines.push(` -> ${imp.resolved}`);
|
|
453
|
+
}
|
|
454
|
+
if (imp.names && imp.names.length > 0 && imp.names[0] !== '*') {
|
|
455
|
+
lines.push(` ${imp.names.join(', ')}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (external.length > 0) {
|
|
461
|
+
if (internal.length > 0) lines.push('');
|
|
462
|
+
lines.push('EXTERNAL:');
|
|
463
|
+
for (const imp of external) {
|
|
464
|
+
lines.push(` ${imp.module}`);
|
|
465
|
+
if (imp.names && imp.names.length > 0) {
|
|
466
|
+
lines.push(` ${imp.names.join(', ')}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return lines.join('\n');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Format exporters command output - text
|
|
476
|
+
*/
|
|
477
|
+
function formatExporters(exporters, filePath) {
|
|
478
|
+
const lines = [`Files that import ${filePath}:\n`];
|
|
479
|
+
|
|
480
|
+
if (exporters.length === 0) {
|
|
481
|
+
lines.push(' (none found)');
|
|
482
|
+
} else {
|
|
483
|
+
for (const exp of exporters) {
|
|
484
|
+
if (exp.importLine) {
|
|
485
|
+
lines.push(` ${exp.file}:${exp.importLine}`);
|
|
486
|
+
} else {
|
|
487
|
+
lines.push(` ${exp.file}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return lines.join('\n');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Format typedef command output - text
|
|
497
|
+
*/
|
|
498
|
+
function formatTypedef(types, name) {
|
|
499
|
+
const lines = [`Type definitions for "${name}":\n`];
|
|
500
|
+
|
|
501
|
+
if (types.length === 0) {
|
|
502
|
+
lines.push(' (none found)');
|
|
503
|
+
} else {
|
|
504
|
+
for (const t of types) {
|
|
505
|
+
lines.push(` ${t.relativePath}:${t.startLine} ${t.type} ${t.name}`);
|
|
506
|
+
if (t.usageCount !== undefined) {
|
|
507
|
+
lines.push(` (${t.usageCount} usages)`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return lines.join('\n');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Format tests command output - text
|
|
517
|
+
*/
|
|
518
|
+
function formatTests(tests, name) {
|
|
519
|
+
const lines = [`Tests for "${name}":\n`];
|
|
520
|
+
|
|
521
|
+
if (tests.length === 0) {
|
|
522
|
+
lines.push(' (no tests found)');
|
|
523
|
+
} else {
|
|
524
|
+
const totalMatches = tests.reduce((sum, t) => sum + t.matches.length, 0);
|
|
525
|
+
lines.push(`Found ${totalMatches} matches in ${tests.length} test file(s):\n`);
|
|
526
|
+
|
|
527
|
+
for (const testFile of tests) {
|
|
528
|
+
lines.push(testFile.file);
|
|
529
|
+
for (const match of testFile.matches) {
|
|
530
|
+
const typeLabel = match.matchType === 'test-case' ? '[test]' :
|
|
531
|
+
match.matchType === 'import' ? '[import]' :
|
|
532
|
+
match.matchType === 'call' ? '[call]' : '';
|
|
533
|
+
lines.push(` ${match.line}: ${typeLabel} ${match.content}`);
|
|
534
|
+
}
|
|
535
|
+
lines.push('');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return lines.join('\n');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Format api command output - text
|
|
544
|
+
*/
|
|
545
|
+
function formatApi(symbols, filePath) {
|
|
546
|
+
const title = filePath
|
|
547
|
+
? `Exports from ${filePath}:`
|
|
548
|
+
: 'Project API (exported symbols):';
|
|
549
|
+
const lines = [title + '\n'];
|
|
550
|
+
|
|
551
|
+
if (symbols.length === 0) {
|
|
552
|
+
lines.push(' (none found)');
|
|
553
|
+
} else {
|
|
554
|
+
// Group by file
|
|
555
|
+
const byFile = new Map();
|
|
556
|
+
for (const sym of symbols) {
|
|
557
|
+
if (!byFile.has(sym.file)) {
|
|
558
|
+
byFile.set(sym.file, []);
|
|
559
|
+
}
|
|
560
|
+
byFile.get(sym.file).push(sym);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
for (const [file, syms] of byFile) {
|
|
564
|
+
lines.push(file);
|
|
565
|
+
for (const s of syms) {
|
|
566
|
+
const sig = s.signature || `${s.type} ${s.name}`;
|
|
567
|
+
lines.push(` ${lineRange(s.startLine, s.endLine)} ${sig}`);
|
|
568
|
+
}
|
|
569
|
+
lines.push('');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return lines.join('\n');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Format disambiguation prompt - text
|
|
578
|
+
*/
|
|
579
|
+
function formatDisambiguation(matches, name, command) {
|
|
580
|
+
const lines = [`Multiple matches for "${name}":\n`];
|
|
581
|
+
|
|
582
|
+
for (const m of matches) {
|
|
583
|
+
const sig = m.params !== undefined
|
|
584
|
+
? formatFunctionSignature(m)
|
|
585
|
+
: formatClassSignature(m);
|
|
586
|
+
lines.push(` ${m.relativePath}:${m.startLine} ${sig}`);
|
|
587
|
+
if (m.usageCount !== undefined) {
|
|
588
|
+
lines.push(` (${m.usageCount} usages)`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
lines.push('');
|
|
593
|
+
lines.push(`Use: ucn . ${command} ${name} --file <pattern>`);
|
|
594
|
+
|
|
595
|
+
return lines.join('\n');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ============================================================================
|
|
599
|
+
// NEW JSON FORMATTERS
|
|
600
|
+
// ============================================================================
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Format exporters as JSON
|
|
604
|
+
*/
|
|
605
|
+
function formatExportersJson(exporters, filePath) {
|
|
606
|
+
return JSON.stringify({
|
|
607
|
+
file: filePath,
|
|
608
|
+
importerCount: exporters.length,
|
|
609
|
+
importers: exporters
|
|
610
|
+
}, null, 2);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Format typedef as JSON
|
|
615
|
+
*/
|
|
616
|
+
function formatTypedefJson(types, name) {
|
|
617
|
+
return JSON.stringify({
|
|
618
|
+
query: name,
|
|
619
|
+
count: types.length,
|
|
620
|
+
types: types.map(t => ({
|
|
621
|
+
name: t.name,
|
|
622
|
+
type: t.type,
|
|
623
|
+
file: t.relativePath || t.file,
|
|
624
|
+
startLine: t.startLine,
|
|
625
|
+
endLine: t.endLine,
|
|
626
|
+
...(t.usageCount !== undefined && { usageCount: t.usageCount })
|
|
627
|
+
}))
|
|
628
|
+
}, null, 2);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Format tests as JSON
|
|
633
|
+
*/
|
|
634
|
+
function formatTestsJson(tests, name) {
|
|
635
|
+
return JSON.stringify({
|
|
636
|
+
query: name,
|
|
637
|
+
testFileCount: tests.length,
|
|
638
|
+
totalMatches: tests.reduce((sum, t) => sum + t.matches.length, 0),
|
|
639
|
+
testFiles: tests
|
|
640
|
+
}, null, 2);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Format api as JSON
|
|
645
|
+
*/
|
|
646
|
+
function formatApiJson(symbols, filePath) {
|
|
647
|
+
return JSON.stringify({
|
|
648
|
+
...(filePath && { file: filePath }),
|
|
649
|
+
exportCount: symbols.length,
|
|
650
|
+
exports: symbols.map(s => ({
|
|
651
|
+
name: s.name,
|
|
652
|
+
type: s.type,
|
|
653
|
+
file: s.file,
|
|
654
|
+
startLine: s.startLine,
|
|
655
|
+
endLine: s.endLine,
|
|
656
|
+
...(s.params && { params: s.params }),
|
|
657
|
+
...(s.returnType && { returnType: s.returnType }),
|
|
658
|
+
...(s.signature && { signature: s.signature })
|
|
659
|
+
}))
|
|
660
|
+
}, null, 2);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Format trace command output - text
|
|
665
|
+
* Shows call tree visualization
|
|
666
|
+
*/
|
|
667
|
+
function formatTrace(trace) {
|
|
668
|
+
if (!trace) {
|
|
669
|
+
return 'Function not found.';
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const lines = [];
|
|
673
|
+
|
|
674
|
+
// Header
|
|
675
|
+
lines.push(`Call tree for ${trace.root}`);
|
|
676
|
+
lines.push('═'.repeat(60));
|
|
677
|
+
lines.push(`${trace.file}:${trace.line}`);
|
|
678
|
+
lines.push(`Direction: ${trace.direction}, Max depth: ${trace.maxDepth}`);
|
|
679
|
+
lines.push('');
|
|
680
|
+
|
|
681
|
+
// Render tree
|
|
682
|
+
const renderNode = (node, prefix = '', isLast = true) => {
|
|
683
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
684
|
+
const extension = isLast ? ' ' : '│ ';
|
|
685
|
+
|
|
686
|
+
let label = node.name;
|
|
687
|
+
if (node.external) {
|
|
688
|
+
label += ' [external]';
|
|
689
|
+
} else if (node.file) {
|
|
690
|
+
label += ` (${node.file}:${node.line})`;
|
|
691
|
+
}
|
|
692
|
+
if (node.weight && node.weight !== 'normal') {
|
|
693
|
+
label += ` [${node.weight}]`;
|
|
694
|
+
}
|
|
695
|
+
if (node.callCount) {
|
|
696
|
+
label += ` ${node.callCount}x`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
lines.push(prefix + connector + label);
|
|
700
|
+
|
|
701
|
+
if (node.children) {
|
|
702
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
703
|
+
const isChildLast = i === node.children.length - 1;
|
|
704
|
+
renderNode(node.children[i], prefix + extension, isChildLast);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// Root node
|
|
710
|
+
lines.push(trace.root);
|
|
711
|
+
if (trace.tree && trace.tree.children) {
|
|
712
|
+
for (let i = 0; i < trace.tree.children.length; i++) {
|
|
713
|
+
const isLast = i === trace.tree.children.length - 1;
|
|
714
|
+
renderNode(trace.tree.children[i], '', isLast);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Callers section
|
|
719
|
+
if (trace.callers && trace.callers.length > 0) {
|
|
720
|
+
lines.push('');
|
|
721
|
+
lines.push('CALLED BY:');
|
|
722
|
+
for (const c of trace.callers) {
|
|
723
|
+
lines.push(` ${c.name} - ${c.file}:${c.line}`);
|
|
724
|
+
lines.push(` ${c.expression}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return lines.join('\n');
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Format trace command output - JSON
|
|
733
|
+
*/
|
|
734
|
+
function formatTraceJson(trace) {
|
|
735
|
+
if (!trace) {
|
|
736
|
+
return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
|
|
737
|
+
}
|
|
738
|
+
return JSON.stringify(trace, null, 2);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Format related command output - text
|
|
743
|
+
*/
|
|
744
|
+
function formatRelated(related) {
|
|
745
|
+
if (!related) {
|
|
746
|
+
return 'Function not found.';
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const lines = [];
|
|
750
|
+
|
|
751
|
+
// Header
|
|
752
|
+
lines.push(`Related to ${related.target.name}`);
|
|
753
|
+
lines.push('═'.repeat(60));
|
|
754
|
+
lines.push(`${related.target.file}:${related.target.line}`);
|
|
755
|
+
lines.push('');
|
|
756
|
+
|
|
757
|
+
// Same file
|
|
758
|
+
if (related.sameFile.length > 0) {
|
|
759
|
+
lines.push(`SAME FILE (${related.sameFile.length}):`);
|
|
760
|
+
for (const f of related.sameFile.slice(0, 8)) {
|
|
761
|
+
const params = f.params ? `(${f.params})` : '';
|
|
762
|
+
lines.push(` :${f.line} ${f.name}${params}`);
|
|
763
|
+
}
|
|
764
|
+
if (related.sameFile.length > 8) {
|
|
765
|
+
lines.push(` ... and ${related.sameFile.length - 8} more`);
|
|
766
|
+
}
|
|
767
|
+
lines.push('');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Similar names
|
|
771
|
+
if (related.similarNames.length > 0) {
|
|
772
|
+
lines.push(`SIMILAR NAMES (${related.similarNames.length}):`);
|
|
773
|
+
for (const s of related.similarNames) {
|
|
774
|
+
lines.push(` ${s.name} - ${s.file}:${s.line}`);
|
|
775
|
+
lines.push(` shared: ${s.sharedParts.join(', ')}`);
|
|
776
|
+
}
|
|
777
|
+
lines.push('');
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Shared callers
|
|
781
|
+
if (related.sharedCallers.length > 0) {
|
|
782
|
+
lines.push(`CALLED BY SAME FUNCTIONS (${related.sharedCallers.length}):`);
|
|
783
|
+
for (const s of related.sharedCallers) {
|
|
784
|
+
lines.push(` ${s.name} - ${s.file}:${s.line} (${s.sharedCallerCount} shared callers)`);
|
|
785
|
+
}
|
|
786
|
+
lines.push('');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Shared callees
|
|
790
|
+
if (related.sharedCallees.length > 0) {
|
|
791
|
+
lines.push(`CALLS SAME FUNCTIONS (${related.sharedCallees.length}):`);
|
|
792
|
+
for (const s of related.sharedCallees) {
|
|
793
|
+
lines.push(` ${s.name} - ${s.file}:${s.line} (${s.sharedCalleeCount} shared callees)`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return lines.join('\n');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Format related command output - JSON
|
|
802
|
+
*/
|
|
803
|
+
function formatRelatedJson(related) {
|
|
804
|
+
if (!related) {
|
|
805
|
+
return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
|
|
806
|
+
}
|
|
807
|
+
return JSON.stringify(related, null, 2);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Format impact command output - text
|
|
812
|
+
* Shows what would need updating if a function signature changes
|
|
813
|
+
*/
|
|
814
|
+
function formatImpact(impact) {
|
|
815
|
+
if (!impact) {
|
|
816
|
+
return 'Function not found.';
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const lines = [];
|
|
820
|
+
|
|
821
|
+
// Header
|
|
822
|
+
lines.push(`Impact analysis for ${impact.function}`);
|
|
823
|
+
lines.push('═'.repeat(60));
|
|
824
|
+
lines.push(`${impact.file}:${impact.startLine}`);
|
|
825
|
+
lines.push(impact.signature);
|
|
826
|
+
lines.push('');
|
|
827
|
+
|
|
828
|
+
// Summary
|
|
829
|
+
lines.push(`CALL SITES: ${impact.totalCallSites}`);
|
|
830
|
+
lines.push(` Files affected: ${impact.byFile.length}`);
|
|
831
|
+
|
|
832
|
+
// Patterns
|
|
833
|
+
const p = impact.patterns;
|
|
834
|
+
if (p) {
|
|
835
|
+
const patternParts = [];
|
|
836
|
+
if (p.constantArgs > 0) patternParts.push(`${p.constantArgs} with literals`);
|
|
837
|
+
if (p.variableArgs > 0) patternParts.push(`${p.variableArgs} with variables`);
|
|
838
|
+
if (p.awaitedCalls > 0) patternParts.push(`${p.awaitedCalls} awaited`);
|
|
839
|
+
if (p.chainedCalls > 0) patternParts.push(`${p.chainedCalls} chained`);
|
|
840
|
+
if (p.spreadCalls > 0) patternParts.push(`${p.spreadCalls} with spread`);
|
|
841
|
+
if (patternParts.length > 0) {
|
|
842
|
+
lines.push(` Patterns: ${patternParts.join(', ')}`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// By file
|
|
847
|
+
lines.push('');
|
|
848
|
+
lines.push('BY FILE:');
|
|
849
|
+
for (const fileGroup of impact.byFile) {
|
|
850
|
+
lines.push(`\n${fileGroup.file} (${fileGroup.count} calls)`);
|
|
851
|
+
for (const site of fileGroup.sites.slice(0, 10)) { // Limit to 10 per file
|
|
852
|
+
const caller = site.callerName ? `[${site.callerName}]` : '';
|
|
853
|
+
lines.push(` :${site.line} ${caller}`);
|
|
854
|
+
lines.push(` ${site.expression}`);
|
|
855
|
+
if (site.args && site.args.length > 0) {
|
|
856
|
+
lines.push(` args: ${site.args.join(', ')}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (fileGroup.count > 10) {
|
|
860
|
+
lines.push(` ... and ${fileGroup.count - 10} more`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return lines.join('\n');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Format impact command output - JSON
|
|
869
|
+
*/
|
|
870
|
+
function formatImpactJson(impact) {
|
|
871
|
+
if (!impact) {
|
|
872
|
+
return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
|
|
873
|
+
}
|
|
874
|
+
return JSON.stringify(impact, null, 2);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Format plan command output - text
|
|
879
|
+
* Shows before/after signatures and all changes needed
|
|
880
|
+
*/
|
|
881
|
+
function formatPlan(plan) {
|
|
882
|
+
if (!plan) {
|
|
883
|
+
return 'Function not found.';
|
|
884
|
+
}
|
|
885
|
+
if (!plan.found) {
|
|
886
|
+
if (plan.error) {
|
|
887
|
+
return `Error: ${plan.error}\nCurrent parameters: ${plan.currentParams?.join(', ') || 'none'}`;
|
|
888
|
+
}
|
|
889
|
+
return `Function "${plan.function}" not found.`;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const lines = [];
|
|
893
|
+
|
|
894
|
+
// Header
|
|
895
|
+
lines.push(`Refactoring plan: ${plan.operation}`);
|
|
896
|
+
lines.push('═'.repeat(60));
|
|
897
|
+
lines.push(`${plan.file}:${plan.startLine}`);
|
|
898
|
+
lines.push('');
|
|
899
|
+
|
|
900
|
+
// Before/After
|
|
901
|
+
lines.push('SIGNATURE CHANGE:');
|
|
902
|
+
lines.push(` Before: ${plan.before.signature}`);
|
|
903
|
+
lines.push(` After: ${plan.after.signature}`);
|
|
904
|
+
lines.push('');
|
|
905
|
+
|
|
906
|
+
// Summary
|
|
907
|
+
lines.push(`CHANGES NEEDED: ${plan.totalChanges}`);
|
|
908
|
+
lines.push(` Files affected: ${plan.filesAffected}`);
|
|
909
|
+
lines.push('');
|
|
910
|
+
|
|
911
|
+
// Group by file
|
|
912
|
+
const byFile = new Map();
|
|
913
|
+
for (const change of plan.changes) {
|
|
914
|
+
if (!byFile.has(change.file)) {
|
|
915
|
+
byFile.set(change.file, []);
|
|
916
|
+
}
|
|
917
|
+
byFile.get(change.file).push(change);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
lines.push('BY FILE:');
|
|
921
|
+
for (const [file, changes] of byFile) {
|
|
922
|
+
lines.push(`\n${file} (${changes.length} changes)`);
|
|
923
|
+
for (const change of changes.slice(0, 10)) {
|
|
924
|
+
lines.push(` :${change.line}`);
|
|
925
|
+
lines.push(` ${change.expression}`);
|
|
926
|
+
lines.push(` → ${change.suggestion}`);
|
|
927
|
+
}
|
|
928
|
+
if (changes.length > 10) {
|
|
929
|
+
lines.push(` ... and ${changes.length - 10} more`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return lines.join('\n');
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Format stack trace command output - text
|
|
938
|
+
* Shows code context for each stack frame
|
|
939
|
+
*/
|
|
940
|
+
function formatStackTrace(result) {
|
|
941
|
+
if (!result || result.frameCount === 0) {
|
|
942
|
+
return 'No stack frames found in input.';
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const lines = [];
|
|
946
|
+
lines.push(`Stack trace: ${result.frameCount} frames`);
|
|
947
|
+
lines.push('═'.repeat(60));
|
|
948
|
+
|
|
949
|
+
for (let i = 0; i < result.frames.length; i++) {
|
|
950
|
+
const frame = result.frames[i];
|
|
951
|
+
lines.push('');
|
|
952
|
+
lines.push(`Frame ${i}: ${frame.function || '(anonymous)'}`);
|
|
953
|
+
lines.push('─'.repeat(40));
|
|
954
|
+
|
|
955
|
+
if (frame.found) {
|
|
956
|
+
lines.push(` ${frame.resolvedFile}:${frame.line}`);
|
|
957
|
+
|
|
958
|
+
// Show code context
|
|
959
|
+
if (frame.context) {
|
|
960
|
+
lines.push('');
|
|
961
|
+
for (const ctx of frame.context) {
|
|
962
|
+
const marker = ctx.isCurrent ? '→ ' : ' ';
|
|
963
|
+
const lineNum = ctx.line.toString().padStart(4);
|
|
964
|
+
lines.push(` ${marker}${lineNum} │ ${ctx.code}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Show function info if available
|
|
969
|
+
if (frame.functionInfo) {
|
|
970
|
+
lines.push('');
|
|
971
|
+
lines.push(` In: ${frame.functionInfo.name}(${frame.functionInfo.params || ''})`);
|
|
972
|
+
lines.push(` Range: ${frame.functionInfo.startLine}-${frame.functionInfo.endLine}`);
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
lines.push(` ${frame.file}:${frame.line} (file not found in project)`);
|
|
976
|
+
lines.push(` Raw: ${frame.raw}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return lines.join('\n');
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Format verify command output - text
|
|
985
|
+
* Shows call site validation results
|
|
986
|
+
*/
|
|
987
|
+
function formatVerify(result) {
|
|
988
|
+
if (!result) {
|
|
989
|
+
return 'Function not found.';
|
|
990
|
+
}
|
|
991
|
+
if (!result.found) {
|
|
992
|
+
return `Function "${result.function}" not found.`;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const lines = [];
|
|
996
|
+
|
|
997
|
+
// Header
|
|
998
|
+
lines.push(`Verification: ${result.function}`);
|
|
999
|
+
lines.push('═'.repeat(60));
|
|
1000
|
+
lines.push(`${result.file}:${result.startLine}`);
|
|
1001
|
+
lines.push(result.signature);
|
|
1002
|
+
lines.push('');
|
|
1003
|
+
|
|
1004
|
+
// Expected args
|
|
1005
|
+
const { min, max } = result.expectedArgs;
|
|
1006
|
+
const expectedStr = min === max ? `${min}` : `${min}-${max}`;
|
|
1007
|
+
lines.push(`Expected arguments: ${expectedStr}`);
|
|
1008
|
+
lines.push('');
|
|
1009
|
+
|
|
1010
|
+
// Summary
|
|
1011
|
+
const status = result.mismatches === 0 ? '✓ All calls valid' : '✗ Mismatches found';
|
|
1012
|
+
lines.push(`STATUS: ${status}`);
|
|
1013
|
+
lines.push(` Total calls: ${result.totalCalls}`);
|
|
1014
|
+
lines.push(` Valid: ${result.valid}`);
|
|
1015
|
+
lines.push(` Mismatches: ${result.mismatches}`);
|
|
1016
|
+
lines.push(` Uncertain: ${result.uncertain}`);
|
|
1017
|
+
|
|
1018
|
+
// Show mismatches
|
|
1019
|
+
if (result.mismatchDetails.length > 0) {
|
|
1020
|
+
lines.push('');
|
|
1021
|
+
lines.push('MISMATCHES:');
|
|
1022
|
+
for (const m of result.mismatchDetails.slice(0, 10)) {
|
|
1023
|
+
lines.push(` ${m.file}:${m.line}`);
|
|
1024
|
+
lines.push(` ${m.expression}`);
|
|
1025
|
+
lines.push(` Expected ${m.expected}, got ${m.actual}: [${m.args?.join(', ') || ''}]`);
|
|
1026
|
+
}
|
|
1027
|
+
if (result.mismatchDetails.length > 10) {
|
|
1028
|
+
lines.push(` ... and ${result.mismatchDetails.length - 10} more`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Show uncertain
|
|
1033
|
+
if (result.uncertainDetails.length > 0) {
|
|
1034
|
+
lines.push('');
|
|
1035
|
+
lines.push('UNCERTAIN (manual check needed):');
|
|
1036
|
+
for (const u of result.uncertainDetails.slice(0, 5)) {
|
|
1037
|
+
lines.push(` ${u.file}:${u.line}`);
|
|
1038
|
+
lines.push(` ${u.expression}`);
|
|
1039
|
+
lines.push(` Reason: ${u.reason}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (result.uncertainDetails.length > 5) {
|
|
1042
|
+
lines.push(` ... and ${result.uncertainDetails.length - 5} more`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return lines.join('\n');
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Format about command output - text
|
|
1051
|
+
* The "tell me everything" output for AI agents
|
|
1052
|
+
*/
|
|
1053
|
+
function formatAbout(about, options = {}) {
|
|
1054
|
+
if (!about) {
|
|
1055
|
+
return 'Symbol not found.';
|
|
1056
|
+
}
|
|
1057
|
+
if (!about.found) {
|
|
1058
|
+
const lines = ['Symbol not found.\n'];
|
|
1059
|
+
if (about.suggestions && about.suggestions.length > 0) {
|
|
1060
|
+
lines.push('Did you mean:');
|
|
1061
|
+
for (const s of about.suggestions) {
|
|
1062
|
+
lines.push(` ${s.name} (${s.type}) - ${s.file}:${s.line}`);
|
|
1063
|
+
lines.push(` ${s.usageCount} usages`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return lines.join('\n');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const lines = [];
|
|
1070
|
+
const sym = about.symbol;
|
|
1071
|
+
const { expand, root, depth } = options;
|
|
1072
|
+
|
|
1073
|
+
// Depth=0: location only
|
|
1074
|
+
if (depth === '0') {
|
|
1075
|
+
return `${sym.file}:${sym.startLine}`;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Depth=1: location + signature + usage counts
|
|
1079
|
+
if (depth === '1') {
|
|
1080
|
+
lines.push(`${sym.file}:${sym.startLine}`);
|
|
1081
|
+
if (sym.signature) {
|
|
1082
|
+
lines.push(sym.signature);
|
|
1083
|
+
}
|
|
1084
|
+
lines.push(`(${about.totalUsages} usages: ${about.usages.calls} calls, ${about.usages.imports} imports, ${about.usages.references} refs)`);
|
|
1085
|
+
return lines.join('\n');
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Header with signature
|
|
1089
|
+
lines.push(`${sym.name} (${sym.type})`);
|
|
1090
|
+
lines.push('═'.repeat(60));
|
|
1091
|
+
lines.push(`${sym.file}:${sym.startLine}-${sym.endLine}`);
|
|
1092
|
+
if (sym.signature) {
|
|
1093
|
+
lines.push(sym.signature);
|
|
1094
|
+
}
|
|
1095
|
+
if (sym.docstring) {
|
|
1096
|
+
lines.push(`"${sym.docstring}"`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Warnings (show early for visibility)
|
|
1100
|
+
if (about.warnings && about.warnings.length > 0) {
|
|
1101
|
+
lines.push('');
|
|
1102
|
+
lines.push('⚠️ WARNINGS:');
|
|
1103
|
+
for (const w of about.warnings) {
|
|
1104
|
+
lines.push(` ${w.message}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Usage summary
|
|
1109
|
+
lines.push('');
|
|
1110
|
+
lines.push(`USAGES: ${about.totalUsages} total`);
|
|
1111
|
+
lines.push(` ${about.usages.calls} calls, ${about.usages.imports} imports, ${about.usages.references} references`);
|
|
1112
|
+
|
|
1113
|
+
// Callers
|
|
1114
|
+
if (about.callers.total > 0) {
|
|
1115
|
+
lines.push('');
|
|
1116
|
+
lines.push(`CALLERS (${about.callers.total} total, showing top ${about.callers.top.length}):`);
|
|
1117
|
+
for (const c of about.callers.top) {
|
|
1118
|
+
const caller = c.callerName ? `[${c.callerName}]` : '';
|
|
1119
|
+
lines.push(` ${c.file}:${c.line} ${caller}`);
|
|
1120
|
+
lines.push(` ${c.expression}`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Callees
|
|
1125
|
+
if (about.callees.total > 0) {
|
|
1126
|
+
lines.push('');
|
|
1127
|
+
lines.push(`CALLEES (${about.callees.total} total, showing top ${about.callees.top.length}):`);
|
|
1128
|
+
for (const c of about.callees.top) {
|
|
1129
|
+
const weight = c.weight !== 'normal' ? `[${c.weight}]` : '';
|
|
1130
|
+
lines.push(` ${c.name} ${weight} - ${c.file}:${c.line} (${c.callCount}x)`);
|
|
1131
|
+
|
|
1132
|
+
// Inline expansion: show first 3 lines of callee code
|
|
1133
|
+
if (expand && root && c.file && c.startLine) {
|
|
1134
|
+
try {
|
|
1135
|
+
const filePath = path.isAbsolute(c.file) ? c.file : path.join(root, c.file);
|
|
1136
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1137
|
+
const fileLines = content.split('\n');
|
|
1138
|
+
const endLine = c.endLine || c.startLine + 5;
|
|
1139
|
+
const previewLines = Math.min(3, endLine - c.startLine + 1);
|
|
1140
|
+
for (let i = 0; i < previewLines && c.startLine - 1 + i < fileLines.length; i++) {
|
|
1141
|
+
const codeLine = fileLines[c.startLine - 1 + i];
|
|
1142
|
+
lines.push(` │ ${codeLine}`);
|
|
1143
|
+
}
|
|
1144
|
+
if (endLine - c.startLine + 1 > 3) {
|
|
1145
|
+
lines.push(` │ ... (${endLine - c.startLine - 2} more lines)`);
|
|
1146
|
+
}
|
|
1147
|
+
} catch (e) {
|
|
1148
|
+
// Skip expansion on error
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Tests
|
|
1155
|
+
if (about.tests.totalMatches > 0) {
|
|
1156
|
+
lines.push('');
|
|
1157
|
+
lines.push(`TESTS: ${about.tests.totalMatches} matches in ${about.tests.fileCount} file(s)`);
|
|
1158
|
+
for (const f of about.tests.files) {
|
|
1159
|
+
lines.push(` ${f}`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Other definitions
|
|
1164
|
+
if (about.otherDefinitions.length > 0) {
|
|
1165
|
+
lines.push('');
|
|
1166
|
+
lines.push(`OTHER DEFINITIONS (${about.otherDefinitions.length}):`);
|
|
1167
|
+
for (const d of about.otherDefinitions) {
|
|
1168
|
+
lines.push(` ${d.file}:${d.line} (${d.usageCount} usages)`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Types
|
|
1173
|
+
if (about.types && about.types.length > 0) {
|
|
1174
|
+
lines.push('');
|
|
1175
|
+
lines.push('TYPES:');
|
|
1176
|
+
for (const t of about.types) {
|
|
1177
|
+
lines.push(` ${t.name} (${t.type}) - ${t.file}:${t.line}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Completeness warnings
|
|
1182
|
+
if (about.completeness && about.completeness.warnings && about.completeness.warnings.length > 0) {
|
|
1183
|
+
lines.push('');
|
|
1184
|
+
lines.push('⚠ COMPLETENESS:');
|
|
1185
|
+
for (const w of about.completeness.warnings) {
|
|
1186
|
+
lines.push(` ${w.message}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Code
|
|
1191
|
+
if (about.code) {
|
|
1192
|
+
lines.push('');
|
|
1193
|
+
lines.push('─── CODE ───');
|
|
1194
|
+
lines.push(about.code);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
return lines.join('\n');
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Format about command output - JSON
|
|
1202
|
+
*/
|
|
1203
|
+
function formatAboutJson(about) {
|
|
1204
|
+
if (!about) {
|
|
1205
|
+
return JSON.stringify({ found: false, error: 'Symbol not found' }, null, 2);
|
|
1206
|
+
}
|
|
1207
|
+
return JSON.stringify(about, null, 2);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
module.exports = {
|
|
1211
|
+
// Utilities
|
|
1212
|
+
normalizeParams,
|
|
1213
|
+
lineNum,
|
|
1214
|
+
lineRange,
|
|
1215
|
+
lineLoc,
|
|
1216
|
+
formatFunctionSignature,
|
|
1217
|
+
formatClassSignature,
|
|
1218
|
+
formatMemberSignature,
|
|
1219
|
+
|
|
1220
|
+
// Text output
|
|
1221
|
+
header,
|
|
1222
|
+
subheader,
|
|
1223
|
+
printUsage,
|
|
1224
|
+
printDefinition,
|
|
1225
|
+
|
|
1226
|
+
// JSON formatters
|
|
1227
|
+
formatTocJson,
|
|
1228
|
+
formatSymbolJson,
|
|
1229
|
+
formatUsagesJson,
|
|
1230
|
+
formatContextJson,
|
|
1231
|
+
formatFunctionJson,
|
|
1232
|
+
formatSearchJson,
|
|
1233
|
+
formatImportsJson,
|
|
1234
|
+
formatStatsJson,
|
|
1235
|
+
formatGraphJson,
|
|
1236
|
+
formatSmartJson,
|
|
1237
|
+
|
|
1238
|
+
// New formatters (v2 migration)
|
|
1239
|
+
formatImports,
|
|
1240
|
+
formatExporters,
|
|
1241
|
+
formatTypedef,
|
|
1242
|
+
formatTests,
|
|
1243
|
+
formatApi,
|
|
1244
|
+
formatDisambiguation,
|
|
1245
|
+
formatExportersJson,
|
|
1246
|
+
formatTypedefJson,
|
|
1247
|
+
formatTestsJson,
|
|
1248
|
+
formatApiJson,
|
|
1249
|
+
|
|
1250
|
+
// About command
|
|
1251
|
+
formatAbout,
|
|
1252
|
+
formatAboutJson,
|
|
1253
|
+
|
|
1254
|
+
// Impact command
|
|
1255
|
+
formatImpact,
|
|
1256
|
+
formatImpactJson,
|
|
1257
|
+
|
|
1258
|
+
// Plan command
|
|
1259
|
+
formatPlan,
|
|
1260
|
+
|
|
1261
|
+
// Stack trace command
|
|
1262
|
+
formatStackTrace,
|
|
1263
|
+
|
|
1264
|
+
// Verify command
|
|
1265
|
+
formatVerify,
|
|
1266
|
+
|
|
1267
|
+
// Trace command
|
|
1268
|
+
formatTrace,
|
|
1269
|
+
formatTraceJson,
|
|
1270
|
+
|
|
1271
|
+
// Related command
|
|
1272
|
+
formatRelated,
|
|
1273
|
+
formatRelatedJson
|
|
1274
|
+
};
|