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,1169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automated Finding Verifier - Batched & Concurrent
|
|
3
|
+
*
|
|
4
|
+
* This module automatically verifies validation findings to eliminate false positives
|
|
5
|
+
* without requiring human intervention. Uses batching and concurrency for performance.
|
|
6
|
+
*
|
|
7
|
+
* Verification Strategies:
|
|
8
|
+
* 1. Usage Pattern Analysis - Check for dynamic/indirect usage
|
|
9
|
+
* 2. Git History Analysis - Check if code is actually used in recent commits
|
|
10
|
+
* 3. Framework Pattern Detection - Recognize framework-specific patterns (callbacks, handlers)
|
|
11
|
+
* 4. Cross-File Reference Check - Look for references in other files
|
|
12
|
+
* 5. Test Coverage Analysis - Check if "dead" code is actually tested
|
|
13
|
+
*
|
|
14
|
+
* Performance Optimizations:
|
|
15
|
+
* - File-based batching: Groups findings by file to minimize I/O
|
|
16
|
+
* - Concurrent processing: Verifies multiple files in parallel with limits
|
|
17
|
+
* - Intelligent caching: File contents and git status cached per batch
|
|
18
|
+
* - Batched git operations: Single git status call for all files
|
|
19
|
+
*
|
|
20
|
+
* @format
|
|
21
|
+
*/
|
|
22
|
+
import { logger } from "../utils/logger.js";
|
|
23
|
+
import { exec } from "child_process";
|
|
24
|
+
import { promisify } from "util";
|
|
25
|
+
const execAsync = promisify(exec);
|
|
26
|
+
import * as fs from "fs/promises";
|
|
27
|
+
import * as path from "path";
|
|
28
|
+
import { extractSymbolsAST } from "../tools/validation/extractors/index.js";
|
|
29
|
+
import { checkPackageRegistry } from "../tools/validation/registry.js";
|
|
30
|
+
import { impactAnalyzer } from "./impactAnalyzer.js";
|
|
31
|
+
/**
|
|
32
|
+
* Lightweight wrapper around the same semantic tracing that powers
|
|
33
|
+
* [`get_dependency_graph`](src/tools/getDependencyGraph.ts:61).
|
|
34
|
+
*
|
|
35
|
+
* Used during automated verification to eliminate "unused export" false positives
|
|
36
|
+
* without requiring the LLM to call a separate tool.
|
|
37
|
+
*/
|
|
38
|
+
function getDependencyEvidenceForSymbol(symbolName, ctx, depth = 2, definedInFile) {
|
|
39
|
+
const graph = ctx.projectContext.symbolGraph;
|
|
40
|
+
if (!graph)
|
|
41
|
+
return null;
|
|
42
|
+
// Safety: only use dependency evidence when the symbol is actually defined in the
|
|
43
|
+
// file associated with the finding. This reduces false negatives from name collisions
|
|
44
|
+
// (e.g., multiple "getById" methods across different files).
|
|
45
|
+
if (definedInFile) {
|
|
46
|
+
const definingFiles = graph.symbolToFiles.get(symbolName);
|
|
47
|
+
if (!definingFiles || !definingFiles.has(definedInFile)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const blast = impactAnalyzer.traceBlastRadius(symbolName, graph, Math.min(depth, 5));
|
|
52
|
+
const isUsed = blast.affectedFiles.length > 0 || blast.impactedSymbols.length > 0;
|
|
53
|
+
return { blast, isUsed };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Detect file language from extension. Used when ctx.language is "all"
|
|
57
|
+
* in full-stack projects to ensure the correct parser is used per file.
|
|
58
|
+
*/
|
|
59
|
+
function detectFileLanguage(filePath, fallback) {
|
|
60
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
61
|
+
switch (ext) {
|
|
62
|
+
case ".py": return "python";
|
|
63
|
+
case ".ts":
|
|
64
|
+
case ".tsx": return "typescript";
|
|
65
|
+
case ".js":
|
|
66
|
+
case ".jsx":
|
|
67
|
+
case ".mjs":
|
|
68
|
+
case ".cjs": return "javascript";
|
|
69
|
+
default: return fallback === "all" ? "typescript" : fallback;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Configuration
|
|
74
|
+
// ============================================================================
|
|
75
|
+
/** Maximum concurrent file verifications */
|
|
76
|
+
const MAX_CONCURRENCY = 8;
|
|
77
|
+
/** Batch size for findings within a file */
|
|
78
|
+
const FILE_BATCH_SIZE = 50;
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Main Entry Point - Batched & Concurrent
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Automatically verify all findings using batched concurrent processing.
|
|
84
|
+
* Groups findings by file, caches file data, and processes files in parallel.
|
|
85
|
+
*/
|
|
86
|
+
export async function verifyFindingsAutomatically(hallucinations, deadCode, projectContext, projectPath, language, onProgress) {
|
|
87
|
+
const allFindings = [...hallucinations, ...deadCode];
|
|
88
|
+
logger.info(`Starting batched verification of ${allFindings.length} findings ` +
|
|
89
|
+
`(${hallucinations.length} hallucinations, ${deadCode.length} dead code)...`);
|
|
90
|
+
const ctx = {
|
|
91
|
+
projectPath,
|
|
92
|
+
projectContext,
|
|
93
|
+
language,
|
|
94
|
+
gitAvailable: await checkGitAvailable(projectPath),
|
|
95
|
+
fileContentCache: new Map(),
|
|
96
|
+
};
|
|
97
|
+
// Group findings by file for efficient batch processing
|
|
98
|
+
const fileBatches = groupFindingsByFile(allFindings);
|
|
99
|
+
logger.info(`Grouped into ${fileBatches.length} file batches for concurrent processing`);
|
|
100
|
+
// Pre-batch git status for ALL files in a single git call (instead of per-file)
|
|
101
|
+
if (ctx.gitAvailable) {
|
|
102
|
+
ctx.gitStatusBatch = await batchGitStatus(ctx.projectPath);
|
|
103
|
+
ctx.featureBranchCached = await checkFeatureBranch(ctx);
|
|
104
|
+
}
|
|
105
|
+
const progress = {
|
|
106
|
+
totalFiles: fileBatches.length,
|
|
107
|
+
processedFiles: 0,
|
|
108
|
+
totalFindings: allFindings.length,
|
|
109
|
+
processedFindings: 0,
|
|
110
|
+
};
|
|
111
|
+
// Process file batches concurrently with limit
|
|
112
|
+
const verifiedResults = [];
|
|
113
|
+
for (let i = 0; i < fileBatches.length; i += MAX_CONCURRENCY) {
|
|
114
|
+
const batch = fileBatches.slice(i, i + MAX_CONCURRENCY);
|
|
115
|
+
// Process this concurrent batch
|
|
116
|
+
const batchResults = await Promise.all(batch.map(async (fileBatch) => {
|
|
117
|
+
// Create file cache for this file
|
|
118
|
+
const fileCache = {};
|
|
119
|
+
// Pre-load file data (git status, content if needed)
|
|
120
|
+
await preloadFileCache(fileBatch.filePath, ctx, fileCache);
|
|
121
|
+
// Verify all findings for this file
|
|
122
|
+
const results = [];
|
|
123
|
+
for (const finding of fileBatch.findings) {
|
|
124
|
+
const verified = await verifyFindingWithCache(finding, ctx, fileCache);
|
|
125
|
+
results.push(verified);
|
|
126
|
+
}
|
|
127
|
+
// Update progress
|
|
128
|
+
progress.processedFiles++;
|
|
129
|
+
progress.processedFindings += fileBatch.findings.length;
|
|
130
|
+
onProgress?.(progress);
|
|
131
|
+
return results;
|
|
132
|
+
}));
|
|
133
|
+
// Flatten results
|
|
134
|
+
for (const results of batchResults) {
|
|
135
|
+
verifiedResults.push(...results);
|
|
136
|
+
}
|
|
137
|
+
// Yield to event loop between batches
|
|
138
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
139
|
+
}
|
|
140
|
+
// Categorize results
|
|
141
|
+
const confirmed = verifiedResults.filter((v) => v.status === "confirmed");
|
|
142
|
+
const falsePositives = verifiedResults.filter((v) => v.status === "false_positive");
|
|
143
|
+
const uncertain = verifiedResults.filter((v) => v.status === "uncertain");
|
|
144
|
+
logger.info(`Verification complete: ${confirmed.length} confirmed, ` +
|
|
145
|
+
`${falsePositives.length} false positives, ${uncertain.length} uncertain ` +
|
|
146
|
+
`(${fileBatches.length} files processed)`);
|
|
147
|
+
return {
|
|
148
|
+
confirmed,
|
|
149
|
+
falsePositives,
|
|
150
|
+
uncertain,
|
|
151
|
+
stats: {
|
|
152
|
+
totalAnalyzed: allFindings.length,
|
|
153
|
+
confirmedCount: confirmed.length,
|
|
154
|
+
falsePositiveCount: falsePositives.length,
|
|
155
|
+
uncertainCount: uncertain.length,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Batching & Caching
|
|
161
|
+
// ============================================================================
|
|
162
|
+
function groupFindingsByFile(findings) {
|
|
163
|
+
const fileMap = new Map();
|
|
164
|
+
for (const finding of findings) {
|
|
165
|
+
// Use a virtual key for findings without a file path (inline newCode validation)
|
|
166
|
+
const filePath = finding.file || "(inline)";
|
|
167
|
+
if (!fileMap.has(filePath)) {
|
|
168
|
+
fileMap.set(filePath, []);
|
|
169
|
+
}
|
|
170
|
+
fileMap.get(filePath).push(finding);
|
|
171
|
+
}
|
|
172
|
+
return Array.from(fileMap.entries()).map(([filePath, findings]) => ({
|
|
173
|
+
filePath,
|
|
174
|
+
findings,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
async function preloadFileCache(filePath, ctx, cache) {
|
|
178
|
+
// Use pre-batched git status (single git call for all files)
|
|
179
|
+
if (ctx.gitAvailable && ctx.gitStatusBatch) {
|
|
180
|
+
cache.gitStatus = ctx.gitStatusBatch.get(filePath) ?? { isNew: false, isModified: false };
|
|
181
|
+
}
|
|
182
|
+
// Feature branch is already cached on context during init
|
|
183
|
+
if (ctx.gitAvailable) {
|
|
184
|
+
cache.featureBranch = ctx.featureBranchCached;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function verifyFindingWithCache(finding, ctx, cache) {
|
|
188
|
+
// Route to appropriate verifier based on finding type
|
|
189
|
+
if ("type" in finding && isValidationIssue(finding)) {
|
|
190
|
+
return await verifyHallucinationWithCache(finding, ctx, cache);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
return await verifyDeadCodeWithCache(finding, ctx, cache);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function isValidationIssue(finding) {
|
|
197
|
+
return "code" in finding && typeof finding.line === "number";
|
|
198
|
+
}
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Hallucination Verification with Caching
|
|
201
|
+
// ============================================================================
|
|
202
|
+
async function verifyHallucinationWithCache(issue, ctx, cache) {
|
|
203
|
+
const reasons = [];
|
|
204
|
+
let confidence = 0;
|
|
205
|
+
let status = "uncertain";
|
|
206
|
+
let method = "";
|
|
207
|
+
switch (issue.type) {
|
|
208
|
+
case "nonExistentFunction":
|
|
209
|
+
case "nonExistentClass":
|
|
210
|
+
case "undefinedVariable": {
|
|
211
|
+
// Extra guard: if the symbol is defined locally in the same file
|
|
212
|
+
// (e.g., function parameter, destructured param, local const/let), this is a false positive.
|
|
213
|
+
if (issue.type === "undefinedVariable") {
|
|
214
|
+
const localDef = await checkSymbolDefinedLocally(issue, ctx, cache);
|
|
215
|
+
if (localDef.isDefined) {
|
|
216
|
+
status = "false_positive";
|
|
217
|
+
confidence = 95;
|
|
218
|
+
method = "local_scope";
|
|
219
|
+
reasons.push(`Symbol is defined locally in the file (${localDef.hint})`);
|
|
220
|
+
reasons.push("Not a hallucination - this is valid local scope");
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Check if symbol exists in any form (maybe just not imported)
|
|
225
|
+
const existsInProject = checkSymbolExistsInProject(issue, ctx);
|
|
226
|
+
if (existsInProject.exists) {
|
|
227
|
+
status = "false_positive";
|
|
228
|
+
confidence = 90;
|
|
229
|
+
method = "cross_reference";
|
|
230
|
+
reasons.push(`Symbol exists in project at ${existsInProject.location}`);
|
|
231
|
+
reasons.push("This is a missing import, not a hallucination");
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Use cached future feature detection
|
|
235
|
+
const futureFeatureCheck = await detectFutureFeatureWithCache(issue, ctx, cache);
|
|
236
|
+
if (futureFeatureCheck.isFutureFeature) {
|
|
237
|
+
status = "false_positive";
|
|
238
|
+
confidence = futureFeatureCheck.confidence;
|
|
239
|
+
method = "future_feature_detection";
|
|
240
|
+
reasons.push("This appears to be part of an incomplete/new feature:");
|
|
241
|
+
reasons.push(...futureFeatureCheck.reasons);
|
|
242
|
+
reasons.push("Not a hallucination - code is for planned functionality");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Quick git check using cached status
|
|
246
|
+
if (cache.gitStatus?.isNew || cache.gitStatus?.isModified) {
|
|
247
|
+
status = "false_positive";
|
|
248
|
+
confidence = 85;
|
|
249
|
+
method = "git_history";
|
|
250
|
+
reasons.push("Symbol found in recent git changes");
|
|
251
|
+
reasons.push("This may be part of an incomplete feature");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
status = "confirmed";
|
|
255
|
+
confidence = 95;
|
|
256
|
+
method = "static_analysis";
|
|
257
|
+
reasons.push("Symbol not found anywhere in project");
|
|
258
|
+
reasons.push("No indicators of planned/incomplete feature");
|
|
259
|
+
reasons.push("This is a true hallucination");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "nonExistentImport": {
|
|
266
|
+
const moduleCheck = await checkModuleExports(issue, ctx);
|
|
267
|
+
if (moduleCheck.moduleExists && !moduleCheck.exportExists) {
|
|
268
|
+
status = "confirmed";
|
|
269
|
+
confidence = 98;
|
|
270
|
+
method = "module_resolution";
|
|
271
|
+
reasons.push("Module exists but export does not");
|
|
272
|
+
reasons.push("This is a true hallucination");
|
|
273
|
+
}
|
|
274
|
+
else if (!moduleCheck.moduleExists) {
|
|
275
|
+
status = "confirmed";
|
|
276
|
+
confidence = 99;
|
|
277
|
+
method = "module_resolution";
|
|
278
|
+
reasons.push("Module does not exist in project");
|
|
279
|
+
reasons.push("This is a true hallucination");
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
status = "false_positive";
|
|
283
|
+
confidence = 80;
|
|
284
|
+
method = "module_resolution";
|
|
285
|
+
reasons.push("Module and export both exist");
|
|
286
|
+
reasons.push("May be a resolution path issue");
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "dependencyHallucination": {
|
|
291
|
+
const pkgName = extractPackageName(issue.message);
|
|
292
|
+
const fileLang = issue.file ? detectFileLanguage(issue.file, ctx.language) : ctx.language;
|
|
293
|
+
const existsOnNpm = await checkPackageExistsOnRegistry(pkgName, fileLang);
|
|
294
|
+
if (!existsOnNpm) {
|
|
295
|
+
status = "confirmed";
|
|
296
|
+
confidence = 99;
|
|
297
|
+
method = "registry_check";
|
|
298
|
+
reasons.push("Package does not exist on npm registry");
|
|
299
|
+
reasons.push("This is definitely a hallucination");
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
status = "false_positive";
|
|
303
|
+
confidence = 85;
|
|
304
|
+
method = "registry_check";
|
|
305
|
+
reasons.push("Package exists on npm registry");
|
|
306
|
+
reasons.push("This is a missing dependency, not a hallucination");
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "missingDependency": {
|
|
311
|
+
// Check if the package exists in a nearby package.json (subdirectory).
|
|
312
|
+
// The manifest loader may have already been fixed, but this is a safety net.
|
|
313
|
+
const missingPkgName = extractPackageName(issue.message);
|
|
314
|
+
if (missingPkgName) {
|
|
315
|
+
const foundInSubdir = await checkPackageInSubdirectoryManifest(missingPkgName, ctx.projectPath);
|
|
316
|
+
if (foundInSubdir) {
|
|
317
|
+
status = "false_positive";
|
|
318
|
+
confidence = 98;
|
|
319
|
+
method = "subdirectory_manifest_check";
|
|
320
|
+
reasons.push(`Package '${missingPkgName}' found in a subdirectory package.json`);
|
|
321
|
+
reasons.push("This is not missing — the manifest loader missed it");
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// If not found in subdirectory, it's a genuine missing dependency (but low severity)
|
|
326
|
+
status = "confirmed";
|
|
327
|
+
confidence = 70;
|
|
328
|
+
method = "manifest_check";
|
|
329
|
+
reasons.push("Package not found in any project manifest");
|
|
330
|
+
reasons.push("This is a missing dependency (not a hallucination — it exists on the registry)");
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case "wrongParamCount": {
|
|
334
|
+
const paramCheck = await verifyParameterCount(issue, ctx);
|
|
335
|
+
if (paramCheck.isMismatch) {
|
|
336
|
+
status = "confirmed";
|
|
337
|
+
confidence = 92;
|
|
338
|
+
method = "signature_analysis";
|
|
339
|
+
reasons.push("Parameter count mismatch confirmed");
|
|
340
|
+
reasons.push(`Expected ${paramCheck.expected}, got ${paramCheck.actual}`);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
status = "false_positive";
|
|
344
|
+
confidence = 88;
|
|
345
|
+
method = "signature_analysis";
|
|
346
|
+
reasons.push("Parameter count appears correct on re-check");
|
|
347
|
+
reasons.push("May be variadic or have optional parameters");
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case "unusedImport": {
|
|
352
|
+
// Special case: async validation injects per-file "unused local" findings into the
|
|
353
|
+
// ValidationIssue stream using type=unusedImport.
|
|
354
|
+
// These findings look like:
|
|
355
|
+
// "Variable 'getRecipeDetails' is defined but never used in this file"
|
|
356
|
+
// For exported/service methods, this is frequently a false positive.
|
|
357
|
+
// Use dependency tracing (same engine as get_dependency_graph) to confirm.
|
|
358
|
+
if (issue.message.includes("defined but never used in this file") &&
|
|
359
|
+
typeof issue.file === "string" &&
|
|
360
|
+
issue.file.length > 0) {
|
|
361
|
+
const symbolName = extractSymbolName(issue);
|
|
362
|
+
if (symbolName) {
|
|
363
|
+
const dep = getDependencyEvidenceForSymbol(symbolName, ctx, 2, issue.file);
|
|
364
|
+
if (dep?.isUsed) {
|
|
365
|
+
status = "false_positive";
|
|
366
|
+
confidence = 98;
|
|
367
|
+
method = "dependency_graph";
|
|
368
|
+
reasons.push(`Dependency graph shows '${symbolName}' is referenced by ${dep.blast.affectedFiles.length} file(s).`);
|
|
369
|
+
const sampleFiles = dep.blast.affectedFiles
|
|
370
|
+
.slice(0, 4)
|
|
371
|
+
.map((f) => f)
|
|
372
|
+
.join(", ");
|
|
373
|
+
if (sampleFiles) {
|
|
374
|
+
reasons.push(`Example consumers: ${sampleFiles}${dep.blast.affectedFiles.length > 4 ? ", …" : ""}`);
|
|
375
|
+
}
|
|
376
|
+
reasons.push("Not an unused local — this symbol is reachable via cross-file consumers");
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const usageCheck = await checkImportUsage(issue, ctx);
|
|
382
|
+
if (usageCheck.isUsed) {
|
|
383
|
+
status = "false_positive";
|
|
384
|
+
confidence = 90;
|
|
385
|
+
method = "usage_analysis";
|
|
386
|
+
reasons.push("Import is actually used");
|
|
387
|
+
if (usageCheck.usageType) {
|
|
388
|
+
reasons.push(`Usage type: ${usageCheck.usageType}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
status = "confirmed";
|
|
393
|
+
confidence = 95;
|
|
394
|
+
method = "usage_analysis";
|
|
395
|
+
reasons.push("Import is truly unused");
|
|
396
|
+
reasons.push("No references found in file");
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "nonExistentMethod": {
|
|
401
|
+
// First check: does the method exist in the project at all?
|
|
402
|
+
const methodNameMatch = issue.message.match(/Method '([^']+)'/);
|
|
403
|
+
const objectNameMatch = issue.message.match(/on '([^']+)'/);
|
|
404
|
+
const methodName = methodNameMatch?.[1];
|
|
405
|
+
const objectName = objectNameMatch?.[1];
|
|
406
|
+
if (methodName) {
|
|
407
|
+
// Check if method exists with matching scope
|
|
408
|
+
const methodExists = checkMethodExistsInProject(methodName, objectName, ctx);
|
|
409
|
+
if (methodExists.exists) {
|
|
410
|
+
status = "false_positive";
|
|
411
|
+
confidence = 90;
|
|
412
|
+
method = "symbol_lookup";
|
|
413
|
+
reasons.push(`Method '${methodName}' exists in project at ${methodExists.location}`);
|
|
414
|
+
if (methodExists.scope) {
|
|
415
|
+
reasons.push(`Method is defined on object: ${methodExists.scope}`);
|
|
416
|
+
}
|
|
417
|
+
reasons.push("This is not a hallucination - the method exists");
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Fallback: check inheritance chain (for class-based methods)
|
|
422
|
+
const inheritanceCheck = await checkInheritanceChain(issue, ctx);
|
|
423
|
+
if (inheritanceCheck.foundInParent) {
|
|
424
|
+
status = "false_positive";
|
|
425
|
+
confidence = 85;
|
|
426
|
+
method = "inheritance_analysis";
|
|
427
|
+
reasons.push("Method found in parent class or interface");
|
|
428
|
+
reasons.push("This is not a hallucination");
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
status = "confirmed";
|
|
432
|
+
confidence = 80;
|
|
433
|
+
method = "inheritance_analysis";
|
|
434
|
+
reasons.push("Method not found in class hierarchy");
|
|
435
|
+
reasons.push("May be a dynamic method (not verifiable)");
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
default: {
|
|
440
|
+
status = issue.confidence && issue.confidence > 90 ? "confirmed" : "uncertain";
|
|
441
|
+
confidence = issue.confidence || 50;
|
|
442
|
+
method = "fallback";
|
|
443
|
+
reasons.push("Using original confidence score");
|
|
444
|
+
reasons.push("No specific verification available for this issue type");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
original: issue,
|
|
449
|
+
status,
|
|
450
|
+
confidence,
|
|
451
|
+
reasons,
|
|
452
|
+
verificationMethod: method,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Dead Code Verification with Caching
|
|
457
|
+
// ============================================================================
|
|
458
|
+
async function verifyDeadCodeWithCache(issue, ctx, cache) {
|
|
459
|
+
const reasons = [];
|
|
460
|
+
let confidence = 0;
|
|
461
|
+
let status = "uncertain";
|
|
462
|
+
let method = "";
|
|
463
|
+
switch (issue.type) {
|
|
464
|
+
case "unusedExport": {
|
|
465
|
+
// Highest-signal guard: if the symbol graph shows *any* downstream usage,
|
|
466
|
+
// this is not dead code. This prevents the exact class of false positives
|
|
467
|
+
// where the dead-code scan misses a semantic consumer, but the dependency
|
|
468
|
+
// tracer can prove a call chain exists.
|
|
469
|
+
const dep = getDependencyEvidenceForSymbol(issue.name, ctx, 2);
|
|
470
|
+
if (dep?.isUsed) {
|
|
471
|
+
status = "false_positive";
|
|
472
|
+
confidence = 98;
|
|
473
|
+
method = "dependency_graph";
|
|
474
|
+
const sampleFiles = dep.blast.affectedFiles
|
|
475
|
+
.slice(0, 4)
|
|
476
|
+
.map((f) => f)
|
|
477
|
+
.join(", ");
|
|
478
|
+
reasons.push(`Dependency graph shows '${issue.name}' is used by ${dep.blast.affectedFiles.length} file(s).`);
|
|
479
|
+
if (sampleFiles) {
|
|
480
|
+
reasons.push(`Example consumers: ${sampleFiles}${dep.blast.affectedFiles.length > 4 ? ", …" : ""}`);
|
|
481
|
+
}
|
|
482
|
+
reasons.push("Not dead code — keep this export or refactor with care");
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
// Use cached future feature detection
|
|
486
|
+
const futureFeatureCheck = await detectFutureFeatureWithCache(issue, ctx, cache);
|
|
487
|
+
if (futureFeatureCheck.isFutureFeature) {
|
|
488
|
+
status = "false_positive";
|
|
489
|
+
confidence = futureFeatureCheck.confidence;
|
|
490
|
+
method = "future_feature_detection";
|
|
491
|
+
reasons.push("This export appears to be part of an incomplete/new feature:");
|
|
492
|
+
reasons.push(...futureFeatureCheck.reasons);
|
|
493
|
+
reasons.push("Not dead code - will likely be used when feature is complete");
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
const isPublicApi = await checkIfPublicApi(issue, ctx);
|
|
497
|
+
const isTestFile = issue.file.includes('.test.') || issue.file.includes('.spec.') || issue.file.includes('/test/');
|
|
498
|
+
if (isPublicApi) {
|
|
499
|
+
status = "false_positive";
|
|
500
|
+
confidence = 88;
|
|
501
|
+
method = "api_analysis";
|
|
502
|
+
reasons.push("This appears to be a public API export");
|
|
503
|
+
reasons.push("External consumers may use this");
|
|
504
|
+
}
|
|
505
|
+
else if (isTestFile) {
|
|
506
|
+
status = "false_positive";
|
|
507
|
+
confidence = 75;
|
|
508
|
+
method = "test_file_analysis";
|
|
509
|
+
reasons.push("This is a test file - exports may be used by test runners");
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
status = "confirmed";
|
|
513
|
+
confidence = 92;
|
|
514
|
+
method = "dead_code_detector_trust";
|
|
515
|
+
reasons.push("Dead code detector performed thorough cross-file analysis");
|
|
516
|
+
reasons.push("No imports, type references, or runtime usages found");
|
|
517
|
+
reasons.push("No indicators of planned/incomplete feature");
|
|
518
|
+
reasons.push("This is confirmed dead code");
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
case "unusedFunction": {
|
|
523
|
+
const indirectCheck = await checkIndirectUsage(issue, ctx);
|
|
524
|
+
if (indirectCheck.isUsed) {
|
|
525
|
+
status = "false_positive";
|
|
526
|
+
confidence = 85;
|
|
527
|
+
method = "indirect_usage_analysis";
|
|
528
|
+
reasons.push("Function has indirect usage");
|
|
529
|
+
reasons.push(`Usage pattern: ${indirectCheck.pattern}`);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
status = "confirmed";
|
|
533
|
+
confidence = 88;
|
|
534
|
+
method = "static_analysis";
|
|
535
|
+
reasons.push("No direct or indirect usages found");
|
|
536
|
+
reasons.push("This is likely dead code");
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
case "orphanedFile": {
|
|
541
|
+
const importCheck = await checkFileImports(issue, ctx);
|
|
542
|
+
if (importCheck.isImported) {
|
|
543
|
+
status = "false_positive";
|
|
544
|
+
confidence = 95;
|
|
545
|
+
method = "import_analysis";
|
|
546
|
+
reasons.push(`File is imported by ${importCheck.importers.length} file(s)`);
|
|
547
|
+
reasons.push("This is not an orphaned file");
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const isEntryPoint = await checkIfEntryPoint(issue, ctx);
|
|
551
|
+
if (isEntryPoint) {
|
|
552
|
+
status = "false_positive";
|
|
553
|
+
confidence = 90;
|
|
554
|
+
method = "entry_point_analysis";
|
|
555
|
+
reasons.push("This appears to be an entry point or config file");
|
|
556
|
+
reasons.push("Not expected to be imported");
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
status = "confirmed";
|
|
560
|
+
confidence = 85;
|
|
561
|
+
method = "import_analysis";
|
|
562
|
+
reasons.push("No imports found");
|
|
563
|
+
reasons.push("Not a recognized entry point");
|
|
564
|
+
reasons.push("This may be an orphaned file");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
default: {
|
|
570
|
+
status = "uncertain";
|
|
571
|
+
confidence = 50;
|
|
572
|
+
method = "fallback";
|
|
573
|
+
reasons.push("Unknown dead code type");
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
original: issue,
|
|
578
|
+
status,
|
|
579
|
+
confidence,
|
|
580
|
+
reasons,
|
|
581
|
+
verificationMethod: method,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
async function detectFutureFeatureWithCache(issue, ctx, cache) {
|
|
585
|
+
const reasons = [];
|
|
586
|
+
let signalCount = 0;
|
|
587
|
+
const signals = [];
|
|
588
|
+
// Signal 1: Check git status (cached)
|
|
589
|
+
if (cache.gitStatus?.isNew || cache.gitStatus?.isModified) {
|
|
590
|
+
signalCount++;
|
|
591
|
+
signals.push("uncommitted changes");
|
|
592
|
+
reasons.push(`File has uncommitted ${cache.gitStatus.isNew ? "changes (new file)" : "modifications"}`);
|
|
593
|
+
}
|
|
594
|
+
// Signal 2: Check for TODO/FIXME comments (lazy load & cache)
|
|
595
|
+
if (cache.hasTodoComments === undefined) {
|
|
596
|
+
cache.hasTodoComments = await checkForTodoComments(issue.file, ctx, cache);
|
|
597
|
+
}
|
|
598
|
+
if (cache.hasTodoComments) {
|
|
599
|
+
signalCount++;
|
|
600
|
+
signals.push("todo comments");
|
|
601
|
+
reasons.push("File contains TODO/FIXME comments indicating planned work");
|
|
602
|
+
}
|
|
603
|
+
// Signal 3: Check if this is a stub implementation (lazy load & cache)
|
|
604
|
+
if (cache.isStub === undefined) {
|
|
605
|
+
cache.isStub = await checkIfStubImplementation(issue.file, issue, ctx, cache);
|
|
606
|
+
}
|
|
607
|
+
if (cache.isStub) {
|
|
608
|
+
signalCount++;
|
|
609
|
+
signals.push("stub implementation");
|
|
610
|
+
reasons.push("Code appears to be a stub/placeholder for future implementation");
|
|
611
|
+
}
|
|
612
|
+
// Signal 4: Check feature branch naming (cached)
|
|
613
|
+
if (cache.featureBranch) {
|
|
614
|
+
signalCount++;
|
|
615
|
+
signals.push("feature branch");
|
|
616
|
+
reasons.push("Working on a feature branch (not main/master)");
|
|
617
|
+
}
|
|
618
|
+
// Signal 5: Check if the referenced symbol looks like a planned feature
|
|
619
|
+
const symbolName = extractSymbolName(issue);
|
|
620
|
+
if (symbolName && looksLikePlannedFeature(symbolName)) {
|
|
621
|
+
signalCount += 0.5;
|
|
622
|
+
signals.push("planned naming");
|
|
623
|
+
reasons.push(`Symbol name "${symbolName}" suggests planned functionality`);
|
|
624
|
+
}
|
|
625
|
+
// Determine result based on signal strength
|
|
626
|
+
const isFutureFeature = signalCount >= 2 || (signalCount >= 1 && signals.includes("todo comments"));
|
|
627
|
+
const confidence = Math.min(95, 60 + signalCount * 15);
|
|
628
|
+
return {
|
|
629
|
+
isFutureFeature,
|
|
630
|
+
confidence,
|
|
631
|
+
reasons,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
// ============================================================================
|
|
635
|
+
// Git Operations (Optimized)
|
|
636
|
+
// ============================================================================
|
|
637
|
+
async function checkGitAvailable(projectPath) {
|
|
638
|
+
try {
|
|
639
|
+
await execAsync("git rev-parse --git-dir", { cwd: projectPath });
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Batch git status for ALL files in a single git call.
|
|
648
|
+
* Replaces per-file `git status --porcelain <file>` which spawned 100+ processes.
|
|
649
|
+
*/
|
|
650
|
+
async function batchGitStatus(projectPath) {
|
|
651
|
+
const statusMap = new Map();
|
|
652
|
+
try {
|
|
653
|
+
const { stdout } = await execAsync("git status --porcelain", { cwd: projectPath, maxBuffer: 10 * 1024 * 1024 });
|
|
654
|
+
for (const line of stdout.split("\n")) {
|
|
655
|
+
if (!line || line.length < 4)
|
|
656
|
+
continue;
|
|
657
|
+
const statusCode = line.substring(0, 2);
|
|
658
|
+
const filePart = line.substring(3).trim();
|
|
659
|
+
if (!filePart)
|
|
660
|
+
continue;
|
|
661
|
+
const absPath = path.isAbsolute(filePart) ? filePart : path.join(projectPath, filePart);
|
|
662
|
+
statusMap.set(absPath, {
|
|
663
|
+
isNew: statusCode === "??" || statusCode.startsWith("A"),
|
|
664
|
+
isModified: statusCode.includes("M"),
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// git not available or error — return empty map
|
|
670
|
+
}
|
|
671
|
+
return statusMap;
|
|
672
|
+
}
|
|
673
|
+
async function checkFeatureBranch(ctx) {
|
|
674
|
+
if (!ctx.gitAvailable)
|
|
675
|
+
return false;
|
|
676
|
+
try {
|
|
677
|
+
const { stdout } = await execAsync("git branch --show-current", { cwd: ctx.projectPath });
|
|
678
|
+
const branchName = stdout.trim();
|
|
679
|
+
const featurePatterns = [
|
|
680
|
+
/^feature\//i,
|
|
681
|
+
/^feat\//i,
|
|
682
|
+
/^wip\//i,
|
|
683
|
+
/^wip-/i,
|
|
684
|
+
/-wip$/i,
|
|
685
|
+
/^new\//i,
|
|
686
|
+
/^implement/i,
|
|
687
|
+
/^add-/i,
|
|
688
|
+
];
|
|
689
|
+
return featurePatterns.some((pattern) => pattern.test(branchName));
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// File Content Checks
|
|
697
|
+
// ============================================================================
|
|
698
|
+
async function checkForTodoComments(filePath, ctx, cache) {
|
|
699
|
+
if (!filePath)
|
|
700
|
+
return false;
|
|
701
|
+
try {
|
|
702
|
+
// Use cached content if available, otherwise load and cache
|
|
703
|
+
if (cache && !cache.content) {
|
|
704
|
+
cache.content = await fs.readFile(filePath, "utf-8");
|
|
705
|
+
}
|
|
706
|
+
const content = cache?.content ?? await fs.readFile(filePath, "utf-8");
|
|
707
|
+
const todoPatterns = [
|
|
708
|
+
/\/\/\s*TODO/i,
|
|
709
|
+
/\/\/\s*FIXME/i,
|
|
710
|
+
/\/\/\s*XXX/i,
|
|
711
|
+
/\/\*\s*TODO/i,
|
|
712
|
+
/\/\*\s*FIXME/i,
|
|
713
|
+
/#\s*TODO/i,
|
|
714
|
+
/#\s*FIXME/i,
|
|
715
|
+
/\{\s*\/\*\s*TODO/i,
|
|
716
|
+
];
|
|
717
|
+
return todoPatterns.some((pattern) => pattern.test(content));
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async function checkIfStubImplementation(filePath, issue, ctx, cache) {
|
|
724
|
+
if (!filePath)
|
|
725
|
+
return false;
|
|
726
|
+
try {
|
|
727
|
+
// Use cached content if available, otherwise load and cache
|
|
728
|
+
if (cache && !cache.content) {
|
|
729
|
+
cache.content = await fs.readFile(filePath, "utf-8");
|
|
730
|
+
}
|
|
731
|
+
const content = cache?.content ?? await fs.readFile(filePath, "utf-8");
|
|
732
|
+
const stubPatterns = [
|
|
733
|
+
/throw\s+new\s+Error\s*\(\s*["']\s*not\s+implemented/i,
|
|
734
|
+
/throw\s+new\s+Error\s*\(\s*["']\s*TODO/i,
|
|
735
|
+
/\/\/\s*TODO.*implement/i,
|
|
736
|
+
/return\s+null\s*;\s*\/\/\s*TODO/i,
|
|
737
|
+
/\/\/\s*placeholder/i,
|
|
738
|
+
/\/\/\s*stub/i,
|
|
739
|
+
];
|
|
740
|
+
return stubPatterns.some((pattern) => pattern.test(content));
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function extractSymbolName(issue) {
|
|
747
|
+
if ("message" in issue) {
|
|
748
|
+
const match = issue.message.match(/'([^']+)'/);
|
|
749
|
+
return match?.[1] || null;
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
function looksLikePlannedFeature(symbolName) {
|
|
754
|
+
const plannedPatterns = [
|
|
755
|
+
/new/i,
|
|
756
|
+
/upcoming/i,
|
|
757
|
+
/planned/i,
|
|
758
|
+
/future/i,
|
|
759
|
+
/next/i,
|
|
760
|
+
/v\d+/i,
|
|
761
|
+
/version\d+/i,
|
|
762
|
+
];
|
|
763
|
+
return plannedPatterns.some((pattern) => pattern.test(symbolName));
|
|
764
|
+
}
|
|
765
|
+
// ============================================================================
|
|
766
|
+
// Local Scope Checks (prevent verifier from confirming local vars as hallucinations)
|
|
767
|
+
// ============================================================================
|
|
768
|
+
async function checkSymbolDefinedLocally(issue, ctx, cache) {
|
|
769
|
+
const symbolName = extractSymbolName(issue);
|
|
770
|
+
if (!symbolName || !issue.file)
|
|
771
|
+
return { isDefined: false };
|
|
772
|
+
try {
|
|
773
|
+
// Load file content once (cache)
|
|
774
|
+
if (!cache.content) {
|
|
775
|
+
cache.content = await fs.readFile(issue.file, "utf-8");
|
|
776
|
+
}
|
|
777
|
+
// Parse local symbols from the file itself (includes params when enabled)
|
|
778
|
+
const fileLang = detectFileLanguage(issue.file, ctx.language);
|
|
779
|
+
const symbols = extractSymbolsAST(cache.content, issue.file, fileLang, {
|
|
780
|
+
includeParameterSymbols: true,
|
|
781
|
+
});
|
|
782
|
+
const found = symbols.some((s) => s.name === symbolName);
|
|
783
|
+
return found ? { isDefined: true, hint: "local symbol table" } : { isDefined: false };
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
return { isDefined: false };
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// ============================================================================
|
|
790
|
+
// Symbol & Module Checks
|
|
791
|
+
// ============================================================================
|
|
792
|
+
function checkSymbolExistsInProject(issue, ctx) {
|
|
793
|
+
const symbolName = issue.message.match(/'([^']+)'/)?.[1];
|
|
794
|
+
if (!symbolName)
|
|
795
|
+
return { exists: false };
|
|
796
|
+
for (const [name, definitions] of ctx.projectContext.symbolIndex) {
|
|
797
|
+
if (name === symbolName || name.toLowerCase() === symbolName.toLowerCase()) {
|
|
798
|
+
return {
|
|
799
|
+
exists: true,
|
|
800
|
+
location: definitions[0]?.file || "unknown",
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return { exists: false };
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Check if a method exists in the project, optionally with a specific scope (object name).
|
|
808
|
+
* This handles object literal methods like: const api = { method: () => {} }
|
|
809
|
+
*/
|
|
810
|
+
function checkMethodExistsInProject(methodName, objectName, ctx) {
|
|
811
|
+
// Search through all symbols in the project context
|
|
812
|
+
for (const [name, definitions] of ctx.projectContext.symbolIndex) {
|
|
813
|
+
if (name === methodName) {
|
|
814
|
+
// Check all definitions for this method name
|
|
815
|
+
for (const def of definitions) {
|
|
816
|
+
// Check if it's a method type
|
|
817
|
+
if (def.symbol.kind === "method") {
|
|
818
|
+
// If object name is provided, check scope matches
|
|
819
|
+
if (!objectName || !def.symbol.scope || def.symbol.scope === objectName) {
|
|
820
|
+
return {
|
|
821
|
+
exists: true,
|
|
822
|
+
location: def.file || "unknown",
|
|
823
|
+
scope: def.symbol.scope,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return { exists: false };
|
|
831
|
+
}
|
|
832
|
+
async function checkModuleExports(issue, ctx) {
|
|
833
|
+
const moduleMatch = issue.message.match(/module ['"]([^'"]+)['"]/);
|
|
834
|
+
const exportMatch = issue.message.match(/export ['"]([^'"]+)['"]/);
|
|
835
|
+
const moduleName = moduleMatch?.[1];
|
|
836
|
+
const exportName = exportMatch?.[1];
|
|
837
|
+
if (!moduleName) {
|
|
838
|
+
return { moduleExists: false, exportExists: false };
|
|
839
|
+
}
|
|
840
|
+
// Convert Python dotted module paths to file paths (e.g., app.api.cli_tokens -> app/api/cli_tokens)
|
|
841
|
+
const moduleAsPath = moduleName.replace(/\./g, "/");
|
|
842
|
+
const possiblePaths = [
|
|
843
|
+
path.join(ctx.projectPath, moduleName),
|
|
844
|
+
path.join(ctx.projectPath, `${moduleName}.ts`),
|
|
845
|
+
path.join(ctx.projectPath, `${moduleName}.tsx`),
|
|
846
|
+
path.join(ctx.projectPath, `${moduleName}.js`),
|
|
847
|
+
path.join(ctx.projectPath, `${moduleName}/index.ts`),
|
|
848
|
+
path.join(ctx.projectPath, `${moduleName}/index.js`),
|
|
849
|
+
// Python module paths (dotted notation)
|
|
850
|
+
path.join(ctx.projectPath, `${moduleAsPath}.py`),
|
|
851
|
+
path.join(ctx.projectPath, moduleAsPath, "__init__.py"),
|
|
852
|
+
];
|
|
853
|
+
let moduleExists = false;
|
|
854
|
+
let modulePath = "";
|
|
855
|
+
for (const p of possiblePaths) {
|
|
856
|
+
try {
|
|
857
|
+
await fs.access(p);
|
|
858
|
+
moduleExists = true;
|
|
859
|
+
modulePath = p;
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
// Continue checking
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (!moduleExists || !exportName) {
|
|
867
|
+
return { moduleExists, exportExists: false };
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
const content = await fs.readFile(modulePath, "utf-8");
|
|
871
|
+
const isPython = modulePath.endsWith(".py");
|
|
872
|
+
const exportPatterns = [
|
|
873
|
+
new RegExp(`export\\s+(?:const|let|var|function|class|interface|type)\\s+${exportName}\\b`),
|
|
874
|
+
new RegExp(`export\\s*\\{[^}]*\\b${exportName}\\b`),
|
|
875
|
+
new RegExp(`export\\s+default\\s+(?:class|function)?\\s*\\b${exportName}\\b`),
|
|
876
|
+
];
|
|
877
|
+
if (isPython) {
|
|
878
|
+
// Python: check for class/def/variable definitions
|
|
879
|
+
exportPatterns.push(new RegExp(`^class\\s+${exportName}\\b`, "m"), new RegExp(`^def\\s+${exportName}\\b`, "m"), new RegExp(`^${exportName}\\s*=`, "m"));
|
|
880
|
+
// Python: check for module-level imports (re-imports are valid namespace members)
|
|
881
|
+
exportPatterns.push(new RegExp(`^from\\s+\\S+\\s+import\\s+.*\\b${exportName}\\b`, "m"), new RegExp(`^import\\s+.*\\b${exportName}\\b`, "m"));
|
|
882
|
+
}
|
|
883
|
+
const exportExists = exportPatterns.some((pattern) => pattern.test(content));
|
|
884
|
+
return { moduleExists: true, exportExists };
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
return { moduleExists: true, exportExists: false };
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async function checkPackageExistsOnRegistry(pkgName, language) {
|
|
891
|
+
if (!pkgName)
|
|
892
|
+
return false;
|
|
893
|
+
const commonPackages = new Set([
|
|
894
|
+
"react", "react-dom", "vue", "angular", "svelte",
|
|
895
|
+
"lodash", "underscore", "ramda",
|
|
896
|
+
"axios", "fetch", "node-fetch",
|
|
897
|
+
"express", "koa", "fastify", "hapi",
|
|
898
|
+
"jest", "mocha", "jasmine", "vitest", "playwright",
|
|
899
|
+
"typescript", "ts-node", "tsx",
|
|
900
|
+
"webpack", "vite", "rollup", "esbuild", "parcel",
|
|
901
|
+
"eslint", "prettier", "babel",
|
|
902
|
+
"mongoose", "sequelize", "prisma", "typeorm",
|
|
903
|
+
"mongodb", "redis", "pg", "mysql",
|
|
904
|
+
"jsonwebtoken", "bcrypt", "passport",
|
|
905
|
+
"winston", "pino", "morgan",
|
|
906
|
+
"dotenv", "cross-env", "rimraf",
|
|
907
|
+
"fs-extra", "glob", "minimatch",
|
|
908
|
+
"chalk", "commander", "inquirer",
|
|
909
|
+
"dayjs", "date-fns", "moment",
|
|
910
|
+
"uuid", "nanoid", "cuid",
|
|
911
|
+
"zod", "yup", "joi", "class-validator",
|
|
912
|
+
"tailwindcss", "styled-components", "emotion",
|
|
913
|
+
"@testing-library/react", "@testing-library/jest-dom",
|
|
914
|
+
"@types/node", "@types/react", "@types/express",
|
|
915
|
+
"next", "nuxt", "gatsby",
|
|
916
|
+
]);
|
|
917
|
+
if (commonPackages.has(pkgName.toLowerCase()))
|
|
918
|
+
return true;
|
|
919
|
+
if (pkgName.startsWith("@")) {
|
|
920
|
+
const scope = pkgName.split("/")[0];
|
|
921
|
+
const name = pkgName.split("/")[1];
|
|
922
|
+
if (!name)
|
|
923
|
+
return false;
|
|
924
|
+
const commonScopes = ["@types", "@babel", "@rollup", "@vitejs", "@nestjs", "@angular", "@mui"];
|
|
925
|
+
if (commonScopes.includes(scope))
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
// Fall back to real registry check (cached + fail-open on network errors)
|
|
929
|
+
return checkPackageRegistry(pkgName, language);
|
|
930
|
+
}
|
|
931
|
+
function extractPackageName(message) {
|
|
932
|
+
const match = message.match(/Package ['"]([^'"]+)['"]/);
|
|
933
|
+
return match?.[1] || "";
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Check if a package exists in any subdirectory's package.json.
|
|
937
|
+
* This catches cases where the main manifest loader missed subdirectory manifests
|
|
938
|
+
* (e.g., monorepo-style projects with frontend/package.json + backend/package.json).
|
|
939
|
+
*/
|
|
940
|
+
async function checkPackageInSubdirectoryManifest(pkgName, projectPath) {
|
|
941
|
+
const COMMON_SUBDIRS = ["frontend", "backend", "client", "server", "app", "web", "api"];
|
|
942
|
+
for (const subdir of COMMON_SUBDIRS) {
|
|
943
|
+
const pkgJsonPath = path.join(projectPath, subdir, "package.json");
|
|
944
|
+
try {
|
|
945
|
+
const content = await fs.readFile(pkgJsonPath, "utf-8");
|
|
946
|
+
const pkg = JSON.parse(content);
|
|
947
|
+
const allDeps = {
|
|
948
|
+
...pkg.dependencies,
|
|
949
|
+
...pkg.devDependencies,
|
|
950
|
+
...pkg.peerDependencies,
|
|
951
|
+
};
|
|
952
|
+
if (pkgName in allDeps) {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
// Also check @types/ mapping
|
|
956
|
+
if (`@types/${pkgName}` in (pkg.devDependencies || {})) {
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
// File doesn't exist or can't be parsed
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
async function verifyParameterCount(issue, ctx) {
|
|
967
|
+
const match = issue.message.match(/expects (\d+) args, got (\d+)/);
|
|
968
|
+
if (!match) {
|
|
969
|
+
return { isMismatch: true };
|
|
970
|
+
}
|
|
971
|
+
const expected = parseInt(match[1], 10);
|
|
972
|
+
const actual = parseInt(match[2], 10);
|
|
973
|
+
const symbolName = issue.message.match(/Function ['"]([^'"]+)['"]/)?.[1];
|
|
974
|
+
if (symbolName) {
|
|
975
|
+
const definitions = ctx.projectContext.symbolIndex.get(symbolName);
|
|
976
|
+
if (definitions && definitions.length > 0) {
|
|
977
|
+
const def = definitions[0];
|
|
978
|
+
if (def.symbol.params?.some((p) => p.name.startsWith("..."))) {
|
|
979
|
+
return { isMismatch: false, expected, actual };
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return { isMismatch: true, expected, actual };
|
|
984
|
+
}
|
|
985
|
+
async function checkImportUsage(issue, ctx) {
|
|
986
|
+
const importName = issue.message.match(/'([^']+)'/)?.[1];
|
|
987
|
+
if (!importName || !issue.file)
|
|
988
|
+
return { isUsed: false };
|
|
989
|
+
try {
|
|
990
|
+
let rawContent = ctx.fileContentCache.get(issue.file);
|
|
991
|
+
if (!rawContent) {
|
|
992
|
+
rawContent = await fs.readFile(issue.file, "utf-8");
|
|
993
|
+
ctx.fileContentCache.set(issue.file, rawContent);
|
|
994
|
+
}
|
|
995
|
+
// Strip import lines, comment-only lines, and the definition line so they
|
|
996
|
+
// don't count as "usage".
|
|
997
|
+
// Previously, `import { ChefHat } from 'lucide-react'; // ChefHat is unused`
|
|
998
|
+
// produced 2 regex matches (import + comment) and was falsely marked as "used".
|
|
999
|
+
//
|
|
1000
|
+
// For local dead code findings (e.g., `const GHOST_REGISTRY_ID = ...` or
|
|
1001
|
+
// `function deprecatedAuditLog()`), the definition line itself contains the
|
|
1002
|
+
// symbol name. Without excluding it, checkImportUsage falsely returns
|
|
1003
|
+
// isUsed=true because the regex matches the definition.
|
|
1004
|
+
const escapedName = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1005
|
+
const issueLine = issue.line; // 1-indexed line number of the finding
|
|
1006
|
+
const content = rawContent
|
|
1007
|
+
.split("\n")
|
|
1008
|
+
.filter((line, index) => {
|
|
1009
|
+
// Exclude the definition/declaration line itself (prevents local dead code
|
|
1010
|
+
// findings like GHOST_REGISTRY_ID from matching their own definition)
|
|
1011
|
+
if (issueLine && index + 1 === issueLine)
|
|
1012
|
+
return false;
|
|
1013
|
+
const trimmed = line.trim();
|
|
1014
|
+
// Exclude import lines
|
|
1015
|
+
if (trimmed.startsWith("import ") || trimmed.startsWith("import{"))
|
|
1016
|
+
return false;
|
|
1017
|
+
// Exclude from...import lines (Python)
|
|
1018
|
+
if (trimmed.startsWith("from "))
|
|
1019
|
+
return false;
|
|
1020
|
+
// Exclude comment-only lines (JS/TS/Python)
|
|
1021
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
|
|
1022
|
+
return false;
|
|
1023
|
+
return true;
|
|
1024
|
+
})
|
|
1025
|
+
.join("\n");
|
|
1026
|
+
const patterns = [
|
|
1027
|
+
{ regex: new RegExp(`\\b${escapedName}\\s*\\(`, "g"), type: "function call" },
|
|
1028
|
+
{ regex: new RegExp(`\\b${escapedName}\\.`, "g"), type: "property access" },
|
|
1029
|
+
{ regex: new RegExp(`<${escapedName}[\\s/>]`, "g"), type: "JSX component" },
|
|
1030
|
+
{ regex: new RegExp(`\\b${escapedName}\\b`, "g"), type: "reference" },
|
|
1031
|
+
{ regex: new RegExp(`type\\s+\\w+.*\\b${escapedName}\\b`, "g"), type: "type usage" },
|
|
1032
|
+
{ regex: new RegExp(`as\\s+${escapedName}\\b`, "g"), type: "type assertion" },
|
|
1033
|
+
];
|
|
1034
|
+
for (const { regex, type } of patterns) {
|
|
1035
|
+
const matches = content.match(regex);
|
|
1036
|
+
if (matches && matches.length >= 1) {
|
|
1037
|
+
return { isUsed: true, usageType: type };
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return { isUsed: false };
|
|
1041
|
+
}
|
|
1042
|
+
catch {
|
|
1043
|
+
return { isUsed: false };
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
async function checkInheritanceChain(issue, ctx) {
|
|
1047
|
+
return { foundInParent: false };
|
|
1048
|
+
}
|
|
1049
|
+
async function checkIfPublicApi(issue, ctx) {
|
|
1050
|
+
const publicPatterns = [
|
|
1051
|
+
/index\.ts$/,
|
|
1052
|
+
/index\.js$/,
|
|
1053
|
+
/\bapi\b/,
|
|
1054
|
+
/\bpublic\b/,
|
|
1055
|
+
/\bexports\b/,
|
|
1056
|
+
/\blib\b/,
|
|
1057
|
+
];
|
|
1058
|
+
return publicPatterns.some((pattern) => pattern.test(issue.file));
|
|
1059
|
+
}
|
|
1060
|
+
async function checkIndirectUsage(issue, ctx) {
|
|
1061
|
+
const functionName = issue.name;
|
|
1062
|
+
const filePath = issue.file;
|
|
1063
|
+
try {
|
|
1064
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
1065
|
+
const patterns = [
|
|
1066
|
+
{ regex: new RegExp(`\\b${functionName}\\b\\s*[,})\\]]`, "g"), pattern: "callback/collection" },
|
|
1067
|
+
{ regex: new RegExp(`['"]\\b${functionName}\\b['"]`, "g"), pattern: "string reference" },
|
|
1068
|
+
{ regex: new RegExp(`\\.\\b${functionName}\\b`, "g"), pattern: "method assignment" },
|
|
1069
|
+
];
|
|
1070
|
+
for (const { regex, pattern } of patterns) {
|
|
1071
|
+
if (regex.test(content)) {
|
|
1072
|
+
return { isUsed: true, pattern };
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return { isUsed: false };
|
|
1076
|
+
}
|
|
1077
|
+
catch {
|
|
1078
|
+
return { isUsed: false };
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function checkFileImports(issue, ctx) {
|
|
1082
|
+
// Use in-memory project context instead of reading every file from disk.
|
|
1083
|
+
// The reverseImportGraph + raw import data is already available and up-to-date.
|
|
1084
|
+
const importers = [];
|
|
1085
|
+
const fileName = issue.name || issue.file;
|
|
1086
|
+
// 1. Check reverseImportGraph (resolved imports)
|
|
1087
|
+
const reverseImportGraph = ctx.projectContext.reverseImportGraph;
|
|
1088
|
+
if (reverseImportGraph && typeof reverseImportGraph[Symbol.iterator] === "function") {
|
|
1089
|
+
for (const [absPath, reverseList] of reverseImportGraph) {
|
|
1090
|
+
// Match by absolute path or by basename
|
|
1091
|
+
if (absPath === fileName ||
|
|
1092
|
+
absPath.endsWith(`/${fileName}`) ||
|
|
1093
|
+
path.basename(absPath).replace(/\.[^.]+$/, "") ===
|
|
1094
|
+
path.basename(fileName).replace(/\.[^.]+$/, "")) {
|
|
1095
|
+
importers.push(...reverseList);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (importers.length > 0) {
|
|
1100
|
+
return { isImported: true, importers: [...new Set(importers)] };
|
|
1101
|
+
}
|
|
1102
|
+
// 2. Fallback: check raw import sources in context (catches unresolved imports)
|
|
1103
|
+
const baseName = path.basename(fileName).replace(/\.[^.]+$/, "");
|
|
1104
|
+
const files = ctx.projectContext.files;
|
|
1105
|
+
if (!files || typeof files[Symbol.iterator] !== "function") {
|
|
1106
|
+
return { isImported: false, importers: [] };
|
|
1107
|
+
}
|
|
1108
|
+
for (const [filePath, fileInfo] of files) {
|
|
1109
|
+
if (filePath === issue.file)
|
|
1110
|
+
continue;
|
|
1111
|
+
for (const imp of fileInfo.imports) {
|
|
1112
|
+
if (imp.source.includes(baseName)) {
|
|
1113
|
+
importers.push(filePath);
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return { isImported: importers.length > 0, importers: [...new Set(importers)] };
|
|
1119
|
+
}
|
|
1120
|
+
async function checkIfEntryPoint(issue, ctx) {
|
|
1121
|
+
const entryPatterns = [
|
|
1122
|
+
/main\.(ts|js)$/,
|
|
1123
|
+
/index\.(ts|js)$/,
|
|
1124
|
+
/app\.(ts|js)$/,
|
|
1125
|
+
/server\.(ts|js)$/,
|
|
1126
|
+
/cli\.(ts|js)$/,
|
|
1127
|
+
/vite\.config\./,
|
|
1128
|
+
/webpack\.config\./,
|
|
1129
|
+
/next\.config\./,
|
|
1130
|
+
/tsup\.config\./,
|
|
1131
|
+
/rollup\.config\./,
|
|
1132
|
+
/jest\.config\./,
|
|
1133
|
+
/vitest\.config\./,
|
|
1134
|
+
/playwright\.config\./,
|
|
1135
|
+
/cypress\.config\./,
|
|
1136
|
+
/tailwind\.config\./,
|
|
1137
|
+
/postcss\.config\./,
|
|
1138
|
+
/eslint\.config\./,
|
|
1139
|
+
/prettier\.config\./,
|
|
1140
|
+
];
|
|
1141
|
+
return entryPatterns.some((pattern) => pattern.test(issue.file));
|
|
1142
|
+
}
|
|
1143
|
+
// ============================================================================
|
|
1144
|
+
// Result Filtering Helpers
|
|
1145
|
+
// ============================================================================
|
|
1146
|
+
export function getConfirmedFindings(result) {
|
|
1147
|
+
// Include confirmed findings
|
|
1148
|
+
const confirmedHallucinations = result.confirmed
|
|
1149
|
+
.filter((f) => isValidationIssue(f.original))
|
|
1150
|
+
.map((f) => f.original);
|
|
1151
|
+
const confirmedDeadCode = result.confirmed
|
|
1152
|
+
.filter((f) => !isValidationIssue(f.original))
|
|
1153
|
+
.map((f) => f.original);
|
|
1154
|
+
// Also include high-confidence uncertain findings (confidence >= 85)
|
|
1155
|
+
// These are likely real issues that we couldn't fully verify
|
|
1156
|
+
const highConfidenceUncertainHallucinations = result.uncertain
|
|
1157
|
+
.filter((f) => f.confidence >= 85 && isValidationIssue(f.original))
|
|
1158
|
+
.map((f) => f.original);
|
|
1159
|
+
const highConfidenceUncertainDeadCode = result.uncertain
|
|
1160
|
+
.filter((f) => f.confidence >= 85 && !isValidationIssue(f.original))
|
|
1161
|
+
.map((f) => f.original);
|
|
1162
|
+
return {
|
|
1163
|
+
hallucinations: [...confirmedHallucinations, ...highConfidenceUncertainHallucinations],
|
|
1164
|
+
deadCode: [...confirmedDeadCode, ...highConfidenceUncertainDeadCode],
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
// Keep backward compatibility - alias for the main function
|
|
1168
|
+
export { verifyFindingsAutomatically as verifyFindings };
|
|
1169
|
+
//# sourceMappingURL=findingVerifier.js.map
|