ucn 3.8.22 → 3.8.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.claude/skills/ucn/SKILL.md +114 -11
  2. package/README.md +152 -156
  3. package/cli/index.js +363 -37
  4. package/core/analysis.js +960 -37
  5. package/core/bridge.js +1111 -0
  6. package/core/brief.js +408 -0
  7. package/core/cache.js +213 -59
  8. package/core/callers.js +117 -41
  9. package/core/check.js +200 -0
  10. package/core/deadcode.js +31 -2
  11. package/core/discovery.js +57 -34
  12. package/core/entrypoints.js +638 -4
  13. package/core/execute.js +304 -5
  14. package/core/git-enrich.js +130 -0
  15. package/core/graph-build.js +4 -4
  16. package/core/graph.js +31 -12
  17. package/core/output/analysis.js +157 -25
  18. package/core/output/brief.js +100 -0
  19. package/core/output/check.js +79 -0
  20. package/core/output/doctor.js +85 -0
  21. package/core/output/endpoints.js +239 -0
  22. package/core/output/extraction.js +2 -0
  23. package/core/output/find.js +126 -39
  24. package/core/output/graph.js +48 -15
  25. package/core/output/refactoring.js +103 -5
  26. package/core/output/reporting.js +63 -23
  27. package/core/output/search.js +110 -17
  28. package/core/output/shared.js +56 -2
  29. package/core/output.js +4 -0
  30. package/core/parallel-build.js +10 -7
  31. package/core/parser.js +8 -2
  32. package/core/project.js +147 -41
  33. package/core/registry.js +30 -14
  34. package/core/reporting.js +465 -2
  35. package/core/search.js +139 -15
  36. package/core/shared.js +101 -5
  37. package/core/tracing.js +31 -12
  38. package/core/verify.js +982 -95
  39. package/languages/go.js +91 -6
  40. package/languages/html.js +10 -0
  41. package/languages/java.js +151 -35
  42. package/languages/javascript.js +290 -33
  43. package/languages/python.js +78 -11
  44. package/languages/rust.js +267 -12
  45. package/languages/utils.js +315 -3
  46. package/mcp/server.js +91 -16
  47. package/package.json +10 -2
package/core/project.js CHANGED
@@ -77,6 +77,7 @@ class ProjectIndex {
77
77
  this._opContentCache = new Map();
78
78
  this._opUsagesCache = new Map();
79
79
  this._opCallsCountCache = new Map();
80
+ this._opEnclosingFnCache = new Map();
80
81
  this._opDepth = 0;
81
82
  }
82
83
  this._opDepth++;
