ucn 3.7.18 → 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
@@ -183,14 +186,8 @@ function printOutput(result, jsonFn, textFn) {
183
186
  // MAIN
184
187
  // ============================================================================
185
188
 
186
- // All valid commands - used to detect if first arg is command vs path
187
- const COMMANDS = new Set([
188
- 'toc', 'find', 'usages', 'fn', 'class', 'lines', 'search', 'typedef', 'api',
189
- 'context', 'smart', 'about', 'impact', 'trace', 'related', 'example', 'expand',
190
- 'tests', 'verify', 'plan', 'deadcode', 'stats', 'stacktrace', 'stack',
191
- 'imports', 'what-imports', 'exporters', 'who-imports', 'graph', 'file-exports', 'what-exports',
192
- 'diff-impact'
193
- ]);
189
+ // All valid commands - derived from canonical registry
190
+ const COMMANDS = getCliCommandSet();
194
191
 
195
192
  function main() {
196
193
  // Determine target and command based on positional args
@@ -360,16 +357,15 @@ function runFileCommand(filePath, command, arg) {
360
357
  const projectRoot = findProjectRoot(path.dirname(filePath));
361
358
 
362
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;
363
361
  let effectiveArg = arg;
364
- if ((command === 'imports' || command === 'what-imports' ||
365
- command === 'exporters' || command === 'who-imports' ||
366
- command === 'graph' || command === 'file-exports' ||
367
- command === 'what-exports') && !arg) {
362
+ if ((fileCanonical === 'imports' || fileCanonical === 'exporters' ||
363
+ fileCanonical === 'fileExports' || fileCanonical === 'graph') && !arg) {
368
364
  effectiveArg = filePath;
369
365
  }
370
366
 
371
367
  // For stats/deadcode, no arg needed
372
- if (command === 'stats' || command === 'deadcode') {
368
+ if (fileCanonical === 'stats' || fileCanonical === 'deadcode') {
373
369
  effectiveArg = arg; // may be undefined, that's ok
374
370
  }
375
371
 
@@ -700,10 +696,17 @@ function runProjectCommand(rootDir, command, arg) {
700
696
  }
701
697
  }
702
698
 
703
- 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
+
704
706
  case 'toc': {
705
- const toc = index.getToc({ detailed: flags.detailed, topLevel: flags.topLevel, all: flags.all, top: flags.top });
706
- 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, {
707
710
  detailedHint: 'Add --detailed to list all functions, or "ucn . about <name>" for full details on a symbol',
708
711
  uncertainHint: 'use --include-uncertain to include all'
709
712
  }));
@@ -711,15 +714,9 @@ function runProjectCommand(rootDir, command, arg) {
711
714
  }
712
715
 
713
716
  case 'find': {
714
- requireArg(arg, 'Usage: ucn . find <name>');
715
- const findExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
716
- const found = index.find(arg, {
717
- file: flags.file,
718
- exact: flags.exact,
719
- exclude: findExclude,
720
- in: flags.in
721
- });
722
- 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,
723
720
  r => output.formatSymbolJson(r, arg),
724
721
  r => { printSymbols(r, arg, { depth: flags.depth, top: flags.top, all: flags.all }); }
725
722
  );
@@ -727,15 +724,9 @@ function runProjectCommand(rootDir, command, arg) {
727
724
  }
728
725
 
729
726
  case 'usages': {
730
- requireArg(arg, 'Usage: ucn . usages <name>');
731
- const usagesExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
732
- const usages = index.usages(arg, {
733
- codeOnly: flags.codeOnly,
734
- context: flags.context,
735
- exclude: usagesExclude,
736
- in: flags.in
737
- });
738
- 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,
739
730
  r => output.formatUsagesJson(r, arg),
740
731
  r => output.formatUsages(r, arg)
741
732
  );
@@ -743,9 +734,9 @@ function runProjectCommand(rootDir, command, arg) {
743
734
  }
744
735
 
745
736
  case 'example': {
746
- requireArg(arg, 'Usage: ucn . example <name>');
747
- const exampleResult = index.example(arg);
748
- printOutput(exampleResult,
737
+ const { ok, result, error } = execute(index, 'example', { name: arg });
738
+ if (!ok) { console.error(error); process.exit(1); }
739
+ printOutput(result,
749
740
  r => output.formatExampleJson(r, arg),
750
741
  r => output.formatExample(r, arg)
751
742
  );
@@ -753,17 +744,8 @@ function runProjectCommand(rootDir, command, arg) {
753
744
  }
754
745
 
755
746
  case 'context': {
756
- requireArg(arg, 'Usage: ucn . context <name>');
757
- const ctx = index.context(arg, {
758
- includeMethods: flags.includeMethods,
759
- includeUncertain: flags.includeUncertain,
760
- file: flags.file,
761
- exclude: flags.exclude
762
- });
763
- if (!ctx) {
764
- console.log(`Symbol "${arg}" not found.`);
765
- break;
766
- }
747
+ const { ok, result: ctx, error } = execute(index, 'context', { name: arg, ...flags });
748
+ if (!ok) { console.log(error); break; }
767
749
  if (flags.json) {
768
750
  console.log(output.formatContextJson(ctx));
769
751
  } else {
@@ -825,27 +807,18 @@ function runProjectCommand(rootDir, command, arg) {
825
807
  }
826
808
 
827
809
  case 'smart': {
828
- requireArg(arg, 'Usage: ucn . smart <name>');
829
- const smart = index.smart(arg, {
830
- file: flags.file,
831
- withTypes: flags.withTypes,
832
- includeMethods: flags.includeMethods,
833
- includeUncertain: flags.includeUncertain
834
- });
835
- if (smart) {
836
- printOutput(smart, output.formatSmartJson, r => output.formatSmart(r, {
837
- uncertainHint: 'use --include-uncertain to include all'
838
- }));
839
- } else {
840
- console.error(`Function "${arg}" not found`);
841
- }
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
+ }));
842
815
  break;
843
816
  }
844
817
 
845
818
  case 'about': {
846
- requireArg(arg, 'Usage: ucn . about <name>');
847
- const aboutResult = index.about(arg, { withTypes: flags.withTypes, file: flags.file, all: flags.all, includeMethods: flags.includeMethods, includeUncertain: flags.includeUncertain, exclude: flags.exclude, maxCallers: flags.top || undefined, maxCallees: flags.top || undefined });
848
- 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,
849
822
  output.formatAboutJson,
850
823
  r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth })
851
824
  );
@@ -853,63 +826,51 @@ function runProjectCommand(rootDir, command, arg) {
853
826
  }
854
827
 
855
828
  case 'impact': {
856
- requireArg(arg, 'Usage: ucn . impact <name>');
857
- const impactResult = index.impact(arg, { file: flags.file, exclude: flags.exclude });
858
- 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);
859
832
  break;
860
833
  }
861
834
 
862
835
  case 'plan': {
863
- requireArg(arg, 'Usage: ucn . plan <name> [--add-param=name] [--remove-param=name] [--rename-to=name]');
864
- if (!flags.addParam && !flags.removeParam && !flags.renameTo) {
865
- console.error('Plan requires an operation: --add-param, --remove-param, or --rename-to');
866
- process.exit(1);
867
- }
868
- const planResult = index.plan(arg, {
869
- addParam: flags.addParam,
870
- removeParam: flags.removeParam,
871
- renameTo: flags.renameTo,
872
- defaultValue: flags.defaultValue,
873
- file: flags.file
874
- });
875
- 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);
876
839
  break;
877
840
  }
