codeguardian-mcp 1.3.9 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"autoValidator.d.ts","sourceRoot":"","sources":["../../src/agent/autoValidator.ts"],"names":[],"mappings":"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"}
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, } from "../tools/validation/extractors/index.js";
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 = 2;
52
- static MAX_CONCURRENT_REFRESHES = 3;
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
- '/frontend/', '/client/', '/web/', '/app/', '/src/',
63
- '/components/', '/pages/', '/views/', '/hooks/', '/services/'
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
- '/backend/', '/server/', '/api/', '/services/',
67
- '/routes/', '/routers/', '/controllers/', '/models/', '/db/'
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
- '/shared/', '/common/', '/types/', '/interfaces/', '/utils/'
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 = this.language === "python" ? this.pyManifest : this.tsManifest;
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 { /* Not found */ }
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 { /* Not found */ }
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('.tsx') || filePath.endsWith('.jsx')) {
448
+ if (filePath.endsWith(".tsx") || filePath.endsWith(".jsx")) {
425
449
  return "frontend";
426
450
  }
427
- if (filePath.endsWith('.py') && !filePath.includes('test')) {
451
+ if (filePath.endsWith(".py") && !filePath.includes("test")) {
428
452
  // Check if it's clearly a backend file
429
- if (normalizedPath.includes('main.py') || normalizedPath.includes('app.py')) {
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 === 'critical' ||
446
- issue.severity === 'high' ||
447
- issue.type === 'unusedImport' ||
448
- issue.type === 'unusedFunction' ||
449
- issue.type === 'unusedExport' ||
450
- issue.type === 'dependencyHallucination' ||
451
- issue.type === 'missingDependency');
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 === 'critical')
480
+ if (issue.severity === "critical")
456
481
  return true;
457
482
  // Always show API contract mismatches (they affect both sides)
458
- if (issue.type === 'apiContractMismatch')
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 === 'unusedFunction' || issue.type === 'unusedExport' || issue.type === 'orphanedFile') {
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 === 'high') {
493
+ if (issue.severity === "high") {
467
494
  // If we can't determine scope, show all high severity
468
- if (fileScope === 'unknown')
495
+ if (fileScope === "unknown")
469
496
  return true;
470
497
  // Dead code in shared files affects everyone
471
- if (issue.type === 'deadCode' && fileScope === 'shared')
498
+ if (issue.type === "deadCode" && fileScope === "shared")
472
499
  return true;
473
500
  // Unused imports are always relevant
474
- if (issue.type === 'unusedImport')
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 === 'frontend') {
506
+ if (fileScope === "frontend") {
480
507
  // In frontend files, prioritize frontend-specific issues
481
- if (issue.type === 'unusedImport')
508
+ if (issue.type === "unusedImport")
482
509
  return true;
483
- if (issue.type === 'nonExistentFunction')
510
+ if (issue.type === "nonExistentFunction")
484
511
  return true;
485
- if (issue.type === 'nonExistentMethod')
512
+ if (issue.type === "nonExistentMethod")
486
513
  return true;
487
514
  return false; // Filter out less relevant issues
488
515
  }
489
- if (fileScope === 'backend') {
516
+ if (fileScope === "backend") {
490
517
  // In backend files, prioritize backend-specific issues
491
- if (issue.type === 'unusedImport')
518
+ if (issue.type === "unusedImport")
492
519
  return true;
493
- if (issue.type === 'nonExistentFunction')
520
+ if (issue.type === "nonExistentFunction")
494
521
  return true;
495
- if (issue.type === 'nonExistentMethod')
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 ? path.relative(this.projectPath, issue.file) : undefined,
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 ? path.relative(this.projectPath, dc.file) : undefined,
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, { filePath });
667
- const typeReferences = fileLang === "typescript" || fileLang === "javascript" ?
668
- extractTypeReferencesAST(content, fileLang, { filePath })
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 = [...confirmedPerFile.hallucinations, ...confirmedPerFile.deadCode];
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 ? path.relative(this.projectPath, issue.file) : undefined,
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.filter((i) => i.severity === "critical").slice(0, 3);
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 ? path.relative(this.projectPath, issue.file) : undefined,
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" && this.projectFileCount >= AutoValidator.LEARNING_MODE_FILE_COUNT) {
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
- }, 500);
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 && this.activeValidationCount < AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
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 ALL pending context refreshes to complete before validating.
1009
- // This prevents race conditions where validation runs on stale/partial context
1010
- // (e.g., reverseImportGraph missing entries because refreshFileContext hasn't finished).
1011
- if (this.pendingRefreshes.size > 0) {
1012
- logger.debug(`Waiting for ${this.pendingRefreshes.size} pending context refreshes...`);
1013
- await Promise.all(this.pendingRefreshes.values());
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 ? "all" : this.resolveContextLanguage(fileLang);
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, { filePath });
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 === 'interface' || sym.type === '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) || imp.defaultImport === 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 = [...manifestIssues, ...symbolIssues, ...patternIssues, ...deadCodeIssues, ...impactIssues];
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 !== 'deadCode' && i.type !== 'unusedExport' && i.type !== 'unusedFunction' && i.type !== 'orphanedFile'), allIssues.filter(i => i.type === 'deadCode' || i.type === 'unusedExport' || i.type === 'unusedFunction' || i.type === 'orphanedFile'), context, this.projectPath, fileLang);
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 !== 'architecturalDeviation');
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 !== 'deadCode' && i.type !== 'orphanedFile');
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.map((i, idx) => {
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
- }).join("\n");
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}`;