codeguardian-mcp 1.3.8 → 1.4.0
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/dist/agent/autoValidator.d.ts.map +1 -1
- package/dist/agent/autoValidator.js +155 -77
- package/dist/agent/autoValidator.js.map +1 -1
- package/dist/agent/fileWatcher.d.ts.map +1 -1
- package/dist/agent/fileWatcher.js +2 -2
- package/dist/agent/fileWatcher.js.map +1 -1
- package/dist/analyzers/findingVerifier.d.ts.map +1 -1
- package/dist/analyzers/findingVerifier.js +318 -65
- package/dist/analyzers/findingVerifier.js.map +1 -1
- package/dist/api-contract/extractors/typescript.d.ts +15 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -1
- package/dist/api-contract/extractors/typescript.js +127 -11
- package/dist/api-contract/extractors/typescript.js.map +1 -1
- package/dist/api-contract/validators/index.js +9 -0
- package/dist/api-contract/validators/index.js.map +1 -1
- package/dist/queue/validationJob.d.ts.map +1 -1
- package/dist/queue/validationJob.js +47 -14
- package/dist/queue/validationJob.js.map +1 -1
- package/dist/tools/validation/deadCode.d.ts +19 -1
- package/dist/tools/validation/deadCode.d.ts.map +1 -1
- package/dist/tools/validation/deadCode.js +268 -32
- package/dist/tools/validation/deadCode.js.map +1 -1
- package/dist/tools/validation/validation.d.ts.map +1 -1
- package/dist/tools/validation/validation.js +63 -0
- package/dist/tools/validation/validation.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoValidator.d.ts","sourceRoot":"","sources":["../../src/agent/autoValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"autoValidator.d.ts","sourceRoot":"","sources":["../../src/agent/autoValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,17 +10,17 @@ import * as fs from "fs/promises";
|
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { FileWatcher } from "./fileWatcher.js";
|
|
12
12
|
import { orchestrateContext } from "../context/contextOrchestrator.js";
|
|
13
|
-
import { refreshFileContext, markGuardianActive, markGuardianInactive } from "../context/projectContext.js";
|
|
13
|
+
import { refreshFileContext, markGuardianActive, markGuardianInactive, } from "../context/projectContext.js";
|
|
14
14
|
import { extractUsagesAST, extractImportsAST, extractImportsASTWithOptions, extractTypeReferencesAST, } from "../tools/validation/extractors/index.js";
|
|
15
15
|
import { loadManifestDependencies, loadPythonModuleExports, } from "../tools/validation/manifest.js";
|
|
16
|
-
import { extractSymbolsAST
|
|
16
|
+
import { extractSymbolsAST } from "../tools/validation/extractors/index.js";
|
|
17
17
|
import { validateManifest, validateSymbols, buildSymbolTable, validateUsagePatterns, } from "../tools/validation/validation.js";
|
|
18
|
-
import { detectDeadCode, detectUnusedLocals, shouldSkipFrameworkPattern } from "../tools/validation/deadCode.js";
|
|
18
|
+
import { detectDeadCode, detectUnusedLocals, detectUnusedClassMethods, shouldSkipFrameworkPattern, } from "../tools/validation/deadCode.js";
|
|
19
19
|
import { impactAnalyzer } from "../analyzers/impactAnalyzer.js";
|
|
20
20
|
import { logger } from "../utils/logger.js";
|
|
21
21
|
import { shouldExcludeFile } from "../utils/fileFilter.js";
|
|
22
22
|
import { PROMPT_PATTERNS, VALIDATION_CONSTRAINTS } from "../prompts/library.js";
|
|
23
|
-
import { generateAntiPatternContext, enrichIssuesWithAntiPatterns } from "../analyzers/antiPatterns.js";
|
|
23
|
+
import { generateAntiPatternContext, enrichIssuesWithAntiPatterns, } from "../analyzers/antiPatterns.js";
|
|
24
24
|
import { verifyFindingsAutomatically, getConfirmedFindings, } from "../analyzers/findingVerifier.js";
|
|
25
25
|
import { validateApiContracts, } from "../api-contract/index.js";
|
|
26
26
|
export class AutoValidator {
|
|
@@ -48,8 +48,8 @@ export class AutoValidator {
|
|
|
48
48
|
activeValidationCount = 0;
|
|
49
49
|
validationQueue = new Set();
|
|
50
50
|
activeRefreshCount = 0;
|
|
51
|
-
static MAX_CONCURRENT_VALIDATIONS =
|
|
52
|
-
static MAX_CONCURRENT_REFRESHES =
|
|
51
|
+
static MAX_CONCURRENT_VALIDATIONS = 4;
|
|
52
|
+
static MAX_CONCURRENT_REFRESHES = 5;
|
|
53
53
|
// During guardian initialization, we start the watcher early but buffer events.
|
|
54
54
|
// This prevents missing changes that occur while context/manifest scans are running.
|
|
55
55
|
isInitialized = false;
|
|
@@ -59,15 +59,34 @@ export class AutoValidator {
|
|
|
59
59
|
static LEARNING_MODE_FILE_COUNT = 10; // After 10 files created, switch to strict
|
|
60
60
|
// Common path patterns for scope detection
|
|
61
61
|
static FRONTEND_PATTERNS = [
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
"/frontend/",
|
|
63
|
+
"/client/",
|
|
64
|
+
"/web/",
|
|
65
|
+
"/app/",
|
|
66
|
+
"/src/",
|
|
67
|
+
"/components/",
|
|
68
|
+
"/pages/",
|
|
69
|
+
"/views/",
|
|
70
|
+
"/hooks/",
|
|
71
|
+
"/services/",
|
|
64
72
|
];
|
|
65
73
|
static BACKEND_PATTERNS = [
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
"/backend/",
|
|
75
|
+
"/server/",
|
|
76
|
+
"/api/",
|
|
77
|
+
"/services/",
|
|
78
|
+
"/routes/",
|
|
79
|
+
"/routers/",
|
|
80
|
+
"/controllers/",
|
|
81
|
+
"/models/",
|
|
82
|
+
"/db/",
|
|
68
83
|
];
|
|
69
84
|
static SHARED_PATTERNS = [
|
|
70
|
-
|
|
85
|
+
"/shared/",
|
|
86
|
+
"/common/",
|
|
87
|
+
"/types/",
|
|
88
|
+
"/interfaces/",
|
|
89
|
+
"/utils/",
|
|
71
90
|
];
|
|
72
91
|
/**
|
|
73
92
|
* Detect language from file extension for per-file validation
|
|
@@ -147,7 +166,8 @@ export class AutoValidator {
|
|
|
147
166
|
this.pyManifest = await loadManifestDependencies(pyRoot, "python");
|
|
148
167
|
this.pythonExports = await loadPythonModuleExports(pyRoot);
|
|
149
168
|
// Keep this.manifest as the "primary" for backward compat
|
|
150
|
-
this.manifest =
|
|
169
|
+
this.manifest =
|
|
170
|
+
this.language === "python" ? this.pyManifest : this.tsManifest;
|
|
151
171
|
}
|
|
152
172
|
else {
|
|
153
173
|
this.manifest = await loadManifestDependencies(this.projectPath, this.language);
|
|
@@ -246,7 +266,9 @@ export class AutoValidator {
|
|
|
246
266
|
dirHasPython = true;
|
|
247
267
|
break;
|
|
248
268
|
}
|
|
249
|
-
catch {
|
|
269
|
+
catch {
|
|
270
|
+
/* Not found */
|
|
271
|
+
}
|
|
250
272
|
}
|
|
251
273
|
for (const m of ["package.json", "tsconfig.json"]) {
|
|
252
274
|
try {
|
|
@@ -254,7 +276,9 @@ export class AutoValidator {
|
|
|
254
276
|
dirHasTS = true;
|
|
255
277
|
break;
|
|
256
278
|
}
|
|
257
|
-
catch {
|
|
279
|
+
catch {
|
|
280
|
+
/* Not found */
|
|
281
|
+
}
|
|
258
282
|
}
|
|
259
283
|
if (dirHasPython && dirHasTS)
|
|
260
284
|
return "both";
|
|
@@ -408,7 +432,7 @@ export class AutoValidator {
|
|
|
408
432
|
for (const pattern of AutoValidator.FRONTEND_PATTERNS) {
|
|
409
433
|
if (normalizedPath.includes(pattern)) {
|
|
410
434
|
// But make sure it's not in a backend directory
|
|
411
|
-
const isBackend = AutoValidator.BACKEND_PATTERNS.some(p => normalizedPath.includes(p));
|
|
435
|
+
const isBackend = AutoValidator.BACKEND_PATTERNS.some((p) => normalizedPath.includes(p));
|
|
412
436
|
if (!isBackend) {
|
|
413
437
|
return "frontend";
|
|
414
438
|
}
|
|
@@ -421,12 +445,13 @@ export class AutoValidator {
|
|
|
421
445
|
}
|
|
422
446
|
}
|
|
423
447
|
// Detect by file extension and content patterns
|
|
424
|
-
if (filePath.endsWith(
|
|
448
|
+
if (filePath.endsWith(".tsx") || filePath.endsWith(".jsx")) {
|
|
425
449
|
return "frontend";
|
|
426
450
|
}
|
|
427
|
-
if (filePath.endsWith(
|
|
451
|
+
if (filePath.endsWith(".py") && !filePath.includes("test")) {
|
|
428
452
|
// Check if it's clearly a backend file
|
|
429
|
-
if (normalizedPath.includes(
|
|
453
|
+
if (normalizedPath.includes("main.py") ||
|
|
454
|
+
normalizedPath.includes("app.py")) {
|
|
430
455
|
return "backend";
|
|
431
456
|
}
|
|
432
457
|
}
|
|
@@ -442,57 +467,59 @@ export class AutoValidator {
|
|
|
442
467
|
// PLUS keep issue types that the later lenient filter explicitly preserves:
|
|
443
468
|
// - unusedImport, unusedFunction, unusedExport (per-file code hygiene)
|
|
444
469
|
// - dependencyHallucination, missingDependency (build-breaking or suspicious)
|
|
445
|
-
return issues.filter(issue => issue.severity ===
|
|
446
|
-
issue.severity ===
|
|
447
|
-
issue.type ===
|
|
448
|
-
issue.type ===
|
|
449
|
-
issue.type ===
|
|
450
|
-
issue.type ===
|
|
451
|
-
issue.type ===
|
|
470
|
+
return issues.filter((issue) => issue.severity === "critical" ||
|
|
471
|
+
issue.severity === "high" ||
|
|
472
|
+
issue.type === "unusedImport" ||
|
|
473
|
+
issue.type === "unusedFunction" ||
|
|
474
|
+
issue.type === "unusedExport" ||
|
|
475
|
+
issue.type === "dependencyHallucination" ||
|
|
476
|
+
issue.type === "missingDependency");
|
|
452
477
|
}
|
|
453
|
-
return issues.filter(issue => {
|
|
478
|
+
return issues.filter((issue) => {
|
|
454
479
|
// Always show critical issues
|
|
455
|
-
if (issue.severity ===
|
|
480
|
+
if (issue.severity === "critical")
|
|
456
481
|
return true;
|
|
457
482
|
// Always show API contract mismatches (they affect both sides)
|
|
458
|
-
if (issue.type ===
|
|
483
|
+
if (issue.type === "apiContractMismatch")
|
|
459
484
|
return true;
|
|
460
485
|
// Always show dead-code hygiene findings (local unuseds) regardless of scope.
|
|
461
486
|
// These are high-signal and should not be dropped by scope heuristics.
|
|
462
|
-
if (issue.type ===
|
|
487
|
+
if (issue.type === "unusedFunction" ||
|
|
488
|
+
issue.type === "unusedExport" ||
|
|
489
|
+
issue.type === "orphanedFile") {
|
|
463
490
|
return true;
|
|
464
491
|
}
|
|
465
492
|
// For high severity, show if relevant to scope
|
|
466
|
-
if (issue.severity ===
|
|
493
|
+
if (issue.severity === "high") {
|
|
467
494
|
// If we can't determine scope, show all high severity
|
|
468
|
-
if (fileScope ===
|
|
495
|
+
if (fileScope === "unknown")
|
|
469
496
|
return true;
|
|
470
497
|
// Dead code in shared files affects everyone
|
|
471
|
-
if (issue.type ===
|
|
498
|
+
if (issue.type === "deadCode" && fileScope === "shared")
|
|
472
499
|
return true;
|
|
473
500
|
// Unused imports are always relevant
|
|
474
|
-
if (issue.type ===
|
|
501
|
+
if (issue.type === "unusedImport")
|
|
475
502
|
return true;
|
|
476
503
|
return true; // Show other high severity issues
|
|
477
504
|
}
|
|
478
505
|
// For medium/low severity, be more selective
|
|
479
|
-
if (fileScope ===
|
|
506
|
+
if (fileScope === "frontend") {
|
|
480
507
|
// In frontend files, prioritize frontend-specific issues
|
|
481
|
-
if (issue.type ===
|
|
508
|
+
if (issue.type === "unusedImport")
|
|
482
509
|
return true;
|
|
483
|
-
if (issue.type ===
|
|
510
|
+
if (issue.type === "nonExistentFunction")
|
|
484
511
|
return true;
|
|
485
|
-
if (issue.type ===
|
|
512
|
+
if (issue.type === "nonExistentMethod")
|
|
486
513
|
return true;
|
|
487
514
|
return false; // Filter out less relevant issues
|
|
488
515
|
}
|
|
489
|
-
if (fileScope ===
|
|
516
|
+
if (fileScope === "backend") {
|
|
490
517
|
// In backend files, prioritize backend-specific issues
|
|
491
|
-
if (issue.type ===
|
|
518
|
+
if (issue.type === "unusedImport")
|
|
492
519
|
return true;
|
|
493
|
-
if (issue.type ===
|
|
520
|
+
if (issue.type === "nonExistentFunction")
|
|
494
521
|
return true;
|
|
495
|
-
if (issue.type ===
|
|
522
|
+
if (issue.type === "nonExistentMethod")
|
|
496
523
|
return true;
|
|
497
524
|
return false;
|
|
498
525
|
}
|
|
@@ -544,7 +571,9 @@ export class AutoValidator {
|
|
|
544
571
|
message: issue.message,
|
|
545
572
|
suggestion: issue.suggestion,
|
|
546
573
|
line: issue.line,
|
|
547
|
-
file: issue.file
|
|
574
|
+
file: issue.file
|
|
575
|
+
? path.relative(this.projectPath, issue.file)
|
|
576
|
+
: undefined,
|
|
548
577
|
})),
|
|
549
578
|
timestamp: Date.now(),
|
|
550
579
|
llmMessage: this.createApiContractMessage(apiContractResult),
|
|
@@ -591,7 +620,9 @@ export class AutoValidator {
|
|
|
591
620
|
message: dc.message,
|
|
592
621
|
suggestion: dc.suggestion,
|
|
593
622
|
line: dc.line,
|
|
594
|
-
file: dc.file
|
|
623
|
+
file: dc.file
|
|
624
|
+
? path.relative(this.projectPath, dc.file)
|
|
625
|
+
: undefined,
|
|
595
626
|
})),
|
|
596
627
|
timestamp: Date.now(),
|
|
597
628
|
llmMessage: this.createInitialScanMessage(confirmedDeadCode),
|
|
@@ -663,9 +694,11 @@ export class AutoValidator {
|
|
|
663
694
|
// Tier 1: Symbol validation — catches hallucinated bare function calls
|
|
664
695
|
// (e.g., reportInventoryMetricsToCloud(), dispatchSystemDiagnostics())
|
|
665
696
|
// These are NOT imports, so validateManifest can't catch them.
|
|
666
|
-
const usages = extractUsagesAST(content, fileLang, imports, {
|
|
667
|
-
|
|
668
|
-
|
|
697
|
+
const usages = extractUsagesAST(content, fileLang, imports, {
|
|
698
|
+
filePath,
|
|
699
|
+
});
|
|
700
|
+
const typeReferences = fileLang === "typescript" || fileLang === "javascript"
|
|
701
|
+
? extractTypeReferencesAST(content, fileLang, { filePath })
|
|
669
702
|
: [];
|
|
670
703
|
const symbolIssues = validateSymbols(usages, symbolTable, content, fileLang, strictModeForInitialScan, imports, this.pythonExports, context, filePath, undefined, // missingPackages
|
|
671
704
|
typeReferences);
|
|
@@ -676,6 +709,12 @@ export class AutoValidator {
|
|
|
676
709
|
// Tier 2: Local dead code — catches unused non-exported functions/constants
|
|
677
710
|
const localDeadCode = detectUnusedLocals(content, filePath);
|
|
678
711
|
perFileDeadCode.push(...localDeadCode);
|
|
712
|
+
// Tier 2b: Class method dead code — catches methods on exported singletons
|
|
713
|
+
// that are never called across the project (e.g. spoonacularService.getRecipeNutrition).
|
|
714
|
+
// detectUnusedLocals hard-skips sym.type === "method"; detectDeadCode only tracks
|
|
715
|
+
// top-level exported symbols — so this cross-file instance-method check fills the gap.
|
|
716
|
+
const classMethodDeadCode = await detectUnusedClassMethods(content, filePath, context);
|
|
717
|
+
perFileDeadCode.push(...classMethodDeadCode);
|
|
679
718
|
}
|
|
680
719
|
catch (err) {
|
|
681
720
|
logger.debug(`Skipping initial scan of ${filePath}: ${err}`);
|
|
@@ -688,7 +727,10 @@ export class AutoValidator {
|
|
|
688
727
|
logger.debug(`Verifying ${allPerFileFindings.length} per-file findings...`);
|
|
689
728
|
const perFileVerification = await verifyFindingsAutomatically(perFileHallucinations, perFileDeadCode, context, this.projectPath, verifyLang);
|
|
690
729
|
const confirmedPerFile = getConfirmedFindings(perFileVerification);
|
|
691
|
-
const confirmedPerFileAll = [
|
|
730
|
+
const confirmedPerFileAll = [
|
|
731
|
+
...confirmedPerFile.hallucinations,
|
|
732
|
+
...confirmedPerFile.deadCode,
|
|
733
|
+
];
|
|
692
734
|
logger.debug(`Per-file verification: ${confirmedPerFileAll.length} confirmed (filtered ${perFileVerification.stats.falsePositiveCount} false positives)`);
|
|
693
735
|
if (confirmedPerFileAll.length > 0) {
|
|
694
736
|
const perFileAlert = {
|
|
@@ -699,7 +741,9 @@ export class AutoValidator {
|
|
|
699
741
|
message: issue.message || "",
|
|
700
742
|
suggestion: issue.suggestion,
|
|
701
743
|
line: issue.line,
|
|
702
|
-
file: issue.file
|
|
744
|
+
file: issue.file
|
|
745
|
+
? path.relative(this.projectPath, issue.file)
|
|
746
|
+
: undefined,
|
|
703
747
|
})),
|
|
704
748
|
timestamp: Date.now(),
|
|
705
749
|
llmMessage: this.createPerFileScanMessage(confirmedPerFile.hallucinations, confirmedPerFile.deadCode),
|
|
@@ -765,7 +809,9 @@ export class AutoValidator {
|
|
|
765
809
|
lines.push("");
|
|
766
810
|
if (result.summary.critical > 0) {
|
|
767
811
|
lines.push("**Critical Issues (Fix Immediately):**");
|
|
768
|
-
const critical = result.issues
|
|
812
|
+
const critical = result.issues
|
|
813
|
+
.filter((i) => i.severity === "critical")
|
|
814
|
+
.slice(0, 3);
|
|
769
815
|
critical.forEach((issue) => {
|
|
770
816
|
lines.push(`- ${issue.message}`);
|
|
771
817
|
lines.push(` Suggestion: ${issue.suggestion}`);
|
|
@@ -855,7 +901,9 @@ export class AutoValidator {
|
|
|
855
901
|
message: issue.message,
|
|
856
902
|
suggestion: issue.suggestion,
|
|
857
903
|
line: issue.line,
|
|
858
|
-
file: issue.file
|
|
904
|
+
file: issue.file
|
|
905
|
+
? path.relative(this.projectPath, issue.file)
|
|
906
|
+
: undefined,
|
|
859
907
|
})),
|
|
860
908
|
timestamp: Date.now(),
|
|
861
909
|
llmMessage: this.createApiContractMessage(apiContractResult),
|
|
@@ -905,7 +953,8 @@ export class AutoValidator {
|
|
|
905
953
|
logger.debug(`New file detected: ${event.path} (${fileLang}) - refreshing context...`);
|
|
906
954
|
refreshPromise = this.throttledRefresh(event.path, refreshLang);
|
|
907
955
|
// Check if we should exit learning mode
|
|
908
|
-
if (this.mode === "auto" &&
|
|
956
|
+
if (this.mode === "auto" &&
|
|
957
|
+
this.projectFileCount >= AutoValidator.LEARNING_MODE_FILE_COUNT) {
|
|
909
958
|
// Only switch if we were previously in learning mode (implied by auto + threshold)
|
|
910
959
|
// But for simplicity, we just let the next detectProjectMode call handle it
|
|
911
960
|
}
|
|
@@ -947,7 +996,7 @@ export class AutoValidator {
|
|
|
947
996
|
const timer = setTimeout(() => {
|
|
948
997
|
this.enqueueValidation(event.path);
|
|
949
998
|
this.debounceTimers.delete(event.path);
|
|
950
|
-
},
|
|
999
|
+
}, 150);
|
|
951
1000
|
this.debounceTimers.set(event.path, timer);
|
|
952
1001
|
}
|
|
953
1002
|
/**
|
|
@@ -958,7 +1007,7 @@ export class AutoValidator {
|
|
|
958
1007
|
async throttledRefresh(filePath, language) {
|
|
959
1008
|
// Wait if too many refreshes are already running
|
|
960
1009
|
while (this.activeRefreshCount >= AutoValidator.MAX_CONCURRENT_REFRESHES) {
|
|
961
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1010
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
962
1011
|
}
|
|
963
1012
|
this.activeRefreshCount++;
|
|
964
1013
|
try {
|
|
@@ -995,7 +1044,8 @@ export class AutoValidator {
|
|
|
995
1044
|
}
|
|
996
1045
|
processValidationQueue() {
|
|
997
1046
|
// Drain queue up to concurrency limit
|
|
998
|
-
while (this.validationQueue.size > 0 &&
|
|
1047
|
+
while (this.validationQueue.size > 0 &&
|
|
1048
|
+
this.activeValidationCount < AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
|
|
999
1049
|
const next = this.validationQueue.values().next().value;
|
|
1000
1050
|
if (!next)
|
|
1001
1051
|
break;
|
|
@@ -1005,12 +1055,17 @@ export class AutoValidator {
|
|
|
1005
1055
|
}
|
|
1006
1056
|
async validateFile(filePath) {
|
|
1007
1057
|
try {
|
|
1008
|
-
// Wait for
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1058
|
+
// Wait only for THIS file's own context refresh, not all pending refreshes.
|
|
1059
|
+
// Waiting for every in-flight refresh (e.g. 5 simultaneous agent edits) was
|
|
1060
|
+
// the primary head-of-line-blocking bottleneck in multi-agent vibe-coding:
|
|
1061
|
+
// file E's validation would stall until files A-D finished refreshing, even
|
|
1062
|
+
// though those refreshes updated unrelated parts of the context.
|
|
1063
|
+
// Each file's reverseImportGraph entry is updated by its own refresh, so
|
|
1064
|
+
// awaiting only the current file's refresh is sufficient for correctness.
|
|
1065
|
+
const ownRefresh = this.pendingRefreshes.get(filePath);
|
|
1066
|
+
if (ownRefresh) {
|
|
1067
|
+
logger.debug(`Waiting for own context refresh: ${path.relative(this.projectPath, filePath)}`);
|
|
1068
|
+
await ownRefresh;
|
|
1014
1069
|
}
|
|
1015
1070
|
const isNewFile = this.newFilesTracked.has(filePath);
|
|
1016
1071
|
const mode = this.detectProjectMode();
|
|
@@ -1040,7 +1095,9 @@ export class AutoValidator {
|
|
|
1040
1095
|
// Use orchestrateContext with "all" for full-stack, or the FILE's language
|
|
1041
1096
|
// so that TSX changes are validated against a TypeScript-aware context
|
|
1042
1097
|
// even if the guardian was started with a different language.
|
|
1043
|
-
const contextLanguage = this.isFullStack
|
|
1098
|
+
const contextLanguage = this.isFullStack
|
|
1099
|
+
? "all"
|
|
1100
|
+
: this.resolveContextLanguage(fileLang);
|
|
1044
1101
|
const orchestration = await orchestrateContext({
|
|
1045
1102
|
projectPath: this.projectPath,
|
|
1046
1103
|
language: contextLanguage,
|
|
@@ -1051,12 +1108,14 @@ export class AutoValidator {
|
|
|
1051
1108
|
const imports = extractImportsASTWithOptions(content, fileLang, {
|
|
1052
1109
|
filePath,
|
|
1053
1110
|
});
|
|
1054
|
-
const usedSymbols = extractUsagesAST(content, fileLang, imports, {
|
|
1111
|
+
const usedSymbols = extractUsagesAST(content, fileLang, imports, {
|
|
1112
|
+
filePath,
|
|
1113
|
+
});
|
|
1055
1114
|
const symbolTable = buildSymbolTable(context, orchestration.relevantSymbols);
|
|
1056
1115
|
// Extract type references for unused import detection
|
|
1057
1116
|
// This is essential for TypeScript where imports might only be used as types
|
|
1058
|
-
const typeReferences = fileLang === "typescript" || fileLang === "javascript"
|
|
1059
|
-
extractTypeReferencesAST(content, fileLang, { filePath })
|
|
1117
|
+
const typeReferences = fileLang === "typescript" || fileLang === "javascript"
|
|
1118
|
+
? extractTypeReferencesAST(content, fileLang, { filePath })
|
|
1060
1119
|
: [];
|
|
1061
1120
|
// Tier 0: Check manifest dependencies (use the correct manifest for this file's language)
|
|
1062
1121
|
let manifestIssues = [];
|
|
@@ -1104,14 +1163,14 @@ export class AutoValidator {
|
|
|
1104
1163
|
// We only run this for the changed file to avoid full project scans.
|
|
1105
1164
|
const exportedDeadCodeIssues = [];
|
|
1106
1165
|
const fileSymbols = extractSymbolsAST(content, filePath, fileLang);
|
|
1107
|
-
const exportedSymbols = fileSymbols.filter(s => s.isExported);
|
|
1166
|
+
const exportedSymbols = fileSymbols.filter((s) => s.isExported);
|
|
1108
1167
|
if (exportedSymbols.length > 0 && context.reverseImportGraph) {
|
|
1109
1168
|
for (const sym of exportedSymbols) {
|
|
1110
1169
|
// Skip React components and framework patterns
|
|
1111
1170
|
if (shouldSkipFrameworkPattern(sym.name))
|
|
1112
1171
|
continue;
|
|
1113
1172
|
// Skip type exports (interfaces, types) - they might be used as type annotations
|
|
1114
|
-
if (sym.type ===
|
|
1173
|
+
if (sym.type === "interface" || sym.type === "type")
|
|
1115
1174
|
continue;
|
|
1116
1175
|
// Check if this exported symbol is imported anywhere
|
|
1117
1176
|
const importers = context.reverseImportGraph.get(filePath);
|
|
@@ -1133,7 +1192,8 @@ export class AutoValidator {
|
|
|
1133
1192
|
const importerInfo = context.files.get(importerPath);
|
|
1134
1193
|
if (importerInfo) {
|
|
1135
1194
|
for (const imp of importerInfo.imports) {
|
|
1136
|
-
if (imp.namedImports.includes(sym.name) ||
|
|
1195
|
+
if (imp.namedImports.includes(sym.name) ||
|
|
1196
|
+
imp.defaultImport === sym.name) {
|
|
1137
1197
|
isImported = true;
|
|
1138
1198
|
break;
|
|
1139
1199
|
}
|
|
@@ -1156,8 +1216,21 @@ export class AutoValidator {
|
|
|
1156
1216
|
}
|
|
1157
1217
|
}
|
|
1158
1218
|
deadCodeIssues.push(...exportedDeadCodeIssues);
|
|
1219
|
+
// Tier 2c: Class method dead code — catches instance methods on exported singletons
|
|
1220
|
+
// that are never called anywhere in the project
|
|
1221
|
+
// (e.g. spoonacularService.getRecipeNutrition(), spoonacularService.autocompleteIngredient()).
|
|
1222
|
+
// detectUnusedLocals hard-skips sym.type === "method"; detectDeadCode only walks
|
|
1223
|
+
// top-level exported symbols — this cross-file, instance-origin-tracked check fills that gap.
|
|
1224
|
+
const classMethodDeadCode = await detectUnusedClassMethods(content, filePath, context);
|
|
1225
|
+
deadCodeIssues.push(...classMethodDeadCode);
|
|
1159
1226
|
// Combine all issues for verification
|
|
1160
|
-
let allIssues = [
|
|
1227
|
+
let allIssues = [
|
|
1228
|
+
...manifestIssues,
|
|
1229
|
+
...symbolIssues,
|
|
1230
|
+
...patternIssues,
|
|
1231
|
+
...deadCodeIssues,
|
|
1232
|
+
...impactIssues,
|
|
1233
|
+
];
|
|
1161
1234
|
// === SMART SCOPE FILTERING (NEW) ===
|
|
1162
1235
|
// Detect file scope and filter issues by relevance
|
|
1163
1236
|
const fileScope = this.detectFileScope(filePath);
|
|
@@ -1168,26 +1241,29 @@ export class AutoValidator {
|
|
|
1168
1241
|
// === AUTOMATED VERIFICATION (eliminates false positives) ===
|
|
1169
1242
|
if (allIssues.length > 0) {
|
|
1170
1243
|
logger.debug(`Verifying ${allIssues.length} findings to eliminate false positives...`);
|
|
1171
|
-
const verificationResult = await verifyFindingsAutomatically(allIssues.filter(i => i.type !==
|
|
1244
|
+
const verificationResult = await verifyFindingsAutomatically(allIssues.filter((i) => i.type !== "deadCode" &&
|
|
1245
|
+
i.type !== "unusedExport" &&
|
|
1246
|
+
i.type !== "unusedFunction" &&
|
|
1247
|
+
i.type !== "orphanedFile"), allIssues.filter((i) => i.type === "deadCode" ||
|
|
1248
|
+
i.type === "unusedExport" ||
|
|
1249
|
+
i.type === "unusedFunction" ||
|
|
1250
|
+
i.type === "orphanedFile"), context, this.projectPath, fileLang);
|
|
1172
1251
|
// Replace with confirmed findings only
|
|
1173
1252
|
const confirmed = getConfirmedFindings(verificationResult);
|
|
1174
1253
|
// Reconstruct allIssues with verified findings
|
|
1175
|
-
allIssues = [
|
|
1176
|
-
...confirmed.hallucinations,
|
|
1177
|
-
...confirmed.deadCode,
|
|
1178
|
-
];
|
|
1254
|
+
allIssues = [...confirmed.hallucinations, ...confirmed.deadCode];
|
|
1179
1255
|
logger.debug(`Verification complete: ${allIssues.length} confirmed (filtered ${verificationResult.stats.falsePositiveCount} false positives)`);
|
|
1180
1256
|
}
|
|
1181
1257
|
// === SMART MODE FILTERING ===
|
|
1182
1258
|
if (isLenient) {
|
|
1183
1259
|
// In learning/new file mode:
|
|
1184
1260
|
// 1. Ignore architectural deviations (we are learning patterns)
|
|
1185
|
-
allIssues = allIssues.filter(i => i.type !==
|
|
1261
|
+
allIssues = allIssues.filter((i) => i.type !== "architecturalDeviation");
|
|
1186
1262
|
// 2. Ignore PROJECT-WIDE dead code in new/changing files (orphaned files, etc.)
|
|
1187
1263
|
// but KEEP per-file local dead code (unusedFunction, unusedExport from detectUnusedLocals)
|
|
1188
1264
|
// because local unused functions/constants are genuine code hygiene issues
|
|
1189
1265
|
// that are cheap to detect and valuable to report.
|
|
1190
|
-
allIssues = allIssues.filter(i => i.type !==
|
|
1266
|
+
allIssues = allIssues.filter((i) => i.type !== "deadCode" && i.type !== "orphanedFile");
|
|
1191
1267
|
// 3. Filter out "Medium" severity symbol issues
|
|
1192
1268
|
// BUT keep unusedImport, unusedFunction, and unusedExport warnings
|
|
1193
1269
|
// - unusedImport: the #1 vibecoder mistake
|
|
@@ -1250,7 +1326,8 @@ export class AutoValidator {
|
|
|
1250
1326
|
// Include validation constraints from the library
|
|
1251
1327
|
const constrainedMsg = PROMPT_PATTERNS.withConstraints(roleMsg, VALIDATION_CONSTRAINTS);
|
|
1252
1328
|
// Add specific issues
|
|
1253
|
-
const issuesList = issues
|
|
1329
|
+
const issuesList = issues
|
|
1330
|
+
.map((i, idx) => {
|
|
1254
1331
|
let item = `${idx + 1}. [${i.severity.toUpperCase()}] ${i.type}: ${i.message}`;
|
|
1255
1332
|
if (i.line)
|
|
1256
1333
|
item += ` (Line ${i.line})`;
|
|
@@ -1261,7 +1338,8 @@ export class AutoValidator {
|
|
|
1261
1338
|
item += `\n 📚 ${i.antiPattern.id}: ${i.antiPattern.name} - ${i.antiPattern.description}`;
|
|
1262
1339
|
}
|
|
1263
1340
|
return item;
|
|
1264
|
-
})
|
|
1341
|
+
})
|
|
1342
|
+
.join("\n");
|
|
1265
1343
|
// Generate anti-pattern context for LLM
|
|
1266
1344
|
const antiPatternContext = await generateAntiPatternContext(issues, fileLang);
|
|
1267
1345
|
let message = `${constrainedMsg}\n\nDETECTED ISSUES:\n${issuesList}`;
|