878
841
 
879
842
  case 'trace': {
880
- requireArg(arg, 'Usage: ucn . trace <name>');
881
- const traceDepth = flags.depth ? parseInt(flags.depth) : 3;
882
- const depthExplicit = flags.depth !== null;
883
- const traceResult = index.trace(arg, { depth: traceDepth, file: flags.file, all: flags.all || depthExplicit, includeMethods: flags.includeMethods, includeUncertain: flags.includeUncertain });
884
- 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);
885
846
  break;
886
847
  }
887
848
 
888
- case 'stacktrace':
889
- case 'stack': {
890
- requireArg(arg, 'Usage: ucn . stacktrace "<stack trace text>"\nExample: ucn . stacktrace "Error: failed\\n at parseFile (core/parser.js:90:5)"');
891
- const stackResult = index.parseStackTrace(arg);
892
- 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);
893
853
  break;
894
854
  }
895
855
 
896
856
  case 'verify': {
897
- requireArg(arg, 'Usage: ucn . verify <name>');
898
- const verifyResult = index.verify(arg, { file: flags.file });
899
- 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);
900
860
  break;
901
861
  }
902
862
 
903
863
  case 'related': {
904
- requireArg(arg, 'Usage: ucn . related <name>');
905
- const relatedResult = index.related(arg, { file: flags.file, top: flags.top, all: flags.all });
906
- printOutput(relatedResult, output.formatRelatedJson, r => output.formatRelated(r, { showAll: flags.all, top: flags.top }));
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 }));
907
867
  break;
