ucn 3.7.23 → 3.7.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/cli/index.js +285 -1054
- package/core/cache.js +193 -0
- package/core/callers.js +817 -0
- package/core/deadcode.js +320 -0
- package/core/discovery.js +1 -1
- package/core/execute.js +207 -10
- package/core/expand-cache.js +16 -5
- package/core/imports.js +21 -15
- package/core/output.js +380 -38
- package/core/project.js +365 -2259
- package/core/shared.js +11 -1
- package/core/stacktrace.js +313 -0
- package/core/verify.js +533 -0
- package/languages/go.js +57 -21
- package/languages/html.js +14 -3
- package/languages/java.js +4 -2
- package/languages/javascript.js +36 -9
- package/languages/rust.js +49 -17
- package/mcp/server.js +39 -172
- package/package.json +1 -1
package/core/output.js
CHANGED
|
@@ -130,13 +130,12 @@ function formatMemberSignature(member) {
|
|
|
130
130
|
// Generator
|
|
131
131
|
if (member.isGenerator) parts.push('*');
|
|
132
132
|
|
|
133
|
-
// Name
|
|
134
|
-
parts.push(member.name);
|
|
135
|
-
|
|
136
|
-
// Parameters
|
|
133
|
+
// Name + Parameters (no space between name and parens)
|
|
137
134
|
if (member.params !== undefined) {
|
|
138
135
|
const params = normalizeParams(member.params);
|
|
139
|
-
parts.push(
|
|
136
|
+
parts.push(`${member.name}(${params})`);
|
|
137
|
+
} else {
|
|
138
|
+
parts.push(member.name);
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
// Return type
|
|
@@ -204,12 +203,14 @@ function printDefinition(def, relativePath) {
|
|
|
204
203
|
* Format TOC data as JSON
|
|
205
204
|
*/
|
|
206
205
|
function formatTocJson(data) {
|
|
207
|
-
|
|
206
|
+
const obj = {
|
|
208
207
|
meta: data.meta || { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 },
|
|
209
208
|
totals: data.totals,
|
|
210
209
|
summary: data.summary,
|
|
211
210
|
files: data.files
|
|
212
|
-
}
|
|
211
|
+
};
|
|
212
|
+
if (data.hiddenFiles > 0) obj.hiddenFiles = data.hiddenFiles;
|
|
213
|
+
return JSON.stringify(obj);
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
/**
|
|
@@ -289,7 +290,7 @@ function formatUsagesJson(usages, name) {
|
|
|
289
290
|
function formatContextJson(context) {
|
|
290
291
|
const meta = context.meta || { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 };
|
|
291
292
|
// Handle struct/interface types differently
|
|
292
|
-
if (context.type && ['struct', 'interface', 'type'].includes(context.type)) {
|
|
293
|
+
if (context.type && ['class', 'struct', 'interface', 'type'].includes(context.type)) {
|
|
293
294
|
const callers = context.callers || [];
|
|
294
295
|
const methods = context.methods || [];
|
|
295
296
|
return JSON.stringify({
|
|
@@ -374,7 +375,8 @@ function formatFunctionJson(fn, code) {
|
|
|
374
375
|
* Format search results as JSON
|
|
375
376
|
*/
|
|
376
377
|
function formatSearchJson(results, term) {
|
|
377
|
-
|
|
378
|
+
const meta = results.meta;
|
|
379
|
+
const obj = {
|
|
378
380
|
term,
|
|
379
381
|
totalMatches: results.reduce((sum, r) => sum + r.matches.length, 0),
|
|
380
382
|
files: results.map(r => ({
|
|
@@ -385,7 +387,14 @@ function formatSearchJson(results, term) {
|
|
|
385
387
|
content: m.content // FULL content
|
|
386
388
|
}))
|
|
387
389
|
}))
|
|
388
|
-
}
|
|
390
|
+
};
|
|
391
|
+
if (meta) {
|
|
392
|
+
obj.filesScanned = meta.filesScanned;
|
|
393
|
+
obj.filesSkipped = meta.filesSkipped;
|
|
394
|
+
obj.totalFiles = meta.totalFiles;
|
|
395
|
+
if (meta.regexFallback) obj.regexFallback = meta.regexFallback;
|
|
396
|
+
}
|
|
397
|
+
return JSON.stringify(obj, null, 2);
|
|
389
398
|
}
|
|
390
399
|
|
|
391
400
|
/**
|
|
@@ -418,11 +427,15 @@ function formatStatsJson(stats) {
|
|
|
418
427
|
*/
|
|
419
428
|
function formatGraphJson(graph) {
|
|
420
429
|
if (graph?.error) return JSON.stringify({ found: false, error: graph.error, file: graph.filePath }, null, 2);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
const result = {
|
|
431
|
+
root: graph.root,
|
|
432
|
+
direction: graph.direction,
|
|
433
|
+
nodes: graph.nodes,
|
|
434
|
+
edges: graph.edges
|
|
435
|
+
};
|
|
436
|
+
if (graph.imports) result.imports = graph.imports;
|
|
437
|
+
if (graph.importers) result.importers = graph.importers;
|
|
438
|
+
return JSON.stringify(result, null, 2);
|
|
426
439
|
}
|
|
427
440
|
|
|
428
441
|
/**
|
|
@@ -430,6 +443,7 @@ function formatGraphJson(graph) {
|
|
|
430
443
|
* Includes function + all dependencies
|
|
431
444
|
*/
|
|
432
445
|
function formatSmartJson(result) {
|
|
446
|
+
if (!result) return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
|
|
433
447
|
const meta = result.meta || { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 };
|
|
434
448
|
return JSON.stringify({
|
|
435
449
|
meta,
|
|
@@ -806,7 +820,7 @@ function formatTrace(trace, options = {}) {
|
|
|
806
820
|
lines.push(`\nSome results truncated. ${allHint}`);
|
|
807
821
|
}
|
|
808
822
|
|
|
809
|
-
if (
|
|
823
|
+
if (trace.includeMethods === false) {
|
|
810
824
|
const methodsHint = options.methodsHint || 'Note: obj.method() calls excluded (--include-methods=false). Remove flag to include them (default).';
|
|
811
825
|
lines.push(`\n${methodsHint}`);
|
|
812
826
|
}
|
|
@@ -843,7 +857,7 @@ function formatRelated(related, options = {}) {
|
|
|
843
857
|
// Same file
|
|
844
858
|
let relatedTruncated = false;
|
|
845
859
|
if (related.sameFile.length > 0) {
|
|
846
|
-
const maxSameFile = options.top || (options.
|
|
860
|
+
const maxSameFile = options.top || (options.all ? Infinity : 8);
|
|
847
861
|
lines.push(`SAME FILE (${related.sameFile.length}):`);
|
|
848
862
|
for (const f of related.sameFile.slice(0, maxSameFile)) {
|
|
849
863
|
const params = f.params ? `(${f.params})` : '';
|
|
@@ -974,11 +988,11 @@ function formatPlan(plan, options = {}) {
|
|
|
974
988
|
return 'Function not found.';
|
|
975
989
|
}
|
|
976
990
|
if (!plan.found) {
|
|
977
|
-
if (plan.error) {
|
|
978
|
-
return `Error: ${plan.error}\nCurrent parameters: ${plan.currentParams?.join(', ') || 'none'}`;
|
|
979
|
-
}
|
|
980
991
|
return `Function "${plan.function}" not found.`;
|
|
981
992
|
}
|
|
993
|
+
if (plan.error) {
|
|
994
|
+
return `Error: ${plan.error}\nCurrent parameters: ${plan.currentParams?.join(', ') || 'none'}`;
|
|
995
|
+
}
|
|
982
996
|
|
|
983
997
|
const lines = [];
|
|
984
998
|
|
|
@@ -1153,12 +1167,12 @@ function formatAbout(about, options = {}) {
|
|
|
1153
1167
|
const { expand, root, depth } = options;
|
|
1154
1168
|
|
|
1155
1169
|
// Depth=0: location only
|
|
1156
|
-
if (depth ===
|
|
1170
|
+
if (depth !== null && depth !== undefined && Number(depth) === 0) {
|
|
1157
1171
|
return `${sym.file}:${sym.startLine}`;
|
|
1158
1172
|
}
|
|
1159
1173
|
|
|
1160
1174
|
// Depth=1: location + signature + usage counts
|
|
1161
|
-
if (depth ===
|
|
1175
|
+
if (depth !== null && depth !== undefined && Number(depth) === 1) {
|
|
1162
1176
|
lines.push(`${sym.file}:${sym.startLine}`);
|
|
1163
1177
|
if (sym.signature) {
|
|
1164
1178
|
lines.push(sym.signature);
|
|
@@ -1217,8 +1231,8 @@ function formatAbout(about, options = {}) {
|
|
|
1217
1231
|
lines.push(`CALLEES (${about.callees.total}):`);
|
|
1218
1232
|
}
|
|
1219
1233
|
for (const c of about.callees.top) {
|
|
1220
|
-
const weight = c.weight !== 'normal' ? `[${c.weight}]` : '';
|
|
1221
|
-
lines.push(` ${c.name}
|
|
1234
|
+
const weight = c.weight && c.weight !== 'normal' ? ` [${c.weight}]` : '';
|
|
1235
|
+
lines.push(` ${c.name}${weight} - ${c.file}:${c.line} (${c.callCount}x)`);
|
|
1222
1236
|
|
|
1223
1237
|
// Inline expansion: show first 3 lines of callee code
|
|
1224
1238
|
if (expand && root && c.file && c.startLine) {
|
|
@@ -1315,7 +1329,7 @@ function formatAboutJson(about) {
|
|
|
1315
1329
|
* Format example result as text
|
|
1316
1330
|
*/
|
|
1317
1331
|
function formatExample(result, name) {
|
|
1318
|
-
if (!result) return `No call examples found for "${name}"`;
|
|
1332
|
+
if (!result || !result.best) return `No call examples found for "${name}"`;
|
|
1319
1333
|
|
|
1320
1334
|
const best = result.best;
|
|
1321
1335
|
const lines = [];
|
|
@@ -1455,7 +1469,9 @@ function formatFind(symbols, query, top) {
|
|
|
1455
1469
|
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
1456
1470
|
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
1457
1471
|
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
1458
|
-
lines.push(
|
|
1472
|
+
lines.push(parts.length > 0
|
|
1473
|
+
? ` (${c.total} usages: ${parts.join(', ')})`
|
|
1474
|
+
: ` (${c.total} usages)`);
|
|
1459
1475
|
} else if (s.usageCount !== undefined) {
|
|
1460
1476
|
lines.push(` (${s.usageCount} usages)`);
|
|
1461
1477
|
}
|
|
@@ -1578,7 +1594,7 @@ function formatContext(ctx, options = {}) {
|
|
|
1578
1594
|
num: itemNum++,
|
|
1579
1595
|
type: 'method',
|
|
1580
1596
|
name: m.name,
|
|
1581
|
-
file:
|
|
1597
|
+
file: null,
|
|
1582
1598
|
relativePath: m.file,
|
|
1583
1599
|
startLine: m.line,
|
|
1584
1600
|
endLine: m.endLine || m.line
|
|
@@ -1624,7 +1640,7 @@ function formatContext(ctx, options = {}) {
|
|
|
1624
1640
|
}
|
|
1625
1641
|
}
|
|
1626
1642
|
|
|
1627
|
-
if (ctx.meta &&
|
|
1643
|
+
if (ctx.meta && ctx.meta.includeMethods === false) {
|
|
1628
1644
|
lines.push(` ${methodsHint}`);
|
|
1629
1645
|
}
|
|
1630
1646
|
|
|
@@ -1849,10 +1865,11 @@ function formatGraph(graph, options = {}) {
|
|
|
1849
1865
|
let truncatedNodes = 0;
|
|
1850
1866
|
let depthLimited = false;
|
|
1851
1867
|
|
|
1852
|
-
function printNode(file, indent = 0) {
|
|
1868
|
+
function printNode(file, indent = 0, isLast = true) {
|
|
1853
1869
|
const fileEntry = nodes.find(n => n.file === file);
|
|
1854
1870
|
const relPath = fileEntry ? fileEntry.relativePath : file;
|
|
1855
|
-
const
|
|
1871
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
1872
|
+
const prefix = indent === 0 ? '' : ' '.repeat(indent - 1) + connector;
|
|
1856
1873
|
|
|
1857
1874
|
if (ancestors.has(file)) {
|
|
1858
1875
|
lines.push(`${prefix}${relPath} (circular)`);
|
|
@@ -1877,8 +1894,9 @@ function formatGraph(graph, options = {}) {
|
|
|
1877
1894
|
const displayEdges = fileEdges.slice(0, maxChildren);
|
|
1878
1895
|
const hiddenCount = fileEdges.length - displayEdges.length;
|
|
1879
1896
|
|
|
1880
|
-
for (
|
|
1881
|
-
|
|
1897
|
+
for (let i = 0; i < displayEdges.length; i++) {
|
|
1898
|
+
const childIsLast = i === displayEdges.length - 1 && hiddenCount === 0;
|
|
1899
|
+
printNode(displayEdges[i].to, indent + 1, childIsLast);
|
|
1882
1900
|
}
|
|
1883
1901
|
ancestors.delete(file);
|
|
1884
1902
|
|
|
@@ -1899,19 +1917,38 @@ function formatGraph(graph, options = {}) {
|
|
|
1899
1917
|
lines.push(`Dependency graph for ${rootRelPath}`);
|
|
1900
1918
|
lines.push('═'.repeat(60));
|
|
1901
1919
|
|
|
1920
|
+
let totalTruncated = 0;
|
|
1921
|
+
let anyDepthLimited = false;
|
|
1922
|
+
|
|
1902
1923
|
lines.push(`\nIMPORTS (what this file depends on): ${importCount} files`);
|
|
1903
1924
|
if (importCount > 0) {
|
|
1904
|
-
printTree(graph.imports.nodes, graph.imports.edges, graph.root);
|
|
1925
|
+
const r = printTree(graph.imports.nodes, graph.imports.edges, graph.root);
|
|
1926
|
+
totalTruncated += r.truncatedNodes;
|
|
1927
|
+
anyDepthLimited = anyDepthLimited || r.depthLimited;
|
|
1905
1928
|
} else {
|
|
1906
1929
|
lines.push(' (none)');
|
|
1907
1930
|
}
|
|
1908
1931
|
|
|
1909
1932
|
lines.push(`\nIMPORTERS (what depends on this file): ${importerCount} files`);
|
|
1910
1933
|
if (importerCount > 0) {
|
|
1911
|
-
printTree(graph.importers.nodes, graph.importers.edges, graph.root);
|
|
1934
|
+
const r = printTree(graph.importers.nodes, graph.importers.edges, graph.root);
|
|
1935
|
+
totalTruncated += r.truncatedNodes;
|
|
1936
|
+
anyDepthLimited = anyDepthLimited || r.depthLimited;
|
|
1912
1937
|
} else {
|
|
1913
1938
|
lines.push(' (none)');
|
|
1914
1939
|
}
|
|
1940
|
+
|
|
1941
|
+
if (anyDepthLimited || totalTruncated > 0) {
|
|
1942
|
+
lines.push('\n' + '─'.repeat(60));
|
|
1943
|
+
if (anyDepthLimited) {
|
|
1944
|
+
const depthHint = options.depthHint || `Use --depth=N for deeper graph.`;
|
|
1945
|
+
lines.push(`Depth limited to ${maxDepth}. ${depthHint}`);
|
|
1946
|
+
}
|
|
1947
|
+
if (totalTruncated > 0) {
|
|
1948
|
+
const allHint = options.allHint || 'Use --all to show all children.';
|
|
1949
|
+
lines.push(`${totalTruncated} nodes hidden. ${allHint}`);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1915
1952
|
} else {
|
|
1916
1953
|
lines.push(`Dependency graph for ${rootRelPath}`);
|
|
1917
1954
|
lines.push('═'.repeat(60));
|
|
@@ -2060,12 +2097,12 @@ function formatDiffImpact(result) {
|
|
|
2060
2097
|
lines.push(`Diff Impact Analysis (vs ${result.base})`);
|
|
2061
2098
|
lines.push('═'.repeat(60));
|
|
2062
2099
|
|
|
2063
|
-
const s = result.summary;
|
|
2100
|
+
const s = result.summary || {};
|
|
2064
2101
|
const parts = [];
|
|
2065
2102
|
if (s.modifiedFunctions > 0) parts.push(`${s.modifiedFunctions} modified`);
|
|
2066
2103
|
if (s.deletedFunctions > 0) parts.push(`${s.deletedFunctions} deleted`);
|
|
2067
2104
|
if (s.newFunctions > 0) parts.push(`${s.newFunctions} new`);
|
|
2068
|
-
parts.push(`${s.totalCallSites} call sites across ${s.affectedFiles} files`);
|
|
2105
|
+
parts.push(`${s.totalCallSites || 0} call sites across ${s.affectedFiles || 0} files`);
|
|
2069
2106
|
lines.push(parts.join(', '));
|
|
2070
2107
|
lines.push('');
|
|
2071
2108
|
|
|
@@ -2161,6 +2198,13 @@ function formatPlanJson(plan) {
|
|
|
2161
2198
|
...(plan.currentParams && { currentParams: plan.currentParams })
|
|
2162
2199
|
}, null, 2);
|
|
2163
2200
|
}
|
|
2201
|
+
if (plan.error) {
|
|
2202
|
+
return JSON.stringify({
|
|
2203
|
+
found: true,
|
|
2204
|
+
error: plan.error,
|
|
2205
|
+
...(plan.currentParams && { currentParams: plan.currentParams })
|
|
2206
|
+
}, null, 2);
|
|
2207
|
+
}
|
|
2164
2208
|
|
|
2165
2209
|
return JSON.stringify({
|
|
2166
2210
|
found: true,
|
|
@@ -2256,7 +2300,7 @@ function formatVerifyJson(result) {
|
|
|
2256
2300
|
* Format example command output - JSON
|
|
2257
2301
|
*/
|
|
2258
2302
|
function formatExampleJson(result, name) {
|
|
2259
|
-
if (!result) {
|
|
2303
|
+
if (!result || !result.best) {
|
|
2260
2304
|
return JSON.stringify({ found: false, query: name, error: `No call examples found for "${name}"` }, null, 2);
|
|
2261
2305
|
}
|
|
2262
2306
|
|
|
@@ -2302,6 +2346,293 @@ function formatDiffImpactJson(result) {
|
|
|
2302
2346
|
return JSON.stringify(result, null, 2);
|
|
2303
2347
|
}
|
|
2304
2348
|
|
|
2349
|
+
// ============================================================================
|
|
2350
|
+
// Extraction command formatters (fn, class, lines)
|
|
2351
|
+
// ============================================================================
|
|
2352
|
+
|
|
2353
|
+
/**
|
|
2354
|
+
* Format fn handler result (from execute.js).
|
|
2355
|
+
* Notes are NOT included — surfaces render those separately (e.g. stderr for CLI).
|
|
2356
|
+
* @param {{ entries: Array<{match, code}>, notes: string[] }} result
|
|
2357
|
+
*/
|
|
2358
|
+
function formatFnResult(result) {
|
|
2359
|
+
const parts = [];
|
|
2360
|
+
for (const { match, code } of result.entries) {
|
|
2361
|
+
parts.push(formatFn(match, code));
|
|
2362
|
+
}
|
|
2363
|
+
const separator = result.entries.length > 1 ? '\n\n' + '═'.repeat(60) + '\n\n' : '';
|
|
2364
|
+
return parts.join(separator);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* Format fn handler result as JSON.
|
|
2369
|
+
*/
|
|
2370
|
+
function formatFnResultJson(result) {
|
|
2371
|
+
if (result.entries.length === 1) {
|
|
2372
|
+
return formatFunctionJson(result.entries[0].match, result.entries[0].code);
|
|
2373
|
+
}
|
|
2374
|
+
const arr = result.entries.map(({ match, code }) => ({
|
|
2375
|
+
name: match.name,
|
|
2376
|
+
params: match.params,
|
|
2377
|
+
paramsStructured: match.paramsStructured || [],
|
|
2378
|
+
startLine: match.startLine,
|
|
2379
|
+
endLine: match.endLine,
|
|
2380
|
+
modifiers: match.modifiers || [],
|
|
2381
|
+
...(match.returnType && { returnType: match.returnType }),
|
|
2382
|
+
...(match.generics && { generics: match.generics }),
|
|
2383
|
+
...(match.docstring && { docstring: match.docstring }),
|
|
2384
|
+
...(match.isArrow && { isArrow: true }),
|
|
2385
|
+
...(match.isGenerator && { isGenerator: true }),
|
|
2386
|
+
file: match.relativePath || match.file,
|
|
2387
|
+
code,
|
|
2388
|
+
}));
|
|
2389
|
+
return JSON.stringify(arr, null, 2);
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Format class handler result (from execute.js).
|
|
2394
|
+
* @param {{ entries: Array<{match, code, methods?, summaryMode, truncated, totalLines, maxLines?}>, notes: string[] }} result
|
|
2395
|
+
*/
|
|
2396
|
+
function formatClassResult(result) {
|
|
2397
|
+
const parts = [];
|
|
2398
|
+
for (const entry of result.entries) {
|
|
2399
|
+
if (entry.summaryMode) {
|
|
2400
|
+
// Large class summary
|
|
2401
|
+
const lines = [];
|
|
2402
|
+
lines.push(`${entry.match.relativePath}:${entry.match.startLine}`);
|
|
2403
|
+
lines.push(`${lineRange(entry.match.startLine, entry.match.endLine)} ${formatClassSignature(entry.match)}`);
|
|
2404
|
+
lines.push('\u2500'.repeat(60));
|
|
2405
|
+
if (entry.methods && entry.methods.length > 0) {
|
|
2406
|
+
lines.push(`\nMethods (${entry.methods.length}):`);
|
|
2407
|
+
for (const m of entry.methods) {
|
|
2408
|
+
lines.push(` ${formatFunctionSignature(m)} [line ${m.startLine}]`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
lines.push(`\nClass is ${entry.totalLines} lines. Use --max-lines=N to see source, or "fn <method>" for individual methods.`);
|
|
2412
|
+
parts.push(lines.join('\n'));
|
|
2413
|
+
} else if (entry.truncated) {
|
|
2414
|
+
parts.push(formatClass(entry.match, entry.code) + `\n\n... showing ${entry.maxLines} of ${entry.totalLines} lines`);
|
|
2415
|
+
} else {
|
|
2416
|
+
parts.push(formatClass(entry.match, entry.code));
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
return parts.join('\n\n');
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Format class handler result as JSON.
|
|
2425
|
+
*/
|
|
2426
|
+
function formatClassResultJson(result) {
|
|
2427
|
+
if (result.entries.length === 1) {
|
|
2428
|
+
const entry = result.entries[0];
|
|
2429
|
+
return JSON.stringify({
|
|
2430
|
+
...entry.match,
|
|
2431
|
+
code: entry.code,
|
|
2432
|
+
...(entry.summaryMode && { summaryMode: true }),
|
|
2433
|
+
...(entry.methods && { methods: entry.methods }),
|
|
2434
|
+
...(entry.truncated && { truncated: true }),
|
|
2435
|
+
totalLines: entry.totalLines,
|
|
2436
|
+
}, null, 2);
|
|
2437
|
+
}
|
|
2438
|
+
const arr = result.entries.map(entry => ({
|
|
2439
|
+
...entry.match,
|
|
2440
|
+
code: entry.code,
|
|
2441
|
+
...(entry.summaryMode && { summaryMode: true }),
|
|
2442
|
+
...(entry.methods && { methods: entry.methods }),
|
|
2443
|
+
...(entry.truncated && { truncated: true }),
|
|
2444
|
+
totalLines: entry.totalLines,
|
|
2445
|
+
}));
|
|
2446
|
+
return JSON.stringify(arr, null, 2);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
/**
|
|
2450
|
+
* Format lines handler result (from execute.js).
|
|
2451
|
+
* @param {{ relativePath: string, lines: string[], startLine: number, endLine: number }} result
|
|
2452
|
+
*/
|
|
2453
|
+
function formatLines(result) {
|
|
2454
|
+
const lines = [];
|
|
2455
|
+
lines.push(`${result.relativePath}:${result.startLine}-${result.endLine}`);
|
|
2456
|
+
lines.push('\u2500'.repeat(60));
|
|
2457
|
+
for (let i = 0; i < result.lines.length; i++) {
|
|
2458
|
+
lines.push(`${lineNum(result.startLine + i)} \u2502 ${result.lines[i]}`);
|
|
2459
|
+
}
|
|
2460
|
+
return lines.join('\n');
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// ============================================================================
|
|
2464
|
+
// FIND DETAILED (moved from CLI — depth/confidence features)
|
|
2465
|
+
// ============================================================================
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* Count depth of nested generic brackets.
|
|
2469
|
+
*/
|
|
2470
|
+
function countNestedGenerics(str) {
|
|
2471
|
+
let maxDepth = 0;
|
|
2472
|
+
let depth = 0;
|
|
2473
|
+
for (const char of str) {
|
|
2474
|
+
if (char === '<') {
|
|
2475
|
+
depth++;
|
|
2476
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
2477
|
+
} else if (char === '>') {
|
|
2478
|
+
depth--;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return maxDepth;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/**
|
|
2485
|
+
* Compute confidence level for a symbol match.
|
|
2486
|
+
* @returns {{ level: 'high'|'medium'|'low', reasons: string[] }}
|
|
2487
|
+
*/
|
|
2488
|
+
function computeConfidence(symbol) {
|
|
2489
|
+
const reasons = [];
|
|
2490
|
+
let score = 100;
|
|
2491
|
+
|
|
2492
|
+
const span = (symbol.endLine || symbol.startLine) - symbol.startLine;
|
|
2493
|
+
if (span > 500) {
|
|
2494
|
+
score -= 30;
|
|
2495
|
+
reasons.push('very long function (>500 lines)');
|
|
2496
|
+
} else if (span > 200) {
|
|
2497
|
+
score -= 15;
|
|
2498
|
+
reasons.push('long function (>200 lines)');
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
const params = Array.isArray(symbol.params) ? symbol.params : [];
|
|
2502
|
+
const signature = params.map(p => p.type || '').join(' ') + (symbol.returnType || '');
|
|
2503
|
+
const genericDepth = countNestedGenerics(signature);
|
|
2504
|
+
if (genericDepth > 3) {
|
|
2505
|
+
score -= 20;
|
|
2506
|
+
reasons.push('complex nested generics');
|
|
2507
|
+
} else if (genericDepth > 2) {
|
|
2508
|
+
score -= 10;
|
|
2509
|
+
reasons.push('nested generics');
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
if (symbol.file) {
|
|
2513
|
+
try {
|
|
2514
|
+
const stats = fs.statSync(symbol.file);
|
|
2515
|
+
const sizeKB = stats.size / 1024;
|
|
2516
|
+
if (sizeKB > 500) {
|
|
2517
|
+
score -= 20;
|
|
2518
|
+
reasons.push('very large file (>500KB)');
|
|
2519
|
+
} else if (sizeKB > 200) {
|
|
2520
|
+
score -= 10;
|
|
2521
|
+
reasons.push('large file (>200KB)');
|
|
2522
|
+
}
|
|
2523
|
+
} catch (e) {
|
|
2524
|
+
// Skip file size check on error
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
let level = 'high';
|
|
2529
|
+
if (score < 50) level = 'low';
|
|
2530
|
+
else if (score < 80) level = 'medium';
|
|
2531
|
+
|
|
2532
|
+
return { level, reasons };
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/**
|
|
2536
|
+
* Format find results with depth/confidence features (detailed view).
|
|
2537
|
+
* Returns a string. Used by CLI and interactive mode.
|
|
2538
|
+
*
|
|
2539
|
+
* @param {Array} symbols - Find result array
|
|
2540
|
+
* @param {string} query - Original search query
|
|
2541
|
+
* @param {object} options - { depth, top, all }
|
|
2542
|
+
*/
|
|
2543
|
+
function formatFindDetailed(symbols, query, options = {}) {
|
|
2544
|
+
const { depth, top, all } = options;
|
|
2545
|
+
const DEFAULT_LIMIT = 5;
|
|
2546
|
+
|
|
2547
|
+
if (symbols.length === 0) {
|
|
2548
|
+
return `No symbols found for "${query}"`;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
const lines = [];
|
|
2552
|
+
const limit = all ? symbols.length : (top > 0 ? top : DEFAULT_LIMIT);
|
|
2553
|
+
const showing = Math.min(limit, symbols.length);
|
|
2554
|
+
const hidden = symbols.length - showing;
|
|
2555
|
+
|
|
2556
|
+
if (hidden > 0) {
|
|
2557
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}" (showing top ${showing}):`);
|
|
2558
|
+
} else {
|
|
2559
|
+
lines.push(`Found ${symbols.length} match(es) for "${query}":`);
|
|
2560
|
+
}
|
|
2561
|
+
lines.push('─'.repeat(60));
|
|
2562
|
+
|
|
2563
|
+
for (let i = 0; i < showing; i++) {
|
|
2564
|
+
const s = symbols[i];
|
|
2565
|
+
// Depth 0: just location
|
|
2566
|
+
if (depth === '0') {
|
|
2567
|
+
lines.push(`${s.relativePath}:${s.startLine}`);
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// Depth 1 (default): location + signature
|
|
2572
|
+
const sig = s.params !== undefined
|
|
2573
|
+
? formatFunctionSignature(s)
|
|
2574
|
+
: formatClassSignature(s);
|
|
2575
|
+
|
|
2576
|
+
const confidence = computeConfidence(s);
|
|
2577
|
+
const confStr = confidence.level !== 'high' ? ` [${confidence.level}]` : '';
|
|
2578
|
+
|
|
2579
|
+
lines.push(`${s.relativePath}:${s.startLine} ${sig}${confStr}`);
|
|
2580
|
+
if (s.usageCounts !== undefined) {
|
|
2581
|
+
const c = s.usageCounts;
|
|
2582
|
+
const parts = [];
|
|
2583
|
+
if (c.calls > 0) parts.push(`${c.calls} calls`);
|
|
2584
|
+
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
2585
|
+
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
2586
|
+
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
2587
|
+
lines.push(` (${c.total} usages: ${parts.join(', ')})`);
|
|
2588
|
+
} else if (s.usageCount !== undefined) {
|
|
2589
|
+
lines.push(` (${s.usageCount} usages)`);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
if (confidence.level !== 'high' && confidence.reasons.length > 0) {
|
|
2593
|
+
lines.push(` ⚠ ${confidence.reasons.join(', ')}`);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
// Depth 2: + first 10 lines of code
|
|
2597
|
+
if (depth === '2' || depth === 'full') {
|
|
2598
|
+
try {
|
|
2599
|
+
const content = fs.readFileSync(s.file, 'utf-8');
|
|
2600
|
+
const fileLines = content.split('\n');
|
|
2601
|
+
const maxLines = depth === 'full' ? (s.endLine - s.startLine + 1) : 10;
|
|
2602
|
+
const endLine = Math.min(s.startLine + maxLines - 1, s.endLine);
|
|
2603
|
+
lines.push(' ───');
|
|
2604
|
+
for (let j = s.startLine - 1; j < endLine; j++) {
|
|
2605
|
+
lines.push(` ${fileLines[j]}`);
|
|
2606
|
+
}
|
|
2607
|
+
if (depth === '2' && s.endLine > endLine) {
|
|
2608
|
+
lines.push(` ... (${s.endLine - endLine} more lines)`);
|
|
2609
|
+
}
|
|
2610
|
+
} catch (e) {
|
|
2611
|
+
// Skip code extraction on error
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
lines.push('');
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
if (hidden > 0) {
|
|
2618
|
+
lines.push(`... ${hidden} more result(s). Use --all to see all, or --top=N to see more.`);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
return lines.join('\n');
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
/**
|
|
2625
|
+
* Format lines handler result as JSON.
|
|
2626
|
+
*/
|
|
2627
|
+
function formatLinesJson(result) {
|
|
2628
|
+
return JSON.stringify({
|
|
2629
|
+
file: result.relativePath,
|
|
2630
|
+
startLine: result.startLine,
|
|
2631
|
+
endLine: result.endLine,
|
|
2632
|
+
lines: result.lines,
|
|
2633
|
+
}, null, 2);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2305
2636
|
module.exports = {
|
|
2306
2637
|
// Utilities
|
|
2307
2638
|
normalizeParams,
|
|
@@ -2394,5 +2725,16 @@ module.exports = {
|
|
|
2394
2725
|
|
|
2395
2726
|
// Diff impact command
|
|
2396
2727
|
formatDiffImpact,
|
|
2397
|
-
formatDiffImpactJson
|
|
2728
|
+
formatDiffImpactJson,
|
|
2729
|
+
|
|
2730
|
+
// Find detailed (depth/confidence)
|
|
2731
|
+
formatFindDetailed,
|
|
2732
|
+
|
|
2733
|
+
// Extraction commands (fn, class, lines)
|
|
2734
|
+
formatFnResult,
|
|
2735
|
+
formatFnResultJson,
|
|
2736
|
+
formatClassResult,
|
|
2737
|
+
formatClassResultJson,
|
|
2738
|
+
formatLines,
|
|
2739
|
+
formatLinesJson
|
|
2398
2740
|
};
|