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 +265 -83
- package/core/discovery.js +8 -2
- package/core/output.js +158 -0
- package/core/project.js +133 -42
- package/core/shared.js +43 -0
- package/languages/go.js +61 -1
- package/languages/java.js +78 -2
- package/languages/rust.js +71 -2
- package/mcp/server.js +54 -33
- package/package.json +1 -1
- package/test/mcp-edge-cases.js +81 -0
- package/test/parser.test.js +559 -8
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 && !
|
|
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 (
|
|
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 && !
|
|
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 (
|
|
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
|
|
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 && !
|
|
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 (
|
|
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 && !
|
|
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 (
|
|
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
|
|
1850
|
-
const command =
|
|
1851
|
-
const
|
|
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
|
-
|
|
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 "
|
|
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:
|
|
1897
|
-
console.log(output.formatAbout(aboutResult, { expand:
|
|
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:
|
|
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 "
|
|
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:
|
|
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
|
|
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 '
|
|
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:
|
|
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:
|
|
2026
|
-
staged:
|
|
2027
|
-
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
|
-
|
|
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'
|
|
417
|
+
extensions.push('java');
|
|
412
418
|
}
|
|
413
419
|
};
|
|
414
420
|
|