908
868
  }
909
869
 
870
+ // ── Commands staying in adapter (complex I/O) ───────────────────
871
+
910
872
  case 'fn': {
911
873
  requireArg(arg, 'Usage: ucn . fn <name>');
912
- // Support comma-separated names for bulk extraction
913
874
  if (arg.includes(',')) {
914
875
  const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
915
876
  for (let i = 0; i < fnNames.length; i++) {
@@ -928,32 +889,73 @@ function runProjectCommand(rootDir, command, arg) {
928
889
  break;
929
890
  }
930
891
 
931
- case 'imports':
932
- case 'what-imports': {
933
- requireArg(arg, 'Usage: ucn . imports <file>');
934
- const imports = index.imports(arg);
935
- 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,
936
913
  r => output.formatImportsJson(r, arg),
937
914
  r => output.formatImports(r, arg)
938
915
  );
939
916
  break;
940
917
  }
941
918
 
942
- case 'exporters':
943
- case 'who-imports': {
944
- requireArg(arg, 'Usage: ucn . exporters <file>');
945
- const exporters = index.exporters(arg);
946
- 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,
947
923
  r => output.formatExportersJson(r, arg),
948
924
  r => output.formatExporters(r, arg)
949
925
  );
950
926
  break;
951
927
  }
952
928
 
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)
935
+ );
936
+ break;
937
+ }
938
+
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 })
949
+ );
950
+ break;
951
+ }
952
+
953
+ // ── Remaining commands ──────────────────────────────────────────
954
+
953
955
  case 'typedef': {
954
- requireArg(arg, 'Usage: ucn . typedef <name>');
955
- const typedefs = index.typedef(arg, { exact: flags.exact });
956
- printOutput(typedefs,
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,
957
959
  r => output.formatTypedefJson(r, arg),
958
960
  r => output.formatTypedef(r, arg)
959
961
  );
@@ -961,9 +963,9 @@ function runProjectCommand(rootDir, command, arg) {
961
963
  }
962
964
 
963
965
  case 'tests': {
964
- requireArg(arg, 'Usage: ucn . tests <name>');
965
- const tests = index.tests(arg, { callsOnly: flags.callsOnly });
966
- printOutput(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,
967
969
  r => output.formatTestsJson(r, arg),
968
970
  r => output.formatTests(r, arg)
969
971
  );
@@ -971,110 +973,65 @@ function runProjectCommand(rootDir, command, arg) {
971
973
  }
972
974
 
973
975
  case 'api': {
974
- const api = index.api(arg); // arg is optional file path
975
- printOutput(api,
976
+ const { ok, result, error } = execute(index, 'api', { file: arg });
977
+ if (!ok) { console.error(error); process.exit(1); }
978
+ printOutput(result,
976
979
  r => output.formatApiJson(r, arg),
977
980
  r => output.formatApi(r, arg)
978
981
  );
979
982
  break;
980
983
  }
981
984
 
982
- case 'file-exports':
983
- case 'what-exports': {
984
- requireArg(arg, 'Usage: ucn . file-exports <file>');
985
- const fileExports = index.fileExports(arg);
986
- printOutput(fileExports,
987
- r => JSON.stringify({ file: arg, exports: r }, null, 2),
988
- r => output.formatFileExports(r, arg)
985
+ case 'search': {
986
+ const { ok, result, error } = execute(index, 'search', { term: arg, ...flags });
987
+ if (!ok) { console.error(error); process.exit(1); }
988
+ printOutput(result,
989
+ r => output.formatSearchJson(r, arg),
990
+ r => output.formatSearch(r, arg)
989
991
  );
990
992
  break;
991
993
  }
992
994
 
993
995
  case 'deadcode': {
994
- const deadcodeResults = index.deadcode({
995
- includeExported: flags.includeExported,
996
- includeDecorated: flags.includeDecorated,
997
- includeTests: flags.includeTests,
998
- exclude: flags.exclude,
999
- in: flags.in || subdirScope
1000
- });
1001
- printOutput(deadcodeResults,
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,
1002
999
  output.formatDeadcodeJson,
1003
1000
  r => output.formatDeadcode(r, {
1004
1001
  top: flags.top,
1005
- decoratedHint: !flags.includeDecorated && deadcodeResults.excludedDecorated > 0 ? `${deadcodeResults.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use --include-decorated to include them.` : undefined,
1006
- exportedHint: !flags.includeExported && deadcodeResults.excludedExported > 0 ? `${deadcodeResults.excludedExported} exported symbol(s) excluded (all have callers). Use --include-exported to audit them.` : undefined
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
1007
1004
  })
1008
1005
  );
1009
1006
  break;
1010
1007
  }
1011
1008
 
1012
- case 'graph': {
1013
- requireArg(arg, 'Usage: ucn . graph <file>');
1014
- const graphDepth = flags.depth ?? 2; // Default to 2 for cleaner output
1015
- const graphDirection = flags.direction || 'both';
1016
- const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
1017
- printOutput(graphResult,
1018
- r => JSON.stringify({
1019
- root: path.relative(index.root, r.root),
1020
- nodes: r.nodes.map(n => ({ file: n.relativePath, depth: n.depth })),
1021
- edges: r.edges.map(e => ({ from: path.relative(index.root, e.from), to: path.relative(index.root, e.to) }))
1022
- }, null, 2),
1023
- r => output.formatGraph(r, { showAll: flags.all || flags.depth !== undefined, maxDepth: graphDepth, file: arg })
1024
- );
1025
- break;
1026
- }
1027
-
1028
- case 'search': {
1029
- requireArg(arg, 'Usage: ucn . search <term>');
1030
- const searchExclude = flags.includeTests ? flags.exclude : addTestExclusions(flags.exclude);
1031
- const searchResults = index.search(arg, { codeOnly: flags.codeOnly, context: flags.context, caseSensitive: flags.caseSensitive, exclude: searchExclude, in: flags.in, regex: flags.regex });
1032
- printOutput(searchResults,
1033
- r => output.formatSearchJson(r, arg),
1034
- r => output.formatSearch(r, arg)
1035
- );
1036
- break;
1037
- }
1038
-
1039
- case 'lines': {
1040
- if (!arg || !flags.file) {
1041
- console.error('Usage: ucn . lines <range> --file <path>');
1042
- process.exit(1);
1043
- }
1044
- const filePath = index.findFile(flags.file);
1045
- if (!filePath) {
1046
- console.error(`File not found: ${flags.file}`);
1047
- process.exit(1);
1048
- }
1049
- const fileContent = fs.readFileSync(filePath, 'utf-8');
1050
- printLines(fileContent.split('\n'), arg);
1051
- break;
1052
- }
1053
-
1054
1009
  case 'stats': {
1055
- const stats = index.getStats({ functions: flags.functions });
1056
- 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,
1057
1013
  output.formatStatsJson,
1058
1014
  r => output.formatStats(r, { top: flags.top })
1059
1015
  );
1060
1016
  break;
1061
1017
  }
1062
1018
 
1063
- case 'diff-impact': {
1064
- const diffResult = index.diffImpact({
1065
- base: flags.base || 'HEAD',
1066
- staged: flags.staged,
1067
- file: flags.file
1068
- });
1069
- 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);
1070
1023
  break;
1071
1024
  }
1072
1025
 
1073
1026
  default:
1074
- console.error(`Unknown command: ${command}`);
1027
+ console.error(`Unknown command: ${canonical}`);
1075
1028
  printUsage();
1076
1029
  process.exit(1);
1077
1030
  }
1031
+ } catch (e) {
1032
+ console.error(`Error: ${e.message}`);
1033
+ process.exit(1);
1034
+ }
1078
1035
  }
