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.
Files changed (335) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/agent/agentTools.d.ts +26 -0
  4. package/dist/agent/agentTools.d.ts.map +1 -0
  5. package/dist/agent/agentTools.js +699 -0
  6. package/dist/agent/agentTools.js.map +1 -0
  7. package/dist/agent/autoValidator.d.ts +110 -0
  8. package/dist/agent/autoValidator.d.ts.map +1 -0
  9. package/dist/agent/autoValidator.js +964 -0
  10. package/dist/agent/autoValidator.js.map +1 -0
  11. package/dist/agent/fileWatcher.d.ts +28 -0
  12. package/dist/agent/fileWatcher.d.ts.map +1 -0
  13. package/dist/agent/fileWatcher.js +88 -0
  14. package/dist/agent/fileWatcher.js.map +1 -0
  15. package/dist/agent/guardianPersistence.d.ts +98 -0
  16. package/dist/agent/guardianPersistence.d.ts.map +1 -0
  17. package/dist/agent/guardianPersistence.js +296 -0
  18. package/dist/agent/guardianPersistence.js.map +1 -0
  19. package/dist/agent/mcpNotifications.d.ts +38 -0
  20. package/dist/agent/mcpNotifications.d.ts.map +1 -0
  21. package/dist/agent/mcpNotifications.js +81 -0
  22. package/dist/agent/mcpNotifications.js.map +1 -0
  23. package/dist/analyzers/aiPatterns.d.ts +16 -0
  24. package/dist/analyzers/aiPatterns.d.ts.map +1 -0
  25. package/dist/analyzers/aiPatterns.js +103 -0
  26. package/dist/analyzers/aiPatterns.js.map +1 -0
  27. package/dist/analyzers/antiPatterns.d.ts +60 -0
  28. package/dist/analyzers/antiPatterns.d.ts.map +1 -0
  29. package/dist/analyzers/antiPatterns.js +198 -0
  30. package/dist/analyzers/antiPatterns.js.map +1 -0
  31. package/dist/analyzers/builtinTypes.d.ts +18 -0
  32. package/dist/analyzers/builtinTypes.d.ts.map +1 -0
  33. package/dist/analyzers/builtinTypes.js +1275 -0
  34. package/dist/analyzers/builtinTypes.js.map +1 -0
  35. package/dist/analyzers/complexity.d.ts +14 -0
  36. package/dist/analyzers/complexity.d.ts.map +1 -0
  37. package/dist/analyzers/complexity.js +610 -0
  38. package/dist/analyzers/complexity.js.map +1 -0
  39. package/dist/analyzers/findingVerifier.d.ts +59 -0
  40. package/dist/analyzers/findingVerifier.d.ts.map +1 -0
  41. package/dist/analyzers/findingVerifier.js +1169 -0
  42. package/dist/analyzers/findingVerifier.js.map +1 -0
  43. package/dist/analyzers/impactAnalyzer.d.ts +53 -0
  44. package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
  45. package/dist/analyzers/impactAnalyzer.js +152 -0
  46. package/dist/analyzers/impactAnalyzer.js.map +1 -0
  47. package/dist/analyzers/languageDetector.d.ts +48 -0
  48. package/dist/analyzers/languageDetector.d.ts.map +1 -0
  49. package/dist/analyzers/languageDetector.js +404 -0
  50. package/dist/analyzers/languageDetector.js.map +1 -0
  51. package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
  52. package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
  53. package/dist/analyzers/parsers/incrementalParser.js +193 -0
  54. package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
  55. package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
  56. package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
  57. package/dist/analyzers/parsers/scopeResolver.js +324 -0
  58. package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
  59. package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
  60. package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
  61. package/dist/analyzers/parsers/semanticIndex.js +429 -0
  62. package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
  63. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
  64. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
  65. package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
  66. package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
  67. package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
  68. package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
  69. package/dist/analyzers/parsers/treeSitterParser.js +709 -0
  70. package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
  71. package/dist/analyzers/relevanceScorer.d.ts +43 -0
  72. package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
  73. package/dist/analyzers/relevanceScorer.js +200 -0
  74. package/dist/analyzers/relevanceScorer.js.map +1 -0
  75. package/dist/analyzers/standardLibrary.d.ts +22 -0
  76. package/dist/analyzers/standardLibrary.d.ts.map +1 -0
  77. package/dist/analyzers/standardLibrary.js +211 -0
  78. package/dist/analyzers/standardLibrary.js.map +1 -0
  79. package/dist/analyzers/symbolGraph.d.ts +30 -0
  80. package/dist/analyzers/symbolGraph.d.ts.map +1 -0
  81. package/dist/analyzers/symbolGraph.js +380 -0
  82. package/dist/analyzers/symbolGraph.js.map +1 -0
  83. package/dist/analyzers/symbolTable.d.ts +18 -0
  84. package/dist/analyzers/symbolTable.d.ts.map +1 -0
  85. package/dist/analyzers/symbolTable.js +176 -0
  86. package/dist/analyzers/symbolTable.js.map +1 -0
  87. package/dist/analyzers/typeChecker.d.ts +13 -0
  88. package/dist/analyzers/typeChecker.d.ts.map +1 -0
  89. package/dist/analyzers/typeChecker.js +580 -0
  90. package/dist/analyzers/typeChecker.js.map +1 -0
  91. package/dist/analyzers/usagePatterns.d.ts +42 -0
  92. package/dist/analyzers/usagePatterns.d.ts.map +1 -0
  93. package/dist/analyzers/usagePatterns.js +75 -0
  94. package/dist/analyzers/usagePatterns.js.map +1 -0
  95. package/dist/api-contract/context/backend.d.ts +19 -0
  96. package/dist/api-contract/context/backend.d.ts.map +1 -0
  97. package/dist/api-contract/context/backend.js +64 -0
  98. package/dist/api-contract/context/backend.js.map +1 -0
  99. package/dist/api-contract/context/contract.d.ts +34 -0
  100. package/dist/api-contract/context/contract.d.ts.map +1 -0
  101. package/dist/api-contract/context/contract.js +306 -0
  102. package/dist/api-contract/context/contract.js.map +1 -0
  103. package/dist/api-contract/context/frontend.d.ts +19 -0
  104. package/dist/api-contract/context/frontend.d.ts.map +1 -0
  105. package/dist/api-contract/context/frontend.js +64 -0
  106. package/dist/api-contract/context/frontend.js.map +1 -0
  107. package/dist/api-contract/detector.d.ts +28 -0
  108. package/dist/api-contract/detector.d.ts.map +1 -0
  109. package/dist/api-contract/detector.js +393 -0
  110. package/dist/api-contract/detector.js.map +1 -0
  111. package/dist/api-contract/extractors/python.d.ts +32 -0
  112. package/dist/api-contract/extractors/python.d.ts.map +1 -0
  113. package/dist/api-contract/extractors/python.js +521 -0
  114. package/dist/api-contract/extractors/python.js.map +1 -0
  115. package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
  116. package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
  117. package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
  118. package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
  119. package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
  120. package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
  121. package/dist/api-contract/extractors/tsAstUtils.js +173 -0
  122. package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
  123. package/dist/api-contract/extractors/typescript.d.ts +32 -0
  124. package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
  125. package/dist/api-contract/extractors/typescript.js +666 -0
  126. package/dist/api-contract/extractors/typescript.js.map +1 -0
  127. package/dist/api-contract/index.d.ts +104 -0
  128. package/dist/api-contract/index.d.ts.map +1 -0
  129. package/dist/api-contract/index.js +232 -0
  130. package/dist/api-contract/index.js.map +1 -0
  131. package/dist/api-contract/types.d.ts +151 -0
  132. package/dist/api-contract/types.d.ts.map +1 -0
  133. package/dist/api-contract/types.js +19 -0
  134. package/dist/api-contract/types.js.map +1 -0
  135. package/dist/api-contract/validators/endpoint.d.ts +21 -0
  136. package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
  137. package/dist/api-contract/validators/endpoint.js +224 -0
  138. package/dist/api-contract/validators/endpoint.js.map +1 -0
  139. package/dist/api-contract/validators/index.d.ts +40 -0
  140. package/dist/api-contract/validators/index.d.ts.map +1 -0
  141. package/dist/api-contract/validators/index.js +875 -0
  142. package/dist/api-contract/validators/index.js.map +1 -0
  143. package/dist/api-contract/validators/parameter.d.ts +17 -0
  144. package/dist/api-contract/validators/parameter.d.ts.map +1 -0
  145. package/dist/api-contract/validators/parameter.js +250 -0
  146. package/dist/api-contract/validators/parameter.js.map +1 -0
  147. package/dist/api-contract/validators/type.d.ts +38 -0
  148. package/dist/api-contract/validators/type.d.ts.map +1 -0
  149. package/dist/api-contract/validators/type.js +244 -0
  150. package/dist/api-contract/validators/type.js.map +1 -0
  151. package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
  152. package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
  153. package/dist/context/apiContract/complexTypeSupport.js +665 -0
  154. package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
  155. package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
  156. package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
  157. package/dist/context/apiContract/graphqlSupport.js +671 -0
  158. package/dist/context/apiContract/graphqlSupport.js.map +1 -0
  159. package/dist/context/apiContract/index.d.ts +14 -0
  160. package/dist/context/apiContract/index.d.ts.map +1 -0
  161. package/dist/context/apiContract/index.js +17 -0
  162. package/dist/context/apiContract/index.js.map +1 -0
  163. package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
  164. package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
  165. package/dist/context/apiContract/webSocketSupport.js +465 -0
  166. package/dist/context/apiContract/webSocketSupport.js.map +1 -0
  167. package/dist/context/apiContractContext.d.ts +15 -0
  168. package/dist/context/apiContractContext.d.ts.map +1 -0
  169. package/dist/context/apiContractContext.js +979 -0
  170. package/dist/context/apiContractContext.js.map +1 -0
  171. package/dist/context/apiContractExtraction.d.ts +52 -0
  172. package/dist/context/apiContractExtraction.d.ts.map +1 -0
  173. package/dist/context/apiContractExtraction.js +438 -0
  174. package/dist/context/apiContractExtraction.js.map +1 -0
  175. package/dist/context/contextLineage.d.ts +79 -0
  176. package/dist/context/contextLineage.d.ts.map +1 -0
  177. package/dist/context/contextLineage.js +259 -0
  178. package/dist/context/contextLineage.js.map +1 -0
  179. package/dist/context/contextOrchestrator.d.ts +57 -0
  180. package/dist/context/contextOrchestrator.d.ts.map +1 -0
  181. package/dist/context/contextOrchestrator.js +162 -0
  182. package/dist/context/contextOrchestrator.js.map +1 -0
  183. package/dist/context/intentTracker.d.ts +73 -0
  184. package/dist/context/intentTracker.d.ts.map +1 -0
  185. package/dist/context/intentTracker.js +168 -0
  186. package/dist/context/intentTracker.js.map +1 -0
  187. package/dist/context/projectContext.d.ts +219 -0
  188. package/dist/context/projectContext.d.ts.map +1 -0
  189. package/dist/context/projectContext.js +1984 -0
  190. package/dist/context/projectContext.js.map +1 -0
  191. package/dist/prompts/index.d.ts +17 -0
  192. package/dist/prompts/index.d.ts.map +1 -0
  193. package/dist/prompts/index.js +260 -0
  194. package/dist/prompts/index.js.map +1 -0
  195. package/dist/prompts/library.d.ts +51 -0
  196. package/dist/prompts/library.d.ts.map +1 -0
  197. package/dist/prompts/library.js +65 -0
  198. package/dist/prompts/library.js.map +1 -0
  199. package/dist/prompts/templates.d.ts +44 -0
  200. package/dist/prompts/templates.d.ts.map +1 -0
  201. package/dist/prompts/templates.js +97 -0
  202. package/dist/prompts/templates.js.map +1 -0
  203. package/dist/queue/jobPersistence.d.ts +46 -0
  204. package/dist/queue/jobPersistence.d.ts.map +1 -0
  205. package/dist/queue/jobPersistence.js +158 -0
  206. package/dist/queue/jobPersistence.js.map +1 -0
  207. package/dist/queue/jobQueue.d.ts +116 -0
  208. package/dist/queue/jobQueue.d.ts.map +1 -0
  209. package/dist/queue/jobQueue.js +275 -0
  210. package/dist/queue/jobQueue.js.map +1 -0
  211. package/dist/queue/validationJob.d.ts +69 -0
  212. package/dist/queue/validationJob.d.ts.map +1 -0
  213. package/dist/queue/validationJob.js +435 -0
  214. package/dist/queue/validationJob.js.map +1 -0
  215. package/dist/resources/index.d.ts +15 -0
  216. package/dist/resources/index.d.ts.map +1 -0
  217. package/dist/resources/index.js +328 -0
  218. package/dist/resources/index.js.map +1 -0
  219. package/dist/resources/validationReportStore.d.ts +170 -0
  220. package/dist/resources/validationReportStore.d.ts.map +1 -0
  221. package/dist/resources/validationReportStore.js +515 -0
  222. package/dist/resources/validationReportStore.js.map +1 -0
  223. package/dist/server.d.ts +12 -0
  224. package/dist/server.d.ts.map +1 -0
  225. package/dist/server.js +102 -0
  226. package/dist/server.js.map +1 -0
  227. package/dist/tools/asyncValidation.d.ts +19 -0
  228. package/dist/tools/asyncValidation.d.ts.map +1 -0
  229. package/dist/tools/asyncValidation.js +346 -0
  230. package/dist/tools/asyncValidation.js.map +1 -0
  231. package/dist/tools/buildContext.d.ts +17 -0
  232. package/dist/tools/buildContext.d.ts.map +1 -0
  233. package/dist/tools/buildContext.js +188 -0
  234. package/dist/tools/buildContext.js.map +1 -0
  235. package/dist/tools/getDependencyGraph.d.ts +16 -0
  236. package/dist/tools/getDependencyGraph.d.ts.map +1 -0
  237. package/dist/tools/getDependencyGraph.js +436 -0
  238. package/dist/tools/getDependencyGraph.js.map +1 -0
  239. package/dist/tools/incrementalValidation.d.ts +71 -0
  240. package/dist/tools/incrementalValidation.d.ts.map +1 -0
  241. package/dist/tools/incrementalValidation.js +203 -0
  242. package/dist/tools/incrementalValidation.js.map +1 -0
  243. package/dist/tools/index.d.ts +24 -0
  244. package/dist/tools/index.d.ts.map +1 -0
  245. package/dist/tools/index.js +106 -0
  246. package/dist/tools/index.js.map +1 -0
  247. package/dist/tools/validateCode.d.ts +17 -0
  248. package/dist/tools/validateCode.d.ts.map +1 -0
  249. package/dist/tools/validateCode.js +368 -0
  250. package/dist/tools/validateCode.js.map +1 -0
  251. package/dist/tools/validateCodeLite.d.ts +2 -0
  252. package/dist/tools/validateCodeLite.d.ts.map +1 -0
  253. package/dist/tools/validateCodeLite.js +2 -0
  254. package/dist/tools/validateCodeLite.js.map +1 -0
  255. package/dist/tools/validation/builtins.d.ts +92 -0
  256. package/dist/tools/validation/builtins.d.ts.map +1 -0
  257. package/dist/tools/validation/builtins.js +2184 -0
  258. package/dist/tools/validation/builtins.js.map +1 -0
  259. package/dist/tools/validation/contextualNaming.d.ts +99 -0
  260. package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
  261. package/dist/tools/validation/contextualNaming.js +959 -0
  262. package/dist/tools/validation/contextualNaming.js.map +1 -0
  263. package/dist/tools/validation/deadCode.d.ts +115 -0
  264. package/dist/tools/validation/deadCode.d.ts.map +1 -0
  265. package/dist/tools/validation/deadCode.js +861 -0
  266. package/dist/tools/validation/deadCode.js.map +1 -0
  267. package/dist/tools/validation/extractors/index.d.ts +131 -0
  268. package/dist/tools/validation/extractors/index.d.ts.map +1 -0
  269. package/dist/tools/validation/extractors/index.js +233 -0
  270. package/dist/tools/validation/extractors/index.js.map +1 -0
  271. package/dist/tools/validation/extractors/javascript.d.ts +73 -0
  272. package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
  273. package/dist/tools/validation/extractors/javascript.js +1841 -0
  274. package/dist/tools/validation/extractors/javascript.js.map +1 -0
  275. package/dist/tools/validation/extractors/python.d.ts +93 -0
  276. package/dist/tools/validation/extractors/python.d.ts.map +1 -0
  277. package/dist/tools/validation/extractors/python.js +799 -0
  278. package/dist/tools/validation/extractors/python.js.map +1 -0
  279. package/dist/tools/validation/manifest.d.ts +45 -0
  280. package/dist/tools/validation/manifest.d.ts.map +1 -0
  281. package/dist/tools/validation/manifest.js +719 -0
  282. package/dist/tools/validation/manifest.js.map +1 -0
  283. package/dist/tools/validation/parser.d.ts +58 -0
  284. package/dist/tools/validation/parser.d.ts.map +1 -0
  285. package/dist/tools/validation/parser.js +232 -0
  286. package/dist/tools/validation/parser.js.map +1 -0
  287. package/dist/tools/validation/registry.d.ts +15 -0
  288. package/dist/tools/validation/registry.d.ts.map +1 -0
  289. package/dist/tools/validation/registry.js +169 -0
  290. package/dist/tools/validation/registry.js.map +1 -0
  291. package/dist/tools/validation/scoring.d.ts +54 -0
  292. package/dist/tools/validation/scoring.d.ts.map +1 -0
  293. package/dist/tools/validation/scoring.js +242 -0
  294. package/dist/tools/validation/scoring.js.map +1 -0
  295. package/dist/tools/validation/types.d.ts +120 -0
  296. package/dist/tools/validation/types.d.ts.map +1 -0
  297. package/dist/tools/validation/types.js +11 -0
  298. package/dist/tools/validation/types.js.map +1 -0
  299. package/dist/tools/validation/unusedLocals.d.ts +36 -0
  300. package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
  301. package/dist/tools/validation/unusedLocals.js +333 -0
  302. package/dist/tools/validation/unusedLocals.js.map +1 -0
  303. package/dist/tools/validation/validation.d.ts +98 -0
  304. package/dist/tools/validation/validation.d.ts.map +1 -0
  305. package/dist/tools/validation/validation.js +1837 -0
  306. package/dist/tools/validation/validation.js.map +1 -0
  307. package/dist/types/codeGraph.d.ts +163 -0
  308. package/dist/types/codeGraph.d.ts.map +1 -0
  309. package/dist/types/codeGraph.js +9 -0
  310. package/dist/types/codeGraph.js.map +1 -0
  311. package/dist/types/symbolGraph.d.ts +68 -0
  312. package/dist/types/symbolGraph.d.ts.map +1 -0
  313. package/dist/types/symbolGraph.js +10 -0
  314. package/dist/types/symbolGraph.js.map +1 -0
  315. package/dist/types/tools.d.ts +43 -0
  316. package/dist/types/tools.d.ts.map +1 -0
  317. package/dist/types/tools.js +7 -0
  318. package/dist/types/tools.js.map +1 -0
  319. package/dist/utils/fileFilter.d.ts +37 -0
  320. package/dist/utils/fileFilter.d.ts.map +1 -0
  321. package/dist/utils/fileFilter.js +91 -0
  322. package/dist/utils/fileFilter.js.map +1 -0
  323. package/dist/utils/gitUtils.d.ts +28 -0
  324. package/dist/utils/gitUtils.d.ts.map +1 -0
  325. package/dist/utils/gitUtils.js +81 -0
  326. package/dist/utils/gitUtils.js.map +1 -0
  327. package/dist/utils/logger.d.ts +15 -0
  328. package/dist/utils/logger.d.ts.map +1 -0
  329. package/dist/utils/logger.js +38 -0
  330. package/dist/utils/logger.js.map +1 -0
  331. package/dist/utils/serialization.d.ts +25 -0
  332. package/dist/utils/serialization.d.ts.map +1 -0
  333. package/dist/utils/serialization.js +53 -0
  334. package/dist/utils/serialization.js.map +1 -0
  335. 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