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,875 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Contract Guardian - Validators Index
|
|
3
|
+
*
|
|
4
|
+
* Validates API contracts using the ProjectContext.
|
|
5
|
+
* This version works with the integrated context system.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Main Validation Function
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Validate API contracts from the ProjectContext
|
|
14
|
+
* This is the main entry point for API Contract validation
|
|
15
|
+
*/
|
|
16
|
+
export function validateApiContractsFromContext(context) {
|
|
17
|
+
if (!context.apiContract) {
|
|
18
|
+
return {
|
|
19
|
+
issues: [],
|
|
20
|
+
summary: {
|
|
21
|
+
totalIssues: 0,
|
|
22
|
+
critical: 0,
|
|
23
|
+
high: 0,
|
|
24
|
+
medium: 0,
|
|
25
|
+
low: 0,
|
|
26
|
+
matchedEndpoints: 0,
|
|
27
|
+
matchedTypes: 0,
|
|
28
|
+
unmatchedFrontend: 0,
|
|
29
|
+
unmatchedBackend: 0,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const apiContract = context.apiContract;
|
|
34
|
+
const issues = [];
|
|
35
|
+
// Validate endpoint mappings
|
|
36
|
+
issues.push(...validateEndpoints(apiContract));
|
|
37
|
+
// Validate type mappings
|
|
38
|
+
issues.push(...validateTypes(apiContract));
|
|
39
|
+
// Validate unmatched frontend services
|
|
40
|
+
issues.push(...validateUnmatchedFrontend(apiContract));
|
|
41
|
+
// Calculate summary
|
|
42
|
+
const summary = {
|
|
43
|
+
totalIssues: issues.length,
|
|
44
|
+
critical: issues.filter((i) => i.severity === "critical").length,
|
|
45
|
+
high: issues.filter((i) => i.severity === "high").length,
|
|
46
|
+
medium: issues.filter((i) => i.severity === "medium").length,
|
|
47
|
+
low: issues.filter((i) => i.severity === "low").length,
|
|
48
|
+
matchedEndpoints: apiContract.endpointMappings.size,
|
|
49
|
+
matchedTypes: apiContract.typeMappings.size,
|
|
50
|
+
unmatchedFrontend: apiContract.unmatchedFrontend.length,
|
|
51
|
+
unmatchedBackend: apiContract.unmatchedBackend.length,
|
|
52
|
+
};
|
|
53
|
+
return { issues, summary };
|
|
54
|
+
}
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Endpoint Validation
|
|
57
|
+
// ============================================================================
|
|
58
|
+
function validateEndpoints(apiContract) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
for (const [endpoint, mapping] of apiContract.endpointMappings) {
|
|
61
|
+
// Check HTTP method match
|
|
62
|
+
if (mapping.frontend.method !== mapping.backend.method) {
|
|
63
|
+
issues.push({
|
|
64
|
+
type: "apiMethodMismatch",
|
|
65
|
+
severity: "critical",
|
|
66
|
+
message: `HTTP method mismatch: frontend uses ${mapping.frontend.method}, backend expects ${mapping.backend.method}`,
|
|
67
|
+
file: mapping.frontend.file,
|
|
68
|
+
line: mapping.frontend.line,
|
|
69
|
+
endpoint,
|
|
70
|
+
suggestion: `Change frontend to use ${mapping.backend.method}`,
|
|
71
|
+
confidence: 95,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Check if there are multiple methods available for this endpoint
|
|
75
|
+
if (mapping.hasMultipleMethods && mapping.availableMethods) {
|
|
76
|
+
const currentMethod = mapping.frontend.method;
|
|
77
|
+
const otherMethods = mapping.availableMethods.filter(m => m !== currentMethod);
|
|
78
|
+
if (otherMethods.length > 0) {
|
|
79
|
+
// Check if the function name suggests a different method should be used
|
|
80
|
+
const functionName = mapping.frontend.name.toLowerCase();
|
|
81
|
+
const suggestedMethod = inferMethodFromFunctionName(functionName);
|
|
82
|
+
if (suggestedMethod && suggestedMethod !== currentMethod && mapping.availableMethods.includes(suggestedMethod)) {
|
|
83
|
+
issues.push({
|
|
84
|
+
type: "apiMethodMismatch",
|
|
85
|
+
severity: "high",
|
|
86
|
+
message: `Suspicious HTTP method: function '${mapping.frontend.name}' suggests ${suggestedMethod}, but frontend uses ${currentMethod}. Backend also supports: ${otherMethods.join(', ')}`,
|
|
87
|
+
file: mapping.frontend.file,
|
|
88
|
+
line: mapping.frontend.line,
|
|
89
|
+
endpoint,
|
|
90
|
+
suggestion: `Consider using ${suggestedMethod} instead of ${currentMethod} for '${mapping.frontend.name}'`,
|
|
91
|
+
confidence: 85,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Check path compatibility
|
|
97
|
+
const pathIssues = validatePathCompatibility(mapping.frontend.endpoint, mapping.backend.path, mapping.frontend.file, mapping.frontend.line);
|
|
98
|
+
issues.push(...pathIssues);
|
|
99
|
+
// Validate request/response body types
|
|
100
|
+
const bodyIssues = validateRequestResponseTypes(mapping, apiContract, endpoint);
|
|
101
|
+
issues.push(...bodyIssues);
|
|
102
|
+
}
|
|
103
|
+
return issues;
|
|
104
|
+
}
|
|
105
|
+
function inferMethodFromFunctionName(functionName) {
|
|
106
|
+
const methodPatterns = {
|
|
107
|
+
'GET': ['get', 'fetch', 'load', 'retrieve', 'read', 'list', 'find', 'search'],
|
|
108
|
+
'POST': ['create', 'add', 'post', 'submit', 'save', 'insert', 'register', 'login', 'logout'],
|
|
109
|
+
'PUT': ['update', 'edit', 'modify', 'change', 'replace'],
|
|
110
|
+
'PATCH': ['patch', 'partial', 'tweak'],
|
|
111
|
+
'DELETE': ['delete', 'remove', 'destroy', 'clear', 'drop']
|
|
112
|
+
};
|
|
113
|
+
for (const [method, patterns] of Object.entries(methodPatterns)) {
|
|
114
|
+
if (patterns.some(pattern => functionName.includes(pattern))) {
|
|
115
|
+
return method;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function validatePathCompatibility(frontendPath, backendPath, file, line) {
|
|
121
|
+
const issues = [];
|
|
122
|
+
// Normalize both paths (converting all param formats to {param})
|
|
123
|
+
const normalizedFrontend = normalizePathForComparison(frontendPath);
|
|
124
|
+
const normalizedBackend = normalizePathForComparison(backendPath);
|
|
125
|
+
// Check if paths match (accounting for API prefixes and param formats)
|
|
126
|
+
if (!pathsMatch(normalizedFrontend, normalizedBackend)) {
|
|
127
|
+
const frontendWithoutPrefix = removeApiPrefix(normalizedFrontend);
|
|
128
|
+
const backendWithoutPrefix = removeApiPrefix(normalizedBackend);
|
|
129
|
+
if (!pathsMatch(frontendWithoutPrefix, backendWithoutPrefix)) {
|
|
130
|
+
issues.push({
|
|
131
|
+
type: "apiPathMismatch",
|
|
132
|
+
severity: "high",
|
|
133
|
+
message: `Endpoint path mismatch: frontend calls '${frontendPath}', backend route is '${backendPath}'`,
|
|
134
|
+
file,
|
|
135
|
+
line,
|
|
136
|
+
endpoint: frontendPath,
|
|
137
|
+
suggestion: `Update frontend path to match backend: '${backendPath}'`,
|
|
138
|
+
confidence: 90,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check for path parameter consistency using normalized paths
|
|
143
|
+
const frontendParams = extractPathParams(frontendPath);
|
|
144
|
+
const backendParams = extractPathParams(backendPath);
|
|
145
|
+
// Use prefix-stripped, fully normalized paths for positional comparison
|
|
146
|
+
// so frontend "roles/{role_id}/sops" matches backend "api/roles/{id}/sops"
|
|
147
|
+
const feStripped = removeApiPrefix(normalizedFrontend);
|
|
148
|
+
const beStripped = removeApiPrefix(normalizedBackend);
|
|
149
|
+
const frontendSegments = feStripped.split('/');
|
|
150
|
+
const backendSegments = beStripped.split('/');
|
|
151
|
+
// Build positional param map: match params by their position in the path
|
|
152
|
+
const positionallyMatched = new Set();
|
|
153
|
+
const positionallyMatchedBackend = new Set();
|
|
154
|
+
if (frontendSegments.length === backendSegments.length) {
|
|
155
|
+
for (let i = 0; i < frontendSegments.length; i++) {
|
|
156
|
+
const fSeg = frontendSegments[i];
|
|
157
|
+
const bSeg = backendSegments[i];
|
|
158
|
+
if (isPathParam(fSeg) && isPathParam(bSeg)) {
|
|
159
|
+
// Both are path params at the same position — they're equivalent
|
|
160
|
+
const fParam = fSeg.replace(/[{}\$:]/g, '').split(':')[0];
|
|
161
|
+
const bParam = bSeg.replace(/[{}\$:]/g, '').split(':')[0];
|
|
162
|
+
// Convert camelCase frontend param to snake_case for comparison
|
|
163
|
+
const fParamSnake = fParam.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
164
|
+
positionallyMatched.add(fParam);
|
|
165
|
+
positionallyMatched.add(fParamSnake);
|
|
166
|
+
positionallyMatchedBackend.add(bParam);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Check for missing parameters in frontend (skip positionally matched ones)
|
|
171
|
+
for (const param of backendParams) {
|
|
172
|
+
if (!frontendParams.includes(param) && !positionallyMatchedBackend.has(param)) {
|
|
173
|
+
issues.push({
|
|
174
|
+
type: "apiPathMismatch",
|
|
175
|
+
severity: "high",
|
|
176
|
+
message: `Missing path parameter '${param}' in frontend URL`,
|
|
177
|
+
file,
|
|
178
|
+
line,
|
|
179
|
+
endpoint: frontendPath,
|
|
180
|
+
suggestion: `Add '${param}' parameter to frontend path: '${backendPath}'`,
|
|
181
|
+
confidence: 95,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Check for extra parameters in frontend (skip positionally matched ones)
|
|
186
|
+
for (const param of frontendParams) {
|
|
187
|
+
if (!backendParams.includes(param) && !positionallyMatched.has(param)) {
|
|
188
|
+
issues.push({
|
|
189
|
+
type: "apiPathMismatch",
|
|
190
|
+
severity: "medium",
|
|
191
|
+
message: `Extra path parameter '${param}' in frontend URL not found in backend`,
|
|
192
|
+
file,
|
|
193
|
+
line,
|
|
194
|
+
endpoint: frontendPath,
|
|
195
|
+
suggestion: `Remove '${param}' parameter or update backend route`,
|
|
196
|
+
confidence: 80,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return issues;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Validate request and response body types between frontend and backend
|
|
204
|
+
*/
|
|
205
|
+
function validateRequestResponseTypes(mapping, apiContract, endpoint) {
|
|
206
|
+
const issues = [];
|
|
207
|
+
// Validate request body type
|
|
208
|
+
if (mapping.frontend.requestType && mapping.backend.requestModel) {
|
|
209
|
+
const frontendRequestType = mapping.frontend.requestType;
|
|
210
|
+
const backendRequestModel = mapping.backend.requestModel;
|
|
211
|
+
// Skip validation for complex types that couldn't be extracted properly
|
|
212
|
+
if (isComplexType(frontendRequestType) || isComplexType(backendRequestModel)) {
|
|
213
|
+
// Complex types - skip detailed validation
|
|
214
|
+
}
|
|
215
|
+
else if (frontendRequestType !== backendRequestModel) {
|
|
216
|
+
// Check if they're similar type names
|
|
217
|
+
if (!areSimilarTypeNames(frontendRequestType.toLowerCase(), backendRequestModel.toLowerCase())) {
|
|
218
|
+
// Check if there's a type mapping for this
|
|
219
|
+
const typeMapping = apiContract.typeMappings.get(frontendRequestType);
|
|
220
|
+
if (!typeMapping || typeMapping.backend?.name !== backendRequestModel) {
|
|
221
|
+
// Check if FE sends an array type that the BE wraps in a model
|
|
222
|
+
// e.g., FE sends PaymentMilestoneCreate[] → BE expects PaymentScheduleSetup { milestones: List[PaymentMilestoneCreate] }
|
|
223
|
+
const isWrappedArray = isArrayTypeWrappedInModel(frontendRequestType, backendRequestModel, apiContract);
|
|
224
|
+
if (!isWrappedArray) {
|
|
225
|
+
issues.push({
|
|
226
|
+
type: "apiTypeMismatch",
|
|
227
|
+
severity: "high",
|
|
228
|
+
message: `Request body type mismatch: frontend sends '${frontendRequestType}', backend expects '${backendRequestModel}'`,
|
|
229
|
+
file: mapping.frontend.file,
|
|
230
|
+
line: mapping.frontend.line,
|
|
231
|
+
endpoint,
|
|
232
|
+
suggestion: `Ensure frontend type '${frontendRequestType}' matches backend model '${backendRequestModel}'`,
|
|
233
|
+
confidence: 85,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Validate field compatibility if we have the type mapping
|
|
240
|
+
const typeMapping = apiContract.typeMappings.get(frontendRequestType);
|
|
241
|
+
if (typeMapping) {
|
|
242
|
+
const backendModel = apiContract.backendModels.find(m => m.name === backendRequestModel);
|
|
243
|
+
if (backendModel) {
|
|
244
|
+
// Check for missing required fields
|
|
245
|
+
for (const backendField of backendModel.fields) {
|
|
246
|
+
if (backendField.required) {
|
|
247
|
+
const frontendField = typeMapping.frontend.fields.find((f) => normalizeName(f.name) === normalizeName(backendField.name));
|
|
248
|
+
if (!frontendField) {
|
|
249
|
+
issues.push({
|
|
250
|
+
type: "apiMissingRequiredField",
|
|
251
|
+
severity: "critical",
|
|
252
|
+
message: `Missing required field '${backendField.name}' in request body. Backend model '${backendRequestModel}' requires this field`,
|
|
253
|
+
file: mapping.frontend.file,
|
|
254
|
+
line: mapping.frontend.line,
|
|
255
|
+
endpoint,
|
|
256
|
+
suggestion: `Add '${backendField.name}: ${backendField.type}' to frontend type '${frontendRequestType}'`,
|
|
257
|
+
confidence: 95,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Validate response body type
|
|
266
|
+
if (mapping.frontend.responseType && mapping.backend.responseModel) {
|
|
267
|
+
const frontendResponseType = mapping.frontend.responseType;
|
|
268
|
+
const backendResponseModel = mapping.backend.responseModel;
|
|
269
|
+
// Handle array types (e.g., Client[])
|
|
270
|
+
const normalizedFrontendType = frontendResponseType.replace(/\[\]$/, "");
|
|
271
|
+
const normalizedBackendModel = backendResponseModel.replace(/\[\]$/, "");
|
|
272
|
+
if (normalizedFrontendType !== normalizedBackendModel) {
|
|
273
|
+
// Check if there's a type mapping for this
|
|
274
|
+
const typeMapping = apiContract.typeMappings.get(normalizedFrontendType);
|
|
275
|
+
if (!typeMapping || typeMapping.backend?.name !== normalizedBackendModel) {
|
|
276
|
+
issues.push({
|
|
277
|
+
type: "apiTypeMismatch",
|
|
278
|
+
severity: "medium",
|
|
279
|
+
message: `Response type mismatch: frontend expects '${frontendResponseType}', backend returns '${backendResponseModel}'`,
|
|
280
|
+
file: mapping.frontend.file,
|
|
281
|
+
line: mapping.frontend.line,
|
|
282
|
+
endpoint,
|
|
283
|
+
suggestion: `Ensure frontend type '${frontendResponseType}' is compatible with backend model '${backendResponseModel}'`,
|
|
284
|
+
confidence: 80,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Validate query parameters
|
|
290
|
+
const queryParamIssues = validateQueryParameters(mapping, endpoint);
|
|
291
|
+
issues.push(...queryParamIssues);
|
|
292
|
+
return issues;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Validate query parameters between frontend and backend
|
|
296
|
+
*/
|
|
297
|
+
function validateQueryParameters(mapping, endpoint) {
|
|
298
|
+
const issues = [];
|
|
299
|
+
const frontendParams = mapping.frontend.queryParams || [];
|
|
300
|
+
const backendParams = mapping.backend.queryParams || [];
|
|
301
|
+
// Check for missing required query params in frontend
|
|
302
|
+
for (const backendParam of backendParams) {
|
|
303
|
+
if (backendParam.required) {
|
|
304
|
+
const frontendParam = frontendParams.find((p) => p.name === backendParam.name);
|
|
305
|
+
if (!frontendParam) {
|
|
306
|
+
issues.push({
|
|
307
|
+
type: "apiContractMismatch",
|
|
308
|
+
severity: "medium",
|
|
309
|
+
message: `Missing required query parameter '${backendParam.name}' in frontend. Backend expects ${backendParam.type}`,
|
|
310
|
+
file: mapping.frontend.file,
|
|
311
|
+
line: mapping.frontend.line,
|
|
312
|
+
endpoint,
|
|
313
|
+
suggestion: `Add query parameter '${backendParam.name}: ${backendParam.type}' to frontend request`,
|
|
314
|
+
confidence: 90,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Check for type mismatches in query params
|
|
320
|
+
for (const frontendParam of frontendParams) {
|
|
321
|
+
const backendParam = backendParams.find((p) => p.name === frontendParam.name);
|
|
322
|
+
if (backendParam) {
|
|
323
|
+
const result = areTypesCompatible(frontendParam.type, backendParam.type);
|
|
324
|
+
if (!result.compatible || result.severity) {
|
|
325
|
+
issues.push({
|
|
326
|
+
type: "apiTypeMismatch",
|
|
327
|
+
severity: result.severity || "medium",
|
|
328
|
+
message: result.reason || `Query parameter '${frontendParam.name}' type mismatch: frontend uses '${frontendParam.type}', backend expects '${backendParam.type}'`,
|
|
329
|
+
file: mapping.frontend.file,
|
|
330
|
+
line: mapping.frontend.line,
|
|
331
|
+
endpoint,
|
|
332
|
+
suggestion: result.suggestion || `Ensure query parameter '${frontendParam.name}' types are compatible`,
|
|
333
|
+
confidence: 85,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return issues;
|
|
339
|
+
}
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Type Validation
|
|
342
|
+
// ============================================================================
|
|
343
|
+
function validateTypes(apiContract) {
|
|
344
|
+
const issues = [];
|
|
345
|
+
for (const [typeName, mapping] of apiContract.typeMappings) {
|
|
346
|
+
const frontendType = mapping.frontend;
|
|
347
|
+
const backendModel = mapping.backend;
|
|
348
|
+
// Check for missing required fields in frontend
|
|
349
|
+
for (const backendField of backendModel.fields) {
|
|
350
|
+
if (backendField.required) {
|
|
351
|
+
const frontendField = frontendType.fields.find((f) => normalizeName(f.name) === normalizeName(backendField.name));
|
|
352
|
+
if (!frontendField) {
|
|
353
|
+
issues.push({
|
|
354
|
+
type: "apiMissingRequiredField",
|
|
355
|
+
severity: "high",
|
|
356
|
+
message: `Missing required field '${backendField.name}' in frontend type '${frontendType.name}'`,
|
|
357
|
+
file: frontendType.file,
|
|
358
|
+
line: frontendType.line,
|
|
359
|
+
suggestion: `Add '${backendField.name}: ${backendField.type}' to ${frontendType.name} interface`,
|
|
360
|
+
confidence: 95,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Check for naming convention mismatches
|
|
366
|
+
// Skip if both files are TypeScript/JavaScript (same naming convention expected)
|
|
367
|
+
const isSameLanguage = isSameLanguageProject(frontendType.file, backendModel.file);
|
|
368
|
+
if (!isSameLanguage) {
|
|
369
|
+
for (const frontendField of frontendType.fields) {
|
|
370
|
+
const backendField = backendModel.fields.find((f) => normalizeName(f.name) === normalizeName(frontendField.name));
|
|
371
|
+
if (backendField && frontendField.name !== backendField.name) {
|
|
372
|
+
issues.push({
|
|
373
|
+
type: "apiNamingConventionMismatch",
|
|
374
|
+
severity: "medium",
|
|
375
|
+
message: `Naming convention mismatch: '${frontendField.name}' should be '${backendField.name}'`,
|
|
376
|
+
file: frontendType.file,
|
|
377
|
+
line: frontendType.line,
|
|
378
|
+
suggestion: `Rename to '${backendField.name}' to match backend convention`,
|
|
379
|
+
confidence: 90,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Check type compatibility
|
|
385
|
+
for (const frontendField of frontendType.fields) {
|
|
386
|
+
const backendField = backendModel.fields.find((f) => normalizeName(f.name) === normalizeName(frontendField.name));
|
|
387
|
+
if (backendField) {
|
|
388
|
+
const result = areTypesCompatible(frontendField.type, backendField.type);
|
|
389
|
+
if (!result.compatible || result.severity) {
|
|
390
|
+
issues.push({
|
|
391
|
+
type: "apiTypeMismatch",
|
|
392
|
+
severity: result.severity || "medium",
|
|
393
|
+
message: result.reason || `Type mismatch for field '${frontendField.name}': frontend uses '${frontendField.type}', backend expects '${backendField.type}'`,
|
|
394
|
+
file: frontendType.file,
|
|
395
|
+
line: frontendType.line,
|
|
396
|
+
suggestion: result.suggestion || getTypeCompatibilitySuggestion(frontendField.type, backendField.type),
|
|
397
|
+
confidence: 85,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return issues;
|
|
404
|
+
}
|
|
405
|
+
// ============================================================================
|
|
406
|
+
// Unmatched Frontend Validation
|
|
407
|
+
// ============================================================================
|
|
408
|
+
function validateUnmatchedFrontend(apiContract) {
|
|
409
|
+
const issues = [];
|
|
410
|
+
for (const service of apiContract.unmatchedFrontend) {
|
|
411
|
+
// Skip ignored endpoints (webhooks, admin, internal, etc.)
|
|
412
|
+
if (shouldIgnoreEndpoint(service.endpoint)) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
// Check if there's a similar endpoint with different method or path
|
|
416
|
+
const similarRoute = findSimilarRoute(service, apiContract.backendRoutes);
|
|
417
|
+
if (similarRoute) {
|
|
418
|
+
// Check if it's a method mismatch
|
|
419
|
+
if (similarRoute.path === service.endpoint ||
|
|
420
|
+
normalizePath(similarRoute.path) === normalizePath(service.endpoint)) {
|
|
421
|
+
issues.push({
|
|
422
|
+
type: "apiMethodMismatch",
|
|
423
|
+
severity: "critical",
|
|
424
|
+
message: `HTTP method mismatch: frontend uses ${service.method}, backend expects ${similarRoute.method} for '${service.endpoint}'`,
|
|
425
|
+
file: service.file,
|
|
426
|
+
line: service.line,
|
|
427
|
+
endpoint: service.endpoint,
|
|
428
|
+
suggestion: `Change frontend to use ${similarRoute.method} instead of ${service.method}`,
|
|
429
|
+
confidence: 95,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// It's a path mismatch
|
|
434
|
+
issues.push({
|
|
435
|
+
type: "apiPathMismatch",
|
|
436
|
+
severity: "high",
|
|
437
|
+
message: `Endpoint path mismatch: frontend calls '${service.endpoint}', similar backend route is '${similarRoute.path}' (${similarRoute.method})`,
|
|
438
|
+
file: service.file,
|
|
439
|
+
line: service.line,
|
|
440
|
+
endpoint: service.endpoint,
|
|
441
|
+
suggestion: `Update frontend path to '${similarRoute.path}' to match backend, or verify the endpoint URL is correct`,
|
|
442
|
+
confidence: 90,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// No similar route found
|
|
448
|
+
issues.push({
|
|
449
|
+
type: "apiEndpointNotFound",
|
|
450
|
+
severity: "critical",
|
|
451
|
+
message: `Endpoint '${service.method} ${service.endpoint}' not found in backend`,
|
|
452
|
+
file: service.file,
|
|
453
|
+
line: service.line,
|
|
454
|
+
endpoint: service.endpoint,
|
|
455
|
+
suggestion: "Check if backend route exists or verify the endpoint URL",
|
|
456
|
+
confidence: 95,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return issues;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Default ignore patterns for common false positives
|
|
464
|
+
*/
|
|
465
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
466
|
+
// Webhook endpoints
|
|
467
|
+
/\/webhook/i,
|
|
468
|
+
/\/webhooks/i,
|
|
469
|
+
/\/stripe\/webhook/i,
|
|
470
|
+
/\/paypal\/webhook/i,
|
|
471
|
+
/\/github\/webhook/i,
|
|
472
|
+
/\/slack\/webhook/i,
|
|
473
|
+
// Admin routes
|
|
474
|
+
/\/admin/i,
|
|
475
|
+
/\/api\/admin/i,
|
|
476
|
+
/\/management/i,
|
|
477
|
+
/\/api\/management/i,
|
|
478
|
+
// Internal/debug routes
|
|
479
|
+
/\/debug/i,
|
|
480
|
+
/\/internal/i,
|
|
481
|
+
/\/api\/internal/i,
|
|
482
|
+
/\/health/i,
|
|
483
|
+
/\/ping/i,
|
|
484
|
+
/\/metrics/i,
|
|
485
|
+
/\/ready/i,
|
|
486
|
+
/\/alive/i,
|
|
487
|
+
// OAuth/Auth callbacks
|
|
488
|
+
/\/auth\/callback/i,
|
|
489
|
+
/\/oauth/i,
|
|
490
|
+
/\/oauth2/i,
|
|
491
|
+
/\/callback/i,
|
|
492
|
+
// API documentation
|
|
493
|
+
/\/docs/i,
|
|
494
|
+
/\/swagger/i,
|
|
495
|
+
/\/openapi/i,
|
|
496
|
+
/\/redoc/i,
|
|
497
|
+
];
|
|
498
|
+
/**
|
|
499
|
+
* Check if an endpoint should be ignored
|
|
500
|
+
*/
|
|
501
|
+
function shouldIgnoreEndpoint(endpoint) {
|
|
502
|
+
return DEFAULT_IGNORE_PATTERNS.some((pattern) => pattern.test(endpoint));
|
|
503
|
+
}
|
|
504
|
+
function findSimilarRoute(service, backendRoutes) {
|
|
505
|
+
const normalizedEndpoint = normalizePathForComparison(service.endpoint);
|
|
506
|
+
// First, check for exact path match with different method (normalizing params)
|
|
507
|
+
const samePathDifferentMethod = backendRoutes.find(route => {
|
|
508
|
+
const normalizedRoute = normalizePathForComparison(route.path);
|
|
509
|
+
return normalizedRoute === normalizedEndpoint &&
|
|
510
|
+
route.method.toUpperCase() !== service.method.toUpperCase();
|
|
511
|
+
});
|
|
512
|
+
if (samePathDifferentMethod) {
|
|
513
|
+
return samePathDifferentMethod;
|
|
514
|
+
}
|
|
515
|
+
// Also check with API prefix stripped
|
|
516
|
+
const endpointNoPrefix = removeApiPrefix(normalizedEndpoint);
|
|
517
|
+
const samePathNoPrefixDiffMethod = backendRoutes.find(route => {
|
|
518
|
+
const normalizedRoute = removeApiPrefix(normalizePathForComparison(route.path));
|
|
519
|
+
return normalizedRoute === endpointNoPrefix &&
|
|
520
|
+
route.method.toUpperCase() !== service.method.toUpperCase();
|
|
521
|
+
});
|
|
522
|
+
if (samePathNoPrefixDiffMethod) {
|
|
523
|
+
return samePathNoPrefixDiffMethod;
|
|
524
|
+
}
|
|
525
|
+
// Then, check for similar paths (same number of segments, similar structure)
|
|
526
|
+
const endpointSegments = normalizedEndpoint.split('/');
|
|
527
|
+
for (const route of backendRoutes) {
|
|
528
|
+
const normalizedRoute = normalizePathForComparison(route.path);
|
|
529
|
+
const routeSegments = normalizedRoute.split('/');
|
|
530
|
+
// Must have same number of segments
|
|
531
|
+
if (routeSegments.length !== endpointSegments.length) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
// Check if most segments match (allowing for one different segment)
|
|
535
|
+
let matchingSegments = 0;
|
|
536
|
+
let totalSegments = endpointSegments.length;
|
|
537
|
+
for (let i = 0; i < endpointSegments.length; i++) {
|
|
538
|
+
const endpointSeg = endpointSegments[i];
|
|
539
|
+
const routeSeg = routeSegments[i];
|
|
540
|
+
// Match if they're identical, or both are parameters, or one is a parameter
|
|
541
|
+
if (endpointSeg === routeSeg ||
|
|
542
|
+
isPathParam(endpointSeg) ||
|
|
543
|
+
isPathParam(routeSeg)) {
|
|
544
|
+
matchingSegments++;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// If at least 70% of segments match, consider it similar
|
|
548
|
+
if (matchingSegments / totalSegments >= 0.7) {
|
|
549
|
+
return route;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Try again with API prefix stripped
|
|
553
|
+
const endpointSegmentsNoPrefix = endpointNoPrefix.split('/');
|
|
554
|
+
for (const route of backendRoutes) {
|
|
555
|
+
const normalizedRoute = removeApiPrefix(normalizePathForComparison(route.path));
|
|
556
|
+
const routeSegments = normalizedRoute.split('/');
|
|
557
|
+
if (routeSegments.length !== endpointSegmentsNoPrefix.length)
|
|
558
|
+
continue;
|
|
559
|
+
let matchingSegments = 0;
|
|
560
|
+
for (let i = 0; i < endpointSegmentsNoPrefix.length; i++) {
|
|
561
|
+
if (endpointSegmentsNoPrefix[i] === routeSegments[i] ||
|
|
562
|
+
isPathParam(endpointSegmentsNoPrefix[i]) ||
|
|
563
|
+
isPathParam(routeSegments[i])) {
|
|
564
|
+
matchingSegments++;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (matchingSegments / endpointSegmentsNoPrefix.length >= 0.7) {
|
|
568
|
+
return route;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
// ============================================================================
|
|
574
|
+
// Utility Functions
|
|
575
|
+
// ============================================================================
|
|
576
|
+
/**
|
|
577
|
+
* Check if frontend and backend files are in the same language family
|
|
578
|
+
* (both TS/JS = same naming convention, no need to flag camelCase vs snake_case)
|
|
579
|
+
*/
|
|
580
|
+
function isSameLanguageProject(frontendFile, backendFile) {
|
|
581
|
+
const tsJsExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
582
|
+
const pyExtensions = [".py"];
|
|
583
|
+
const feIsTs = tsJsExtensions.some(ext => frontendFile.endsWith(ext));
|
|
584
|
+
const beIsTs = tsJsExtensions.some(ext => backendFile.endsWith(ext));
|
|
585
|
+
const beIsPy = pyExtensions.some(ext => backendFile.endsWith(ext));
|
|
586
|
+
// Same language if both are TS/JS
|
|
587
|
+
if (feIsTs && beIsTs)
|
|
588
|
+
return true;
|
|
589
|
+
// Different languages if FE is TS and BE is Python
|
|
590
|
+
if (feIsTs && beIsPy)
|
|
591
|
+
return false;
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
function normalizePath(path) {
|
|
595
|
+
return path.replace(/\/+/g, "/").replace(/\/$/, "").replace(/^\//, "");
|
|
596
|
+
}
|
|
597
|
+
function removeApiPrefix(path) {
|
|
598
|
+
return path.replace(/^(api|v\d+|rest)\//, "");
|
|
599
|
+
}
|
|
600
|
+
function normalizeName(name) {
|
|
601
|
+
return name
|
|
602
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
603
|
+
.toLowerCase()
|
|
604
|
+
.replace(/_/g, "");
|
|
605
|
+
}
|
|
606
|
+
function pathsMatch(path1, path2) {
|
|
607
|
+
// Normalize both paths to use {param} format for comparison
|
|
608
|
+
const normalized1 = normalizePathForComparison(path1);
|
|
609
|
+
const normalized2 = normalizePathForComparison(path2);
|
|
610
|
+
if (normalized1 === normalized2)
|
|
611
|
+
return true;
|
|
612
|
+
const segments1 = normalized1.split("/");
|
|
613
|
+
const segments2 = normalized2.split("/");
|
|
614
|
+
if (segments1.length !== segments2.length)
|
|
615
|
+
return false;
|
|
616
|
+
for (let i = 0; i < segments1.length; i++) {
|
|
617
|
+
const seg1 = segments1[i];
|
|
618
|
+
const seg2 = segments2[i];
|
|
619
|
+
if (isPathParam(seg1) || isPathParam(seg2))
|
|
620
|
+
continue;
|
|
621
|
+
if (seg1 !== seg2)
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Normalize path for comparison by converting all parameter formats to {param}
|
|
628
|
+
*/
|
|
629
|
+
function normalizePathForComparison(path) {
|
|
630
|
+
// First apply basic normalization
|
|
631
|
+
let normalized = normalizePath(path);
|
|
632
|
+
// Convert JavaScript ${variable} to {variable} for comparison
|
|
633
|
+
normalized = normalized.replace(/\$\{(\w+)\}/g, (match, varName) => {
|
|
634
|
+
// Convert camelCase to snake_case
|
|
635
|
+
const snakeCase = varName.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
636
|
+
return `{${snakeCase}}`;
|
|
637
|
+
});
|
|
638
|
+
// Convert Express :param to {param} for comparison
|
|
639
|
+
normalized = normalized.replace(/:([a-zA-Z_]\w*)/g, (match, paramName) => {
|
|
640
|
+
return `{${paramName}}`;
|
|
641
|
+
});
|
|
642
|
+
return normalized;
|
|
643
|
+
}
|
|
644
|
+
function isPathParam(segment) {
|
|
645
|
+
// Python/FastAPI style: {param}
|
|
646
|
+
if (segment.startsWith("{") && segment.endsWith("}"))
|
|
647
|
+
return true;
|
|
648
|
+
// JavaScript template literal style: ${variable}
|
|
649
|
+
if (segment.startsWith("${") && segment.endsWith("}"))
|
|
650
|
+
return true;
|
|
651
|
+
// Express style: :param
|
|
652
|
+
if (segment.startsWith(":") && /^:[a-zA-Z_]\w*$/.test(segment))
|
|
653
|
+
return true;
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
function extractPathParams(path) {
|
|
657
|
+
const params = [];
|
|
658
|
+
// Extract Python/FastAPI style params: {param} or {param:type}
|
|
659
|
+
const pythonMatches = path.match(/\{([^}]+)\}/g);
|
|
660
|
+
if (pythonMatches) {
|
|
661
|
+
for (const match of pythonMatches) {
|
|
662
|
+
const param = match.replace(/[{}]/g, "").split(":")[0];
|
|
663
|
+
params.push(param);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// Extract JavaScript template literal params: ${variable}
|
|
667
|
+
const jsMatches = path.match(/\$\{(\w+)\}/g);
|
|
668
|
+
if (jsMatches) {
|
|
669
|
+
for (const match of jsMatches) {
|
|
670
|
+
const param = match.replace(/[\${}]/g, "");
|
|
671
|
+
// Convert camelCase to snake_case for comparison
|
|
672
|
+
const snakeCase = param.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
673
|
+
params.push(snakeCase);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Extract Express style params: :param
|
|
677
|
+
const expressMatches = path.match(/:([a-zA-Z_]\w*)/g);
|
|
678
|
+
if (expressMatches) {
|
|
679
|
+
for (const match of expressMatches) {
|
|
680
|
+
const param = match.replace(/^:/, "");
|
|
681
|
+
params.push(param);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return params;
|
|
685
|
+
}
|
|
686
|
+
function areTypesCompatible(tsType, pyType) {
|
|
687
|
+
// Skip validation for complex/object types that couldn't be extracted properly
|
|
688
|
+
if (isComplexType(tsType) || isComplexType(pyType)) {
|
|
689
|
+
return { compatible: true };
|
|
690
|
+
}
|
|
691
|
+
const normalizedTs = normalizeType(tsType);
|
|
692
|
+
const normalizedPy = normalizeType(pyType);
|
|
693
|
+
// Exact match
|
|
694
|
+
if (normalizedTs === normalizedPy) {
|
|
695
|
+
return { compatible: true };
|
|
696
|
+
}
|
|
697
|
+
// Known compatible pairs
|
|
698
|
+
const compatibleMap = {
|
|
699
|
+
string: ["str", "text", "email", "uuid", "url", "slug", "char"],
|
|
700
|
+
number: ["int", "float", "integer", "double", "decimal"],
|
|
701
|
+
boolean: ["bool"],
|
|
702
|
+
date: ["datetime", "date", "time"],
|
|
703
|
+
any: ["any"],
|
|
704
|
+
file: ["uploadfile", "file"],
|
|
705
|
+
};
|
|
706
|
+
const compatibleTypes = compatibleMap[normalizedTs] || [];
|
|
707
|
+
if (compatibleTypes.includes(normalizedPy)) {
|
|
708
|
+
return { compatible: true };
|
|
709
|
+
}
|
|
710
|
+
// Check for similar type names (e.g., Client vs ClientResponse)
|
|
711
|
+
if (areSimilarTypeNames(normalizedTs, normalizedPy)) {
|
|
712
|
+
return { compatible: true };
|
|
713
|
+
}
|
|
714
|
+
// Check for problematic pairs
|
|
715
|
+
const problematicResult = checkProblematicTypePairs(tsType, pyType);
|
|
716
|
+
if (problematicResult) {
|
|
717
|
+
return problematicResult;
|
|
718
|
+
}
|
|
719
|
+
// Default: assume compatible with low confidence
|
|
720
|
+
return { compatible: true };
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Check if a type string represents a complex/object type that couldn't be extracted properly
|
|
724
|
+
*/
|
|
725
|
+
function isComplexType(type) {
|
|
726
|
+
// If type contains newlines, braces, or is very long, it's likely a complex object literal
|
|
727
|
+
if (type.includes('\n') || type.includes('{') || type.includes('}')) {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
// If type is very long (>50 chars), it's likely not a simple type name
|
|
731
|
+
if (type.length > 50) {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Check if two type names are similar (e.g., Client vs ClientResponse)
|
|
738
|
+
* But NOT if they differ by operation type (Create vs Update vs Delete)
|
|
739
|
+
*/
|
|
740
|
+
function areSimilarTypeNames(type1, type2) {
|
|
741
|
+
// If both have different CRUD operation suffixes, they're NOT similar
|
|
742
|
+
// (e.g., ClientCreate vs ClientUpdate are different request body types)
|
|
743
|
+
const crudSuffixes = ['create', 'update', 'delete', 'patch'];
|
|
744
|
+
const suffix1 = crudSuffixes.find(s => type1.endsWith(s));
|
|
745
|
+
const suffix2 = crudSuffixes.find(s => type2.endsWith(s));
|
|
746
|
+
if (suffix1 && suffix2 && suffix1 !== suffix2) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
// Remove common non-CRUD suffixes
|
|
750
|
+
const clean1 = type1.replace(/(response|request|model|entity|dto|schema)$/i, '');
|
|
751
|
+
const clean2 = type2.replace(/(response|request|model|entity|dto|schema)$/i, '');
|
|
752
|
+
// If one contains the other, they're likely related
|
|
753
|
+
if (clean1.includes(clean2) || clean2.includes(clean1)) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Check if a frontend array type is wrapped in a backend model
|
|
760
|
+
* e.g., FE sends PaymentMilestoneCreate[] → BE expects PaymentScheduleSetup { milestones: List[PaymentMilestoneCreate] }
|
|
761
|
+
*/
|
|
762
|
+
function isArrayTypeWrappedInModel(frontendType, backendModelName, apiContract) {
|
|
763
|
+
// Only applies when frontend type is an array
|
|
764
|
+
if (!frontendType.endsWith('[]'))
|
|
765
|
+
return false;
|
|
766
|
+
const elementType = frontendType.replace(/\[\]$/, '');
|
|
767
|
+
const backendModel = apiContract.backendModels.find(m => m.name === backendModelName);
|
|
768
|
+
if (!backendModel)
|
|
769
|
+
return false;
|
|
770
|
+
// Check if the backend model has a field whose type is a list of the element type
|
|
771
|
+
for (const field of backendModel.fields) {
|
|
772
|
+
const fieldType = field.type.toLowerCase();
|
|
773
|
+
const elementLower = elementType.toLowerCase();
|
|
774
|
+
// Match patterns like List[PaymentMilestoneCreate], list[PaymentMilestoneCreate], Sequence[...]
|
|
775
|
+
if (fieldType.includes(`list[${elementLower}]`) ||
|
|
776
|
+
fieldType.includes(`sequence[${elementLower}]`) ||
|
|
777
|
+
fieldType === `${elementLower}[]`) {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
function checkProblematicTypePairs(tsType, pyType) {
|
|
784
|
+
const normalizedTs = normalizeType(tsType);
|
|
785
|
+
const normalizedPy = normalizeType(pyType);
|
|
786
|
+
// UUID handling - UUID serializes to string in JSON, so string is compatible
|
|
787
|
+
if (normalizedPy === "uuid") {
|
|
788
|
+
if (normalizedTs === "string" || normalizedTs === "string_or_null") {
|
|
789
|
+
return null; // Compatible — no issue
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
compatible: false,
|
|
793
|
+
severity: "medium",
|
|
794
|
+
reason: `Backend uses UUID type, frontend uses ${tsType}`,
|
|
795
|
+
suggestion: `Use 'string' type for UUID (UUIDs serialize to strings in JSON)`,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
// Decimal/Float handling for monetary values
|
|
799
|
+
if (normalizedPy === "decimal" && normalizedTs === "number") {
|
|
800
|
+
return {
|
|
801
|
+
compatible: true,
|
|
802
|
+
severity: "medium",
|
|
803
|
+
reason: `Backend uses Decimal for precision, frontend uses number`,
|
|
804
|
+
suggestion: `Consider using string for monetary values to avoid floating-point precision issues`,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
// DateTime handling - datetime serializes to ISO string in JSON, so string is compatible
|
|
808
|
+
if (normalizedPy === "datetime" || normalizedPy === "date") {
|
|
809
|
+
if (normalizedTs === "string" || normalizedTs === "string_or_null") {
|
|
810
|
+
return null; // Compatible — no issue
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
compatible: false,
|
|
814
|
+
severity: "high",
|
|
815
|
+
reason: `Backend uses ${pyType}, frontend uses ${tsType}`,
|
|
816
|
+
suggestion: `Use 'string' type for dates (dates serialize to ISO strings)`,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
// String vs Number mismatch
|
|
820
|
+
if (normalizedTs === "string" && ["int", "float", "integer", "decimal"].includes(normalizedPy)) {
|
|
821
|
+
return {
|
|
822
|
+
compatible: false,
|
|
823
|
+
severity: "high",
|
|
824
|
+
reason: `Type mismatch: frontend uses string, backend expects ${pyType}`,
|
|
825
|
+
suggestion: `Change frontend type to 'number' or backend to accept string`,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
// Number vs String mismatch
|
|
829
|
+
if (normalizedTs === "number" && ["str", "text", "email"].includes(normalizedPy)) {
|
|
830
|
+
return {
|
|
831
|
+
compatible: false,
|
|
832
|
+
severity: "high",
|
|
833
|
+
reason: `Type mismatch: frontend uses number, backend expects ${pyType}`,
|
|
834
|
+
suggestion: `Change frontend type to 'string' or backend to accept number`,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
// Array/List handling
|
|
838
|
+
if (tsType.includes("[]") && !(pyType.includes("List") || pyType.includes("list") || pyType.includes("Sequence"))) {
|
|
839
|
+
return {
|
|
840
|
+
compatible: false,
|
|
841
|
+
severity: "high",
|
|
842
|
+
reason: `Frontend expects array, backend uses ${pyType}`,
|
|
843
|
+
suggestion: `Ensure backend field is a list/array type`,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
function normalizeType(type) {
|
|
849
|
+
return type
|
|
850
|
+
.replace(/^[\s:]+/, "") // Strip leading colons/whitespace from extraction artifacts
|
|
851
|
+
.toLowerCase()
|
|
852
|
+
.replace(/\s+/g, "")
|
|
853
|
+
.replace(/\[\]/g, "array")
|
|
854
|
+
.replace(/optional\[/g, "")
|
|
855
|
+
.replace(/\]/g, "")
|
|
856
|
+
.replace(/list\[/g, "array_")
|
|
857
|
+
.replace(/\|/g, "_or_")
|
|
858
|
+
.replace(/\?/g, ""); // Remove TypeScript optional marker
|
|
859
|
+
}
|
|
860
|
+
function getTypeCompatibilitySuggestion(tsType, pyType) {
|
|
861
|
+
const suggestions = {
|
|
862
|
+
string: {
|
|
863
|
+
uuid: "Use string type for UUID (serialization)",
|
|
864
|
+
datetime: "Use string type for dates (ISO format)",
|
|
865
|
+
decimal: "Use string type for decimals (precision)",
|
|
866
|
+
},
|
|
867
|
+
number: {
|
|
868
|
+
decimal: "Use string type for monetary values (precision)",
|
|
869
|
+
int: "Ensure value is an integer",
|
|
870
|
+
float: "Ensure value is a number",
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
return (suggestions[tsType]?.[pyType] || `Ensure types are compatible: ${tsType} vs ${pyType}`);
|
|
874
|
+
}
|
|
875
|
+
//# sourceMappingURL=index.js.map
|