1079
1036
 
1080
1037
  function extractFunctionFromProject(index, name, overrideFlags) {
@@ -1745,6 +1702,8 @@ UNDERSTAND CODE (UCN's strength - semantic analysis)
1745
1702
  smart <name> Function + all dependencies inline
1746
1703
  impact <name> What breaks if changed (call sites grouped by file)
1747
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
1748
1707
 
1749
1708
  ═══════════════════════════════════════════════════════════════════════════════
1750
1709
  FIND CODE
@@ -1778,7 +1737,6 @@ REFACTORING HELPERS
1778
1737
  verify <name> Check all call sites match signature
1779
1738
  diff-impact What changed in git diff and who calls it (--base, --staged)
1780
1739
  deadcode Find unused functions/classes
1781
- related <name> Find similar functions (same file, shared deps)
1782
1740
 
1783
1741
  ═══════════════════════════════════════════════════════════════════════════════
1784
1742
  OTHER
@@ -1787,7 +1745,6 @@ OTHER
1787
1745
  typedef <name> Find type definitions
1788
1746
  stats Project statistics (--functions for per-function line counts)
1789
1747
  stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
1790
- example <name> Best usage example with context
1791
1748
 
1792
1749
  Common Flags:
1793
1750
  --file <pattern> Filter by file path (e.g., --file=routes)
@@ -1812,6 +1769,8 @@ Common Flags:
1812
1769
  --calls-only Only show call/test-case matches (tests)
1813
1770
  --case-sensitive Case-sensitive text search (search)
1814
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)
1815
1774
  --no-cache Disable caching
1816
1775
  --clear-cache Clear cache before running
1817
1776
  --base=<ref> Git ref for diff-impact (default: HEAD)
@@ -1838,6 +1797,7 @@ function runInteractive(rootDir) {
1838
1797
  console.log('Building index...');
1839
1798
  const index = new ProjectIndex(rootDir);
1840
1799
  index.build(null, { quiet: true });
1800
+ const iExpandCache = new ExpandCache({ maxSize: 20 });
1841
1801
  console.log(`Index ready: ${index.files.size} files, ${index.symbols.size} symbols`);
1842
1802
  console.log('Type commands (e.g., "find parseFile", "about main", "toc")');
1843
1803
  console.log('Type "help" for commands, "quit" to exit\n');
@@ -1920,7 +1880,8 @@ Flags can be added per-command: context myFunc --include-methods
1920
1880
  const iflags = parseInteractiveFlags(flagTokens);
1921
1881
 
1922
1882
  try {
1923
- executeInteractiveCommand(index, command, arg, iflags);
1883
+ const iCanonical = resolveCommand(command, 'cli') || command;
1884
+ executeInteractiveCommand(index, iCanonical, arg, iflags, iExpandCache);
1924
1885
  } catch (e) {
1925
1886
  console.error(`Error: ${e.message}`);
1926
1887
  }
@@ -1972,347 +1933,290 @@ function parseInteractiveFlags(tokens) {
1972
1933
  };
1973
1934
  }
1974
1935
 
1975
- function executeInteractiveCommand(index, command, arg, iflags = {}) {
1936
+ function executeInteractiveCommand(index, command, arg, iflags = {}, cache = null) {
1976
1937
  switch (command) {
1977
- case 'toc': {
1978
- const toc = index.getToc({ detailed: iflags.detailed, topLevel: iflags.topLevel, all: iflags.all, top: iflags.top });
1979
- console.log(output.formatToc(toc, {
1980
- detailedHint: 'Add --detailed to list all functions, or "about <name>" for full details on a symbol',
1981
- uncertainHint: 'use --include-uncertain to include all'
1982
- }));
1983
- break;
1984
- }
1985
1938
 
1986
- case 'find': {
1939
+ // ── Special commands (complex I/O, stay in adapter) ──────────────
1940
+
1941
+ case 'fn': {
1987
1942
  if (!arg) {
1988
- console.log('Usage: find <name>');
1943
+ console.log('Usage: fn <name>[,name2,...] [--file=<pattern>]');
1989
1944
  return;
1990
1945
  }
1991
- const findExclude = iflags.includeTests ? iflags.exclude : addTestExclusions(iflags.exclude);
1992
- const found = index.find(arg, { file: iflags.file, exact: iflags.exact, exclude: findExclude, in: iflags.in });
1993
- if (found.length === 0) {
1994
- 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
+ }
1995
1953
  } else {
1996
- printSymbols(found, arg, { top: iflags.top });
1954
+ extractFunctionFromProject(index, arg, iflags);
1997
1955
  }
1998
1956
  break;
1999
1957
  }
2000
1958
 
2001
- case 'about': {
1959
+ case 'class': {
2002
1960
  if (!arg) {
2003
- console.log('Usage: about <name>');
1961
+ console.log('Usage: class <name> [--file=<pattern>]');
2004
1962
  return;
2005
1963
  }
2006
- const aboutResult = index.about(arg, { withTypes: iflags.withTypes, file: iflags.file, all: iflags.all, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain, exclude: iflags.exclude, maxCallers: iflags.top || undefined, maxCallees: iflags.top || undefined });
2007
- console.log(output.formatAbout(aboutResult, { expand: iflags.expand, root: index.root, showAll: iflags.all }));
1964
+ extractClassFromProject(index, arg, iflags);
2008
1965
  break;
2009
1966
  }
2010
1967
 
2011
- case 'usages': {
2012
- if (!arg) {
2013
- console.log('Usage: usages <name>');
1968
+ case 'lines': {
1969
+ if (!arg || !iflags.file) {
1970
+ console.log('Usage: lines <range> --file=<file>');
2014
1971
  return;
2015
1972
  }
2016
- const usagesExclude = iflags.includeTests ? iflags.exclude : addTestExclusions(iflags.exclude);
2017
- const usages = index.usages(arg, { codeOnly: iflags.codeOnly, context: iflags.context, exclude: usagesExclude, in: iflags.in });
2018
- console.log(output.formatUsages(usages, arg));
1973
+ const filePath = index.findFile(iflags.file);
1974
+ if (!filePath) {
1975
+ console.log(`File not found: ${iflags.file}`);
1976
+ return;
1977
+ }
1978
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
1979
+ printLines(fileContent.split('\n'), arg);
2019
1980
  break;
2020
1981
  }
2021
1982
 
2022
- case 'context': {
1983
+ case 'expand': {
2023
1984
  if (!arg) {
2024
- console.log('Usage: context <name>');
1985
+ console.log('Usage: expand <number>');
2025
1986
  return;
2026
1987
  }
2027
- const ctx = index.context(arg, { includeUncertain: iflags.includeUncertain, includeMethods: iflags.includeMethods, file: iflags.file, exclude: iflags.exclude });
2028
- if (!ctx) {
2029
- console.log(`Symbol "${arg}" not found.`);
1988
+ const expandNum = parseInt(arg, 10);
1989
+ if (isNaN(expandNum)) {
1990
+ console.log(`Invalid item number: "${arg}"`);
1991
+ return;
1992
+ }
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);
2030
2006
  } else {
2031
- const { text, expandable } = output.formatContext(ctx, {
2032
- methodsHint: 'Note: obj.method() calls excluded — use --include-methods to include them',
2033
- expandHint: 'Use "expand <N>" to see code for item N',
2034
- uncertainHint: 'use --include-uncertain to include all'
2035
- });
2036
- console.log(text);
2037
- 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);
2038
2019
  }
2039
2020
  break;
2040
2021
  }
2041
2022
 
2042
- case 'smart': {
2043
- if (!arg) {
2044
- console.log('Usage: smart <name>');
2045
- return;
2046
- }
2047
- const smart = index.smart(arg, { file: iflags.file, withTypes: iflags.withTypes, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain });
2048
- if (smart) {
2049
- console.log(output.formatSmart(smart, {
2050
- uncertainHint: 'use --include-uncertain to include all'
2051
- }));
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}"`);
2052
2030
  } else {
2053
- console.log(`Function "${arg}" not found`);
2031
+ printSymbols(result, arg, { top: iflags.top });
2054
2032
  }
2055
2033
  break;
2056
2034
  }
2057
2035
 
2058
- case 'impact': {
2059
- if (!arg) {
2060
- console.log('Usage: impact <name>');
2061
- 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);
2062
2051
  }
2063
- const impactResult = index.impact(arg, { file: iflags.file, exclude: iflags.exclude });
2064
- console.log(output.formatImpact(impactResult));
2065
2052
  break;
2066
2053
  }
2067
2054
 
2068
- case 'trace': {
2069
- if (!arg) {
2070
- console.log('Usage: trace <name>');
2071
- return;
2072
- }
2073
- const traceDepth = iflags.depth ? parseInt(iflags.depth) : 3;
2074
- const traceResult = index.trace(arg, { depth: traceDepth, file: iflags.file, all: iflags.all || !!iflags.depth, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain });
2075
- 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
+ }));
2076
2065
  break;
2077
2066
  }
2078
2067
 
2079
- case 'fn': {
2080
- if (!arg) {
2081
- console.log('Usage: fn <name>[,name2,...] [--file=<pattern>]');
2082
- return;
2083
- }
2084
- // Support comma-separated names for bulk extraction
2085
- if (arg.includes(',')) {
2086
- const fnNames = arg.split(',').map(n => n.trim()).filter(Boolean);
2087
- for (let i = 0; i < fnNames.length; i++) {
2088
- if (i > 0) console.log('\n' + '═'.repeat(60) + '\n');
2089
- extractFunctionFromProject(index, fnNames[i], iflags);
2090
- }
2091
- } else {
2092
- extractFunctionFromProject(index, arg, iflags);
2093
- }
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
+ }));
2094
2077
  break;
