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/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(`(${params})`);
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
- return JSON.stringify({
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
- return JSON.stringify({
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
- }, null, 2);
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
- return JSON.stringify({
422
- file: graph.file,
423
- depth: graph.depth,
424
- dependencies: graph.dependencies
425
- }, null, 2);
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 (!trace.includeMethods) {
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.showAll ? Infinity : 8);
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 === '0') {
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 === '1') {
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} ${weight} - ${c.file}:${c.line} (${c.callCount}x)`);
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(` (${c.total} usages: ${parts.join(', ')})`);
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: m.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 && !ctx.meta.includeMethods) {
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 prefix = indent === 0 ? '' : ' '.repeat(indent - 1) + '├── ';
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 (const edge of displayEdges) {
1881
- printNode(edge.to, indent + 1);
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
  };