codeguardian-mcp 1.0.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/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/agent/agentTools.d.ts +26 -0
- package/dist/agent/agentTools.d.ts.map +1 -0
- package/dist/agent/agentTools.js +699 -0
- package/dist/agent/agentTools.js.map +1 -0
- package/dist/agent/autoValidator.d.ts +110 -0
- package/dist/agent/autoValidator.d.ts.map +1 -0
- package/dist/agent/autoValidator.js +964 -0
- package/dist/agent/autoValidator.js.map +1 -0
- package/dist/agent/fileWatcher.d.ts +28 -0
- package/dist/agent/fileWatcher.d.ts.map +1 -0
- package/dist/agent/fileWatcher.js +88 -0
- package/dist/agent/fileWatcher.js.map +1 -0
- package/dist/agent/guardianPersistence.d.ts +98 -0
- package/dist/agent/guardianPersistence.d.ts.map +1 -0
- package/dist/agent/guardianPersistence.js +296 -0
- package/dist/agent/guardianPersistence.js.map +1 -0
- package/dist/agent/mcpNotifications.d.ts +38 -0
- package/dist/agent/mcpNotifications.d.ts.map +1 -0
- package/dist/agent/mcpNotifications.js +81 -0
- package/dist/agent/mcpNotifications.js.map +1 -0
- package/dist/analyzers/aiPatterns.d.ts +16 -0
- package/dist/analyzers/aiPatterns.d.ts.map +1 -0
- package/dist/analyzers/aiPatterns.js +103 -0
- package/dist/analyzers/aiPatterns.js.map +1 -0
- package/dist/analyzers/antiPatterns.d.ts +60 -0
- package/dist/analyzers/antiPatterns.d.ts.map +1 -0
- package/dist/analyzers/antiPatterns.js +198 -0
- package/dist/analyzers/antiPatterns.js.map +1 -0
- package/dist/analyzers/builtinTypes.d.ts +18 -0
- package/dist/analyzers/builtinTypes.d.ts.map +1 -0
- package/dist/analyzers/builtinTypes.js +1275 -0
- package/dist/analyzers/builtinTypes.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +14 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +610 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/findingVerifier.d.ts +59 -0
- package/dist/analyzers/findingVerifier.d.ts.map +1 -0
- package/dist/analyzers/findingVerifier.js +1169 -0
- package/dist/analyzers/findingVerifier.js.map +1 -0
- package/dist/analyzers/impactAnalyzer.d.ts +53 -0
- package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/impactAnalyzer.js +152 -0
- package/dist/analyzers/impactAnalyzer.js.map +1 -0
- package/dist/analyzers/languageDetector.d.ts +48 -0
- package/dist/analyzers/languageDetector.d.ts.map +1 -0
- package/dist/analyzers/languageDetector.js +404 -0
- package/dist/analyzers/languageDetector.js.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.js +193 -0
- package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.js +324 -0
- package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.js +429 -0
- package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.js +709 -0
- package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
- package/dist/analyzers/relevanceScorer.d.ts +43 -0
- package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
- package/dist/analyzers/relevanceScorer.js +200 -0
- package/dist/analyzers/relevanceScorer.js.map +1 -0
- package/dist/analyzers/standardLibrary.d.ts +22 -0
- package/dist/analyzers/standardLibrary.d.ts.map +1 -0
- package/dist/analyzers/standardLibrary.js +211 -0
- package/dist/analyzers/standardLibrary.js.map +1 -0
- package/dist/analyzers/symbolGraph.d.ts +30 -0
- package/dist/analyzers/symbolGraph.d.ts.map +1 -0
- package/dist/analyzers/symbolGraph.js +380 -0
- package/dist/analyzers/symbolGraph.js.map +1 -0
- package/dist/analyzers/symbolTable.d.ts +18 -0
- package/dist/analyzers/symbolTable.d.ts.map +1 -0
- package/dist/analyzers/symbolTable.js +176 -0
- package/dist/analyzers/symbolTable.js.map +1 -0
- package/dist/analyzers/typeChecker.d.ts +13 -0
- package/dist/analyzers/typeChecker.d.ts.map +1 -0
- package/dist/analyzers/typeChecker.js +580 -0
- package/dist/analyzers/typeChecker.js.map +1 -0
- package/dist/analyzers/usagePatterns.d.ts +42 -0
- package/dist/analyzers/usagePatterns.d.ts.map +1 -0
- package/dist/analyzers/usagePatterns.js +75 -0
- package/dist/analyzers/usagePatterns.js.map +1 -0
- package/dist/api-contract/context/backend.d.ts +19 -0
- package/dist/api-contract/context/backend.d.ts.map +1 -0
- package/dist/api-contract/context/backend.js +64 -0
- package/dist/api-contract/context/backend.js.map +1 -0
- package/dist/api-contract/context/contract.d.ts +34 -0
- package/dist/api-contract/context/contract.d.ts.map +1 -0
- package/dist/api-contract/context/contract.js +306 -0
- package/dist/api-contract/context/contract.js.map +1 -0
- package/dist/api-contract/context/frontend.d.ts +19 -0
- package/dist/api-contract/context/frontend.d.ts.map +1 -0
- package/dist/api-contract/context/frontend.js +64 -0
- package/dist/api-contract/context/frontend.js.map +1 -0
- package/dist/api-contract/detector.d.ts +28 -0
- package/dist/api-contract/detector.d.ts.map +1 -0
- package/dist/api-contract/detector.js +393 -0
- package/dist/api-contract/detector.js.map +1 -0
- package/dist/api-contract/extractors/python.d.ts +32 -0
- package/dist/api-contract/extractors/python.d.ts.map +1 -0
- package/dist/api-contract/extractors/python.js +521 -0
- package/dist/api-contract/extractors/python.js.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
- package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.js +173 -0
- package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/typescript.d.ts +32 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
- package/dist/api-contract/extractors/typescript.js +666 -0
- package/dist/api-contract/extractors/typescript.js.map +1 -0
- package/dist/api-contract/index.d.ts +104 -0
- package/dist/api-contract/index.d.ts.map +1 -0
- package/dist/api-contract/index.js +232 -0
- package/dist/api-contract/index.js.map +1 -0
- package/dist/api-contract/types.d.ts +151 -0
- package/dist/api-contract/types.d.ts.map +1 -0
- package/dist/api-contract/types.js +19 -0
- package/dist/api-contract/types.js.map +1 -0
- package/dist/api-contract/validators/endpoint.d.ts +21 -0
- package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
- package/dist/api-contract/validators/endpoint.js +224 -0
- package/dist/api-contract/validators/endpoint.js.map +1 -0
- package/dist/api-contract/validators/index.d.ts +40 -0
- package/dist/api-contract/validators/index.d.ts.map +1 -0
- package/dist/api-contract/validators/index.js +875 -0
- package/dist/api-contract/validators/index.js.map +1 -0
- package/dist/api-contract/validators/parameter.d.ts +17 -0
- package/dist/api-contract/validators/parameter.d.ts.map +1 -0
- package/dist/api-contract/validators/parameter.js +250 -0
- package/dist/api-contract/validators/parameter.js.map +1 -0
- package/dist/api-contract/validators/type.d.ts +38 -0
- package/dist/api-contract/validators/type.d.ts.map +1 -0
- package/dist/api-contract/validators/type.js +244 -0
- package/dist/api-contract/validators/type.js.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.js +665 -0
- package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
- package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
- package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
- package/dist/context/apiContract/graphqlSupport.js +671 -0
- package/dist/context/apiContract/graphqlSupport.js.map +1 -0
- package/dist/context/apiContract/index.d.ts +14 -0
- package/dist/context/apiContract/index.d.ts.map +1 -0
- package/dist/context/apiContract/index.js +17 -0
- package/dist/context/apiContract/index.js.map +1 -0
- package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
- package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
- package/dist/context/apiContract/webSocketSupport.js +465 -0
- package/dist/context/apiContract/webSocketSupport.js.map +1 -0
- package/dist/context/apiContractContext.d.ts +15 -0
- package/dist/context/apiContractContext.d.ts.map +1 -0
- package/dist/context/apiContractContext.js +979 -0
- package/dist/context/apiContractContext.js.map +1 -0
- package/dist/context/apiContractExtraction.d.ts +52 -0
- package/dist/context/apiContractExtraction.d.ts.map +1 -0
- package/dist/context/apiContractExtraction.js +438 -0
- package/dist/context/apiContractExtraction.js.map +1 -0
- package/dist/context/contextLineage.d.ts +79 -0
- package/dist/context/contextLineage.d.ts.map +1 -0
- package/dist/context/contextLineage.js +259 -0
- package/dist/context/contextLineage.js.map +1 -0
- package/dist/context/contextOrchestrator.d.ts +57 -0
- package/dist/context/contextOrchestrator.d.ts.map +1 -0
- package/dist/context/contextOrchestrator.js +162 -0
- package/dist/context/contextOrchestrator.js.map +1 -0
- package/dist/context/intentTracker.d.ts +73 -0
- package/dist/context/intentTracker.d.ts.map +1 -0
- package/dist/context/intentTracker.js +168 -0
- package/dist/context/intentTracker.js.map +1 -0
- package/dist/context/projectContext.d.ts +219 -0
- package/dist/context/projectContext.d.ts.map +1 -0
- package/dist/context/projectContext.js +1984 -0
- package/dist/context/projectContext.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +260 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/library.d.ts +51 -0
- package/dist/prompts/library.d.ts.map +1 -0
- package/dist/prompts/library.js +65 -0
- package/dist/prompts/library.js.map +1 -0
- package/dist/prompts/templates.d.ts +44 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +97 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/queue/jobPersistence.d.ts +46 -0
- package/dist/queue/jobPersistence.d.ts.map +1 -0
- package/dist/queue/jobPersistence.js +158 -0
- package/dist/queue/jobPersistence.js.map +1 -0
- package/dist/queue/jobQueue.d.ts +116 -0
- package/dist/queue/jobQueue.d.ts.map +1 -0
- package/dist/queue/jobQueue.js +275 -0
- package/dist/queue/jobQueue.js.map +1 -0
- package/dist/queue/validationJob.d.ts +69 -0
- package/dist/queue/validationJob.d.ts.map +1 -0
- package/dist/queue/validationJob.js +435 -0
- package/dist/queue/validationJob.js.map +1 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +328 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/validationReportStore.d.ts +170 -0
- package/dist/resources/validationReportStore.d.ts.map +1 -0
- package/dist/resources/validationReportStore.js +515 -0
- package/dist/resources/validationReportStore.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +102 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/asyncValidation.d.ts +19 -0
- package/dist/tools/asyncValidation.d.ts.map +1 -0
- package/dist/tools/asyncValidation.js +346 -0
- package/dist/tools/asyncValidation.js.map +1 -0
- package/dist/tools/buildContext.d.ts +17 -0
- package/dist/tools/buildContext.d.ts.map +1 -0
- package/dist/tools/buildContext.js +188 -0
- package/dist/tools/buildContext.js.map +1 -0
- package/dist/tools/getDependencyGraph.d.ts +16 -0
- package/dist/tools/getDependencyGraph.d.ts.map +1 -0
- package/dist/tools/getDependencyGraph.js +436 -0
- package/dist/tools/getDependencyGraph.js.map +1 -0
- package/dist/tools/incrementalValidation.d.ts +71 -0
- package/dist/tools/incrementalValidation.d.ts.map +1 -0
- package/dist/tools/incrementalValidation.js +203 -0
- package/dist/tools/incrementalValidation.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/validateCode.d.ts +17 -0
- package/dist/tools/validateCode.d.ts.map +1 -0
- package/dist/tools/validateCode.js +368 -0
- package/dist/tools/validateCode.js.map +1 -0
- package/dist/tools/validateCodeLite.d.ts +2 -0
- package/dist/tools/validateCodeLite.d.ts.map +1 -0
- package/dist/tools/validateCodeLite.js +2 -0
- package/dist/tools/validateCodeLite.js.map +1 -0
- package/dist/tools/validation/builtins.d.ts +92 -0
- package/dist/tools/validation/builtins.d.ts.map +1 -0
- package/dist/tools/validation/builtins.js +2184 -0
- package/dist/tools/validation/builtins.js.map +1 -0
- package/dist/tools/validation/contextualNaming.d.ts +99 -0
- package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
- package/dist/tools/validation/contextualNaming.js +959 -0
- package/dist/tools/validation/contextualNaming.js.map +1 -0
- package/dist/tools/validation/deadCode.d.ts +115 -0
- package/dist/tools/validation/deadCode.d.ts.map +1 -0
- package/dist/tools/validation/deadCode.js +861 -0
- package/dist/tools/validation/deadCode.js.map +1 -0
- package/dist/tools/validation/extractors/index.d.ts +131 -0
- package/dist/tools/validation/extractors/index.d.ts.map +1 -0
- package/dist/tools/validation/extractors/index.js +233 -0
- package/dist/tools/validation/extractors/index.js.map +1 -0
- package/dist/tools/validation/extractors/javascript.d.ts +73 -0
- package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
- package/dist/tools/validation/extractors/javascript.js +1841 -0
- package/dist/tools/validation/extractors/javascript.js.map +1 -0
- package/dist/tools/validation/extractors/python.d.ts +93 -0
- package/dist/tools/validation/extractors/python.d.ts.map +1 -0
- package/dist/tools/validation/extractors/python.js +799 -0
- package/dist/tools/validation/extractors/python.js.map +1 -0
- package/dist/tools/validation/manifest.d.ts +45 -0
- package/dist/tools/validation/manifest.d.ts.map +1 -0
- package/dist/tools/validation/manifest.js +719 -0
- package/dist/tools/validation/manifest.js.map +1 -0
- package/dist/tools/validation/parser.d.ts +58 -0
- package/dist/tools/validation/parser.d.ts.map +1 -0
- package/dist/tools/validation/parser.js +232 -0
- package/dist/tools/validation/parser.js.map +1 -0
- package/dist/tools/validation/registry.d.ts +15 -0
- package/dist/tools/validation/registry.d.ts.map +1 -0
- package/dist/tools/validation/registry.js +169 -0
- package/dist/tools/validation/registry.js.map +1 -0
- package/dist/tools/validation/scoring.d.ts +54 -0
- package/dist/tools/validation/scoring.d.ts.map +1 -0
- package/dist/tools/validation/scoring.js +242 -0
- package/dist/tools/validation/scoring.js.map +1 -0
- package/dist/tools/validation/types.d.ts +120 -0
- package/dist/tools/validation/types.d.ts.map +1 -0
- package/dist/tools/validation/types.js +11 -0
- package/dist/tools/validation/types.js.map +1 -0
- package/dist/tools/validation/unusedLocals.d.ts +36 -0
- package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
- package/dist/tools/validation/unusedLocals.js +333 -0
- package/dist/tools/validation/unusedLocals.js.map +1 -0
- package/dist/tools/validation/validation.d.ts +98 -0
- package/dist/tools/validation/validation.d.ts.map +1 -0
- package/dist/tools/validation/validation.js +1837 -0
- package/dist/tools/validation/validation.js.map +1 -0
- package/dist/types/codeGraph.d.ts +163 -0
- package/dist/types/codeGraph.d.ts.map +1 -0
- package/dist/types/codeGraph.js +9 -0
- package/dist/types/codeGraph.js.map +1 -0
- package/dist/types/symbolGraph.d.ts +68 -0
- package/dist/types/symbolGraph.d.ts.map +1 -0
- package/dist/types/symbolGraph.js +10 -0
- package/dist/types/symbolGraph.js.map +1 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +7 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/fileFilter.d.ts +37 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +91 -0
- package/dist/utils/fileFilter.js.map +1 -0
- package/dist/utils/gitUtils.d.ts +28 -0
- package/dist/utils/gitUtils.d.ts.map +1 -0
- package/dist/utils/gitUtils.js +81 -0
- package/dist/utils/gitUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/serialization.d.ts +25 -0
- package/dist/utils/serialization.d.ts.map +1 -0
- package/dist/utils/serialization.js +53 -0
- package/dist/utils/serialization.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Validator - Proactive Agent Mode
|
|
3
|
+
*
|
|
4
|
+
* Automatically validates code when files change.
|
|
5
|
+
* Formats issues for LLM consumption and triggers MCP sampling.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "fs/promises";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { FileWatcher } from "./fileWatcher.js";
|
|
12
|
+
import { orchestrateContext } from "../context/contextOrchestrator.js";
|
|
13
|
+
import { refreshFileContext, markGuardianActive, markGuardianInactive } from "../context/projectContext.js";
|
|
14
|
+
import { extractUsagesAST, extractImportsAST, extractImportsASTWithOptions, extractTypeReferencesAST, } from "../tools/validation/extractors/index.js";
|
|
15
|
+
import { loadManifestDependencies, loadPythonModuleExports, } from "../tools/validation/manifest.js";
|
|
16
|
+
import { extractSymbolsAST, } from "../tools/validation/extractors/index.js";
|
|
17
|
+
import { validateManifest, validateSymbols, buildSymbolTable, validateUsagePatterns, } from "../tools/validation/validation.js";
|
|
18
|
+
import { detectDeadCode, detectUnusedLocals } from "../tools/validation/deadCode.js";
|
|
19
|
+
import { impactAnalyzer } from "../analyzers/impactAnalyzer.js";
|
|
20
|
+
import { logger } from "../utils/logger.js";
|
|
21
|
+
import { PROMPT_PATTERNS, VALIDATION_CONSTRAINTS } from "../prompts/library.js";
|
|
22
|
+
import { generateAntiPatternContext, enrichIssuesWithAntiPatterns } from "../analyzers/antiPatterns.js";
|
|
23
|
+
import { verifyFindingsAutomatically, getConfirmedFindings, } from "../analyzers/findingVerifier.js";
|
|
24
|
+
import { validateApiContracts, } from "../api-contract/index.js";
|
|
25
|
+
export class AutoValidator {
|
|
26
|
+
watcher;
|
|
27
|
+
projectPath;
|
|
28
|
+
language;
|
|
29
|
+
onAlert = null;
|
|
30
|
+
debounceTimers = new Map();
|
|
31
|
+
mode = "auto";
|
|
32
|
+
projectFileCount = 0;
|
|
33
|
+
newFilesTracked = new Set();
|
|
34
|
+
manifest = null;
|
|
35
|
+
pythonExports = new Map();
|
|
36
|
+
agentName;
|
|
37
|
+
projectStructure;
|
|
38
|
+
isFullStack = false;
|
|
39
|
+
tsManifest = null;
|
|
40
|
+
pyManifest = null;
|
|
41
|
+
pendingRefreshes = new Map();
|
|
42
|
+
lastApiContractValidation = 0;
|
|
43
|
+
static API_CONTRACT_DEBOUNCE_MS = 30_000; // At most once per 30s
|
|
44
|
+
activeValidationCount = 0;
|
|
45
|
+
validationQueue = new Set();
|
|
46
|
+
activeRefreshCount = 0;
|
|
47
|
+
static MAX_CONCURRENT_VALIDATIONS = 2;
|
|
48
|
+
static MAX_CONCURRENT_REFRESHES = 3;
|
|
49
|
+
// Thresholds for smart mode
|
|
50
|
+
static NEW_PROJECT_THRESHOLD = 5; // Less than 5 files = new project
|
|
51
|
+
static LEARNING_MODE_FILE_COUNT = 10; // After 10 files created, switch to strict
|
|
52
|
+
// Common path patterns for scope detection
|
|
53
|
+
static FRONTEND_PATTERNS = [
|
|
54
|
+
'/frontend/', '/client/', '/web/', '/app/', '/src/',
|
|
55
|
+
'/components/', '/pages/', '/views/', '/hooks/', '/services/'
|
|
56
|
+
];
|
|
57
|
+
static BACKEND_PATTERNS = [
|
|
58
|
+
'/backend/', '/server/', '/api/', '/services/',
|
|
59
|
+
'/routes/', '/routers/', '/controllers/', '/models/', '/db/'
|
|
60
|
+
];
|
|
61
|
+
static SHARED_PATTERNS = [
|
|
62
|
+
'/shared/', '/common/', '/types/', '/interfaces/', '/utils/'
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Detect language from file extension for per-file validation
|
|
66
|
+
*/
|
|
67
|
+
static detectFileLanguage(filePath) {
|
|
68
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
69
|
+
const map = {
|
|
70
|
+
".js": "javascript",
|
|
71
|
+
".jsx": "javascript",
|
|
72
|
+
".mjs": "javascript",
|
|
73
|
+
".cjs": "javascript",
|
|
74
|
+
".ts": "typescript",
|
|
75
|
+
".tsx": "typescript",
|
|
76
|
+
".mts": "typescript",
|
|
77
|
+
".cts": "typescript",
|
|
78
|
+
".py": "python",
|
|
79
|
+
};
|
|
80
|
+
return map[ext] || "unknown";
|
|
81
|
+
}
|
|
82
|
+
constructor(projectPath, language = "typescript", mode = "auto", agentName = "The Guardian") {
|
|
83
|
+
this.projectPath = projectPath;
|
|
84
|
+
this.language = language;
|
|
85
|
+
this.mode = mode;
|
|
86
|
+
this.agentName = agentName;
|
|
87
|
+
this.watcher = new FileWatcher(projectPath);
|
|
88
|
+
this.watcher.on("fileChange", (event) => {
|
|
89
|
+
this.handleFileChange(event);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
setAlertHandler(handler) {
|
|
93
|
+
this.onAlert = handler;
|
|
94
|
+
}
|
|
95
|
+
async start() {
|
|
96
|
+
logger.info(`Starting AutoValidator for: ${this.projectPath}`);
|
|
97
|
+
// Silent initialization by default to avoid UI clutter
|
|
98
|
+
/*
|
|
99
|
+
await sendNotification("guardian_starting", {
|
|
100
|
+
message: `🔄 ${this.agentName}: Initialization started...`,
|
|
101
|
+
projectPath: this.projectPath,
|
|
102
|
+
language: this.language,
|
|
103
|
+
});
|
|
104
|
+
*/
|
|
105
|
+
// Pre-build context: detect full-stack and use "all" to include every language
|
|
106
|
+
logger.info("Loading project context...");
|
|
107
|
+
this.isFullStack = await this.detectFullStackProject();
|
|
108
|
+
const contextLanguage = this.isFullStack ? "all" : this.language;
|
|
109
|
+
if (this.isFullStack) {
|
|
110
|
+
logger.info("Full-stack project detected — building unified multi-language context");
|
|
111
|
+
}
|
|
112
|
+
const orchestration = await orchestrateContext({
|
|
113
|
+
projectPath: this.projectPath,
|
|
114
|
+
language: contextLanguage,
|
|
115
|
+
});
|
|
116
|
+
const context = orchestration.projectContext;
|
|
117
|
+
logger.info("Context built.");
|
|
118
|
+
// Mark this project as guardian-managed — all tools will now
|
|
119
|
+
// use this cached context without TTL/staleness rebuilds
|
|
120
|
+
markGuardianActive(this.projectPath);
|
|
121
|
+
// Detect project type
|
|
122
|
+
this.projectFileCount = context.files?.size || 0;
|
|
123
|
+
const detectedMode = this.detectProjectMode();
|
|
124
|
+
logger.info(`VibeGuard Initialized: Found ${this.projectFileCount} files in ${this.projectPath}`);
|
|
125
|
+
logger.info(`Operating Mode: ${detectedMode} (Language: ${this.isFullStack ? "all (full-stack)" : this.language})`);
|
|
126
|
+
// Load manifests for all detected languages
|
|
127
|
+
logger.info("Loading dependency manifests...");
|
|
128
|
+
if (this.isFullStack) {
|
|
129
|
+
// Full-stack: load manifests from the correct subdirectories
|
|
130
|
+
// (e.g., frontend/package.json and backend/requirements.txt)
|
|
131
|
+
const tsRoot = this.projectStructure?.frontend || this.projectPath;
|
|
132
|
+
const pyRoot = this.projectStructure?.backend || this.projectPath;
|
|
133
|
+
this.tsManifest = await loadManifestDependencies(tsRoot, "typescript");
|
|
134
|
+
this.pyManifest = await loadManifestDependencies(pyRoot, "python");
|
|
135
|
+
this.pythonExports = await loadPythonModuleExports(pyRoot);
|
|
136
|
+
// Keep this.manifest as the "primary" for backward compat
|
|
137
|
+
this.manifest = this.language === "python" ? this.pyManifest : this.tsManifest;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.manifest = await loadManifestDependencies(this.projectPath, this.language);
|
|
141
|
+
if (this.language === "python") {
|
|
142
|
+
this.pythonExports = await loadPythonModuleExports(this.projectPath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Run initial health check on existing codebase (skip for new projects)
|
|
146
|
+
if (detectedMode !== "learning") {
|
|
147
|
+
logger.info("Running initial health check...");
|
|
148
|
+
await this.runInitialHealthCheck(context);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
logger.info("New project detected - skipping initial health check, entering Learning Mode");
|
|
152
|
+
this.pushLearningModeNotification();
|
|
153
|
+
}
|
|
154
|
+
logger.info("Starting file watcher...");
|
|
155
|
+
this.watcher.start();
|
|
156
|
+
// Silent ready status to avoid UI clutter
|
|
157
|
+
/*
|
|
158
|
+
await sendNotification("guardian_ready", {
|
|
159
|
+
message: `✅ ${this.agentName}: Ready and watching ${this.projectFileCount} files!`,
|
|
160
|
+
fileCount: this.projectFileCount,
|
|
161
|
+
mode: detectedMode,
|
|
162
|
+
projectPath: this.projectPath,
|
|
163
|
+
});
|
|
164
|
+
*/
|
|
165
|
+
}
|
|
166
|
+
detectProjectMode() {
|
|
167
|
+
if (this.mode !== "auto")
|
|
168
|
+
return this.mode;
|
|
169
|
+
if (this.projectFileCount < AutoValidator.NEW_PROJECT_THRESHOLD) {
|
|
170
|
+
return "learning";
|
|
171
|
+
}
|
|
172
|
+
return "strict";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Detect if this is a full-stack project with both frontend (TS/JS) and backend (Python) code.
|
|
176
|
+
* Checks for common directory structures and file extensions.
|
|
177
|
+
*/
|
|
178
|
+
async detectFullStackProject() {
|
|
179
|
+
const fs = await import("fs/promises");
|
|
180
|
+
let hasPython = false;
|
|
181
|
+
let hasTypeScript = false;
|
|
182
|
+
let pythonRoot;
|
|
183
|
+
let tsRoot;
|
|
184
|
+
// Quick heuristic: check for common full-stack markers at project root
|
|
185
|
+
const markers = [
|
|
186
|
+
{ path: "requirements.txt", lang: "python" },
|
|
187
|
+
{ path: "pyproject.toml", lang: "python" },
|
|
188
|
+
{ path: "Pipfile", lang: "python" },
|
|
189
|
+
{ path: "package.json", lang: "typescript" },
|
|
190
|
+
{ path: "tsconfig.json", lang: "typescript" },
|
|
191
|
+
];
|
|
192
|
+
for (const marker of markers) {
|
|
193
|
+
try {
|
|
194
|
+
await fs.access(path.join(this.projectPath, marker.path));
|
|
195
|
+
if (marker.lang === "python")
|
|
196
|
+
hasPython = true;
|
|
197
|
+
if (marker.lang === "typescript")
|
|
198
|
+
hasTypeScript = true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Not found
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Helper to detect language of a subdirectory based on its manifest files
|
|
205
|
+
const detectDirLanguage = async (dirPath) => {
|
|
206
|
+
let dirHasPython = false;
|
|
207
|
+
let dirHasTS = false;
|
|
208
|
+
for (const m of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
|
|
209
|
+
try {
|
|
210
|
+
await fs.access(path.join(dirPath, m));
|
|
211
|
+
dirHasPython = true;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
catch { /* Not found */ }
|
|
215
|
+
}
|
|
216
|
+
for (const m of ["package.json", "tsconfig.json"]) {
|
|
217
|
+
try {
|
|
218
|
+
await fs.access(path.join(dirPath, m));
|
|
219
|
+
dirHasTS = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
catch { /* Not found */ }
|
|
223
|
+
}
|
|
224
|
+
if (dirHasPython && dirHasTS)
|
|
225
|
+
return "both";
|
|
226
|
+
if (dirHasPython)
|
|
227
|
+
return "python";
|
|
228
|
+
if (dirHasTS)
|
|
229
|
+
return "typescript";
|
|
230
|
+
return "unknown";
|
|
231
|
+
};
|
|
232
|
+
// Check common subdirectory patterns — detect ACTUAL language from manifests,
|
|
233
|
+
// not from directory name (a "backend/" can be Python OR TypeScript)
|
|
234
|
+
const dirCandidates = [
|
|
235
|
+
{ path: "backend", role: "backend" },
|
|
236
|
+
{ path: "server", role: "backend" },
|
|
237
|
+
{ path: "frontend", role: "frontend" },
|
|
238
|
+
{ path: "client", role: "frontend" },
|
|
239
|
+
];
|
|
240
|
+
for (const check of dirCandidates) {
|
|
241
|
+
try {
|
|
242
|
+
const dirPath = path.join(this.projectPath, check.path);
|
|
243
|
+
const stat = await fs.stat(dirPath);
|
|
244
|
+
if (stat.isDirectory()) {
|
|
245
|
+
const lang = await detectDirLanguage(dirPath);
|
|
246
|
+
if (lang === "python" || lang === "both") {
|
|
247
|
+
hasPython = true;
|
|
248
|
+
if (!pythonRoot)
|
|
249
|
+
pythonRoot = dirPath;
|
|
250
|
+
}
|
|
251
|
+
if (lang === "typescript" || lang === "both") {
|
|
252
|
+
hasTypeScript = true;
|
|
253
|
+
// For TS: prefer frontend-role dirs as tsRoot, backend-role dirs as separate
|
|
254
|
+
if (check.role === "frontend" && !tsRoot) {
|
|
255
|
+
tsRoot = dirPath;
|
|
256
|
+
}
|
|
257
|
+
else if (check.role === "backend" && !tsRoot) {
|
|
258
|
+
// Backend is also TS — tsRoot tracks the frontend for structure
|
|
259
|
+
// but we still know it's TypeScript on both sides (NOT full-stack Python+TS)
|
|
260
|
+
tsRoot = tsRoot || dirPath;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Not found
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Store detected structure for manifest loading and scope resolution
|
|
270
|
+
if (hasPython && hasTypeScript) {
|
|
271
|
+
this.projectStructure = {
|
|
272
|
+
backend: pythonRoot || this.projectPath,
|
|
273
|
+
frontend: tsRoot || this.projectPath,
|
|
274
|
+
};
|
|
275
|
+
logger.info(`Full-stack structure detected: backend=${pythonRoot || this.projectPath}, frontend=${tsRoot || this.projectPath}`);
|
|
276
|
+
}
|
|
277
|
+
return hasPython && hasTypeScript;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the correct manifest for a given file language
|
|
281
|
+
*/
|
|
282
|
+
getManifestForLanguage(fileLang) {
|
|
283
|
+
if (!this.isFullStack)
|
|
284
|
+
return this.manifest;
|
|
285
|
+
if (fileLang === "python")
|
|
286
|
+
return this.pyManifest;
|
|
287
|
+
return this.tsManifest; // typescript, javascript
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Detect the scope of a file (frontend, backend, shared, or unknown)
|
|
291
|
+
* Used for smart filtering of validation issues
|
|
292
|
+
*/
|
|
293
|
+
detectFileScope(filePath) {
|
|
294
|
+
const normalizedPath = filePath.toLowerCase();
|
|
295
|
+
// Check for shared patterns first (highest priority)
|
|
296
|
+
for (const pattern of AutoValidator.SHARED_PATTERNS) {
|
|
297
|
+
if (normalizedPath.includes(pattern)) {
|
|
298
|
+
return "shared";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Check for frontend patterns
|
|
302
|
+
for (const pattern of AutoValidator.FRONTEND_PATTERNS) {
|
|
303
|
+
if (normalizedPath.includes(pattern)) {
|
|
304
|
+
// But make sure it's not in a backend directory
|
|
305
|
+
const isBackend = AutoValidator.BACKEND_PATTERNS.some(p => normalizedPath.includes(p));
|
|
306
|
+
if (!isBackend) {
|
|
307
|
+
return "frontend";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Check for backend patterns
|
|
312
|
+
for (const pattern of AutoValidator.BACKEND_PATTERNS) {
|
|
313
|
+
if (normalizedPath.includes(pattern)) {
|
|
314
|
+
return "backend";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Detect by file extension and content patterns
|
|
318
|
+
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) {
|
|
319
|
+
return "frontend";
|
|
320
|
+
}
|
|
321
|
+
if (filePath.endsWith('.py') && !filePath.includes('test')) {
|
|
322
|
+
// Check if it's clearly a backend file
|
|
323
|
+
if (normalizedPath.includes('main.py') || normalizedPath.includes('app.py')) {
|
|
324
|
+
return "backend";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return "unknown";
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Filter issues based on file scope for smart alerting
|
|
331
|
+
* Shows all critical issues, but filters lower severity by relevance
|
|
332
|
+
*/
|
|
333
|
+
filterIssuesByScope(issues, fileScope, isLenient) {
|
|
334
|
+
if (isLenient) {
|
|
335
|
+
// In lenient mode, show critical/high severity issues
|
|
336
|
+
// PLUS keep issue types that the later lenient filter explicitly preserves:
|
|
337
|
+
// - unusedImport, unusedFunction, unusedExport (per-file code hygiene)
|
|
338
|
+
// - dependencyHallucination, missingDependency (build-breaking or suspicious)
|
|
339
|
+
return issues.filter(issue => issue.severity === 'critical' ||
|
|
340
|
+
issue.severity === 'high' ||
|
|
341
|
+
issue.type === 'unusedImport' ||
|
|
342
|
+
issue.type === 'unusedFunction' ||
|
|
343
|
+
issue.type === 'unusedExport' ||
|
|
344
|
+
issue.type === 'dependencyHallucination' ||
|
|
345
|
+
issue.type === 'missingDependency');
|
|
346
|
+
}
|
|
347
|
+
return issues.filter(issue => {
|
|
348
|
+
// Always show critical issues
|
|
349
|
+
if (issue.severity === 'critical')
|
|
350
|
+
return true;
|
|
351
|
+
// Always show API contract mismatches (they affect both sides)
|
|
352
|
+
if (issue.type === 'apiContractMismatch')
|
|
353
|
+
return true;
|
|
354
|
+
// For high severity, show if relevant to scope
|
|
355
|
+
if (issue.severity === 'high') {
|
|
356
|
+
// If we can't determine scope, show all high severity
|
|
357
|
+
if (fileScope === 'unknown')
|
|
358
|
+
return true;
|
|
359
|
+
// Dead code in shared files affects everyone
|
|
360
|
+
if (issue.type === 'deadCode' && fileScope === 'shared')
|
|
361
|
+
return true;
|
|
362
|
+
// Unused imports are always relevant
|
|
363
|
+
if (issue.type === 'unusedImport')
|
|
364
|
+
return true;
|
|
365
|
+
return true; // Show other high severity issues
|
|
366
|
+
}
|
|
367
|
+
// For medium/low severity, be more selective
|
|
368
|
+
if (fileScope === 'frontend') {
|
|
369
|
+
// In frontend files, prioritize frontend-specific issues
|
|
370
|
+
if (issue.type === 'unusedImport')
|
|
371
|
+
return true;
|
|
372
|
+
if (issue.type === 'nonExistentFunction')
|
|
373
|
+
return true;
|
|
374
|
+
return false; // Filter out less relevant issues
|
|
375
|
+
}
|
|
376
|
+
if (fileScope === 'backend') {
|
|
377
|
+
// In backend files, prioritize backend-specific issues
|
|
378
|
+
if (issue.type === 'unusedImport')
|
|
379
|
+
return true;
|
|
380
|
+
if (issue.type === 'nonExistentFunction')
|
|
381
|
+
return true;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
pushLearningModeNotification() {
|
|
388
|
+
const alert = {
|
|
389
|
+
file: "SYSTEM",
|
|
390
|
+
issues: [],
|
|
391
|
+
timestamp: Date.now(),
|
|
392
|
+
llmMessage: `📚 **${this.agentName}: Learning Mode Active**\n\nThis appears to be a new project (${this.projectFileCount} files). I'll be lenient and learn your patterns as you build.\n\nOnce you have more than ${AutoValidator.LEARNING_MODE_FILE_COUNT} files, I'll switch to strict validation.`,
|
|
393
|
+
isInitialScan: true,
|
|
394
|
+
};
|
|
395
|
+
if (this.onAlert) {
|
|
396
|
+
this.onAlert(alert);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async runInitialHealthCheck(context) {
|
|
400
|
+
try {
|
|
401
|
+
// Run API Contract validation
|
|
402
|
+
logger.info("Running API Contract validation...");
|
|
403
|
+
const apiContractResult = await validateApiContracts(this.projectPath);
|
|
404
|
+
if (apiContractResult.issues.length > 0) {
|
|
405
|
+
const apiContractAlert = {
|
|
406
|
+
file: "API_CONTRACT_SCAN",
|
|
407
|
+
issues: apiContractResult.issues.map((issue) => ({
|
|
408
|
+
type: issue.type,
|
|
409
|
+
severity: issue.severity,
|
|
410
|
+
message: issue.message,
|
|
411
|
+
suggestion: issue.suggestion,
|
|
412
|
+
line: issue.line,
|
|
413
|
+
file: issue.file ? path.relative(this.projectPath, issue.file) : undefined,
|
|
414
|
+
})),
|
|
415
|
+
timestamp: Date.now(),
|
|
416
|
+
llmMessage: this.createApiContractMessage(apiContractResult),
|
|
417
|
+
isInitialScan: true,
|
|
418
|
+
};
|
|
419
|
+
logger.info(`API Contract scan found ${apiContractResult.issues.length} issues`);
|
|
420
|
+
if (this.onAlert) {
|
|
421
|
+
this.onAlert(apiContractAlert);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Run dead code detection on existing codebase
|
|
425
|
+
// For full-stack projects, run per-scope to avoid hitting the export cap
|
|
426
|
+
let deadCodeIssues;
|
|
427
|
+
if (this.isFullStack && this.projectStructure) {
|
|
428
|
+
const backendRoot = this.projectStructure.backend;
|
|
429
|
+
const frontendRoot = this.projectStructure.frontend;
|
|
430
|
+
logger.info(`Running dead code detection per-scope: backend=${backendRoot}, frontend=${frontendRoot}`);
|
|
431
|
+
const [backendDead, frontendDead] = await Promise.all([
|
|
432
|
+
detectDeadCode(context, undefined, (fp) => fp.startsWith(backendRoot)),
|
|
433
|
+
detectDeadCode(context, undefined, (fp) => fp.startsWith(frontendRoot)),
|
|
434
|
+
]);
|
|
435
|
+
deadCodeIssues = [...backendDead, ...frontendDead];
|
|
436
|
+
logger.info(`Dead code per-scope: backend=${backendDead.length}, frontend=${frontendDead.length}`);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
deadCodeIssues = await detectDeadCode(context);
|
|
440
|
+
}
|
|
441
|
+
// Verify findings to eliminate false positives
|
|
442
|
+
// Use "all" for full-stack projects so verification understands both languages
|
|
443
|
+
let confirmedDeadCode = deadCodeIssues;
|
|
444
|
+
const verifyLang = this.isFullStack ? "all" : this.language;
|
|
445
|
+
if (deadCodeIssues.length > 0) {
|
|
446
|
+
logger.debug(`Verifying ${deadCodeIssues.length} dead code findings...`);
|
|
447
|
+
const verificationResult = await verifyFindingsAutomatically([], deadCodeIssues, context, this.projectPath, verifyLang);
|
|
448
|
+
confirmedDeadCode = getConfirmedFindings(verificationResult).deadCode;
|
|
449
|
+
logger.debug(`Verification complete: ${confirmedDeadCode.length} confirmed (filtered ${verificationResult.stats.falsePositiveCount} false positives)`);
|
|
450
|
+
}
|
|
451
|
+
if (confirmedDeadCode.length > 0) {
|
|
452
|
+
const alert = {
|
|
453
|
+
file: "INITIAL_SCAN",
|
|
454
|
+
issues: confirmedDeadCode.map((dc) => ({
|
|
455
|
+
type: "deadCode",
|
|
456
|
+
severity: dc.severity,
|
|
457
|
+
message: dc.message,
|
|
458
|
+
suggestion: dc.suggestion,
|
|
459
|
+
line: dc.line,
|
|
460
|
+
file: dc.file ? path.relative(this.projectPath, dc.file) : undefined,
|
|
461
|
+
})),
|
|
462
|
+
timestamp: Date.now(),
|
|
463
|
+
llmMessage: this.createInitialScanMessage(confirmedDeadCode),
|
|
464
|
+
isInitialScan: true,
|
|
465
|
+
};
|
|
466
|
+
logger.info(`Initial scan found ${confirmedDeadCode.length} confirmed issues in existing codebase`);
|
|
467
|
+
if (this.onAlert) {
|
|
468
|
+
this.onAlert(alert);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
logger.info("Initial scan: No issues found in existing codebase");
|
|
473
|
+
}
|
|
474
|
+
// Phase 3: Per-file hallucination and local dead code scan
|
|
475
|
+
// This catches issues that project-wide scans miss:
|
|
476
|
+
// - Hallucinated imports (packages not in package.json / requirements.txt)
|
|
477
|
+
// - Unused local functions/constants (non-exported dead code)
|
|
478
|
+
// The project-wide scan only finds unused EXPORTS and orphaned files.
|
|
479
|
+
// This per-file scan finds hallucinated dependencies and local dead code.
|
|
480
|
+
logger.info("Running per-file hallucination and local dead code scan...");
|
|
481
|
+
const perFileHallucinations = [];
|
|
482
|
+
const perFileDeadCode = [];
|
|
483
|
+
const MAX_INITIAL_FILES_TO_SCAN = 100;
|
|
484
|
+
let filesScanned = 0;
|
|
485
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
486
|
+
if (filesScanned >= MAX_INITIAL_FILES_TO_SCAN)
|
|
487
|
+
break;
|
|
488
|
+
if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
|
|
489
|
+
continue;
|
|
490
|
+
const fileLang = AutoValidator.detectFileLanguage(filePath);
|
|
491
|
+
if (fileLang === "unknown")
|
|
492
|
+
continue;
|
|
493
|
+
try {
|
|
494
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
495
|
+
filesScanned++;
|
|
496
|
+
// Tier 0: Manifest validation — catches hallucinated imports
|
|
497
|
+
const imports = extractImportsAST(content, fileLang);
|
|
498
|
+
const fileManifest = this.getManifestForLanguage(fileLang);
|
|
499
|
+
if (fileManifest) {
|
|
500
|
+
const manifestIssues = await validateManifest(imports, fileManifest, content, fileLang, filePath);
|
|
501
|
+
for (const issue of manifestIssues) {
|
|
502
|
+
// Attach file path so the verifier can read the file
|
|
503
|
+
issue.file = filePath;
|
|
504
|
+
perFileHallucinations.push(issue);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Tier 2: Local dead code — catches unused non-exported functions/constants
|
|
508
|
+
const localDeadCode = detectUnusedLocals(content, filePath);
|
|
509
|
+
perFileDeadCode.push(...localDeadCode);
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
logger.debug(`Skipping initial scan of ${filePath}: ${err}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
logger.info(`Per-file scan complete: ${filesScanned} files, ${perFileHallucinations.length} hallucinations, ${perFileDeadCode.length} local dead code`);
|
|
516
|
+
// Verify per-file findings to eliminate false positives
|
|
517
|
+
const allPerFileFindings = [...perFileHallucinations, ...perFileDeadCode];
|
|
518
|
+
if (allPerFileFindings.length > 0) {
|
|
519
|
+
logger.debug(`Verifying ${allPerFileFindings.length} per-file findings...`);
|
|
520
|
+
const perFileVerification = await verifyFindingsAutomatically(perFileHallucinations, perFileDeadCode, context, this.projectPath, verifyLang);
|
|
521
|
+
const confirmedPerFile = getConfirmedFindings(perFileVerification);
|
|
522
|
+
const confirmedPerFileAll = [...confirmedPerFile.hallucinations, ...confirmedPerFile.deadCode];
|
|
523
|
+
logger.debug(`Per-file verification: ${confirmedPerFileAll.length} confirmed (filtered ${perFileVerification.stats.falsePositiveCount} false positives)`);
|
|
524
|
+
if (confirmedPerFileAll.length > 0) {
|
|
525
|
+
const perFileAlert = {
|
|
526
|
+
file: "INITIAL_FILE_SCAN",
|
|
527
|
+
issues: confirmedPerFileAll.map((issue) => ({
|
|
528
|
+
type: issue.type || "unknown",
|
|
529
|
+
severity: issue.severity || "medium",
|
|
530
|
+
message: issue.message || "",
|
|
531
|
+
suggestion: issue.suggestion,
|
|
532
|
+
line: issue.line,
|
|
533
|
+
file: issue.file ? path.relative(this.projectPath, issue.file) : undefined,
|
|
534
|
+
})),
|
|
535
|
+
timestamp: Date.now(),
|
|
536
|
+
llmMessage: this.createPerFileScanMessage(confirmedPerFile.hallucinations, confirmedPerFile.deadCode),
|
|
537
|
+
isInitialScan: true,
|
|
538
|
+
};
|
|
539
|
+
logger.info(`Per-file scan found ${confirmedPerFileAll.length} confirmed issues`);
|
|
540
|
+
if (this.onAlert) {
|
|
541
|
+
this.onAlert(perFileAlert);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
logger.error("Error running initial health check:", err);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
createInitialScanMessage(deadCodeIssues) {
|
|
551
|
+
return `📋 **${this.agentName}: Initial Scan Complete** - Found **${deadCodeIssues.length} potential issues** in existing codebase. Use 'get_guardian_alerts' to see details.`;
|
|
552
|
+
}
|
|
553
|
+
createPerFileScanMessage(hallucinations, deadCode) {
|
|
554
|
+
const parts = [];
|
|
555
|
+
parts.push(`🔍 **${this.agentName}: Per-File Scan Complete**`);
|
|
556
|
+
parts.push("");
|
|
557
|
+
if (hallucinations.length > 0) {
|
|
558
|
+
parts.push(`**${hallucinations.length} Hallucinated Import(s):**`);
|
|
559
|
+
for (const h of hallucinations.slice(0, 5)) {
|
|
560
|
+
const relFile = path.relative(this.projectPath, h.file || "");
|
|
561
|
+
parts.push(`- [${h.severity?.toUpperCase()}] ${h.message}${relFile ? ` in \`${relFile}\`` : ""}`);
|
|
562
|
+
if (h.suggestion)
|
|
563
|
+
parts.push(` Suggestion: ${h.suggestion}`);
|
|
564
|
+
}
|
|
565
|
+
if (hallucinations.length > 5) {
|
|
566
|
+
parts.push(` ... and ${hallucinations.length - 5} more`);
|
|
567
|
+
}
|
|
568
|
+
parts.push("");
|
|
569
|
+
}
|
|
570
|
+
if (deadCode.length > 0) {
|
|
571
|
+
parts.push(`**${deadCode.length} Unused Local Function(s)/Constant(s):**`);
|
|
572
|
+
for (const dc of deadCode.slice(0, 5)) {
|
|
573
|
+
const relFile = path.relative(this.projectPath, dc.file || "");
|
|
574
|
+
parts.push(`- [${dc.severity?.toUpperCase()}] ${dc.message}${relFile ? ` in \`${relFile}\`` : ""}`);
|
|
575
|
+
}
|
|
576
|
+
if (deadCode.length > 5) {
|
|
577
|
+
parts.push(` ... and ${deadCode.length - 5} more`);
|
|
578
|
+
}
|
|
579
|
+
parts.push("");
|
|
580
|
+
}
|
|
581
|
+
parts.push("Use 'get_guardian_alerts' to see all details.");
|
|
582
|
+
return parts.join("\n");
|
|
583
|
+
}
|
|
584
|
+
createApiContractMessage(result) {
|
|
585
|
+
const lines = [];
|
|
586
|
+
lines.push(`🔗 **${this.agentName}: API Contract Validation**`);
|
|
587
|
+
lines.push("");
|
|
588
|
+
lines.push(`Found **${result.issues.length}** API contract issues:`);
|
|
589
|
+
lines.push(`- 🔴 Critical: ${result.summary.critical}`);
|
|
590
|
+
lines.push(`- 🟠 High: ${result.summary.high}`);
|
|
591
|
+
lines.push(`- 🟡 Medium: ${result.summary.medium}`);
|
|
592
|
+
lines.push(`- 🟢 Low: ${result.summary.low}`);
|
|
593
|
+
lines.push("");
|
|
594
|
+
lines.push(`📊 Matched Endpoints: ${result.summary.matchedEndpoints}`);
|
|
595
|
+
lines.push(`📊 Matched Types: ${result.summary.matchedTypes}`);
|
|
596
|
+
lines.push("");
|
|
597
|
+
if (result.summary.critical > 0) {
|
|
598
|
+
lines.push("**Critical Issues (Fix Immediately):**");
|
|
599
|
+
const critical = result.issues.filter((i) => i.severity === "critical").slice(0, 3);
|
|
600
|
+
critical.forEach((issue) => {
|
|
601
|
+
lines.push(`- ${issue.message}`);
|
|
602
|
+
lines.push(` Suggestion: ${issue.suggestion}`);
|
|
603
|
+
});
|
|
604
|
+
lines.push("");
|
|
605
|
+
}
|
|
606
|
+
if (result.summary.unmatchedFrontend > 0) {
|
|
607
|
+
lines.push(`⚠️ **${result.summary.unmatchedFrontend} frontend services** don't have matching backend routes`);
|
|
608
|
+
}
|
|
609
|
+
if (result.summary.unmatchedBackend > 0) {
|
|
610
|
+
lines.push(`⚠️ **${result.summary.unmatchedBackend} backend routes** are not used by frontend`);
|
|
611
|
+
}
|
|
612
|
+
return lines.join("\n");
|
|
613
|
+
}
|
|
614
|
+
stop() {
|
|
615
|
+
this.watcher.stop();
|
|
616
|
+
this.debounceTimers.forEach((timer) => clearTimeout(timer));
|
|
617
|
+
this.debounceTimers.clear();
|
|
618
|
+
markGuardianInactive(this.projectPath);
|
|
619
|
+
}
|
|
620
|
+
handleFileChange(event) {
|
|
621
|
+
// Detect language for this specific file (full-stack aware)
|
|
622
|
+
const fileLang = AutoValidator.detectFileLanguage(event.path);
|
|
623
|
+
const refreshLang = this.isFullStack ? "all" : this.language;
|
|
624
|
+
// Track new files and refresh context — store promise so validateFile can await it.
|
|
625
|
+
// Throttle concurrent refreshes to avoid spawning too many git subprocesses.
|
|
626
|
+
let refreshPromise = null;
|
|
627
|
+
if (event.type === "add") {
|
|
628
|
+
this.newFilesTracked.add(event.path);
|
|
629
|
+
this.projectFileCount++;
|
|
630
|
+
logger.debug(`New file detected: ${event.path} (${fileLang}) - refreshing context...`);
|
|
631
|
+
refreshPromise = this.throttledRefresh(event.path, refreshLang);
|
|
632
|
+
// Check if we should exit learning mode
|
|
633
|
+
if (this.mode === "auto" && this.projectFileCount >= AutoValidator.LEARNING_MODE_FILE_COUNT) {
|
|
634
|
+
// Only switch if we were previously in learning mode (implied by auto + threshold)
|
|
635
|
+
// But for simplicity, we just let the next detectProjectMode call handle it
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else if (event.type === "change") {
|
|
639
|
+
logger.debug(`File modified: ${event.path} (${fileLang}) - refreshing context...`);
|
|
640
|
+
refreshPromise = this.throttledRefresh(event.path, refreshLang);
|
|
641
|
+
}
|
|
642
|
+
else if (event.type === "unlink") {
|
|
643
|
+
this.newFilesTracked.delete(event.path);
|
|
644
|
+
this.projectFileCount = Math.max(0, this.projectFileCount - 1);
|
|
645
|
+
logger.debug(`File deleted: ${event.path} (${fileLang}) - removing from context...`);
|
|
646
|
+
refreshPromise = this.throttledRefresh(event.path, refreshLang);
|
|
647
|
+
// File is deleted — emit a clear alert immediately instead of trying to validate.
|
|
648
|
+
// This clears both the per-file alert and any matching initial scan issues.
|
|
649
|
+
const relativePath = path.relative(this.projectPath, event.path);
|
|
650
|
+
if (this.onAlert) {
|
|
651
|
+
const clearAlert = {
|
|
652
|
+
file: relativePath,
|
|
653
|
+
issues: [],
|
|
654
|
+
timestamp: Date.now(),
|
|
655
|
+
llmMessage: `🗑️ ${this.agentName}: File deleted - ${relativePath}. Issues cleared.`,
|
|
656
|
+
};
|
|
657
|
+
this.onAlert(clearAlert);
|
|
658
|
+
}
|
|
659
|
+
// Skip validation queueing for deleted files — nothing to validate
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
// Track the pending refresh so validateFile can await ALL pending refreshes
|
|
663
|
+
if (refreshPromise) {
|
|
664
|
+
this.pendingRefreshes.set(event.path, refreshPromise);
|
|
665
|
+
refreshPromise.finally(() => this.pendingRefreshes.delete(event.path));
|
|
666
|
+
}
|
|
667
|
+
// Debounce rapid changes to the same file
|
|
668
|
+
const existingTimer = this.debounceTimers.get(event.path);
|
|
669
|
+
if (existingTimer) {
|
|
670
|
+
clearTimeout(existingTimer);
|
|
671
|
+
}
|
|
672
|
+
const timer = setTimeout(() => {
|
|
673
|
+
this.enqueueValidation(event.path);
|
|
674
|
+
this.debounceTimers.delete(event.path);
|
|
675
|
+
}, 500);
|
|
676
|
+
this.debounceTimers.set(event.path, timer);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Throttle concurrent refreshFileContext calls.
|
|
680
|
+
* Without this, 30 file changes = 30 concurrent refreshes, each spawning
|
|
681
|
+
* git subprocesses and writing to disk simultaneously.
|
|
682
|
+
*/
|
|
683
|
+
async throttledRefresh(filePath, language) {
|
|
684
|
+
// Wait if too many refreshes are already running
|
|
685
|
+
while (this.activeRefreshCount >= AutoValidator.MAX_CONCURRENT_REFRESHES) {
|
|
686
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
687
|
+
}
|
|
688
|
+
this.activeRefreshCount++;
|
|
689
|
+
try {
|
|
690
|
+
await refreshFileContext(this.projectPath, filePath, { language });
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
logger.warn(`Failed to refresh context for ${filePath}:`, err);
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
this.activeRefreshCount--;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Enqueue a file for validation. Only one validation runs at a time.
|
|
701
|
+
* Without this, N concurrent validateFile calls each spawn git subprocesses,
|
|
702
|
+
* AST parsing, and verification — causing a subprocess storm that can hang the system.
|
|
703
|
+
*/
|
|
704
|
+
enqueueValidation(filePath) {
|
|
705
|
+
if (this.activeValidationCount >= AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
|
|
706
|
+
this.validationQueue.add(filePath);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
this.runValidation(filePath);
|
|
710
|
+
}
|
|
711
|
+
async runValidation(filePath) {
|
|
712
|
+
this.activeValidationCount++;
|
|
713
|
+
try {
|
|
714
|
+
await this.validateFile(filePath);
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
this.activeValidationCount--;
|
|
718
|
+
this.processValidationQueue();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
processValidationQueue() {
|
|
722
|
+
// Drain queue up to concurrency limit
|
|
723
|
+
while (this.validationQueue.size > 0 && this.activeValidationCount < AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
|
|
724
|
+
const next = this.validationQueue.values().next().value;
|
|
725
|
+
if (!next)
|
|
726
|
+
break;
|
|
727
|
+
this.validationQueue.delete(next);
|
|
728
|
+
this.runValidation(next);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async validateFile(filePath) {
|
|
732
|
+
try {
|
|
733
|
+
// Wait for ALL pending context refreshes to complete before validating.
|
|
734
|
+
// This prevents race conditions where validation runs on stale/partial context
|
|
735
|
+
// (e.g., reverseImportGraph missing entries because refreshFileContext hasn't finished).
|
|
736
|
+
if (this.pendingRefreshes.size > 0) {
|
|
737
|
+
logger.debug(`Waiting for ${this.pendingRefreshes.size} pending context refreshes...`);
|
|
738
|
+
await Promise.all(this.pendingRefreshes.values());
|
|
739
|
+
}
|
|
740
|
+
const isNewFile = this.newFilesTracked.has(filePath);
|
|
741
|
+
const mode = this.detectProjectMode();
|
|
742
|
+
const isLenient = mode === "learning" || isNewFile;
|
|
743
|
+
// Detect the correct language for THIS file (not the project-level language)
|
|
744
|
+
const fileLang = AutoValidator.detectFileLanguage(filePath);
|
|
745
|
+
if (fileLang === "unknown") {
|
|
746
|
+
logger.debug(`Skipping unknown language file: ${filePath}`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
750
|
+
const relativePath = path.relative(this.projectPath, filePath);
|
|
751
|
+
logger.info(`Auto-validating (${isLenient ? "Lenient" : "Strict"}) [${fileLang}]: ${relativePath}`);
|
|
752
|
+
// Use orchestrateContext with "all" for full-stack, or the file's language
|
|
753
|
+
const contextLanguage = this.isFullStack ? "all" : this.language;
|
|
754
|
+
const orchestration = await orchestrateContext({
|
|
755
|
+
projectPath: this.projectPath,
|
|
756
|
+
language: contextLanguage,
|
|
757
|
+
currentFile: filePath,
|
|
758
|
+
});
|
|
759
|
+
const context = orchestration.projectContext;
|
|
760
|
+
// Extract symbols and imports using the FILE's language (not the project language)
|
|
761
|
+
const imports = extractImportsASTWithOptions(content, fileLang, {
|
|
762
|
+
filePath,
|
|
763
|
+
});
|
|
764
|
+
const usedSymbols = extractUsagesAST(content, fileLang, imports, { filePath });
|
|
765
|
+
const symbolTable = buildSymbolTable(context, orchestration.relevantSymbols);
|
|
766
|
+
// Extract type references for unused import detection
|
|
767
|
+
// This is essential for TypeScript where imports might only be used as types
|
|
768
|
+
const typeReferences = fileLang === "typescript" || fileLang === "javascript" ?
|
|
769
|
+
extractTypeReferencesAST(content, fileLang, { filePath })
|
|
770
|
+
: [];
|
|
771
|
+
// Tier 0: Check manifest dependencies (use the correct manifest for this file's language)
|
|
772
|
+
let manifestIssues = [];
|
|
773
|
+
const fileManifest = this.getManifestForLanguage(fileLang);
|
|
774
|
+
if (fileManifest) {
|
|
775
|
+
manifestIssues = await validateManifest(imports, fileManifest, content, fileLang, filePath);
|
|
776
|
+
}
|
|
777
|
+
// Tier 1: Validate symbols (hallucination detection)
|
|
778
|
+
let symbolIssues = validateSymbols(usedSymbols, symbolTable, content, fileLang, false, // strictMode
|
|
779
|
+
imports, this.pythonExports, context, filePath, undefined, // missingPackages (calculated internally)
|
|
780
|
+
typeReferences);
|
|
781
|
+
// Validate usage patterns (architectural consistency)
|
|
782
|
+
let patternIssues = validateUsagePatterns(usedSymbols, context);
|
|
783
|
+
// Tier 1.5: Change Impact Analysis (Blast Radius) - Proactive Alerting
|
|
784
|
+
let impactIssues = [];
|
|
785
|
+
if (context.symbolGraph) {
|
|
786
|
+
const symbolsInFile = extractSymbolsAST(content, filePath, fileLang);
|
|
787
|
+
for (const sym of symbolsInFile) {
|
|
788
|
+
// Skip non-exported symbols and very short names to avoid noise
|
|
789
|
+
if (!sym.isExported || sym.name.length <= 2) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (context.symbolIndex.has(sym.name)) {
|
|
793
|
+
const blast = impactAnalyzer.traceBlastRadius(sym.name, context.symbolGraph, 2);
|
|
794
|
+
if (blast.severity === "high") {
|
|
795
|
+
impactIssues.push({
|
|
796
|
+
type: "architecturalDeviation",
|
|
797
|
+
severity: "high",
|
|
798
|
+
message: `Modifying '${sym.name}' has a HIGH project-wide impact affecting ${blast.affectedFiles.length} files.`,
|
|
799
|
+
line: sym.line,
|
|
800
|
+
suggestion: `Run 'get_dependency_graph' for '${sym.name}' to see the full list of affected symbols.`,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
// Tier 2: Per-file dead code checks (unused functions/constants in THIS file only).
|
|
807
|
+
// Project-wide checks (orphaned files, unused exports) run in the initial health check,
|
|
808
|
+
// NOT on every file change — they are slow, race-prone during rapid vibecoding,
|
|
809
|
+
// and produce false positives when the dependency graph is mid-update.
|
|
810
|
+
//
|
|
811
|
+
// Use detectUnusedLocals directly for per-file dead code detection.
|
|
812
|
+
// This finds unused local functions and constants within the file.
|
|
813
|
+
const deadCodeIssues = detectUnusedLocals(content, filePath);
|
|
814
|
+
// Tier 3: API Contract Validation (for service/route files)
|
|
815
|
+
// Debounced to at most once per 30s to avoid full project-wide scans on every keystroke
|
|
816
|
+
let apiContractIssues = [];
|
|
817
|
+
const isServiceFile = filePath.includes('/services/') || filePath.includes('/api/');
|
|
818
|
+
const isRouteFile = filePath.includes('/api/') && filePath.endsWith('.py');
|
|
819
|
+
const apiContractCooldown = Date.now() - this.lastApiContractValidation >= AutoValidator.API_CONTRACT_DEBOUNCE_MS;
|
|
820
|
+
if ((isServiceFile || isRouteFile) && apiContractCooldown) {
|
|
821
|
+
logger.debug(`API Contract file changed: ${relativePath} - running contract validation...`);
|
|
822
|
+
this.lastApiContractValidation = Date.now();
|
|
823
|
+
const apiContractResult = await validateApiContracts(this.projectPath);
|
|
824
|
+
// Only report critical and high severity issues for real-time validation
|
|
825
|
+
apiContractIssues = apiContractResult.issues
|
|
826
|
+
.filter(issue => issue.severity === 'critical' || issue.severity === 'high')
|
|
827
|
+
.map(issue => ({
|
|
828
|
+
type: issue.type,
|
|
829
|
+
severity: issue.severity,
|
|
830
|
+
message: issue.message,
|
|
831
|
+
line: issue.line,
|
|
832
|
+
suggestion: issue.suggestion,
|
|
833
|
+
}));
|
|
834
|
+
if (apiContractIssues.length > 0) {
|
|
835
|
+
logger.info(`API Contract validation found ${apiContractIssues.length} issues in ${relativePath}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Combine all issues for verification
|
|
839
|
+
let allIssues = [...manifestIssues, ...symbolIssues, ...patternIssues, ...deadCodeIssues, ...impactIssues, ...apiContractIssues];
|
|
840
|
+
// === SMART SCOPE FILTERING (NEW) ===
|
|
841
|
+
// Detect file scope and filter issues by relevance
|
|
842
|
+
const fileScope = this.detectFileScope(filePath);
|
|
843
|
+
logger.debug(`File scope detected: ${fileScope} for ${relativePath}`);
|
|
844
|
+
// Apply scope-based filtering
|
|
845
|
+
allIssues = this.filterIssuesByScope(allIssues, fileScope, isLenient);
|
|
846
|
+
logger.debug(`After scope filtering: ${allIssues.length} relevant issues`);
|
|
847
|
+
// === AUTOMATED VERIFICATION (eliminates false positives) ===
|
|
848
|
+
if (allIssues.length > 0) {
|
|
849
|
+
logger.debug(`Verifying ${allIssues.length} findings to eliminate false positives...`);
|
|
850
|
+
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);
|
|
851
|
+
// Replace with confirmed findings only
|
|
852
|
+
const confirmed = getConfirmedFindings(verificationResult);
|
|
853
|
+
// Reconstruct allIssues with verified findings
|
|
854
|
+
allIssues = [
|
|
855
|
+
...confirmed.hallucinations,
|
|
856
|
+
...confirmed.deadCode,
|
|
857
|
+
];
|
|
858
|
+
logger.debug(`Verification complete: ${allIssues.length} confirmed (filtered ${verificationResult.stats.falsePositiveCount} false positives)`);
|
|
859
|
+
}
|
|
860
|
+
// === SMART MODE FILTERING ===
|
|
861
|
+
if (isLenient) {
|
|
862
|
+
// In learning/new file mode:
|
|
863
|
+
// 1. Ignore architectural deviations (we are learning patterns)
|
|
864
|
+
allIssues = allIssues.filter(i => i.type !== 'architecturalDeviation');
|
|
865
|
+
// 2. Ignore PROJECT-WIDE dead code in new/changing files (orphaned files, etc.)
|
|
866
|
+
// but KEEP per-file local dead code (unusedFunction, unusedExport from detectUnusedLocals)
|
|
867
|
+
// because local unused functions/constants are genuine code hygiene issues
|
|
868
|
+
// that are cheap to detect and valuable to report.
|
|
869
|
+
allIssues = allIssues.filter(i => i.type !== 'deadCode' && i.type !== 'orphanedFile');
|
|
870
|
+
// 3. Filter out "Medium" severity symbol issues
|
|
871
|
+
// BUT keep unusedImport, unusedFunction, and unusedExport warnings
|
|
872
|
+
// - unusedImport: the #1 vibecoder mistake
|
|
873
|
+
// - unusedFunction/unusedExport: local dead code (cheap to detect, high signal)
|
|
874
|
+
allIssues = allIssues.filter(i => i.severity === "critical" ||
|
|
875
|
+
i.severity === "high" ||
|
|
876
|
+
i.type === "unusedImport" ||
|
|
877
|
+
i.type === "unusedFunction" ||
|
|
878
|
+
i.type === "unusedExport");
|
|
879
|
+
}
|
|
880
|
+
if (allIssues.length > 0) {
|
|
881
|
+
const alert = await this.formatAlert(filePath, allIssues, fileLang);
|
|
882
|
+
logger.warn(`Found ${allIssues.length} issues in ${relativePath}`);
|
|
883
|
+
if (this.onAlert) {
|
|
884
|
+
this.onAlert(alert);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
logger.info(`No issues found in ${relativePath}`);
|
|
889
|
+
// Notify handler that issues are cleared for this file
|
|
890
|
+
if (this.onAlert) {
|
|
891
|
+
const clearAlert = {
|
|
892
|
+
file: relativePath,
|
|
893
|
+
issues: [],
|
|
894
|
+
timestamp: Date.now(),
|
|
895
|
+
llmMessage: `✅ ${this.agentName}: Issues resolved in ${relativePath}`,
|
|
896
|
+
};
|
|
897
|
+
this.onAlert(clearAlert);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
902
|
+
logger.error(`Error validating ${filePath}:`, err);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
async formatAlert(filePath, issues, fileLang) {
|
|
906
|
+
const relativePath = path.relative(this.projectPath, filePath);
|
|
907
|
+
// Enrich issues with anti-pattern context (async)
|
|
908
|
+
const enrichedIssues = await enrichIssuesWithAntiPatterns(issues, fileLang);
|
|
909
|
+
// Format issues for LLM consumption
|
|
910
|
+
const issueList = enrichedIssues.map((issue) => ({
|
|
911
|
+
type: issue.type,
|
|
912
|
+
severity: issue.severity,
|
|
913
|
+
message: issue.message,
|
|
914
|
+
suggestion: issue.suggestion,
|
|
915
|
+
line: issue.line,
|
|
916
|
+
antiPattern: issue.antiPattern,
|
|
917
|
+
}));
|
|
918
|
+
// Create a natural language message for the LLM (async for anti-pattern context)
|
|
919
|
+
const llmMessage = await this.createLLMMessage(relativePath, enrichedIssues, fileLang);
|
|
920
|
+
return {
|
|
921
|
+
file: relativePath,
|
|
922
|
+
issues: issueList,
|
|
923
|
+
timestamp: Date.now(),
|
|
924
|
+
llmMessage,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async createLLMMessage(file, issues, fileLang) {
|
|
928
|
+
const critical = issues.filter((i) => i.severity === "critical" || i.severity === "high");
|
|
929
|
+
const count = issues.length;
|
|
930
|
+
const task = `There are ${count} issue(s) detected in \`${file}\`. ${critical.length > 0 ? `**${critical.length} issues are CRITICAL.**` : ""} Review the following issues and provide fixes according to the project context.`;
|
|
931
|
+
// Use role-based prompting
|
|
932
|
+
const roleMsg = PROMPT_PATTERNS.role(this.agentName, task);
|
|
933
|
+
// Include validation constraints from the library
|
|
934
|
+
const constrainedMsg = PROMPT_PATTERNS.withConstraints(roleMsg, VALIDATION_CONSTRAINTS);
|
|
935
|
+
// Add specific issues
|
|
936
|
+
const issuesList = issues.map((i, idx) => {
|
|
937
|
+
let item = `${idx + 1}. [${i.severity.toUpperCase()}] ${i.type}: ${i.message}`;
|
|
938
|
+
if (i.line)
|
|
939
|
+
item += ` (Line ${i.line})`;
|
|
940
|
+
if (i.suggestion)
|
|
941
|
+
item += `\n Suggestion: ${i.suggestion}`;
|
|
942
|
+
// Include anti-pattern context if available
|
|
943
|
+
if (i.antiPattern) {
|
|
944
|
+
item += `\n 📚 ${i.antiPattern.id}: ${i.antiPattern.name} - ${i.antiPattern.description}`;
|
|
945
|
+
}
|
|
946
|
+
return item;
|
|
947
|
+
}).join("\n");
|
|
948
|
+
// Generate anti-pattern context for LLM
|
|
949
|
+
const antiPatternContext = await generateAntiPatternContext(issues, fileLang);
|
|
950
|
+
let message = `${constrainedMsg}\n\nDETECTED ISSUES:\n${issuesList}`;
|
|
951
|
+
// Append anti-pattern guidance if relevant
|
|
952
|
+
if (antiPatternContext) {
|
|
953
|
+
message += `\n\n${antiPatternContext}`;
|
|
954
|
+
}
|
|
955
|
+
return message;
|
|
956
|
+
}
|
|
957
|
+
getStatus() {
|
|
958
|
+
return {
|
|
959
|
+
...this.watcher.getStatus(),
|
|
960
|
+
name: this.agentName,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
//# sourceMappingURL=autoValidator.js.map
|