2095
2078
  }
2096
2079
 
2097
- case 'class': {
2098
- if (!arg) {
2099
- console.log('Usage: class <name> [--file=<pattern>]');
2100
- return;
2101
- }
2102
- 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 }));
2103
2084
  break;
2104
2085
  }
2105
2086
 
2106
- case 'lines': {
2107
- if (!arg || !iflags.file) {
2108
- console.log('Usage: lines <range> --file=<file>');
2109
- return;
2110
- }
2111
- const filePath = index.findFile(iflags.file);
2112
- if (!filePath) {
2113
- console.log(`File not found: ${iflags.file}`);
2114
- return;
2115
- }
2116
- const fileContent = fs.readFileSync(filePath, 'utf-8');
2117
- 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));
2118
2114
  break;
2119
2115
  }
2120
2116
 
2121
2117
  case 'graph': {
2122
- if (!arg) {
2123
- console.log('Usage: graph <file> [--direction=imports|importers|both] [--depth=N]');
2124
- return;
2125
- }
2118
+ const { ok, result, error } = execute(index, 'graph', { file: arg, ...iflags });
2119
+ if (!ok) { console.log(error); return; }
2126
2120
  const graphDepth = iflags.depth ? parseInt(iflags.depth) : 2;
2127
- const graphDirection = iflags.direction || 'both';
2128
- const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
2129
- console.log(output.formatGraph(graphResult, { showAll: iflags.all || !!iflags.depth, maxDepth: graphDepth, file: arg }));
2121
+ console.log(output.formatGraph(result, { showAll: iflags.all || !!iflags.depth, maxDepth: graphDepth, file: arg }));
2130
2122
  break;
2131
2123
  }
