ucn 3.7.4 → 3.7.6

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 CHANGED
@@ -15,6 +15,7 @@ const { getParser, getLanguageModule } = require('../languages');
15
15
  const { ProjectIndex } = require('../core/project');
16
16
  const { expandGlob, findProjectRoot, isTestFile } = require('../core/discovery');
17
17
  const output = require('../core/output');
18
+ const { pickBestDefinition, addTestExclusions } = require('../core/shared');
18
19
 
19
20
  // ============================================================================
20
21
  // ARGUMENT PARSING
@@ -141,13 +142,6 @@ const positionalArgs = [
141
142
  * Add test file patterns to exclusion list
142
143
  * Used by find/usages when --include-tests is not specified
143
144
  */
144
- function addTestExclusions(exclude) {
145
- const testPatterns = ['test', 'spec', '__tests__', '__mocks__', 'fixture', 'mock'];
146
- const existing = new Set(exclude.map(e => e.toLowerCase()));
147
- const additions = testPatterns.filter(p => !existing.has(p));
148
- return [...exclude, ...additions];
149
- }
150
-
151
145
  /**
152
146
  * Validate required argument and exit with usage if missing
153
147
  * @param {string} arg - The argument to validate
@@ -740,13 +734,15 @@ function runProjectCommand(rootDir, command, arg) {
740
734
  break;
741
735
  }
742
736
 
743
- case 'example':
744
- if (!arg) {
745
- console.error('Usage: ucn . example <name>');
746
- process.exit(1);
747
- }
748
- console.log(output.formatExample(index.example(arg), arg));
737
+ case 'example': {
738
+ requireArg(arg, 'Usage: ucn . example <name>');
739
+ const exampleResult = index.example(arg);
740
+ printOutput(exampleResult,
741
+ r => output.formatExampleJson(r, arg),
742
+ r => output.formatExample(r, arg)
743
+ );
749
744
  break;
745
+ }
750
746
 
751
747
  case 'context': {
752
748
  requireArg(arg, 'Usage: ucn . context <name>');
@@ -868,7 +864,7 @@ function runProjectCommand(rootDir, command, arg) {
868
864
  defaultValue: flags.defaultValue,
869
865
  file: flags.file
870
866
  });
871
- printOutput(planResult, r => JSON.stringify(r, null, 2), output.formatPlan);
867
+ printOutput(planResult, output.formatPlanJson, output.formatPlan);
872
868
  break;
873
869
  }
874
870
 
@@ -885,14 +881,14 @@ function runProjectCommand(rootDir, command, arg) {
885
881
  case 'stack': {
886
882
  requireArg(arg, 'Usage: ucn . stacktrace "<stack trace text>"\nExample: ucn . stacktrace "Error: failed\\n at parseFile (core/parser.js:90:5)"');
887
883
  const stackResult = index.parseStackTrace(arg);
888
- printOutput(stackResult, r => JSON.stringify(r, null, 2), output.formatStackTrace);
884
+ printOutput(stackResult, output.formatStackTraceJson, output.formatStackTrace);
889
885
  break;
890
886
  }
891
887
 
892
888
  case 'verify': {
893
889
  requireArg(arg, 'Usage: ucn . verify <name>');
894
890
  const verifyResult = index.verify(arg, { file: flags.file });
895
- printOutput(verifyResult, r => JSON.stringify(r, null, 2), output.formatVerify);
891
+ printOutput(verifyResult, output.formatVerifyJson, output.formatVerify);
896
892
  break;
897
893
  }
898
894
 
@@ -986,7 +982,7 @@ function runProjectCommand(rootDir, command, arg) {
986
982
  in: flags.in || subdirScope
987
983
  });
988
984
  printOutput(deadcodeResults,
989
- r => JSON.stringify({ deadcode: r }, null, 2),
985
+ output.formatDeadcodeJson,
990
986
  r => output.formatDeadcode(r)
991
987
  );
992
988
  break;
@@ -1056,15 +1052,16 @@ function runProjectCommand(rootDir, command, arg) {
1056
1052
  }
1057
1053
  }
1058
1054
 
1059
- function extractFunctionFromProject(index, name) {
1060
- const matches = index.find(name, { file: flags.file }).filter(m => m.type === 'function' || m.params !== undefined);
1055
+ function extractFunctionFromProject(index, name, overrideFlags) {
1056
+ const f = overrideFlags || flags;
1057
+ const matches = index.find(name, { file: f.file }).filter(m => m.type === 'function' || m.params !== undefined);
1061
1058
 
1062
1059
  if (matches.length === 0) {
1063
1060
  console.error(`Function "${name}" not found`);
1064
1061
  return;
1065
1062
  }
1066
1063
 
1067
- if (matches.length > 1 && !flags.file && flags.all) {
1064
+ if (matches.length > 1 && !f.file && f.all) {
1068
1065
  // Show all definitions
1069
1066
  for (let i = 0; i < matches.length; i++) {
1070
1067
  const m = matches[i];
@@ -1073,7 +1070,7 @@ function extractFunctionFromProject(index, name) {
1073
1070
  const extracted = lines.slice(m.startLine - 1, m.endLine);
1074
1071
  const fnCode = cleanHtmlScriptTags(extracted, detectLanguage(m.file)).join('\n');
1075
1072
  if (i > 0) console.log('');
1076
- if (flags.json) {
1073
+ if (f.json) {
1077
1074
  console.log(output.formatFunctionJson(m, fnCode));
1078
1075
  } else {
1079
1076
  console.log(output.formatFn(m, fnCode));
@@ -1083,7 +1080,7 @@ function extractFunctionFromProject(index, name) {
1083
1080
  }
1084
1081
 
1085
1082
  let match;
1086
- if (matches.length > 1 && !flags.file) {
1083
+ if (matches.length > 1 && !f.file) {
1087
1084
  // Auto-select best match using same scoring as resolveSymbol
1088
1085
  match = pickBestDefinition(matches);
1089
1086
  const others = matches.filter(m => m !== match).map(m => `${m.relativePath}:${m.startLine}`).join(', ');
@@ -1098,15 +1095,16 @@ function extractFunctionFromProject(index, name) {
1098
1095
  const extracted = lines.slice(match.startLine - 1, match.endLine);
1099
1096
  const fnCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
1100
1097
 
1101
- if (flags.json) {
1098
+ if (f.json) {
1102
1099
  console.log(output.formatFunctionJson(match, fnCode));
1103
1100
  } else {
1104
1101
  console.log(output.formatFn(match, fnCode));
1105
1102
  }
1106
1103
  }
1107
1104
 
1108
- function extractClassFromProject(index, name) {
1109
- const matches = index.find(name, { file: flags.file }).filter(m =>
1105
+ function extractClassFromProject(index, name, overrideFlags) {
1106
+ const f = overrideFlags || flags;
1107
+ const matches = index.find(name, { file: f.file }).filter(m =>
1110
1108
  ['class', 'interface', 'type', 'enum', 'struct', 'trait'].includes(m.type)
1111
1109
  );
1112
1110
 
@@ -1115,7 +1113,7 @@ function extractClassFromProject(index, name) {
1115
1113
  return;
1116
1114
  }
1117
1115
 
1118
- if (matches.length > 1 && !flags.file && flags.all) {
1116
+ if (matches.length > 1 && !f.file && f.all) {
1119
1117
  // Show all definitions using index data (no re-parsing)
1120
1118
  for (let i = 0; i < matches.length; i++) {
1121
1119
  const m = matches[i];
@@ -1124,7 +1122,7 @@ function extractClassFromProject(index, name) {
1124
1122
  const extracted = codeLines.slice(m.startLine - 1, m.endLine);
1125
1123
  const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(m.file)).join('\n');
1126
1124
  if (i > 0) console.log('');
1127
- if (flags.json) {
1125
+ if (f.json) {
1128
1126
  console.log(JSON.stringify({ ...m, code: clsCode }, null, 2));
1129
1127
  } else {
1130
1128
  console.log(output.formatClass(m, clsCode));
@@ -1134,7 +1132,7 @@ function extractClassFromProject(index, name) {
1134
1132
  }
1135
1133
 
1136
1134
  let match;
1137
- if (matches.length > 1 && !flags.file) {
1135
+ if (matches.length > 1 && !f.file) {
1138
1136
  // Auto-select best match using same scoring as resolveSymbol
1139
1137
  match = pickBestDefinition(matches);
1140
1138
  const others = matches.filter(m => m !== match).map(m => `${m.relativePath}:${m.startLine}`).join(', ');
@@ -1149,7 +1147,7 @@ function extractClassFromProject(index, name) {
1149
1147
  const extracted = codeLines.slice(match.startLine - 1, match.endLine);
1150
1148
  const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
1151
1149
 
1152
- if (flags.json) {
1150
+ if (f.json) {
1153
1151
  console.log(JSON.stringify({ ...match, code: clsCode }, null, 2));
1154
1152
  } else {
1155
1153
  console.log(output.formatClass(match, clsCode));
@@ -1628,30 +1626,6 @@ function printLines(lines, range) {
1628
1626
  }
1629
1627
  }
1630
1628
 
1631
- /**
1632
- * Pick the best definition from multiple matches using same scoring as resolveSymbol.
1633
- * Prefers lib/src/core over test/examples/vendor directories.
1634
- */
1635
- function pickBestDefinition(matches) {
1636
- const typeOrder = new Set(['class', 'struct', 'interface', 'type', 'impl']);
1637
- const scored = matches.map(m => {
1638
- let score = 0;
1639
- const rp = m.relativePath || '';
1640
- // Prefer class/struct/interface types (+1000) - same as resolveSymbol
1641
- if (typeOrder.has(m.type)) score += 1000;
1642
- if (isTestFile(rp, detectLanguage(m.file))) score -= 500;
1643
- if (/^(examples?|docs?|vendor|third[_-]?party|benchmarks?|samples?)\//i.test(rp)) score -= 300;
1644
- if (/^(lib|src|core|internal|pkg|crates)\//i.test(rp)) score += 200;
1645
- // Tiebreaker: prefer larger function bodies (more important/complex)
1646
- if (m.startLine && m.endLine) {
1647
- score += Math.min(m.endLine - m.startLine, 100);
1648
- }
1649
- return { match: m, score };
1650
- });
1651
- scored.sort((a, b) => b.score - a.score);
1652
- return scored[0].match;
1653
- }
1654
-
1655
1629
  function suggestSimilar(query, names) {
1656
1630
  const lower = query.toLowerCase();
1657
1631
  const similar = names.filter(n => n.toLowerCase().includes(lower));
@@ -1814,24 +1788,38 @@ function runInteractive(rootDir) {
1814
1788
  if (input === 'help') {
1815
1789
  console.log(`
1816
1790
  Commands:
1817
- toc Project overview
1818
- find <name> Find symbol
1791
+ toc Project overview (--detailed)
1792
+ find <name> Find symbol (--exact, --file=)
1819
1793
  about <name> Everything about a symbol
1820
1794
  usages <name> All usages grouped by type
1821
1795
  context <name> Callers + callees
1796
+ expand <N> Show code for item N from context
1822
1797
  smart <name> Function + dependencies
1823
1798
  impact <name> What breaks if changed
1824
- trace <name> Call tree
1799
+ trace <name> Call tree (--depth=N)
1800
+ example <name> Best usage example
1801
+ related <name> Sibling functions
1802
+ fn <name> Extract function code (--file=)
1803
+ class <name> Extract class code (--file=)
1804
+ lines <range> Extract lines (--file= required)
1805
+ graph <file> File dependency tree (--direction=, --depth=)
1806
+ file-exports <file> File's exported symbols
1825
1807
  imports <file> What file imports
1826
1808
  exporters <file> Who imports file
1827
- tests <name> Find tests
1828
- search <term> Text search
1809
+ tests <name> Find tests (--calls-only)
1810
+ search <term> Text search (--code-only, --case-sensitive)
1829
1811
  typedef <name> Find type definitions
1812
+ deadcode Find unused functions/classes
1813
+ verify <name> Check call sites match signature
1814
+ plan <name> Preview refactoring (--add-param=, --remove-param=, --rename-to=)
1815
+ stacktrace <text> Parse a stack trace
1830
1816
  api Show public symbols
1831
1817
  diff-impact What changed and who's affected
1832
1818
  stats Index statistics
1833
1819
  rebuild Rebuild index
1834
1820
  quit Exit
1821
+
1822
+ Flags can be added per-command: context myFunc --include-methods
1835
1823
  `);
1836
1824
  rl.prompt();
1837
1825
  return;
@@ -1845,13 +1833,16 @@ Commands:
1845
1833
  return;
1846
1834
  }
1847
1835
 
1848
- // Parse command
1849
- const parts = input.split(/\s+/);
1850
- const command = parts[0];
1851
- const arg = parts.slice(1).join(' ');
1836
+ // Parse command, flags, and arg from interactive input
1837
+ const tokens = input.split(/\s+/);
1838
+ const command = tokens[0];
1839
+ const flagTokens = tokens.filter(t => t.startsWith('--'));
1840
+ const argTokens = tokens.slice(1).filter(t => !t.startsWith('--'));
1841
+ const arg = argTokens.join(' ');
1842
+ const iflags = parseInteractiveFlags(flagTokens);
1852
1843
 
1853
1844
  try {
1854
- executeInteractiveCommand(index, command, arg);
1845
+ executeInteractiveCommand(index, command, arg, iflags);
1855
1846
  } catch (e) {
1856
1847
  console.error(`Error: ${e.message}`);
1857
1848
  }
@@ -1864,12 +1855,48 @@ Commands:
1864
1855
  });
1865
1856
  }
1866
1857
 
1867
- function executeInteractiveCommand(index, command, arg) {
1858
+ /**
1859
+ * Parse flags from interactive command tokens.
1860
+ * Returns a flags object similar to the global flags but scoped to this command.
1861
+ */
1862
+ function parseInteractiveFlags(tokens) {
1863
+ return {
1864
+ file: tokens.find(a => a.startsWith('--file='))?.split('=')[1] || null,
1865
+ exclude: tokens.find(a => a.startsWith('--exclude=') || a.startsWith('--not='))?.split('=')[1]?.split(',') || [],
1866
+ in: tokens.find(a => a.startsWith('--in='))?.split('=')[1] || null,
1867
+ includeTests: tokens.includes('--include-tests'),
1868
+ includeExported: tokens.includes('--include-exported'),
1869
+ includeDecorated: tokens.includes('--include-decorated'),
1870
+ includeUncertain: tokens.includes('--include-uncertain'),
1871
+ includeMethods: tokens.includes('--include-methods=false') ? false : tokens.includes('--include-methods') ? true : undefined,
1872
+ detailed: tokens.includes('--detailed'),
1873
+ all: tokens.includes('--all'),
1874
+ exact: tokens.includes('--exact'),
1875
+ callsOnly: tokens.includes('--calls-only'),
1876
+ codeOnly: tokens.includes('--code-only'),
1877
+ caseSensitive: tokens.includes('--case-sensitive'),
1878
+ withTypes: tokens.includes('--with-types'),
1879
+ expand: tokens.includes('--expand'),
1880
+ depth: tokens.find(a => a.startsWith('--depth='))?.split('=')[1] || null,
1881
+ top: parseInt(tokens.find(a => a.startsWith('--top='))?.split('=')[1] || '0'),
1882
+ context: parseInt(tokens.find(a => a.startsWith('--context='))?.split('=')[1] || '0'),
1883
+ direction: tokens.find(a => a.startsWith('--direction='))?.split('=')[1] || null,
1884
+ addParam: tokens.find(a => a.startsWith('--add-param='))?.split('=')[1] || null,
1885
+ removeParam: tokens.find(a => a.startsWith('--remove-param='))?.split('=')[1] || null,
1886
+ renameTo: tokens.find(a => a.startsWith('--rename-to='))?.split('=')[1] || null,
1887
+ defaultValue: tokens.find(a => a.startsWith('--default='))?.split('=')[1] || null,
1888
+ base: tokens.find(a => a.startsWith('--base='))?.split('=')[1] || null,
1889
+ staged: tokens.includes('--staged'),
1890
+ maxLines: parseInt(tokens.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null,
1891
+ };
1892
+ }
1893
+
1894
+ function executeInteractiveCommand(index, command, arg, iflags = {}) {
1868
1895
  switch (command) {
1869
1896
  case 'toc': {
1870
- const toc = index.getToc();
1897
+ const toc = index.getToc({ detailed: iflags.detailed });
1871
1898
  console.log(output.formatToc(toc, {
1872
- detailedHint: 'Add --detailed to list all functions, or "ucn . about <name>" for full details on a symbol'
1899
+ detailedHint: 'Add --detailed to list all functions, or "about <name>" for full details on a symbol'
1873
1900
  }));
1874
1901
  break;
1875
1902
  }
@@ -1879,11 +1906,11 @@ function executeInteractiveCommand(index, command, arg) {
1879
1906
  console.log('Usage: find <name>');
1880
1907
  return;
1881
1908
  }
1882
- const found = index.find(arg, {});
1909
+ const found = index.find(arg, { exact: iflags.exact, exclude: iflags.exclude, in: iflags.in, includeTests: iflags.includeTests });
1883
1910
  if (found.length === 0) {
1884
1911
  console.log(`No symbols found for "${arg}"`);
1885
1912
  } else {
1886
- printSymbols(found, arg, {});
1913
+ printSymbols(found, arg, { top: iflags.top });
1887
1914
  }
1888
1915
  break;
1889
1916
  }
@@ -1893,8 +1920,8 @@ function executeInteractiveCommand(index, command, arg) {
1893
1920
  console.log('Usage: about <name>');
1894
1921
  return;
1895
1922
  }
1896
- const aboutResult = index.about(arg, { includeMethods: flags.includeMethods });
1897
- console.log(output.formatAbout(aboutResult, { expand: flags.expand, root: index.root }));
1923
+ const aboutResult = index.about(arg, { includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain, file: iflags.file, exclude: iflags.exclude });
1924
+ console.log(output.formatAbout(aboutResult, { expand: iflags.expand, root: index.root, showAll: iflags.all }));
1898
1925
  break;
1899
1926
  }
1900
1927
 
@@ -1903,7 +1930,7 @@ function executeInteractiveCommand(index, command, arg) {
1903
1930
  console.log('Usage: usages <name>');
1904
1931
  return;
1905
1932
  }
1906
- const usages = index.usages(arg, {});
1933
+ const usages = index.usages(arg, { context: iflags.context, codeOnly: iflags.codeOnly, includeTests: iflags.includeTests });
1907
1934
  console.log(output.formatUsages(usages, arg));
1908
1935
  break;
1909
1936
  }
@@ -1913,13 +1940,13 @@ function executeInteractiveCommand(index, command, arg) {
1913
1940
  console.log('Usage: context <name>');
1914
1941
  return;
1915
1942
  }
1916
- const ctx = index.context(arg, { includeUncertain: flags.includeUncertain });
1943
+ const ctx = index.context(arg, { includeUncertain: iflags.includeUncertain, includeMethods: iflags.includeMethods, file: iflags.file, exclude: iflags.exclude });
1917
1944
  if (!ctx) {
1918
1945
  console.log(`Symbol "${arg}" not found.`);
1919
1946
  } else {
1920
1947
  const { text, expandable } = output.formatContext(ctx, {
1921
1948
  methodsHint: 'Note: obj.method() calls excluded — use --include-methods to include them',
1922
- expandHint: 'Use "ucn . expand <N>" to see code for item N',
1949
+ expandHint: 'Use "expand <N>" to see code for item N',
1923
1950
  uncertainHint: 'use --include-uncertain to include all'
1924
1951
  });
1925
1952
  console.log(text);
@@ -1933,7 +1960,7 @@ function executeInteractiveCommand(index, command, arg) {
1933
1960
  console.log('Usage: smart <name>');
1934
1961
  return;
1935
1962
  }
1936
- const smart = index.smart(arg, { file: flags.file, includeUncertain: flags.includeUncertain });
1963
+ const smart = index.smart(arg, { file: iflags.file, includeUncertain: iflags.includeUncertain, withTypes: iflags.withTypes });
1937
1964
  if (smart) {
1938
1965
  console.log(output.formatSmart(smart, {
1939
1966
  uncertainHint: 'use --include-uncertain to include all'
@@ -1949,7 +1976,7 @@ function executeInteractiveCommand(index, command, arg) {
1949
1976
  console.log('Usage: impact <name>');
1950
1977
  return;
1951
1978
  }
1952
- const impactResult = index.impact(arg);
1979
+ const impactResult = index.impact(arg, { file: iflags.file });
1953
1980
  console.log(output.formatImpact(impactResult));
1954
1981
  break;
1955
1982
  }
@@ -1959,12 +1986,70 @@ function executeInteractiveCommand(index, command, arg) {
1959
1986
  console.log('Usage: trace <name>');
1960
1987
  return;
1961
1988
  }
1962
- const traceResult = index.trace(arg, { depth: 3 });
1989
+ const traceDepth = iflags.depth ? parseInt(iflags.depth) : 3;
1990
+ const traceResult = index.trace(arg, { depth: traceDepth, file: iflags.file, all: iflags.all || !!iflags.depth, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain });
1963
1991
  console.log(output.formatTrace(traceResult));
1964
1992
  break;
1965
1993
  }
1966
1994
 
1967
- case 'imports': {
1995
+ case 'fn': {
1996
+ if (!arg) {
1997
+ console.log('Usage: fn <name> [--file=<pattern>]');
1998
+ return;
1999
+ }
2000
+ extractFunctionFromProject(index, arg, iflags);
2001
+ break;
2002
+ }
2003
+
2004
+ case 'class': {
2005
+ if (!arg) {
2006
+ console.log('Usage: class <name> [--file=<pattern>]');
2007
+ return;
2008
+ }
2009
+ extractClassFromProject(index, arg, iflags);
2010
+ break;
2011
+ }
2012
+
2013
+ case 'lines': {
2014
+ if (!arg || !iflags.file) {
2015
+ console.log('Usage: lines <range> --file=<file>');
2016
+ return;
2017
+ }
2018
+ const filePath = index.findFile(iflags.file);
2019
+ if (!filePath) {
2020
+ console.log(`File not found: ${iflags.file}`);
2021
+ return;
2022
+ }
2023
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
2024
+ printLines(fileContent.split('\n'), arg);
2025
+ break;
2026
+ }
2027
+
2028
+ case 'graph': {
2029
+ if (!arg) {
2030
+ console.log('Usage: graph <file> [--direction=imports|importers|both] [--depth=N]');
2031
+ return;
2032
+ }
2033
+ const graphDepth = iflags.depth ? parseInt(iflags.depth) : 2;
2034
+ const graphDirection = iflags.direction || 'both';
2035
+ const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
2036
+ console.log(output.formatGraph(graphResult, { showAll: iflags.all, maxDepth: graphDepth }));
2037
+ break;
2038
+ }
2039
+
2040
+ case 'file-exports':
2041
+ case 'what-exports': {
2042
+ if (!arg) {
2043
+ console.log('Usage: file-exports <file>');
2044
+ return;
2045
+ }
2046
+ const fileExports = index.fileExports(arg);
2047
+ console.log(output.formatFileExports(fileExports, arg));
2048
+ break;
2049
+ }
2050
+
2051
+ case 'imports':
2052
+ case 'what-imports': {
1968
2053
  if (!arg) {
1969
2054
  console.log('Usage: imports <file>');
1970
2055
  return;
@@ -1974,7 +2059,8 @@ function executeInteractiveCommand(index, command, arg) {
1974
2059
  break;
1975
2060
  }
1976
2061
 
1977
- case 'exporters': {
2062
+ case 'exporters':
2063
+ case 'who-imports': {
1978
2064
  if (!arg) {
1979
2065
  console.log('Usage: exporters <file>');
1980
2066
  return;
@@ -1989,7 +2075,7 @@ function executeInteractiveCommand(index, command, arg) {
1989
2075
  console.log('Usage: tests <name>');
1990
2076
  return;
1991
2077
  }
1992
- const tests = index.tests(arg, { callsOnly: flags.callsOnly });
2078
+ const tests = index.tests(arg, { callsOnly: iflags.callsOnly });
1993
2079
  console.log(output.formatTests(tests, arg));
1994
2080
  break;
1995
2081
  }
@@ -1999,7 +2085,7 @@ function executeInteractiveCommand(index, command, arg) {
1999
2085
  console.log('Usage: search <term>');
2000
2086
  return;
2001
2087
  }
2002
- const results = index.search(arg, {});
2088
+ const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context });
2003
2089
  console.log(output.formatSearch(results, arg));
2004
2090
  break;
2005
2091
  }
@@ -2022,9 +2108,9 @@ function executeInteractiveCommand(index, command, arg) {
2022
2108
 
2023
2109
  case 'diff-impact': {
2024
2110
  const diffResult = index.diffImpact({
2025
- base: flags.base || 'HEAD',
2026
- staged: flags.staged,
2027
- file: flags.file
2111
+ base: iflags.base || 'HEAD',
2112
+ staged: iflags.staged,
2113
+ file: iflags.file
2028
2114
  });
2029
2115
  console.log(output.formatDiffImpact(diffResult));
2030
2116
  break;
@@ -2036,6 +2122,102 @@ function executeInteractiveCommand(index, command, arg) {
2036
2122
  break;
2037
2123
  }
2038
2124
 
2125
+ case 'expand': {
2126
+ if (!arg) {
2127
+ console.log('Usage: expand <number>');
2128
+ return;
2129
+ }
2130
+ const expandNum = parseInt(arg, 10);
2131
+ if (isNaN(expandNum)) {
2132
+ console.log(`Invalid item number: "${arg}"`);
2133
+ return;
2134
+ }
2135
+ const cached = loadExpandableItems(index.root);
2136
+ if (!cached || !cached.items || cached.items.length === 0) {
2137
+ console.log('No expandable items. Run context first.');
2138
+ return;
2139
+ }
2140
+ const expandMatch = cached.items.find(i => i.num === expandNum);
2141
+ if (!expandMatch) {
2142
+ console.log(`Item ${expandNum} not found. Available: 1-${cached.items.length}`);
2143
+ return;
2144
+ }
2145
+ printExpandedItem(expandMatch, cached.root || index.root);
2146
+ break;
2147
+ }
2148
+
2149
+ case 'deadcode': {
2150
+ const deadResult = index.deadcode({
2151
+ includeExported: iflags.includeExported,
2152
+ includeDecorated: iflags.includeDecorated,
2153
+ includeTests: iflags.includeTests,
2154
+ exclude: iflags.exclude,
2155
+ in: iflags.in
2156
+ });
2157
+ console.log(output.formatDeadcode(deadResult));
2158
+ break;
2159
+ }
2160
+
2161
+ case 'related': {
2162
+ if (!arg) {
2163
+ console.log('Usage: related <name>');
2164
+ return;
2165
+ }
2166
+ const relResult = index.related(arg, { file: iflags.file, all: iflags.all });
2167
+ console.log(output.formatRelated(relResult, { showAll: iflags.all }));
2168
+ break;
2169
+ }
2170
+
2171
+ case 'example': {
2172
+ if (!arg) {
2173
+ console.log('Usage: example <name>');
2174
+ return;
2175
+ }
2176
+ console.log(output.formatExample(index.example(arg), arg));
2177
+ break;
2178
+ }
2179
+
2180
+ case 'plan': {
2181
+ if (!arg) {
2182
+ console.log('Usage: plan <name> --add-param=x | --remove-param=x | --rename-to=x');
2183
+ return;
2184
+ }
2185
+ if (!iflags.addParam && !iflags.removeParam && !iflags.renameTo) {
2186
+ console.log('Plan requires an operation: --add-param, --remove-param, or --rename-to');
2187
+ return;
2188
+ }
2189
+ const planResult = index.plan(arg, {
2190
+ addParam: iflags.addParam,
2191
+ removeParam: iflags.removeParam,
2192
+ renameTo: iflags.renameTo,
2193
+ defaultValue: iflags.defaultValue,
2194
+ file: iflags.file
2195
+ });
2196
+ console.log(output.formatPlan(planResult));
2197
+ break;
2198
+ }
2199
+
2200
+ case 'verify': {
2201
+ if (!arg) {
2202
+ console.log('Usage: verify <name>');
2203
+ return;
2204
+ }
2205
+ const verifyResult = index.verify(arg, { file: iflags.file });
2206
+ console.log(output.formatVerify(verifyResult));
2207
+ break;
2208
+ }
2209
+
2210
+ case 'stacktrace':
2211
+ case 'stack': {
2212
+ if (!arg) {
2213
+ console.log('Usage: stacktrace <stack text>');
2214
+ return;
2215
+ }
2216
+ const stackResult = index.parseStackTrace(arg);
2217
+ console.log(output.formatStackTrace(stackResult));
2218
+ break;
2219
+ }
2220
+
2039
2221
  default:
2040
2222
  console.log(`Unknown command: ${command}. Type "help" for available commands.`);
2041
2223
  }
package/core/discovery.js CHANGED
@@ -335,11 +335,17 @@ function walkDir(dir, options, depth = 0, visited = new Set()) {
335
335
  * @param {string[]} ignores - Patterns to always ignore
336
336
  * @param {string} [parentDir] - Parent directory path (for conditional checks)
337
337
  */
338
+ const _globRegexCache = new Map();
339
+
338
340
  function shouldIgnore(name, ignores, parentDir) {
339
341
  // Check unconditional ignores
340
342
  for (const pattern of ignores) {
341
343
  if (pattern.includes('*')) {
342
- const regex = globToRegex(pattern);
344
+ let regex = _globRegexCache.get(pattern);
345
+ if (!regex) {
346
+ regex = globToRegex(pattern);
347
+ _globRegexCache.set(pattern, regex);
348
+ }
343
349
  if (regex.test(name)) return true;
344
350
  } else if (name === pattern) {
345
351
  return true;
@@ -408,7 +414,7 @@ function detectProjectPattern(projectRoot) {
408
414
 
409
415
  if (fs.existsSync(path.join(dir, 'pom.xml')) ||
410
416
  fs.existsSync(path.join(dir, 'build.gradle'))) {
411
- extensions.push('java', 'kt');
417
+ extensions.push('java');
412
418
  }
413
419
  };
414
420