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.
- package/.claude/skills/ucn/SKILL.md +114 -11
- package/README.md +152 -156
- package/cli/index.js +363 -37
- package/core/analysis.js +960 -37
- package/core/bridge.js +1111 -0
- package/core/brief.js +408 -0
- package/core/cache.js +213 -59
- package/core/callers.js +117 -41
- package/core/check.js +200 -0
- package/core/deadcode.js +31 -2
- package/core/discovery.js +57 -34
- package/core/entrypoints.js +638 -4
- package/core/execute.js +304 -5
- package/core/git-enrich.js +130 -0
- package/core/graph-build.js +4 -4
- package/core/graph.js +31 -12
- package/core/output/analysis.js +157 -25
- package/core/output/brief.js +100 -0
- package/core/output/check.js +79 -0
- package/core/output/doctor.js +85 -0
- package/core/output/endpoints.js +239 -0
- package/core/output/extraction.js +2 -0
- package/core/output/find.js +126 -39
- package/core/output/graph.js +48 -15
- package/core/output/refactoring.js +103 -5
- package/core/output/reporting.js +63 -23
- package/core/output/search.js +110 -17
- package/core/output/shared.js +56 -2
- package/core/output.js +4 -0
- package/core/parallel-build.js +10 -7
- package/core/parser.js +8 -2
- package/core/project.js +147 -41
- package/core/registry.js +30 -14
- package/core/reporting.js +465 -2
- package/core/search.js +139 -15
- package/core/shared.js +101 -5
- package/core/tracing.js +31 -12
- package/core/verify.js +982 -95
- package/languages/go.js +91 -6
- package/languages/html.js +10 -0
- package/languages/java.js +151 -35
- package/languages/javascript.js +290 -33
- package/languages/python.js +78 -11
- package/languages/rust.js +267 -12
- package/languages/utils.js +315 -3
- package/mcp/server.js +91 -16
- 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
|
-
//
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
780
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1562
|
-
|
|
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.
|