2132
2124
 
2133
- case 'file-exports':
2134
- case 'what-exports': {
2135
- if (!arg) {
2136
- console.log('Usage: file-exports <file>');
2137
- return;
2138
- }
2139
- const fileExports = index.fileExports(arg);
2140
- 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));
2141
2129
  break;
2142
2130
  }
2143
2131
 
2144
- case 'imports':
2145
- case 'what-imports': {
2146
- if (!arg) {
2147
- console.log('Usage: imports <file>');
2148
- return;
2149
- }
2150
- const imports = index.imports(arg);
2151
- 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));
2152
2136
  break;
2153
2137
  }
2154
2138
 
2155
- case 'exporters':
2156
- case 'who-imports': {
2157
- if (!arg) {
2158
- console.log('Usage: exporters <file>');
2159
- return;
2160
- }
2161
- const exporters = index.exporters(arg);
2162
- 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));
2163
2143
  break;
2164
2144
  }
2165
2145
 
2166
2146
  case 'tests': {
2167
- if (!arg) {
2168
- console.log('Usage: tests <name>');
2169
- return;
2170
- }
2171
- const tests = index.tests(arg, { callsOnly: iflags.callsOnly });
2172
- 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));
2173
2150
  break;
2174
2151
  }
2175
2152
 
2176
2153
  case 'search': {
2177
- if (!arg) {
2178
- console.log('Usage: search <term>');
2179
- return;
2180
- }
2181
- const searchExclude = iflags.includeTests ? iflags.exclude : addTestExclusions(iflags.exclude);
2182
- const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context, exclude: searchExclude, in: iflags.in, regex: iflags.regex });
2183
- 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));
2184
2157
  break;
