ucn 3.7.17 → 3.7.19

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
@@ -13,9 +13,12 @@ const path = require('path');
13
13
  const { parse, parseFile, extractFunction, extractClass, cleanHtmlScriptTags, detectLanguage, isSupported } = require('../core/parser');
14
14
  const { getParser, getLanguageModule } = require('../languages');
15
15
  const { ProjectIndex } = require('../core/project');
16
- const { expandGlob, findProjectRoot, isTestFile } = require('../core/discovery');
16
+ const { expandGlob, findProjectRoot } = require('../core/discovery');
17
17
  const output = require('../core/output');
18
- const { pickBestDefinition, addTestExclusions } = require('../core/shared');
18
+ const { pickBestDefinition } = require('../core/shared');
19
+ const { getCliCommandSet, resolveCommand } = require('../core/registry');
20
+ const { execute } = require('../core/execute');
21
+ const { ExpandCache, renderExpandItem } = require('../core/expand-cache');
19
22
 
20
23
  // ============================================================================
21
24
  // ARGUMENT PARSING
@@ -86,7 +89,9 @@ const flags = {
86
89
  // Regex search mode (default: ON; --no-regex to force plain text)
87
90
  regex: args.includes('--no-regex') ? false : undefined,
88
91
  // Stats: per-function line counts
89
- functions: args.includes('--functions')
92
+ functions: args.includes('--functions'),
93
+ // Class: max lines to show (0 = no limit)
94
+ maxLines: parseInt(args.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null
90
95
  };
91
96
 
92
97
  // Handle --file flag with space
@@ -106,7 +111,8 @@ const knownFlags = new Set([
106
111
  '--depth', '--direction', '--add-param', '--remove-param', '--rename-to',
107
112
  '--default', '--top', '--no-follow-symlinks',
108
113
  '--base', '--staged',
109
- '--regex', '--no-regex', '--functions'
114
+ '--regex', '--no-regex', '--functions',
115
+ '--max-lines'
110
116
  ]);
111
117
 
112
118
  // Handle help flag
@@ -180,14 +186,8 @@ function printOutput(result, jsonFn, textFn) {
180
186
  // MAIN
181
187
  // ============================================================================
182
188
 
183
- // All valid commands - used to detect if first arg is command vs path
184
- const COMMANDS = new Set([
185
- 'toc', 'find', 'usages', 'fn', 'class', 'lines', 'search', 'typedef', 'api',
186
- 'context', 'smart', 'about', 'impact', 'trace', 'related', 'example', 'expand',
187
- 'tests', 'verify', 'plan', 'deadcode', 'stats', 'stacktrace', 'stack',
188
- 'imports', 'what-imports', 'exporters', 'who-imports', 'graph', 'file-exports', 'what-exports',
189
- 'diff-impact'
190
- ]);
189
+ // All valid commands - derived from canonical registry
190
+ const COMMANDS = getCliCommandSet();
191
191
 
192
192
  function main() {
193
193
  // Determine target and command based on positional args
@@ -357,16 +357,15 @@ function runFileCommand(filePath, command, arg) {
357
357
  const projectRoot = findProjectRoot(path.dirname(filePath));
358
358
 
359
359
  // For file-specific commands (imports/exporters/graph), use the target file as arg if no arg given
360
+ const fileCanonical = resolveCommand(command, 'cli') || command;
360
361
  let effectiveArg = arg;
361
- if ((command === 'imports' || command === 'what-imports' ||
362
- command === 'exporters' || command === 'who-imports' ||
363
- command === 'graph' || command === 'file-exports' ||
364
- command === 'what-exports') && !arg) {
362
+ if ((fileCanonical === 'imports' || fileCanonical === 'exporters' ||
363
+ fileCanonical === 'fileExports' || fileCanonical === 'graph') && !arg) {
365
364
  effectiveArg = filePath;
366
365
  }
367
366
 
368
367
  // For stats/deadcode, no arg needed
369
- if (command === 'stats' || command === 'deadcode') {
368
+ if (fileCanonical === 'stats' || fileCanonical === 'deadcode') {
370
369
  effectiveArg = arg; // may be undefined, that's ok
371
370
  }
372
371
 
@@ -697,10 +696,17 @@ function runProjectCommand(rootDir, command, arg) {
697
696
  }
698
697
  }
699
698
 
700
- switch (command) {
699
+ try {
700
+ // Resolve CLI aliases to canonical command names — dispatch on canonical
701
+ const canonical = resolveCommand(command, 'cli') || command;
702
+
703
+ switch (canonical) {
704
+ // ── Commands using shared executor ───────────────────────────────
705
+
701
706
  case 'toc': {
702
- const toc = index.getToc({ detailed: flags.detailed, topLevel: flags.topLevel, all: flags.all, top: flags.top });
703
- printOutput(toc, output.formatTocJson, r => output.formatToc(r, {
707
+ const { ok, result, error } = execute(index, 'toc', flags);
708
+ if (!ok) { console.error(error); process.exit(1); }
709
+ printOutput(result, output.formatTocJson, r => output.formatToc(r, {
704
710
  detailedHint: 'Add --detailed to list all functions, or "ucn . about <name>" for full details on a symbol',
705
711
  uncertainHint: 'use --include-uncertain to include all'
706
712
  }));
@@ -708,15 +714,9 @@ function runProjectCommand(rootDir, command, arg) {
708
714
  }
709
715
 
710
716
  case 'find': {
711
- requireArg(arg, 'Usage: ucn . find <name>');
712
- const findExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
713
- const found = index.find(arg, {
714
- file: flags.file,
715
- exact: flags.exact,
716
- exclude: findExclude,
717
- in: flags.in
718
- });
719
- printOutput(found,
717
+ const { ok, result, error } = execute(index, 'find', { name: arg, ...flags });
718
+ if (!ok) { console.error(error); process.exit(1); }
719
+ printOutput(result,
720
720
  r => output.formatSymbolJson(r, arg),
721
721
  r => { printSymbols(r, arg, { depth: flags.depth, top: flags.top, all: flags.all }); }
722
722
  );
@@ -724,15 +724,9 @@ function runProjectCommand(rootDir, command, arg) {
724
724
  }
725
725
 
726
726
  case 'usages': {
727
- requireArg(arg, 'Usage: ucn . usages <name>');
728
- const usagesExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
729
- const usages = index.usages(arg, {
730
- codeOnly: flags.codeOnly,
731
- context: flags.context,
732
- exclude: usagesExclude,
733
- in: flags.in
734
- });
735
- printOutput(usages,
727
+ const { ok, result, error } = execute(index, 'usages', { name: arg, ...flags });
728
+ if (!ok) { console.error(error); process.exit(1); }
729
+ printOutput(result,
736
730
  r => output.formatUsagesJson(r, arg),
737
731
  r => output.formatUsages(r, arg)
738
732
  );
@@ -740,9 +734,9 @@ function runProjectCommand(rootDir, command, arg) {
740
734
  }
741
735
 
742
736
  case 'example': {
743
- requireArg(arg, 'Usage: ucn . example <name>');
744
- const exampleResult = index.example(arg);
745
- printOutput(exampleResult,
737
+ const { ok, result, error } = execute(index, 'example', { name: arg });
738
+ if (!ok) { console.error(error); process.exit(1); }
739
+ printOutput(result,
746
740
  r => output.formatExampleJson(r, arg),
747
741
  r => output.formatExample(r, arg)
748
742
  );
@@ -750,17 +744,8 @@ function runProjectCommand(rootDir, command, arg) {
750
744
  }
751
745
 
752
746
  case 'context': {
753
- requireArg(arg, 'Usage: ucn . context <name>');
754
- const ctx = index.context(arg, {
755
- includeMethods: flags.includeMethods,
756
- includeUncertain: flags.includeUncertain,
757
- file: flags.file,
758
- exclude: flags.exclude
759
- });
760
- if (!ctx) {
761
- console.log(`Symbol "${arg}" not found.`);
762
- break;
763
- }
747
+ const { ok, result: ctx, error } = execute(index, 'context', { name: arg, ...flags });
748
+ if (!ok) { console.log(error); break; }
764
749
  if (flags.json) {
765
750
  console.log(output.formatContextJson(ctx));
766
751
  } else {
@@ -822,27 +807,18 @@ function runProjectCommand(rootDir, command, arg) {
822
807
  }
823
808
 
824
809
  case 'smart': {
825
- requireArg(arg, 'Usage: ucn . smart <name>');
826
- const smart = index.smart(arg, {
827
- file: flags.file,
828
- withTypes: flags.withTypes,
829
- includeMethods: flags.includeMethods,
830
- includeUncertain: flags.includeUncertain
831
- });
832
- if (smart) {
833
- printOutput(smart, output.formatSmartJson, r => output.formatSmart(r, {
834
- uncertainHint: 'use --include-uncertain to include all'
835
- }));
836
- } else {
837
- console.error(`Function "${arg}" not found`);
838
- }
810
+ const { ok, result, error } = execute(index, 'smart', { name: arg, ...flags });
811
+ if (!ok) { console.error(error); break; }
812
+ printOutput(result, output.formatSmartJson, r => output.formatSmart(r, {
813
+ uncertainHint: 'use --include-uncertain to include all'
814
+ }));
839
815
  break;
840
816
  }
841
817
 
842
818
  case 'about': {
843
- requireArg(arg, 'Usage: ucn . about <name>');
844
- const aboutResult = index.about(arg, { withTypes: flags.withTypes, file: flags.file, all: flags.all, includeMethods: flags.includeMethods, includeUncertain: flags.includeUncertain, exclude: flags.exclude });
845
- printOutput(aboutResult,
819
+ const { ok, result, error } = execute(index, 'about', { name: arg, ...flags });
820
+ if (!ok) { console.error(error); process.exit(1); }
821
+ printOutput(result,
846
822
  output.formatAboutJson,
847
823
  r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth })
848
824
  );
@@ -850,63 +826,51 @@ function runProjectCommand(rootDir, command, arg) {
850
826
  }
851
827
 
852
828
  case 'impact': {
853
- requireArg(arg, 'Usage: ucn . impact <name>');
854
- const impactResult = index.impact(arg, { file: flags.file, exclude: flags.exclude });
855
- printOutput(impactResult, output.formatImpactJson, output.formatImpact);
829
+ const { ok, result, error } = execute(index, 'impact', { name: arg, ...flags });
830
+ if (!ok) { console.error(error); process.exit(1); }
831
+ printOutput(result, output.formatImpactJson, output.formatImpact);
856
832
  break;
857
833
  }
858
834
 
859
835
  case 'plan': {
860
- requireArg(arg, 'Usage: ucn . plan <name> [--add-param=name] [--remove-param=name] [--rename-to=name]');
861
- if (!flags.addParam && !flags.removeParam && !flags.renameTo) {
862
- console.error('Plan requires an operation: --add-param, --remove-param, or --rename-to');
863
- process.exit(1);
864
- }
865
- const planResult = index.plan(arg, {
866
- addParam: flags.addParam,
867
- removeParam: flags.removeParam,
868
- renameTo: flags.renameTo,
869
- defaultValue: flags.defaultValue,
870
- file: flags.file
871
- });
872
- printOutput(planResult, output.formatPlanJson, output.formatPlan);
836
+ const { ok, result, error } = execute(index, 'plan', { name: arg, ...flags });
837
+ if (!ok) { console.error(error); process.exit(1); }
838
+ printOutput(result, output.formatPlanJson, output.formatPlan);
873
839
  break;
874
840
  }
875
841
 
876
842
  case 'trace': {
877
- requireArg(arg, 'Usage: ucn . trace <name>');
878
- const traceDepth = flags.depth ? parseInt(flags.depth) : 3;
879
- const depthExplicit = flags.depth !== null;
880
- const traceResult = index.trace(arg, { depth: traceDepth, file: flags.file, all: flags.all || depthExplicit, includeMethods: flags.includeMethods, includeUncertain: flags.includeUncertain });
881
- printOutput(traceResult, output.formatTraceJson, output.formatTrace);
843
+ const { ok, result, error } = execute(index, 'trace', { name: arg, ...flags });
844
+ if (!ok) { console.error(error); process.exit(1); }
845
+ printOutput(result, output.formatTraceJson, output.formatTrace);
882
846
  break;
883
847
  }
884
848
 
885
- case 'stacktrace':
886
- case 'stack': {
887
- requireArg(arg, 'Usage: ucn . stacktrace "<stack trace text>"\nExample: ucn . stacktrace "Error: failed\\n at parseFile (core/parser.js:90:5)"');
888
- const stackResult = index.parseStackTrace(arg);
889
- printOutput(stackResult, output.formatStackTraceJson, output.formatStackTrace);
849
+ case 'stacktrace': {
850
+ const { ok, result, error } = execute(index, 'stacktrace', { stack: arg });
851
+ if (!ok) { console.error(error); process.exit(1); }
852
+ printOutput(result, output.formatStackTraceJson, output.formatStackTrace);
890
853
  break;
891
854
  }
892
855
 
893
856
  case 'verify': {
894
- requireArg(arg, 'Usage: ucn . verify <name>');
895
- const verifyResult = index.verify(arg, { file: flags.file });
896
- printOutput(verifyResult, output.formatVerifyJson, output.formatVerify);
857
+ const { ok, result, error } = execute(index, 'verify', { name: arg, file: flags.file });
858
+ if (!ok) { console.error(error); process.exit(1); }
859
+ printOutput(result, output.formatVerifyJson, output.formatVerify);
897
860
  break;
898
861
  }
899
862
 
900
863
  case 'related': {
901
- requireArg(arg, 'Usage: ucn . related <name>');
902
- const relatedResult = index.related(arg, { file: flags.file, all: flags.all });
903
- printOutput(relatedResult, output.formatRelatedJson, r => output.formatRelated(r, { showAll: flags.all }));
864
+ const { ok, result, error } = execute(index, 'related', { name: arg, ...flags });
865
+ if (!ok) { console.error(error); process.exit(1); }
866
+ printOutput(result, output.formatRelatedJson, r => output.formatRelated(r, { showAll: flags.all, top: flags.top }));
904
867
  break;
905
868
  }
906
869
 
870
+ // ── Commands staying in adapter (complex I/O) ───────────────────
871
+
907
872
  case 'fn': {
908
873
  requireArg(arg, 'Usage: ucn . fn <name>');
909
- // Support comma-separated names for bulk extraction
910
874
  if (arg.includes(',')) {
911
875
  const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
912
876
  for (let i = 0; i < fnNames.length; i++) {
@@ -925,149 +889,149 @@ function runProjectCommand(rootDir, command, arg) {
925
889
  break;
926
890
  }
927
891
 
928
- case 'imports':
929
- case 'what-imports': {
930
- requireArg(arg, 'Usage: ucn . imports <file>');
931
- const imports = index.imports(arg);
932
- printOutput(imports,
892
+ case 'lines': {
893
+ if (!arg || !flags.file) {
894
+ console.error('Usage: ucn . lines <range> --file <path>');
895
+ process.exit(1);
896
+ }
897
+ const filePath = index.findFile(flags.file);
898
+ if (!filePath) {
899
+ console.error(`File not found: ${flags.file}`);
900
+ process.exit(1);
901
+ }
902
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
903
+ printLines(fileContent.split('\n'), arg);
904
+ break;
905
+ }
906
+
907
+ // ── File dependency commands ────────────────────────────────────
908
+
909
+ case 'imports': {
910
+ const { ok, result, error } = execute(index, 'imports', { file: arg });
911
+ if (!ok) { console.error(error); process.exit(1); }
912
+ printOutput(result,
933
913
  r => output.formatImportsJson(r, arg),
934
914
  r => output.formatImports(r, arg)
935
915
  );
936
916
  break;
937
917
  }
938
918
 
939
- case 'exporters':
940
- case 'who-imports': {
941
- requireArg(arg, 'Usage: ucn . exporters <file>');
942
- const exporters = index.exporters(arg);
943
- printOutput(exporters,
919
+ case 'exporters': {
920
+ const { ok, result, error } = execute(index, 'exporters', { file: arg });
921
+ if (!ok) { console.error(error); process.exit(1); }
922
+ printOutput(result,
944
923
  r => output.formatExportersJson(r, arg),
945
924
  r => output.formatExporters(r, arg)
946
925
  );
947
926
  break;
948
927
  }
949
928
 
950
- case 'typedef': {
951
- requireArg(arg, 'Usage: ucn . typedef <name>');
952
- const typedefs = index.typedef(arg, { exact: flags.exact });
953
- printOutput(typedefs,
954
- r => output.formatTypedefJson(r, arg),
955
- r => output.formatTypedef(r, arg)
929
+ case 'fileExports': {
930
+ const { ok, result, error } = execute(index, 'fileExports', { file: arg });
931
+ if (!ok) { console.error(error); process.exit(1); }
932
+ printOutput(result,
933
+ r => JSON.stringify({ file: arg, exports: r }, null, 2),
934
+ r => output.formatFileExports(r, arg)
956
935
  );
957
936
  break;
958
937
  }
959
938
 
960
- case 'tests': {
961
- requireArg(arg, 'Usage: ucn . tests <name>');
962
- const tests = index.tests(arg, { callsOnly: flags.callsOnly });
963
- printOutput(tests,
964
- r => output.formatTestsJson(r, arg),
965
- r => output.formatTests(r, arg)
939
+ case 'graph': {
940
+ const { ok, result, error } = execute(index, 'graph', { file: arg, direction: flags.direction, depth: flags.depth, all: flags.all });
941
+ if (!ok) { console.error(error); process.exit(1); }
942
+ printOutput(result,
943
+ r => JSON.stringify({
944
+ root: path.relative(index.root, r.root),
945
+ nodes: r.nodes.map(n => ({ file: n.relativePath, depth: n.depth })),
946
+ edges: r.edges.map(e => ({ from: path.relative(index.root, e.from), to: path.relative(index.root, e.to) }))
947
+ }, null, 2),
948
+ r => output.formatGraph(r, { showAll: flags.all || flags.depth !== undefined, maxDepth: flags.depth ?? 2, file: arg })
966
949
  );
967
950
  break;
968
951
  }
969
952
 
970
- case 'api': {
971
- const api = index.api(arg); // arg is optional file path
972
- printOutput(api,
973
- r => output.formatApiJson(r, arg),
974
- r => output.formatApi(r, arg)
975
- );
976
- break;
977
- }
953
+ // ── Remaining commands ──────────────────────────────────────────
978
954
 
979
- case 'file-exports':
980
- case 'what-exports': {
981
- requireArg(arg, 'Usage: ucn . file-exports <file>');
982
- const fileExports = index.fileExports(arg);
983
- printOutput(fileExports,
984
- r => JSON.stringify({ file: arg, exports: r }, null, 2),
985
- r => output.formatFileExports(r, arg)
955
+ case 'typedef': {
956
+ const { ok, result, error } = execute(index, 'typedef', { name: arg, exact: flags.exact });
957
+ if (!ok) { console.error(error); process.exit(1); }
958
+ printOutput(result,
959
+ r => output.formatTypedefJson(r, arg),
960
+ r => output.formatTypedef(r, arg)
986
961
  );
987
962
  break;
988
963
  }
989
964
 
990
- case 'deadcode': {
991
- const deadcodeResults = index.deadcode({
992
- includeExported: flags.includeExported,
993
- includeDecorated: flags.includeDecorated,
994
- includeTests: flags.includeTests,
995
- exclude: flags.exclude,
996
- in: flags.in || subdirScope
997
- });
998
- printOutput(deadcodeResults,
999
- output.formatDeadcodeJson,
1000
- r => output.formatDeadcode(r, { top: flags.top })
965
+ case 'tests': {
966
+ const { ok, result, error } = execute(index, 'tests', { name: arg, callsOnly: flags.callsOnly });
967
+ if (!ok) { console.error(error); process.exit(1); }
968
+ printOutput(result,
969
+ r => output.formatTestsJson(r, arg),
970
+ r => output.formatTests(r, arg)
1001
971
  );
1002
972
  break;
1003
973
  }
1004
974
 
1005
- case 'graph': {
1006
- requireArg(arg, 'Usage: ucn . graph <file>');
1007
- const graphDepth = flags.depth ?? 2; // Default to 2 for cleaner output
1008
- const graphDirection = flags.direction || 'both';
1009
- const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
1010
- printOutput(graphResult,
1011
- r => JSON.stringify({
1012
- root: path.relative(index.root, r.root),
1013
- nodes: r.nodes.map(n => ({ file: n.relativePath, depth: n.depth })),
1014
- edges: r.edges.map(e => ({ from: path.relative(index.root, e.from), to: path.relative(index.root, e.to) }))
1015
- }, null, 2),
1016
- r => output.formatGraph(r, { showAll: flags.all || flags.depth !== undefined, maxDepth: graphDepth, file: arg })
975
+ case 'api': {
976
+ const { ok, result, error } = execute(index, 'api', { file: arg });
977
+ if (!ok) { console.error(error); process.exit(1); }
978
+ printOutput(result,
979
+ r => output.formatApiJson(r, arg),
980
+ r => output.formatApi(r, arg)
1017
981
  );
1018
982
  break;
1019
983
  }
1020
984
 
1021
985
  case 'search': {
1022
- requireArg(arg, 'Usage: ucn . search <term>');
1023
- const searchExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
1024
- const searchResults = index.search(arg, { codeOnly: flags.codeOnly, context: flags.context, caseSensitive: flags.caseSensitive, exclude: searchExclude, in: flags.in, regex: flags.regex });
1025
- printOutput(searchResults,
986
+ const { ok, result, error } = execute(index, 'search', { term: arg, ...flags });
987
+ if (!ok) { console.error(error); process.exit(1); }
988
+ printOutput(result,
1026
989
  r => output.formatSearchJson(r, arg),
1027
990
  r => output.formatSearch(r, arg)
1028
991
  );
1029
992
  break;
1030
993
  }
1031
994
 
1032
- case 'lines': {
1033
- if (!arg || !flags.file) {
1034
- console.error('Usage: ucn . lines <range> --file <path>');
1035
- process.exit(1);
1036
- }
1037
- const filePath = index.findFile(flags.file);
1038
- if (!filePath) {
1039
- console.error(`File not found: ${flags.file}`);
1040
- process.exit(1);
1041
- }
1042
- const fileContent = fs.readFileSync(filePath, 'utf-8');
1043
- printLines(fileContent.split('\n'), arg);
995
+ case 'deadcode': {
996
+ const { ok, result, error } = execute(index, 'deadcode', { ...flags, in: flags.in || subdirScope });
997
+ if (!ok) { console.error(error); process.exit(1); }
998
+ printOutput(result,
999
+ output.formatDeadcodeJson,
1000
+ r => output.formatDeadcode(r, {
1001
+ top: flags.top,
1002
+ decoratedHint: !flags.includeDecorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use --include-decorated to include them.` : undefined,
1003
+ exportedHint: !flags.includeExported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use --include-exported to audit them.` : undefined
1004
+ })
1005
+ );
1044
1006
  break;
1045
1007
  }
1046
1008
 
1047
1009
  case 'stats': {
1048
- const stats = index.getStats({ functions: flags.functions });
1049
- printOutput(stats,
1010
+ const { ok, result, error } = execute(index, 'stats', { functions: flags.functions });
1011
+ if (!ok) { console.error(error); process.exit(1); }
1012
+ printOutput(result,
1050
1013
  output.formatStatsJson,
1051
1014
  r => output.formatStats(r, { top: flags.top })
1052
1015
  );
1053
1016
  break;
1054
1017
  }
1055
1018
 
1056
- case 'diff-impact': {
1057
- const diffResult = index.diffImpact({
1058
- base: flags.base || 'HEAD',
1059
- staged: flags.staged,
1060
- file: flags.file
1061
- });
1062
- printOutput(diffResult, output.formatDiffImpactJson, output.formatDiffImpact);
1019
+ case 'diffImpact': {
1020
+ const { ok, result, error } = execute(index, 'diffImpact', { base: flags.base, staged: flags.staged, file: flags.file });
1021
+ if (!ok) { console.error(error); process.exit(1); }
1022
+ printOutput(result, output.formatDiffImpactJson, output.formatDiffImpact);
1063
1023
  break;
1064
1024
  }
1065
1025
 
1066
1026
  default:
1067
- console.error(`Unknown command: ${command}`);
1027
+ console.error(`Unknown command: ${canonical}`);
1068
1028
  printUsage();
1069
1029
  process.exit(1);
1070
1030
  }
1031
+ } catch (e) {
1032
+ console.error(`Error: ${e.message}`);
1033
+ process.exit(1);
1034
+ }
1071
1035
  }
1072
1036
 
1073
1037
  function extractFunctionFromProject(index, name, overrideFlags) {
@@ -1162,6 +1126,45 @@ function extractClassFromProject(index, name, overrideFlags) {
1162
1126
  // Use index data directly instead of re-parsing the file
1163
1127
  const code = fs.readFileSync(match.file, 'utf-8');
1164
1128
  const codeLines = code.split('\n');
1129
+ const classLineCount = match.endLine - match.startLine + 1;
1130
+
1131
+ // Large class summary (>200 lines) when no --max-lines specified
1132
+ if (classLineCount > 200 && !f.maxLines) {
1133
+ if (f.json) {
1134
+ const extracted = codeLines.slice(match.startLine - 1, match.endLine);
1135
+ const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
1136
+ console.log(JSON.stringify({ ...match, code: clsCode }, null, 2));
1137
+ } else {
1138
+ const lines = [];
1139
+ lines.push(`${match.relativePath}:${match.startLine}`);
1140
+ lines.push(`${output.lineRange(match.startLine, match.endLine)} ${output.formatClassSignature(match)}`);
1141
+ lines.push('\u2500'.repeat(60));
1142
+ const methods = index.findMethodsForType(match.name);
1143
+ if (methods.length > 0) {
1144
+ lines.push(`\nMethods (${methods.length}):`);
1145
+ for (const m of methods) {
1146
+ lines.push(` ${output.formatFunctionSignature(m)} [line ${m.startLine}]`);
1147
+ }
1148
+ }
1149
+ lines.push(`\nClass is ${classLineCount} lines. Use --max-lines=N to see source, or "fn <method>" for individual methods.`);
1150
+ console.log(lines.join('\n'));
1151
+ }
1152
+ return;
1153
+ }
1154
+
1155
+ // Truncated source with --max-lines
1156
+ if (f.maxLines && classLineCount > f.maxLines) {
1157
+ const truncated = codeLines.slice(match.startLine - 1, match.startLine - 1 + f.maxLines);
1158
+ const truncatedCode = cleanHtmlScriptTags(truncated, detectLanguage(match.file)).join('\n');
1159
+ if (f.json) {
1160
+ console.log(JSON.stringify({ ...match, code: truncatedCode, truncated: true, totalLines: classLineCount }, null, 2));
1161
+ } else {
1162
+ console.log(output.formatClass(match, truncatedCode));
1163
+ console.log(`\n... showing ${f.maxLines} of ${classLineCount} lines`);
1164
+ }
1165
+ return;
1166
+ }
1167
+
1165
1168
  const extracted = codeLines.slice(match.startLine - 1, match.endLine);
1166
1169
  const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
1167
1170
 
@@ -1699,6 +1702,8 @@ UNDERSTAND CODE (UCN's strength - semantic analysis)
1699
1702
  smart <name> Function + all dependencies inline
1700
1703
  impact <name> What breaks if changed (call sites grouped by file)
1701
1704
  trace <name> Call tree visualization (--depth=N expands all children)
1705
+ related <name> Find similar functions (same file, shared deps)
1706
+ example <name> Best usage example with context
1702
1707
 
1703
1708
  ═══════════════════════════════════════════════════════════════════════════════
1704
1709
  FIND CODE
@@ -1732,7 +1737,6 @@ REFACTORING HELPERS
1732
1737
  verify <name> Check all call sites match signature
1733
1738
  diff-impact What changed in git diff and who calls it (--base, --staged)
1734
1739
  deadcode Find unused functions/classes
1735
- related <name> Find similar functions (same file, shared deps)
1736
1740
 
1737
1741
  ═══════════════════════════════════════════════════════════════════════════════
1738
1742
  OTHER
@@ -1741,7 +1745,6 @@ OTHER
1741
1745
  typedef <name> Find type definitions
1742
1746
  stats Project statistics (--functions for per-function line counts)
1743
1747
  stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
1744
- example <name> Best usage example with context
1745
1748
 
1746
1749
  Common Flags:
1747
1750
  --file <pattern> Filter by file path (e.g., --file=routes)
@@ -1766,6 +1769,8 @@ Common Flags:
1766
1769
  --calls-only Only show call/test-case matches (tests)
1767
1770
  --case-sensitive Case-sensitive text search (search)
1768
1771
  --detailed List all symbols in toc (compact by default)
1772
+ --top-level Show only top-level functions in toc
1773
+ --max-lines=N Max source lines for class (large classes show summary)
1769
1774
  --no-cache Disable caching
1770
1775
  --clear-cache Clear cache before running
1771
1776
  --base=<ref> Git ref for diff-impact (default: HEAD)
@@ -1792,6 +1797,7 @@ function runInteractive(rootDir) {
1792
1797
  console.log('Building index...');
1793
1798
  const index = new ProjectIndex(rootDir);
1794
1799
  index.build(null, { quiet: true });
1800
+ const iExpandCache = new ExpandCache({ maxSize: 20 });
1795
1801
  console.log(`Index ready: ${index.files.size} files, ${index.symbols.size} symbols`);
1796
1802
  console.log('Type commands (e.g., "find parseFile", "about main", "toc")');
1797
1803
  console.log('Type "help" for commands, "quit" to exit\n');
@@ -1874,7 +1880,8 @@ Flags can be added per-command: context myFunc --include-methods
1874
1880
  const iflags = parseInteractiveFlags(flagTokens);
1875
1881
 
1876
1882
  try {
1877
- executeInteractiveCommand(index, command, arg, iflags);
1883
+ const iCanonical = resolveCommand(command, 'cli') || command;
1884
+ executeInteractiveCommand(index, iCanonical, arg, iflags, iExpandCache);
1878
1885
  } catch (e) {
1879
1886
  console.error(`Error: ${e.message}`);
1880
1887
  }
@@ -1902,6 +1909,7 @@ function parseInteractiveFlags(tokens) {
1902
1909
  includeUncertain: tokens.includes('--include-uncertain'),
1903
1910
  includeMethods: tokens.includes('--include-methods=false') ? false : tokens.includes('--include-methods') ? true : undefined,
1904
1911
  detailed: tokens.includes('--detailed'),
1912
+ topLevel: tokens.includes('--top-level'),
1905
1913
  all: tokens.includes('--all'),
1906
1914
  exact: tokens.includes('--exact'),
1907
1915
  callsOnly: tokens.includes('--calls-only'),
@@ -1925,339 +1933,290 @@ function parseInteractiveFlags(tokens) {
1925
1933
  };
1926
1934
  }
1927
1935
 
1928
- function executeInteractiveCommand(index, command, arg, iflags = {}) {
1936
+ function executeInteractiveCommand(index, command, arg, iflags = {}, cache = null) {
1929
1937
  switch (command) {
1930
- case 'toc': {
1931
- const toc = index.getToc({ detailed: iflags.detailed });
1932
- console.log(output.formatToc(toc, {
1933
- detailedHint: 'Add --detailed to list all functions, or "about <name>" for full details on a symbol'
1934
- }));
1935
- break;
1936
- }
1937
1938
 
1938
- case 'find': {
1939
+ // ── Special commands (complex I/O, stay in adapter) ──────────────
1940
+
1941
+ case 'fn': {
1939
1942
  if (!arg) {
1940
- console.log('Usage: find <name>');
1943
+ console.log('Usage: fn <name>[,name2,...] [--file=<pattern>]');
1941
1944
  return;
1942
1945
  }
1943
- const found = index.find(arg, { exact: iflags.exact, exclude: iflags.exclude, in: iflags.in, includeTests: iflags.includeTests });
1944
- if (found.length === 0) {
1945
- console.log(`No symbols found for "${arg}"`);
1946
+ // Support comma-separated names for bulk extraction
1947
+ if (arg.includes(',')) {
1948
+ const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
1949
+ for (let i = 0; i < fnNames.length; i++) {
1950
+ if (i > 0) console.log('\n' + '═'.repeat(60) + '\n');
1951
+ extractFunctionFromProject(index, fnNames[i], iflags);
1952
+ }
1946
1953
  } else {
1947
- printSymbols(found, arg, { top: iflags.top });
1954
+ extractFunctionFromProject(index, arg, iflags);
1948
1955
  }
1949
1956
  break;
1950
1957
  }
1951
1958
 
1952
- case 'about': {
1959
+ case 'class': {
1953
1960
  if (!arg) {
1954
- console.log('Usage: about <name>');
1961
+ console.log('Usage: class <name> [--file=<pattern>]');
1955
1962
  return;
1956
1963
  }
1957
- const aboutResult = index.about(arg, { includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain, file: iflags.file, exclude: iflags.exclude });
1958
- console.log(output.formatAbout(aboutResult, { expand: iflags.expand, root: index.root, showAll: iflags.all }));
1964
+ extractClassFromProject(index, arg, iflags);
1959
1965
  break;
1960
1966
  }
1961
1967
 
1962
- case 'usages': {
1963
- if (!arg) {
1964
- console.log('Usage: usages <name>');
1968
+ case 'lines': {
1969
+ if (!arg || !iflags.file) {
1970
+ console.log('Usage: lines <range> --file=<file>');
1971
+ return;
1972
+ }
1973
+ const filePath = index.findFile(iflags.file);
1974
+ if (!filePath) {
1975
+ console.log(`File not found: ${iflags.file}`);
1965
1976
  return;
1966
1977
  }
1967
- const usages = index.usages(arg, { context: iflags.context, codeOnly: iflags.codeOnly, includeTests: iflags.includeTests });
1968
- console.log(output.formatUsages(usages, arg));
1978
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
1979
+ printLines(fileContent.split('\n'), arg);
1969
1980
  break;
1970
1981
  }
1971
1982
 
1972
- case 'context': {
1983
+ case 'expand': {
1973
1984
  if (!arg) {
1974
- console.log('Usage: context <name>');
1985
+ console.log('Usage: expand <number>');
1986
+ return;
1987
+ }
1988
+ const expandNum = parseInt(arg, 10);
1989
+ if (isNaN(expandNum)) {
1990
+ console.log(`Invalid item number: "${arg}"`);
1975
1991
  return;
1976
1992
  }
1977
- const ctx = index.context(arg, { includeUncertain: iflags.includeUncertain, includeMethods: iflags.includeMethods, file: iflags.file, exclude: iflags.exclude });
1978
- if (!ctx) {
1979
- console.log(`Symbol "${arg}" not found.`);
1993
+ if (cache) {
1994
+ const { match, itemCount } = cache.lookup(index.root, expandNum);
1995
+ if (!match && itemCount === 0) {
1996
+ console.log('No expandable items. Run context first.');
1997
+ return;
1998
+ }
1999
+ if (!match) {
2000
+ console.log(`Item ${expandNum} not found. Available: 1-${itemCount}`);
2001
+ return;
2002
+ }
2003
+ const rendered = renderExpandItem(match, index.root);
2004
+ if (!rendered.ok) { console.log(rendered.error); return; }
2005
+ console.log(rendered.text);
1980
2006
  } else {
1981
- const { text, expandable } = output.formatContext(ctx, {
1982
- methodsHint: 'Note: obj.method() calls excluded — use --include-methods to include them',
1983
- expandHint: 'Use "expand <N>" to see code for item N',
1984
- uncertainHint: 'use --include-uncertain to include all'
1985
- });
1986
- console.log(text);
1987
- saveExpandableItems(expandable, index.root);
2007
+ // Fallback to file-based cache (CLI one-shot)
2008
+ const cached = loadExpandableItems(index.root);
2009
+ if (!cached || !cached.items || cached.items.length === 0) {
2010
+ console.log('No expandable items. Run context first.');
2011
+ return;
2012
+ }
2013
+ const expandMatch = cached.items.find(i => i.num === expandNum);
2014
+ if (!expandMatch) {
2015
+ console.log(`Item ${expandNum} not found. Available: 1-${cached.items.length}`);
2016
+ return;
2017
+ }
2018
+ printExpandedItem(expandMatch, cached.root || index.root);
1988
2019
  }
1989
2020
  break;
1990
2021
  }
1991
2022
 
1992
- case 'smart': {
1993
- if (!arg) {
1994
- console.log('Usage: smart <name>');
1995
- return;
1996
- }
1997
- const smart = index.smart(arg, { file: iflags.file, includeUncertain: iflags.includeUncertain, withTypes: iflags.withTypes });
1998
- if (smart) {
1999
- console.log(output.formatSmart(smart, {
2000
- uncertainHint: 'use --include-uncertain to include all'
2001
- }));
2023
+ // ── find: uses printSymbols (interactive-only formatter) ─────────
2024
+
2025
+ case 'find': {
2026
+ const { ok, result, error } = execute(index, 'find', { name: arg, ...iflags });
2027
+ if (!ok) { console.log(error); return; }
2028
+ if (result.length === 0) {
2029
+ console.log(`No symbols found for "${arg}"`);
2002
2030
  } else {
2003
- console.log(`Function "${arg}" not found`);
2031
+ printSymbols(result, arg, { top: iflags.top });
2004
2032
  }
2005
2033
  break;
2006
2034
  }
2007
2035
 
2008
- case 'impact': {
2009
- if (!arg) {
2010
- console.log('Usage: impact <name>');
2011
- return;
2036
+ // ── context: needs expandable items cache ────────────────────────
2037
+
2038
+ case 'context': {
2039
+ const { ok, result, error } = execute(index, 'context', { name: arg, ...iflags });
2040
+ if (!ok) { console.log(error); return; }
2041
+ const { text, expandable } = output.formatContext(result, {
2042
+ methodsHint: 'Note: obj.method() calls excluded — use --include-methods to include them',
2043
+ expandHint: 'Use "expand <N>" to see code for item N',
2044
+ uncertainHint: 'use --include-uncertain to include all'
2045
+ });
2046
+ console.log(text);
2047
+ if (cache) {
2048
+ cache.save(index.root, arg, iflags.file, expandable);
2049
+ } else {
2050
+ saveExpandableItems(expandable, index.root);
2012
2051
  }
2013
- const impactResult = index.impact(arg, { file: iflags.file });
2014
- console.log(output.formatImpact(impactResult));
2015
2052
  break;
2016
2053
  }
2017
2054
 
2018
- case 'trace': {
2019
- if (!arg) {
2020
- console.log('Usage: trace <name>');
2021
- return;
2022
- }
2023
- const traceDepth = iflags.depth ? parseInt(iflags.depth) : 3;
2024
- const traceResult = index.trace(arg, { depth: traceDepth, file: iflags.file, all: iflags.all || !!iflags.depth, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain });
2025
- console.log(output.formatTrace(traceResult));
2055
+ // ── deadcode: needs result fields for hint construction ──────────
2056
+
2057
+ case 'deadcode': {
2058
+ const { ok, result, error } = execute(index, 'deadcode', iflags);
2059
+ if (!ok) { console.log(error); return; }
2060
+ console.log(output.formatDeadcode(result, {
2061
+ top: iflags.top,
2062
+ decoratedHint: !iflags.includeDecorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use --include-decorated to include them.` : undefined,
2063
+ exportedHint: !iflags.includeExported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use --include-exported to audit them.` : undefined
2064
+ }));
2026
2065
  break;
2027
2066
  }
2028
2067
 
2029
- case 'fn': {
2030
- if (!arg) {
2031
- console.log('Usage: fn <name>[,name2,...] [--file=<pattern>]');
2032
- return;
2033
- }
2034
- // Support comma-separated names for bulk extraction
2035
- if (arg.includes(',')) {
2036
- const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
2037
- for (let i = 0; i < fnNames.length; i++) {
2038
- if (i > 0) console.log('\n' + '═'.repeat(60) + '\n');
2039
- extractFunctionFromProject(index, fnNames[i], iflags);
2040
- }
2041
- } else {
2042
- extractFunctionFromProject(index, arg, iflags);
2043
- }
2068
+ // ── Standard commands routed through execute() ───────────────────
2069
+
2070
+ case 'toc': {
2071
+ const { ok, result, error } = execute(index, 'toc', iflags);
2072
+ if (!ok) { console.log(error); return; }
2073
+ console.log(output.formatToc(result, {
2074
+ detailedHint: 'Add --detailed to list all functions, or "about <name>" for full details on a symbol',
2075
+ uncertainHint: 'use --include-uncertain to include all'
2076
+ }));
2044
2077
  break;
2045
2078
  }
2046
2079
 
2047
- case 'class': {
2048
- if (!arg) {
2049
- console.log('Usage: class <name> [--file=<pattern>]');
2050
- return;
2051
- }
2052
- extractClassFromProject(index, arg, iflags);
2080
+ case 'about': {
2081
+ const { ok, result, error } = execute(index, 'about', { name: arg, ...iflags });
2082
+ if (!ok) { console.log(error); return; }
2083
+ console.log(output.formatAbout(result, { expand: iflags.expand, root: index.root, showAll: iflags.all }));
2053
2084
  break;
2054
2085
  }
2055
2086
 
2056
- case 'lines': {
2057
- if (!arg || !iflags.file) {
2058
- console.log('Usage: lines <range> --file=<file>');
2059
- return;
2060
- }
2061
- const filePath = index.findFile(iflags.file);
2062
- if (!filePath) {
2063
- console.log(`File not found: ${iflags.file}`);
2064
- return;
2065
- }
2066
- const fileContent = fs.readFileSync(filePath, 'utf-8');
2067
- printLines(fileContent.split('\n'), arg);
2087
+ case 'usages': {
2088
+ const { ok, result, error } = execute(index, 'usages', { name: arg, ...iflags });
2089
+ if (!ok) { console.log(error); return; }
2090
+ console.log(output.formatUsages(result, arg));
2091
+ break;
2092
+ }
2093
+
2094
+ case 'smart': {
2095
+ const { ok, result, error } = execute(index, 'smart', { name: arg, ...iflags });
2096
+ if (!ok) { console.log(error); return; }
2097
+ console.log(output.formatSmart(result, {
2098
+ uncertainHint: 'use --include-uncertain to include all'
2099
+ }));
2100
+ break;
2101
+ }
2102
+
2103
+ case 'impact': {
2104
+ const { ok, result, error } = execute(index, 'impact', { name: arg, ...iflags });
2105
+ if (!ok) { console.log(error); return; }
2106
+ console.log(output.formatImpact(result));
2107
+ break;
2108
+ }
2109
+
2110
+ case 'trace': {
2111
+ const { ok, result, error } = execute(index, 'trace', { name: arg, ...iflags });
2112
+ if (!ok) { console.log(error); return; }
2113
+ console.log(output.formatTrace(result));
2068
2114
  break;
2069
2115
  }
2070
2116
 
2071
2117
  case 'graph': {
2072
- if (!arg) {
2073
- console.log('Usage: graph <file> [--direction=imports|importers|both] [--depth=N]');
2074
- return;
2075
- }
2118
+ const { ok, result, error } = execute(index, 'graph', { file: arg, ...iflags });
2119
+ if (!ok) { console.log(error); return; }
2076
2120
  const graphDepth = iflags.depth ? parseInt(iflags.depth) : 2;
2077
- const graphDirection = iflags.direction || 'both';
2078
- const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
2079
- console.log(output.formatGraph(graphResult, { showAll: iflags.all, maxDepth: graphDepth }));
2121
+ console.log(output.formatGraph(result, { showAll: iflags.all || !!iflags.depth, maxDepth: graphDepth, file: arg }));
2080
2122
  break;
2081
2123
  }
2082
2124
 
2083
- case 'file-exports':
2084
- case 'what-exports': {
2085
- if (!arg) {
2086
- console.log('Usage: file-exports <file>');
2087
- return;
2088
- }
2089
- const fileExports = index.fileExports(arg);
2090
- console.log(output.formatFileExports(fileExports, arg));
2125
+ case 'fileExports': {
2126
+ const { ok, result, error } = execute(index, 'fileExports', { file: arg });
2127
+ if (!ok) { console.log(error); return; }
2128
+ console.log(output.formatFileExports(result, arg));
2091
2129
  break;
2092
2130
  }
2093
2131
 
2094
- case 'imports':
2095
- case 'what-imports': {
2096
- if (!arg) {
2097
- console.log('Usage: imports <file>');
2098
- return;
2099
- }
2100
- const imports = index.imports(arg);
2101
- console.log(output.formatImports(imports, arg));
2132
+ case 'imports': {
2133
+ const { ok, result, error } = execute(index, 'imports', { file: arg });
2134
+ if (!ok) { console.log(error); return; }
2135
+ console.log(output.formatImports(result, arg));
2102
2136
  break;
2103
2137
  }
2104
2138
 
2105
- case 'exporters':
2106
- case 'who-imports': {
2107
- if (!arg) {
2108
- console.log('Usage: exporters <file>');
2109
- return;
2110
- }
2111
- const exporters = index.exporters(arg);
2112
- console.log(output.formatExporters(exporters, arg));
2139
+ case 'exporters': {
2140
+ const { ok, result, error } = execute(index, 'exporters', { file: arg });
2141
+ if (!ok) { console.log(error); return; }
2142
+ console.log(output.formatExporters(result, arg));
2113
2143
  break;
2114
2144
  }
2115
2145
 
2116
2146
  case 'tests': {
2117
- if (!arg) {
2118
- console.log('Usage: tests <name>');
2119
- return;
2120
- }
2121
- const tests = index.tests(arg, { callsOnly: iflags.callsOnly });
2122
- console.log(output.formatTests(tests, arg));
2147
+ const { ok, result, error } = execute(index, 'tests', { name: arg, ...iflags });
2148
+ if (!ok) { console.log(error); return; }
2149
+ console.log(output.formatTests(result, arg));
2123
2150
  break;
2124
2151
  }
2125
2152
 
2126
2153
  case 'search': {
2127
- if (!arg) {
2128
- console.log('Usage: search <term>');
2129
- return;
2130
- }
2131
- const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context, exclude: iflags.exclude, in: iflags.in, regex: iflags.regex });
2132
- console.log(output.formatSearch(results, arg));
2154
+ const { ok, result, error } = execute(index, 'search', { term: arg, ...iflags });
2155
+ if (!ok) { console.log(error); return; }
2156
+ console.log(output.formatSearch(result, arg));
2133
2157
  break;
2134
2158
  }
2135
2159
 
2136
2160
  case 'typedef': {
2137
- if (!arg) {
2138
- console.log('Usage: typedef <name>');
2139
- return;
2140
- }
2141
- const types = index.typedef(arg);
2142
- console.log(output.formatTypedef(types, arg));
2161
+ const { ok, result, error } = execute(index, 'typedef', { name: arg, ...iflags });
2162
+ if (!ok) { console.log(error); return; }
2163
+ console.log(output.formatTypedef(result, arg));
2143
2164
  break;
2144
2165
  }
2145
2166
 
2146
2167
  case 'api': {
2147
- const api = index.api();
2148
- console.log(output.formatApi(api, '.'));
2168
+ const { ok, result, error } = execute(index, 'api', { file: arg });
2169
+ if (!ok) { console.log(error); return; }
2170
+ console.log(output.formatApi(result, arg || '.'));
2149
2171
  break;
2150
2172
  }
2151
2173
 
2152
- case 'diff-impact': {
2153
- const diffResult = index.diffImpact({
2154
- base: iflags.base || 'HEAD',
2155
- staged: iflags.staged,
2156
- file: iflags.file
2157
- });
2158
- console.log(output.formatDiffImpact(diffResult));
2174
+ case 'diffImpact': {
2175
+ const { ok, result, error } = execute(index, 'diffImpact', iflags);
2176
+ if (!ok) { console.log(error); return; }
2177
+ console.log(output.formatDiffImpact(result));
2159
2178
  break;
2160
2179
  }
2161
2180
 
2162
2181
  case 'stats': {
2163
- const stats = index.getStats({ functions: iflags.functions });
2164
- console.log(output.formatStats(stats, { top: iflags.top }));
2165
- break;
2166
- }
2167
-
2168
- case 'expand': {
2169
- if (!arg) {
2170
- console.log('Usage: expand <number>');
2171
- return;
2172
- }
2173
- const expandNum = parseInt(arg, 10);
2174
- if (isNaN(expandNum)) {
2175
- console.log(`Invalid item number: "${arg}"`);
2176
- return;
2177
- }
2178
- const cached = loadExpandableItems(index.root);
2179
- if (!cached || !cached.items || cached.items.length === 0) {
2180
- console.log('No expandable items. Run context first.');
2181
- return;
2182
- }
2183
- const expandMatch = cached.items.find(i => i.num === expandNum);
2184
- if (!expandMatch) {
2185
- console.log(`Item ${expandNum} not found. Available: 1-${cached.items.length}`);
2186
- return;
2187
- }
2188
- printExpandedItem(expandMatch, cached.root || index.root);
2189
- break;
2190
- }
2191
-
2192
- case 'deadcode': {
2193
- const deadResult = index.deadcode({
2194
- includeExported: iflags.includeExported,
2195
- includeDecorated: iflags.includeDecorated,
2196
- includeTests: iflags.includeTests,
2197
- exclude: iflags.exclude,
2198
- in: iflags.in
2199
- });
2200
- console.log(output.formatDeadcode(deadResult, { top: iflags.top }));
2182
+ const { ok, result, error } = execute(index, 'stats', iflags);
2183
+ if (!ok) { console.log(error); return; }
2184
+ console.log(output.formatStats(result, { top: iflags.top }));
2201
2185
  break;
2202
2186
  }
2203
2187
 
2204
2188
  case 'related': {
2205
- if (!arg) {
2206
- console.log('Usage: related <name>');
2207
- return;
2208
- }
2209
- const relResult = index.related(arg, { file: iflags.file, all: iflags.all });
2210
- console.log(output.formatRelated(relResult, { showAll: iflags.all }));
2189
+ const { ok, result, error } = execute(index, 'related', { name: arg, ...iflags });
2190
+ if (!ok) { console.log(error); return; }
2191
+ console.log(output.formatRelated(result, { showAll: iflags.all, top: iflags.top }));
2211
2192
  break;
2212
2193
  }
2213
2194
 
2214
2195
  case 'example': {
2215
- if (!arg) {
2216
- console.log('Usage: example <name>');
2217
- return;
2218
- }
2219
- console.log(output.formatExample(index.example(arg), arg));
2196
+ const { ok, result, error } = execute(index, 'example', { name: arg });
2197
+ if (!ok) { console.log(error); return; }
2198
+ console.log(output.formatExample(result, arg));
2220
2199
  break;
2221
2200
  }
2222
2201
 
2223
2202
  case 'plan': {
2224
- if (!arg) {
2225
- console.log('Usage: plan <name> --add-param=x | --remove-param=x | --rename-to=x');
2226
- return;
2227
- }
2228
- if (!iflags.addParam && !iflags.removeParam && !iflags.renameTo) {
2229
- console.log('Plan requires an operation: --add-param, --remove-param, or --rename-to');
2230
- return;
2231
- }
2232
- const planResult = index.plan(arg, {
2233
- addParam: iflags.addParam,
2234
- removeParam: iflags.removeParam,
2235
- renameTo: iflags.renameTo,
2236
- defaultValue: iflags.defaultValue,
2237
- file: iflags.file
2238
- });
2239
- console.log(output.formatPlan(planResult));
2203
+ const { ok, result, error } = execute(index, 'plan', { name: arg, ...iflags });
2204
+ if (!ok) { console.log(error); return; }
2205
+ console.log(output.formatPlan(result));
2240
2206
  break;
2241
2207
  }
2242
2208
 
2243
2209
  case 'verify': {
2244
- if (!arg) {
2245
- console.log('Usage: verify <name>');
2246
- return;
2247
- }
2248
- const verifyResult = index.verify(arg, { file: iflags.file });
2249
- console.log(output.formatVerify(verifyResult));
2210
+ const { ok, result, error } = execute(index, 'verify', { name: arg, ...iflags });
2211
+ if (!ok) { console.log(error); return; }
2212
+ console.log(output.formatVerify(result));
2250
2213
  break;
2251
2214
  }
2252
2215
 
2253
- case 'stacktrace':
2254
- case 'stack': {
2255
- if (!arg) {
2256
- console.log('Usage: stacktrace <stack text>');
2257
- return;
2258
- }
2259
- const stackResult = index.parseStackTrace(arg);
2260
- console.log(output.formatStackTrace(stackResult));
2216
+ case 'stacktrace': {
2217
+ const { ok, result, error } = execute(index, 'stacktrace', { stack: arg });
2218
+ if (!ok) { console.log(error); return; }
2219
+ console.log(output.formatStackTrace(result));
2261
2220
  break;
2262
2221
  }
2263
2222