@@ -84,10 +85,17 @@ class ProjectIndex {
84
85
 
85
86
  /** End a per-operation content cache scope (only clears when outermost scope ends) */
86
87
  _endOp() {
88
+ if (!this._opContentCache) return; // Mismatched call — no active operation
87
89
  if (--this._opDepth <= 0) {
88
90
  this._opContentCache = null;
89
91
  this._opUsagesCache = null;
90
92
  this._opCallsCountCache = null;
93
+ this._opEnclosingFnCache = null;
94
+ // Free cached file content from callsCache entries (retained during
95
+ // operation for _readFile caching, not needed between operations)
96
+ for (const entry of this.callsCache.values()) {
97
+ if (entry.content !== undefined) entry.content = undefined;
98
+ }
91
99
  this._opDepth = 0;
92
100
  }
93
101
  }
@@ -215,6 +223,9 @@ class ProjectIndex {
215
223
  // Always invalidate caches on rebuild
216
224
  this._completenessCache = null;
217
225
  this._attrTypeCache = null;
226
+ // Endpoints cache (server routes / client requests / bridges) becomes
227
+ // stale when files change; clear on every rebuild.
228
+ this._endpointsCache = null;
218
229
 
219
230
  let indexed = 0;
220
231
  let changed = 0;
@@ -395,6 +406,9 @@ class ProjectIndex {
395
406
  params: item.params,
396
407
  paramsStructured: item.paramsStructured,
397
408
  returnType: item.returnType,
409
+ ...(item.paramTypes && { paramTypes: item.paramTypes }),
410
+ ...(item.isAsync && { isAsync: true }),
411
+ ...(item.isGenerator && { isGenerator: true }),
398
412
  modifiers: item.modifiers,
399
413
  docstring: item.docstring,
400
414
  bindingId: `${fileEntry.relativePath}:${type}:${item.startLine}`,
@@ -409,6 +423,13 @@ class ProjectIndex {
409
423
  ...(item.memberType && { memberType: item.memberType }),
410
424
  ...(item.fieldType && { fieldType: item.fieldType }),
411
425
  ...(item.decorators && item.decorators.length > 0 && { decorators: item.decorators }),
426
+ // Decorator/annotation/attribute argument capture for endpoints command:
427
+ // these fields hold the parsed first-string-arg of each route-style annotation.
428
+ // Only populated when at least one entry has a string-literal arg, keeping memory
429
+ // overhead minimal for non-route code.
430
+ ...(item.decoratorsWithArgs && item.decoratorsWithArgs.length > 0 && { decoratorsWithArgs: item.decoratorsWithArgs }),
431
+ ...(item.annotationsWithArgs && item.annotationsWithArgs.length > 0 && { annotationsWithArgs: item.annotationsWithArgs }),
432
+ ...(item.attributesWithArgs && item.attributesWithArgs.length > 0 && { attributesWithArgs: item.attributesWithArgs }),
412
433
  ...(item.nameLine && { nameLine: item.nameLine }),
413
434
  ...(item.traitImpl && { traitImpl: true }),
414
435
  ...(item.isSignature && { isSignature: true })
@@ -474,14 +495,20 @@ class ProjectIndex {
474
495
  }
475
496
  }
476
497
 
477
- // Invalidate cached call data for this file
498
+ // Incrementally update callee index before deleting cached calls
499
+ const oldCached = this.callsCache.get(filePath);
500
+ if (oldCached) {
501
+ this._removeFromCalleeIndex(filePath, oldCached.calls);
502
+ }
478
503
  this.callsCache.delete(filePath);
479
504
 
480
- // Invalidate callee index (will be rebuilt lazily)
481
- this.calleeIndex = null;
482
-
483
505
  // Invalidate attribute type cache for this file
484
506
  if (this._attrTypeCache) this._attrTypeCache.delete(filePath);
507
+
508
+ // Invalidate lazy Java file index (will be rebuilt on next use)
509
+ this._javaFileIndex = null;
510
+ // Endpoints cache is project-wide; safest to clear on any file removal.
511
+ this._endpointsCache = null;
485
512
  }
486
513
 
487
514
  /**
@@ -490,6 +517,61 @@ class ProjectIndex {
490
517
  */
491
518
  _buildDirIndex() { graphBuildModule.buildDirIndex(this); }
492
519
 
520
+ /**
521
+ * Add a file's calls to the callee index (name → Set<filePath>).
522
+ * Used by buildCalleeIndex (full build) and getCachedCalls (incremental update).
523
+ */
524
+ _addToCalleeIndex(filePath, calls) {
525
+ if (!this.calleeIndex || !calls) return;
526
+ for (const call of calls) {
527
+ const name = call.name;
528
+ if (!this.calleeIndex.has(name)) {
529
+ this.calleeIndex.set(name, new Set());
530
+ }
531
+ this.calleeIndex.get(name).add(filePath);
532
+ if (call.resolvedName && call.resolvedName !== name) {
533
+ if (!this.calleeIndex.has(call.resolvedName)) {
534
+ this.calleeIndex.set(call.resolvedName, new Set());
535
+ }
536
+ this.calleeIndex.get(call.resolvedName).add(filePath);
537
+ }
538
+ if (call.resolvedNames) {
539
+ for (const rn of call.resolvedNames) {
540
+ if (rn !== name) {
541
+ if (!this.calleeIndex.has(rn)) {
542
+ this.calleeIndex.set(rn, new Set());
543
+ }
544
+ this.calleeIndex.get(rn).add(filePath);
545
+ }
546
+ }
547
+ }
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Remove a file's calls from the callee index.
553
+ * Used by removeFileSymbols for incremental updates instead of full invalidation.
554
+ */
555
+ _removeFromCalleeIndex(filePath, calls) {
556
+ if (!this.calleeIndex || !calls) return;
557
+ for (const call of calls) {
558
+ const removeName = (n) => {
559
+ const fileSet = this.calleeIndex.get(n);
560
+ if (fileSet) {
561
+ fileSet.delete(filePath);
562
+ if (fileSet.size === 0) this.calleeIndex.delete(n);
563
+ }
564
+ };
565
+ removeName(call.name);
566
+ if (call.resolvedName && call.resolvedName !== call.name) removeName(call.resolvedName);
567
+ if (call.resolvedNames) {
568
+ for (const rn of call.resolvedNames) {
569
+ if (rn !== call.name) removeName(rn);
570
+ }
571
+ }
572
+ }
573
+ }
574
+
493
575
  /**
494
576
  * Build inverted call index: callee name -> Set<filePath>.
495
577
  * Built lazily on first findCallers call, from the calls cache.
@@ -497,37 +579,15 @@ class ProjectIndex {
497
579
  */
498
580
  buildCalleeIndex() {
499
581
  const { getCachedCalls } = require('./callers');
582
+ const { ensureCallsCacheLoaded } = require('./cache');
583
+ ensureCallsCacheLoaded(this);
500
584
  this.calleeIndex = new Map();
501
585
 
502
586
  for (const [filePath] of this.files) {
503
587
  // Fast path: use pre-populated callsCache (avoids stat per file)
504
588
  const cached = this.callsCache.get(filePath);
505
589
  const calls = cached ? cached.calls : getCachedCalls(this, filePath);
506
- if (!calls) continue;
507
- for (const call of calls) {
508
- const name = call.name;
509
- if (!this.calleeIndex.has(name)) {
510
- this.calleeIndex.set(name, new Set());
511
- }
512
- this.calleeIndex.get(name).add(filePath);
513
- // Also index resolvedName and resolvedNames for alias resolution
514
- if (call.resolvedName && call.resolvedName !== name) {
515
- if (!this.calleeIndex.has(call.resolvedName)) {
516
- this.calleeIndex.set(call.resolvedName, new Set());
517
- }
518
- this.calleeIndex.get(call.resolvedName).add(filePath);
519
- }
520
- if (call.resolvedNames) {
521
- for (const rn of call.resolvedNames) {
522
- if (rn !== name) {
523
- if (!this.calleeIndex.has(rn)) {
524
- this.calleeIndex.set(rn, new Set());
525
- }
526
- this.calleeIndex.get(rn).add(filePath);
527
- }
528
- }
529
- }
530
- }
590
+ this._addToCalleeIndex(filePath, calls);
531
591
  }
532
592
  }
533
593
 
@@ -548,6 +608,20 @@ class ProjectIndex {
548
608
  * Progressively strips trailing segments to find the class file.
549
609
  */
550
610
  _resolveJavaPackageImport(importModule, javaFileIndex) {
611
+ if (!javaFileIndex) {
612
+ // Lazy-build index to avoid O(N) fallback scan of all files
613
+ if (!this._javaFileIndex) {
614
+ this._javaFileIndex = new Map();
615
+ for (const [fp, fe] of this.files) {
616
+ if (fe.language === 'java') {
617
+ const name = path.basename(fp, '.java');
618
+ if (!this._javaFileIndex.has(name)) this._javaFileIndex.set(name, []);
619
+ this._javaFileIndex.get(name).push(fp);
620
+ }
621
+ }
622
+ }
623
+ javaFileIndex = this._javaFileIndex;
624
+ }
551
625
  return graphBuildModule._resolveJavaPackageImport(this, importModule, javaFileIndex);
552
626
  }
553
627
 
@@ -582,7 +656,7 @@ class ProjectIndex {
582
656
  if (contextFile) {
583
657
  const imports = this.importGraph.get(contextFile);
584
658
  if (imports) {
585
- const imported = entries.find(e => imports.includes(e.file));
659
+ const imported = entries.find(e => imports.has(e.file));
586
660
  if (imported) return imported.parents;
587
661
  }
588
662
  }
@@ -614,7 +688,7 @@ class ProjectIndex {
614
688
  if (contextFile) {
615
689
  const imports = this.importGraph.get(contextFile);
616
690
  if (imports) {
617
- const imported = classSymbols.find(s => imports.includes(s.file));
691
+ const imported = classSymbols.find(s => imports.has(s.file));
618
692
  if (imported) return imported.file;
619
693
  }
620
694
  }
@@ -769,15 +843,27 @@ class ProjectIndex {
769
843
  }
770
844
  }
771
845
 
846
+ // Filter by exact startLine when a handle was supplied. This pins
847
+ // the resolution to one specific definition — no ambiguity allowed.
848
+ if (options.line && Number.isFinite(options.line)) {
849
+ const filtered = definitions.filter(d => d.startLine === options.line);
850
+ if (filtered.length > 0) {
851
+ definitions = filtered;
852
+ }
853
+ }
854
+
772
855
  // Score each definition for selection
773
856
  const typeOrder = new Set(['class', 'struct', 'interface', 'type', 'impl']);
857
+ const { isTestPath } = require('./shared');
774
858
  const scored = definitions.map(d => {
775
859
  let score = 0;
776
860
  const rp = d.relativePath || '';
777
861
  // Prefer class/struct/interface types (+1000)
778
862
  if (typeOrder.has(d.type)) score += 1000;
779
- // Deprioritize test files (-500)
780
- if (isTestFile(rp, detectLanguage(d.file))) {
863
+ // BUG-M4: deprioritize test files (-500). Combine the filename-pattern
864
+ // detector with the path-segment detector so `about` agrees with `find`'s
865
+ // exclusion logic (e.g. `test/agent-benchmark.js` is treated as a test).
866
+ if (isTestFile(rp, detectLanguage(d.file)) || isTestPath(rp)) {
781
867
  score -= 500;
782
868
  }
783
869
  // Deprioritize examples/docs/vendor directories (-300)
@@ -824,7 +910,7 @@ class ProjectIndex {
824
910
  for (const candidate of tiedCandidates) {
825
911
  let importerCount = 0;
826
912
  for (const [, importedFiles] of this.importGraph) {
827
- if (importedFiles.includes(candidate.def.file)) {
913
+ if (importedFiles.has(candidate.def.file)) {
828
914
  importerCount++;
829
915
  }
830
916
  }
@@ -899,8 +985,7 @@ class ProjectIndex {
899
985
  const hasFilters = options.exclude && options.exclude.length > 0;
900
986
 
901
987
  // Pre-compute which files can reference THIS specific definition
902
- const importers = this.exportGraph.get(defFile) || [];
903
- const importersSet = new Set(importers);
988
+ const importersSet = this.exportGraph.get(defFile) || new Set();
904
989
  const defEntry = this.files.get(defFile);
905
990
  const isDirectoryScope = langTraits(defEntry?.language)?.packageScope === 'directory';
906
991
  const defDir = isDirectoryScope ? path.dirname(defFile) : null;
@@ -963,7 +1048,7 @@ class ProjectIndex {
963
1048
 
964
1049
  // Count imports from import graph (files that import from defFile and use this name)
965
1050
  let imports = 0;
966
- for (const importer of importers) {
1051
+ for (const importer of importersSet) {
967
1052
  const fe = this.files.get(importer);
968
1053
  if (!fe) continue;
969
1054
  if (hasFilters && !this.matchesFilters(fe.relativePath, { exclude: options.exclude })) continue;
@@ -1022,7 +1107,7 @@ class ProjectIndex {
1022
1107
 
1023
1108
  while (queue.length > 0) {
1024
1109
  const file = queue.pop();
1025
- const importersArr = this.exportGraph.get(file) || [];
1110
+ const importersArr = this.exportGraph.get(file) || new Set();
1026
1111
  for (const importer of importersArr) {
1027
1112
  if (!relevantFiles.has(importer)) {
1028
1113
  relevantFiles.add(importer);
@@ -1306,7 +1391,10 @@ class ProjectIndex {
1306
1391
  }
1307
1392
  parts.push(def.name);
1308
1393
  if (def.params !== undefined) {
1309
- parts.push(`(${def.params})`);
1394
+ // Prefer typed rendering when paramTypes/paramsStructured carry annotations
1395
+ const { renderTypedParams } = require('./output/shared');
1396
+ const typed = renderTypedParams(def);
1397
+ parts.push(`(${typed != null ? typed : def.params})`);
1310
1398
  }
1311
1399
  if (def.returnType) {
1312
1400
  parts.push(`: ${def.returnType}`);
@@ -1548,6 +1636,15 @@ class ProjectIndex {
1548
1636
  const fileEntry = this.files.get(filePath);
1549
1637
  if (!fileEntry) return null;
1550
1638
 
1639
+ // Per-operation cache: avoid rescanning symbols for same (file, line)
1640
+ const cacheKey = filePath + '\0' + lineNum;
1641
+ if (this._opEnclosingFnCache) {
1642
+ const cached = this._opEnclosingFnCache.get(cacheKey);
1643
+ if (cached !== undefined) {
1644
+ return cached === null ? null : (returnSymbol ? cached : cached.name);
1645
+ }
1646
+ }
1647
+
1551
1648
  let best = null;
1552
1649
  for (const symbol of fileEntry.symbols) {
1553
1650
  if (!NON_CALLABLE_TYPES.has(symbol.type) &&
@@ -1558,8 +1655,11 @@ class ProjectIndex {
1558
1655
  }
1559
1656
  }
1560
1657
  }
1561
- if (!best) return null;
1562
- return returnSymbol ? best : best.name;
1658
+
1659
+ if (this._opEnclosingFnCache) {
1660
+ this._opEnclosingFnCache.set(cacheKey, best);
1661
+ }
1662
+ return best ? (returnSymbol ? best : best.name) : null;
1563
1663
  }
1564
1664
 
1565
1665
  /** Get instance attribute types for a class in a file */
@@ -1868,8 +1968,14 @@ class ProjectIndex {
1868
1968
  /** Analyze a call site using AST for example scoring */
1869
1969
  _analyzeCallSiteAST(filePath, lineNum, funcName) { return verifyModule.analyzeCallSiteAST(this, filePath, lineNum, funcName); }
1870
1970
 
1971
+ /** Analyze a call site's argument shape (used by `example --diverse`) */
1972
+ _analyzeCallShape(filePath, lineNum, funcName) { return verifyModule.analyzeCallShape(this, filePath, lineNum, funcName); }
1973
+
1871
1974
  /** Diff-based impact analysis: find which functions changed and who calls them */
1872
1975
  diffImpact(options) { return analysisModule.diffImpact(this, options); }
1976
+
1977
+ /** Audit async/await: find calls that are likely missing an `await`. */
1978
+ auditAsync(options) { return analysisModule.auditAsync(this, options); }
1873
1979
  }
1874
1980
 
1875
1981
  const { parseDiff } = require('./analysis');
package/core/registry.js CHANGED
@@ -15,17 +15,17 @@
15
15
  // Order: understanding, finding, extracting, file-deps, refactoring, other.
16
16
  const CANONICAL_COMMANDS = [
17
17
  // Understanding code
18
- 'about', 'context', 'impact', 'blast', 'smart', 'trace', 'reverseTrace', 'example', 'related',
18
+ 'about', 'context', 'impact', 'blast', 'smart', 'trace', 'reverseTrace', 'example', 'related', 'brief',
19
19
  // Finding code
20
- 'find', 'usages', 'toc', 'search', 'tests', 'affectedTests', 'deadcode', 'entrypoints',
20
+ 'find', 'usages', 'toc', 'search', 'tests', 'affectedTests', 'deadcode', 'entrypoints', 'endpoints',
21
21
  // Extracting code
22
22
  'fn', 'class', 'lines', 'expand',
23
23
  // File dependencies
24
24
  'imports', 'exporters', 'fileExports', 'graph', 'circularDeps',
25
25
  // Refactoring
26
- 'verify', 'plan', 'diffImpact',
26
+ 'verify', 'plan', 'diffImpact', 'check',
27
27
  // Other
28
- 'typedef', 'stacktrace', 'api', 'stats',
28
+ 'typedef', 'stacktrace', 'api', 'stats', 'doctor', 'auditAsync',
29
29
  ];
30
30
 
31
31
  // ============================================================================
@@ -47,6 +47,9 @@ const CLI_ALIASES = {
47
47
  'circular-deps': 'circularDeps',
48
48
  'circular': 'circularDeps',
49
49
  'cycles': 'circularDeps',
50
+ 'audit-async': 'auditAsync',
51
+ // BUG-3: parity with other multi-word commands (circular-deps, reverse-trace, ...)
52
+ 'entry-points': 'entrypoints',
50
53
  };
51
54
 
52
55
  // MCP uses snake_case for multi-word names.
@@ -56,6 +59,7 @@ const MCP_ALIASES = {
56
59
  'affected_tests': 'affectedTests',
57
60
  'reverse_trace': 'reverseTrace',
58
61
  'circular_deps': 'circularDeps',
62
+ 'audit_async': 'auditAsync',
59
63
  };
60
64
 
61
65
  // ============================================================================
@@ -65,6 +69,7 @@ const MCP_ALIASES = {
65
69
  const PARAM_MAP = {
66
70
  project_dir: 'projectDir',
67
71
  include_tests: 'includeTests',
72
+ exclude_tests: 'excludeTests',
68
73
  include_methods: 'includeMethods',
69
74
  include_uncertain: 'includeUncertain',
70
75
  with_types: 'withTypes',
@@ -74,6 +79,7 @@ const PARAM_MAP = {
74
79
  include_decorated: 'includeDecorated',
75
80
  min_confidence: 'minConfidence',
76
81
  show_confidence: 'showConfidence',
82
+ hide_confidence: 'hideConfidence',
77
83
  calls_only: 'callsOnly',
78
84
  class_name: 'className',
79
85
  max_lines: 'maxLines',
@@ -85,6 +91,10 @@ const PARAM_MAP = {
85
91
  max_files: 'maxFiles',
86
92
  max_chars: 'maxChars',
87
93
  follow_symlinks: 'followSymlinks',
94
+ unreachable_only: 'unreachableOnly',
95
+ server_only: 'serverOnly',
96
+ client_only: 'clientOnly',
97
+ hide_uncertain: 'hideUncertain',
88
98
  };
89
99
 
90
100
  // ============================================================================
@@ -96,24 +106,26 @@ const PARAM_MAP = {
96
106
  // file* = file is the command subject (required), not a filter pattern.
97
107
  const FLAG_APPLICABILITY = {
98
108
  // Understanding code
99
- about: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'includeTests', 'top', 'all', 'withTypes', 'minConfidence', 'showConfidence'],
100
- context: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'minConfidence', 'showConfidence'],
101
- impact: ['name', 'file', 'exclude', 'className', 'top'],
109
+ about: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'includeTests', 'top', 'all', 'withTypes', 'minConfidence', 'showConfidence', 'unreachableOnly', 'compact', 'git'],
110
+ context: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'minConfidence', 'showConfidence', 'unreachableOnly', 'compact'],
111
+ impact: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'top', 'unreachableOnly', 'compact'],
102
112
  blast: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
103
113
  reverseTrace: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
104
114
  smart: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'withTypes', 'minConfidence'],
105
115
  trace: ['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
106
- example: ['name', 'file', 'className'],
116
+ example: ['name', 'file', 'className', 'diverse', 'top', 'includeTests'],
107
117
  related: ['name', 'file', 'className', 'top', 'all'],
118
+ brief: ['name', 'file', 'className', 'git'],
108
119
  // Finding code
109
- find: ['name', 'file', 'exclude', 'className', 'includeTests', 'top', 'limit', 'exact', 'in', 'all', 'depth'],
110
- usages: ['name', 'file', 'exclude', 'className', 'includeTests', 'limit', 'codeOnly', 'context', 'in'],
120
+ find: ['name', 'file', 'exclude', 'className', 'includeTests', 'top', 'limit', 'exact', 'in', 'all', 'depth', 'compact'],
121
+ usages: ['name', 'file', 'exclude', 'className', 'includeTests', 'limit', 'codeOnly', 'context', 'in', 'compact'],
111
122
  toc: ['file', 'exclude', 'top', 'limit', 'all', 'detailed', 'topLevel', 'in'],
112
123
  search: ['term', 'file', 'exclude', 'includeTests', 'top', 'limit', 'codeOnly', 'caseSensitive', 'context', 'regex', 'in', 'type', 'param', 'receiver', 'returns', 'decorator', 'exported', 'unused'],
113
124
  tests: ['name', 'file', 'exclude', 'className', 'callsOnly'],
114
125
  affectedTests:['name', 'file', 'exclude', 'className', 'includeMethods', 'includeUncertain', 'depth', 'minConfidence'],
115
126
  deadcode: ['file', 'exclude', 'includeTests', 'includeExported', 'includeDecorated', 'limit', 'in'],
116
- entrypoints: ['file', 'exclude', 'includeTests', 'limit', 'type', 'framework'],
127
+ entrypoints: ['file', 'exclude', 'includeTests', 'excludeTests', 'limit', 'type', 'framework'],
128
+ endpoints: ['file', 'exclude', 'limit', 'framework', 'bridge', 'serverOnly', 'clientOnly', 'unmatched', 'method', 'prefix', 'hideUncertain'],
117
129
  // Extracting code
118
130
  fn: ['name', 'file', 'className', 'all'],
119
131
  class: ['name', 'file', 'all', 'maxLines'],
@@ -126,21 +138,25 @@ const FLAG_APPLICABILITY = {
126
138
  graph: ['file', 'depth', 'direction', 'all'],
127
139
  circularDeps: ['file', 'exclude'],
128
140
  // Refactoring
129
- verify: ['name', 'file', 'className'],
141
+ verify: ['name', 'file', 'className', 'includeMethods', 'includeUncertain'],
130
142
  plan: ['name', 'file', 'className', 'addParam', 'removeParam', 'renameTo', 'defaultValue'],
131
143
  diffImpact: ['file', 'limit', 'base', 'staged', 'all'],
144
+ check: ['file', 'base', 'staged', 'limit'],
132
145
  // Other
133
146
  typedef: ['name', 'file', 'className', 'exact'],
134
147
  stacktrace: ['stack'],
135
148
  api: ['file', 'limit'],
136
- stats: ['functions', 'top'],
149
+ stats: ['functions', 'hot', 'top'],
150
+ doctor: ['file', 'in', 'limit', 'deep'],
151
+ auditAsync: ['file', 'exclude', 'limit'],
137
152
  };
138
153
 
139
154
  // Commands whose output is project-wide — truncation means you need a filter, not more text.
140
155
  // Used by MCP server for tighter default output limits.
141
156
  const BROAD_COMMANDS = new Set([
142
- 'toc', 'entrypoints', 'diffImpact', 'affectedTests',
157
+ 'toc', 'entrypoints', 'endpoints', 'diffImpact', 'affectedTests',
143
158
  'deadcode', 'usages', 'reverseTrace', 'circularDeps',
159
+ 'doctor', 'check', 'auditAsync',
144
160
  ]);
145
161
 
146
162
  // Commands that can operate on a single file without a project index.