2185
2158
  }
2186
2159
 
2187
2160
  case 'typedef': {
2188
- if (!arg) {
2189
- console.log('Usage: typedef <name>');
2190
- return;
2191
- }
2192
- const types = index.typedef(arg, { exact: iflags.exact });
2193
- 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));
2194
2164
  break;
2195
2165
  }
2196
2166
 
2197
2167
  case 'api': {
2198
- const api = index.api(arg);
2199
- console.log(output.formatApi(api, arg || '.'));
2168
+ const { ok, result, error } = execute(index, 'api', { file: arg });
2169
+ if (!ok) { console.log(error); return; }
2170
+ console.log(output.formatApi(result, arg || '.'));
2200
2171
  break;
2201
2172
  }
2202
2173
 
2203
- case 'diff-impact': {
2204
- const diffResult = index.diffImpact({
2205
- base: iflags.base || 'HEAD',
2206
- staged: iflags.staged,
2207
- file: iflags.file
2208
- });
2209
- 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));
2210
2178
  break;
2211
2179
  }
2212
2180
 
2213
2181
  case 'stats': {
2214
- const stats = index.getStats({ functions: iflags.functions });
2215
- console.log(output.formatStats(stats, { top: iflags.top }));
2216
- break;
2217
- }
2218
-
2219
- case 'expand': {
2220
- if (!arg) {
2221
- console.log('Usage: expand <number>');
2222
- return;
2223
- }
2224
- const expandNum = parseInt(arg, 10);
2225
- if (isNaN(expandNum)) {
2226
- console.log(`Invalid item number: "${arg}"`);
2227
- return;
2228
- }
2229
- const cached = loadExpandableItems(index.root);
2230
- if (!cached || !cached.items || cached.items.length === 0) {
2231
- console.log('No expandable items. Run context first.');
2232
- return;
2233
- }
2234
- const expandMatch = cached.items.find(i => i.num === expandNum);
2235
- if (!expandMatch) {
2236
- console.log(`Item ${expandNum} not found. Available: 1-${cached.items.length}`);
2237
- return;
2238
- }
2239
- printExpandedItem(expandMatch, cached.root || index.root);
2240
- break;
2241
- }
2242
-
2243
- case 'deadcode': {
2244
- const deadResult = index.deadcode({
2245
- includeExported: iflags.includeExported,
2246
- includeDecorated: iflags.includeDecorated,
2247
- includeTests: iflags.includeTests,
2248
- exclude: iflags.exclude,
2249
- in: iflags.in
2250
- });
2251
- console.log(output.formatDeadcode(deadResult, {
2252
- top: iflags.top,
2253
- decoratedHint: !iflags.includeDecorated && deadResult.excludedDecorated > 0 ? `${deadResult.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use --include-decorated to include them.` : undefined,
2254
- exportedHint: !iflags.includeExported && deadResult.excludedExported > 0 ? `${deadResult.excludedExported} exported symbol(s) excluded (all have callers). Use --include-exported to audit them.` : undefined
2255
- }));
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 }));
2256
2185
  break;
2257
2186
  }
2258
2187
 
2259
2188
  case 'related': {
2260
- if (!arg) {
2261
- console.log('Usage: related <name>');
2262
- return;
2263
- }
2264
- const relResult = index.related(arg, { file: iflags.file, top: iflags.top, all: iflags.all });
2265
- console.log(output.formatRelated(relResult, { showAll: iflags.all, top: iflags.top }));
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 }));
2266
2192
  break;
2267
2193
  }
2268
2194
 
2269
2195
  case 'example': {
2270
- if (!arg) {
2271
- console.log('Usage: example <name>');
2272
- return;
2273
- }
2274
- 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));
2275
2199
  break;
2276
2200
  }
2277
2201
 
2278
2202
  case 'plan': {
2279
- if (!arg) {
2280
- console.log('Usage: plan <name> --add-param=x | --remove-param=x | --rename-to=x');
2281
- return;
2282
- }
2283
- if (!iflags.addParam && !iflags.removeParam && !iflags.renameTo) {
2284
- console.log('Plan requires an operation: --add-param, --remove-param, or --rename-to');
2285
- return;
2286
- }
2287
- const planResult = index.plan(arg, {
2288
- addParam: iflags.addParam,
2289
- removeParam: iflags.removeParam,
2290
- renameTo: iflags.renameTo,
2291
- defaultValue: iflags.defaultValue,
2292
- file: iflags.file
2293
- });
2294
- 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));
2295
2206
  break;
2296
2207
  }
2297
2208
 
2298
2209
  case 'verify': {
2299
- if (!arg) {
2300
- console.log('Usage: verify <name>');
2301
- return;
2302
- }
2303
- const verifyResult = index.verify(arg, { file: iflags.file });
2304
- 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));
2305
2213
  break;
2306
2214
  }
2307
2215
 
2308
- case 'stacktrace':
2309
- case 'stack': {
2310
- if (!arg) {
2311
- console.log('Usage: stacktrace <stack text>');
2312
- return;
2313
- }
2314
- const stackResult = index.parseStackTrace(arg);
2315
- 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));
2316
2220
  break;
2317
2221
  }
2318
2222