@archpilotlabs/archpilot 0.2.2 → 0.2.4

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 (298) hide show
  1. package/CHANGELOG.md +538 -0
  2. package/README.md +1 -0
  3. package/dist/archpilot-cli.js +850 -159
  4. package/dist/cli/src/archpilot-cli.js +5523 -0
  5. package/dist/cli/src/archpilot-cli.js.map +1 -0
  6. package/dist/core/src/adr/adrLoader.js +97 -0
  7. package/dist/core/src/adr/adrLoader.js.map +1 -0
  8. package/dist/core/src/adr/adrParser.js +77 -0
  9. package/dist/core/src/adr/adrParser.js.map +1 -0
  10. package/dist/core/src/adr/adrTemplate.js +45 -0
  11. package/dist/core/src/adr/adrTemplate.js.map +1 -0
  12. package/dist/core/src/adr/adrTypes.js +3 -0
  13. package/dist/core/src/adr/adrTypes.js.map +1 -0
  14. package/dist/core/src/adr/adrValidation.js +160 -0
  15. package/dist/core/src/adr/adrValidation.js.map +1 -0
  16. package/dist/core/src/adr/index.js +9 -0
  17. package/dist/core/src/adr/index.js.map +1 -0
  18. package/dist/core/src/adrManagement.js +174 -0
  19. package/dist/core/src/adrManagement.js.map +1 -0
  20. package/dist/core/src/adrRequirementStatus.js +62 -0
  21. package/dist/core/src/adrRequirementStatus.js.map +1 -0
  22. package/dist/core/src/adrScaffoldGeneration.js +325 -0
  23. package/dist/core/src/adrScaffoldGeneration.js.map +1 -0
  24. package/dist/core/src/apiDesignAnalysis.js +869 -0
  25. package/dist/core/src/apiDesignAnalysis.js.map +1 -0
  26. package/dist/core/src/applicationLayeringAnalysis.js +920 -0
  27. package/dist/core/src/applicationLayeringAnalysis.js.map +1 -0
  28. package/dist/core/src/applyRulePack.js +117 -0
  29. package/dist/core/src/applyRulePack.js.map +1 -0
  30. package/dist/core/src/architectureBaseline.js +271 -0
  31. package/dist/core/src/architectureBaseline.js.map +1 -0
  32. package/dist/core/src/architectureComponentDiscoveryDefaults.js +139 -0
  33. package/dist/core/src/architectureComponentDiscoveryDefaults.js.map +1 -0
  34. package/dist/core/src/architectureDependencyContext.js +316 -0
  35. package/dist/core/src/architectureDependencyContext.js.map +1 -0
  36. package/dist/core/src/architectureDriftDetection.js +96 -0
  37. package/dist/core/src/architectureDriftDetection.js.map +1 -0
  38. package/dist/core/src/architectureDriftTimeline.js +221 -0
  39. package/dist/core/src/architectureDriftTimeline.js.map +1 -0
  40. package/dist/core/src/architectureGovernanceBaseline.js +469 -0
  41. package/dist/core/src/architectureGovernanceBaseline.js.map +1 -0
  42. package/dist/core/src/architectureHealthHistory.js +208 -0
  43. package/dist/core/src/architectureHealthHistory.js.map +1 -0
  44. package/dist/core/src/architectureHealthScore.js +657 -0
  45. package/dist/core/src/architectureHealthScore.js.map +1 -0
  46. package/dist/core/src/architectureInit.js +826 -0
  47. package/dist/core/src/architectureInit.js.map +1 -0
  48. package/dist/core/src/architectureMap.js +727 -0
  49. package/dist/core/src/architectureMap.js.map +1 -0
  50. package/dist/core/src/architectureMigrationPreview.js +467 -0
  51. package/dist/core/src/architectureMigrationPreview.js.map +1 -0
  52. package/dist/core/src/architectureModelRefresh.js +209 -0
  53. package/dist/core/src/architectureModelRefresh.js.map +1 -0
  54. package/dist/core/src/architectureReportWorkflow.js +139 -0
  55. package/dist/core/src/architectureReportWorkflow.js.map +1 -0
  56. package/dist/core/src/architectureValidation.js +3879 -0
  57. package/dist/core/src/architectureValidation.js.map +1 -0
  58. package/dist/core/src/architectureWorkspaceModel.js +166 -0
  59. package/dist/core/src/architectureWorkspaceModel.js.map +1 -0
  60. package/dist/core/src/authorizationTenantAnalysis.js +652 -0
  61. package/dist/core/src/authorizationTenantAnalysis.js.map +1 -0
  62. package/dist/core/src/bootstrapArchitectureConfig.js +150 -0
  63. package/dist/core/src/bootstrapArchitectureConfig.js.map +1 -0
  64. package/dist/core/src/changeRisk/changeRiskAssessment.js +251 -0
  65. package/dist/core/src/changeRisk/changeRiskAssessment.js.map +1 -0
  66. package/dist/core/src/changeRisk/changeRiskTypes.js +3 -0
  67. package/dist/core/src/changeRisk/changeRiskTypes.js.map +1 -0
  68. package/dist/core/src/changeRisk/index.js +6 -0
  69. package/dist/core/src/changeRisk/index.js.map +1 -0
  70. package/dist/core/src/ci/githubInlineAnnotations.js +245 -0
  71. package/dist/core/src/ci/githubInlineAnnotations.js.map +1 -0
  72. package/dist/core/src/ci/githubPrCommentAdapter.js +232 -0
  73. package/dist/core/src/ci/githubPrCommentAdapter.js.map +1 -0
  74. package/dist/core/src/ciWorkflowTemplates.js +1042 -0
  75. package/dist/core/src/ciWorkflowTemplates.js.map +1 -0
  76. package/dist/core/src/compliance/complianceExportBuilder.js +122 -0
  77. package/dist/core/src/compliance/complianceExportBuilder.js.map +1 -0
  78. package/dist/core/src/compliance/complianceExportTypes.js +3 -0
  79. package/dist/core/src/compliance/complianceExportTypes.js.map +1 -0
  80. package/dist/core/src/compliance/complianceExportWriter.js +48 -0
  81. package/dist/core/src/compliance/complianceExportWriter.js.map +1 -0
  82. package/dist/core/src/compliance/complianceRulePackResolver.js +62 -0
  83. package/dist/core/src/compliance/complianceRulePackResolver.js.map +1 -0
  84. package/dist/core/src/config/index.js +17 -0
  85. package/dist/core/src/config/index.js.map +1 -0
  86. package/dist/core/src/config/projectConfig.js +224 -0
  87. package/dist/core/src/config/projectConfig.js.map +1 -0
  88. package/dist/core/src/config/projectIdentity.js +160 -0
  89. package/dist/core/src/config/projectIdentity.js.map +1 -0
  90. package/dist/core/src/dataQueryRiskDetection.js +1203 -0
  91. package/dist/core/src/dataQueryRiskDetection.js.map +1 -0
  92. package/dist/core/src/dependencyValidation.js +2557 -0
  93. package/dist/core/src/dependencyValidation.js.map +1 -0
  94. package/dist/core/src/discoverAdrs.js +15 -0
  95. package/dist/core/src/discoverAdrs.js.map +1 -0
  96. package/dist/core/src/drift/driftComparator.js +49 -0
  97. package/dist/core/src/drift/driftComparator.js.map +1 -0
  98. package/dist/core/src/drift/driftSnapshotLoader.js +136 -0
  99. package/dist/core/src/drift/driftSnapshotLoader.js.map +1 -0
  100. package/dist/core/src/drift/driftSnapshotTypes.js +3 -0
  101. package/dist/core/src/drift/driftSnapshotTypes.js.map +1 -0
  102. package/dist/core/src/drift/driftSnapshotWriter.js +48 -0
  103. package/dist/core/src/drift/driftSnapshotWriter.js.map +1 -0
  104. package/dist/core/src/drift/index.js +6 -0
  105. package/dist/core/src/drift/index.js.map +1 -0
  106. package/dist/core/src/eventArchitectureAnalysis.js +925 -0
  107. package/dist/core/src/eventArchitectureAnalysis.js.map +1 -0
  108. package/dist/core/src/findingTargetResolution.js +117 -0
  109. package/dist/core/src/findingTargetResolution.js.map +1 -0
  110. package/dist/core/src/generateArchitectureDiagrams.js +57 -0
  111. package/dist/core/src/generateArchitectureDiagrams.js.map +1 -0
  112. package/dist/core/src/generateImpactAnalysis.js +366 -0
  113. package/dist/core/src/generateImpactAnalysis.js.map +1 -0
  114. package/dist/core/src/generateOnboardingDocs.js +262 -0
  115. package/dist/core/src/generateOnboardingDocs.js.map +1 -0
  116. package/dist/core/src/governance/cloudConnection.js +203 -0
  117. package/dist/core/src/governance/cloudConnection.js.map +1 -0
  118. package/dist/core/src/governance/governanceContracts.js +129 -0
  119. package/dist/core/src/governance/governanceContracts.js.map +1 -0
  120. package/dist/core/src/governance/governanceGitMetadata.js +78 -0
  121. package/dist/core/src/governance/governanceGitMetadata.js.map +1 -0
  122. package/dist/core/src/governance/governanceUploadClient.js +119 -0
  123. package/dist/core/src/governance/governanceUploadClient.js.map +1 -0
  124. package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js +155 -0
  125. package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js.map +1 -0
  126. package/dist/core/src/governance/governanceUploadPayload.js +439 -0
  127. package/dist/core/src/governance/governanceUploadPayload.js.map +1 -0
  128. package/dist/core/src/governance/runGovernanceUpload.js +103 -0
  129. package/dist/core/src/governance/runGovernanceUpload.js.map +1 -0
  130. package/dist/core/src/impactAnalysisEngine.js +376 -0
  131. package/dist/core/src/impactAnalysisEngine.js.map +1 -0
  132. package/dist/core/src/initializationState.js +99 -0
  133. package/dist/core/src/initializationState.js.map +1 -0
  134. package/dist/core/src/managedArtifacts.js +151 -0
  135. package/dist/core/src/managedArtifacts.js.map +1 -0
  136. package/dist/core/src/moduleContractValidation.js +266 -0
  137. package/dist/core/src/moduleContractValidation.js.map +1 -0
  138. package/dist/core/src/moduleContractsGenerator.js +337 -0
  139. package/dist/core/src/moduleContractsGenerator.js.map +1 -0
  140. package/dist/core/src/moduleDiscovery.js +416 -0
  141. package/dist/core/src/moduleDiscovery.js.map +1 -0
  142. package/dist/core/src/moduleScope.js +426 -0
  143. package/dist/core/src/moduleScope.js.map +1 -0
  144. package/dist/core/src/onboardingGuidance.js +74 -0
  145. package/dist/core/src/onboardingGuidance.js.map +1 -0
  146. package/dist/core/src/operationalLogging.js +7 -0
  147. package/dist/core/src/operationalLogging.js.map +1 -0
  148. package/dist/core/src/packageVersion.js +52 -0
  149. package/dist/core/src/packageVersion.js.map +1 -0
  150. package/dist/core/src/policy/effectivePolicyResolver.js +91 -0
  151. package/dist/core/src/policy/effectivePolicyResolver.js.map +1 -0
  152. package/dist/core/src/policy/exceptionLoader.js +285 -0
  153. package/dist/core/src/policy/exceptionLoader.js.map +1 -0
  154. package/dist/core/src/policy/remotePolicyLoader.js +227 -0
  155. package/dist/core/src/policy/remotePolicyLoader.js.map +1 -0
  156. package/dist/core/src/policy/remotePolicyTypes.js +3 -0
  157. package/dist/core/src/policy/remotePolicyTypes.js.map +1 -0
  158. package/dist/core/src/policy/riskPolicyConfig.js +206 -0
  159. package/dist/core/src/policy/riskPolicyConfig.js.map +1 -0
  160. package/dist/core/src/policy/riskPolicyEnforcement.js +203 -0
  161. package/dist/core/src/policy/riskPolicyEnforcement.js.map +1 -0
  162. package/dist/core/src/policy/riskPolicyExitCode.js +16 -0
  163. package/dist/core/src/policy/riskPolicyExitCode.js.map +1 -0
  164. package/dist/core/src/policy/riskPolicyInitializer.js +90 -0
  165. package/dist/core/src/policy/riskPolicyInitializer.js.map +1 -0
  166. package/dist/core/src/quickFix/quickFixEngine.js +132 -0
  167. package/dist/core/src/quickFix/quickFixEngine.js.map +1 -0
  168. package/dist/core/src/quickFix/quickFixMessaging.js +15 -0
  169. package/dist/core/src/quickFix/quickFixMessaging.js.map +1 -0
  170. package/dist/core/src/quickFix/quickFixProviders.js +87 -0
  171. package/dist/core/src/quickFix/quickFixProviders.js.map +1 -0
  172. package/dist/core/src/quickFix/quickFixTypes.js +3 -0
  173. package/dist/core/src/quickFix/quickFixTypes.js.map +1 -0
  174. package/dist/core/src/remediation/fileHelpers.js +86 -0
  175. package/dist/core/src/remediation/fileHelpers.js.map +1 -0
  176. package/dist/core/src/remediation/fixRegistry.js +645 -0
  177. package/dist/core/src/remediation/fixRegistry.js.map +1 -0
  178. package/dist/core/src/remediation/index.js +8 -0
  179. package/dist/core/src/remediation/index.js.map +1 -0
  180. package/dist/core/src/remediation/remediationEngine.js +207 -0
  181. package/dist/core/src/remediation/remediationEngine.js.map +1 -0
  182. package/dist/core/src/reporting/adrReporter.js +59 -0
  183. package/dist/core/src/reporting/adrReporter.js.map +1 -0
  184. package/dist/core/src/reporting/architectureBaselineReporter.js +64 -0
  185. package/dist/core/src/reporting/architectureBaselineReporter.js.map +1 -0
  186. package/dist/core/src/reporting/architectureDiagramReporter.js +148 -0
  187. package/dist/core/src/reporting/architectureDiagramReporter.js.map +1 -0
  188. package/dist/core/src/reporting/architectureDriftTimelineReporter.js +44 -0
  189. package/dist/core/src/reporting/architectureDriftTimelineReporter.js.map +1 -0
  190. package/dist/core/src/reporting/architectureHealthHistoryReporter.js +51 -0
  191. package/dist/core/src/reporting/architectureHealthHistoryReporter.js.map +1 -0
  192. package/dist/core/src/reporting/architectureMapReporter.js +228 -0
  193. package/dist/core/src/reporting/architectureMapReporter.js.map +1 -0
  194. package/dist/core/src/reporting/architecturePrCommentReporter.js +324 -0
  195. package/dist/core/src/reporting/architecturePrCommentReporter.js.map +1 -0
  196. package/dist/core/src/reporting/architecturePrReviewReporter.js +389 -0
  197. package/dist/core/src/reporting/architecturePrReviewReporter.js.map +1 -0
  198. package/dist/core/src/reporting/architectureReportArtifacts.js +166 -0
  199. package/dist/core/src/reporting/architectureReportArtifacts.js.map +1 -0
  200. package/dist/core/src/reporting/architectureReviewReportGenerator.js +382 -0
  201. package/dist/core/src/reporting/architectureReviewReportGenerator.js.map +1 -0
  202. package/dist/core/src/reporting/architectureReviewReporter.js +1633 -0
  203. package/dist/core/src/reporting/architectureReviewReporter.js.map +1 -0
  204. package/dist/core/src/reporting/architectureRiskAssessment.js +101 -0
  205. package/dist/core/src/reporting/architectureRiskAssessment.js.map +1 -0
  206. package/dist/core/src/reporting/architectureScoreBadge.js +177 -0
  207. package/dist/core/src/reporting/architectureScoreBadge.js.map +1 -0
  208. package/dist/core/src/reporting/architectureSupplementalReporters.js +124 -0
  209. package/dist/core/src/reporting/architectureSupplementalReporters.js.map +1 -0
  210. package/dist/core/src/reporting/changeRiskAssessment.js +6 -0
  211. package/dist/core/src/reporting/changeRiskAssessment.js.map +1 -0
  212. package/dist/core/src/reporting/changeRiskTypes.js +3 -0
  213. package/dist/core/src/reporting/changeRiskTypes.js.map +1 -0
  214. package/dist/core/src/reporting/impactAnalysisReporter.js +111 -0
  215. package/dist/core/src/reporting/impactAnalysisReporter.js.map +1 -0
  216. package/dist/core/src/reporting/onboardingDocsReporter.js +113 -0
  217. package/dist/core/src/reporting/onboardingDocsReporter.js.map +1 -0
  218. package/dist/core/src/reporting/rulePackReporter.js +61 -0
  219. package/dist/core/src/reporting/rulePackReporter.js.map +1 -0
  220. package/dist/core/src/reporting/safetyControlsDetection.js +343 -0
  221. package/dist/core/src/reporting/safetyControlsDetection.js.map +1 -0
  222. package/dist/core/src/resolveRuleAdrs.js +31 -0
  223. package/dist/core/src/resolveRuleAdrs.js.map +1 -0
  224. package/dist/core/src/reviewGenerationWorkflow.js +220 -0
  225. package/dist/core/src/reviewGenerationWorkflow.js.map +1 -0
  226. package/dist/core/src/rulePacks/index.js +17 -0
  227. package/dist/core/src/rulePacks/index.js.map +1 -0
  228. package/dist/core/src/rulePacks/layeredMonolithRulePack.js +45 -0
  229. package/dist/core/src/rulePacks/layeredMonolithRulePack.js.map +1 -0
  230. package/dist/core/src/rulePacks/modularMonolithRulePack.js +41 -0
  231. package/dist/core/src/rulePacks/modularMonolithRulePack.js.map +1 -0
  232. package/dist/core/src/rulePacks/types.js +3 -0
  233. package/dist/core/src/rulePacks/types.js.map +1 -0
  234. package/dist/core/src/setupNextSteps.js +939 -0
  235. package/dist/core/src/setupNextSteps.js.map +1 -0
  236. package/dist/core/src/setupRefresh.js +152 -0
  237. package/dist/core/src/setupRefresh.js.map +1 -0
  238. package/dist/core/src/smartInit/apiDbHintDetection.js +836 -0
  239. package/dist/core/src/smartInit/apiDbHintDetection.js.map +1 -0
  240. package/dist/core/src/smartInit/architectureProposalBuilder.js +1215 -0
  241. package/dist/core/src/smartInit/architectureProposalBuilder.js.map +1 -0
  242. package/dist/core/src/smartInit/architectureStyleDetection.js +472 -0
  243. package/dist/core/src/smartInit/architectureStyleDetection.js.map +1 -0
  244. package/dist/core/src/smartInit/archpilotIgnore.js +139 -0
  245. package/dist/core/src/smartInit/archpilotIgnore.js.map +1 -0
  246. package/dist/core/src/smartInit/componentDetection.js +1084 -0
  247. package/dist/core/src/smartInit/componentDetection.js.map +1 -0
  248. package/dist/core/src/smartInit/implementationProfileResolution.js +53 -0
  249. package/dist/core/src/smartInit/implementationProfileResolution.js.map +1 -0
  250. package/dist/core/src/smartInit/index.js +31 -0
  251. package/dist/core/src/smartInit/index.js.map +1 -0
  252. package/dist/core/src/smartInit/moduleDetection.js +982 -0
  253. package/dist/core/src/smartInit/moduleDetection.js.map +1 -0
  254. package/dist/core/src/smartInit/projectKindDetection.js +437 -0
  255. package/dist/core/src/smartInit/projectKindDetection.js.map +1 -0
  256. package/dist/core/src/smartInit/repoTopologyDetection.js +470 -0
  257. package/dist/core/src/smartInit/repoTopologyDetection.js.map +1 -0
  258. package/dist/core/src/smartInit/runContext.js +112 -0
  259. package/dist/core/src/smartInit/runContext.js.map +1 -0
  260. package/dist/core/src/smartInit/smartInitEngine.js +94 -0
  261. package/dist/core/src/smartInit/smartInitEngine.js.map +1 -0
  262. package/dist/core/src/smartInit/stackDetection.js +503 -0
  263. package/dist/core/src/smartInit/stackDetection.js.map +1 -0
  264. package/dist/core/src/smartInit/toolingHintDetection.js +345 -0
  265. package/dist/core/src/smartInit/toolingHintDetection.js.map +1 -0
  266. package/dist/core/src/sourceScopeClassification.js +83 -0
  267. package/dist/core/src/sourceScopeClassification.js.map +1 -0
  268. package/dist/core/src/stackAdapters.js +2475 -0
  269. package/dist/core/src/stackAdapters.js.map +1 -0
  270. package/dist/core/src/stackMetadata.js +157 -0
  271. package/dist/core/src/stackMetadata.js.map +1 -0
  272. package/dist/core/src/suppressions/index.js +3 -0
  273. package/dist/core/src/suppressions/index.js.map +1 -0
  274. package/dist/core/src/suppressions/suppressionReporter.js +38 -0
  275. package/dist/core/src/suppressions/suppressionReporter.js.map +1 -0
  276. package/dist/core/src/suppressions/suppressionTypes.js +3 -0
  277. package/dist/core/src/suppressions/suppressionTypes.js.map +1 -0
  278. package/dist/core/src/suppressions/suppressionValidator.js +119 -0
  279. package/dist/core/src/suppressions/suppressionValidator.js.map +1 -0
  280. package/dist/core/src/transactionDomainAnalysis.js +1416 -0
  281. package/dist/core/src/transactionDomainAnalysis.js.map +1 -0
  282. package/dist/core/src/validation/changedFilesResolver.js +165 -0
  283. package/dist/core/src/validation/changedFilesResolver.js.map +1 -0
  284. package/dist/core/src/validationProfiling.js +33 -0
  285. package/dist/core/src/validationProfiling.js.map +1 -0
  286. package/dist/core/src/validationRuleExplanations.js +69 -0
  287. package/dist/core/src/validationRuleExplanations.js.map +1 -0
  288. package/dist/core/src/validationSourceInventory.js +200 -0
  289. package/dist/core/src/validationSourceInventory.js.map +1 -0
  290. package/dist/core/src/validationSuppressions.js +231 -0
  291. package/dist/core/src/validationSuppressions.js.map +1 -0
  292. package/dist/core/src/validationTypeScriptAstCache.js +82 -0
  293. package/dist/core/src/validationTypeScriptAstCache.js.map +1 -0
  294. package/dist/core/src/validationWorkflow.js +222 -0
  295. package/dist/core/src/validationWorkflow.js.map +1 -0
  296. package/dist/core/src/workspaceRoot.js +68 -0
  297. package/dist/core/src/workspaceRoot.js.map +1 -0
  298. package/package.json +3 -2
@@ -0,0 +1,1203 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.detectDataQueryRiskFindings = detectDataQueryRiskFindings;
37
+ const path = __importStar(require("node:path"));
38
+ const node_fs_1 = require("node:fs");
39
+ const ts = __importStar(require("typescript"));
40
+ const stackAdapters_1 = require("./stackAdapters");
41
+ const validationProfiling_1 = require("./validationProfiling");
42
+ const sourceScopeClassification_1 = require("./sourceScopeClassification");
43
+ const maxFileSizeBytes = 256 * 1024;
44
+ const supplementalRiskScanExtensions = ['.json', '.yaml', '.yml', '.sql'];
45
+ const backendCodeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.java', '.cs', '.py', '.php', '.go'];
46
+ const supplementalIgnoredDirectoryNames = ['.vscode-test', '.tmp'];
47
+ const batchingEvidencePattern = /\b(?:DataLoader|Promise\.all|findMany\s*\([\s\S]{0,300}\bin\b|where\s*:\s*\{[\s\S]{0,300}\bin\s*:|include\s*:|join\s*\(|leftJoin|innerJoin|JOIN\b)\b/iu;
48
+ const queryReadPattern = /\b((?:this\.)?(?:prisma\.(\w+)|([A-Za-z_$][\w$]*Repository|repository))\.(find\w*|count|aggregate|groupBy)\s*\()/giu;
49
+ const unboundedLoadPattern = /\b(?:const|let|var)\s+(\w+)\s*=\s*await\s+((?:this\.)?(?:prisma\.\w+|\w*Repository|repository)\.(?:findMany|findAll|find))\s*\(([\s\S]{0,700}?)\)\s*;?[\s\S]{0,1000}\b\1\.(?:filter|map|reduce)\s*\(/iu;
50
+ const boundEvidencePattern = /\b(?:take|limit|page|cursor|skip|offset|PageRequest|Pageable|findOne|findOneOrFail|findOneBy|findOneByOrFail|findUnique|findFirst|getOne|getRawOne|first)\b/iu;
51
+ const repeatedQueryPattern = /\b((?:this\.)?(?:prisma\.(\w+)|(\w*Repository|repository))\.(find\w*|count|aggregate|groupBy)\s*\(\s*(?:\{[\s\S]{0,300}?\}|\w+)\s*\))/giu;
52
+ const tenantPredicatePattern = /\b(?:tenantId|tenant_id|organizationId|organization_id|orgId|org_id|accountId|account_id|workspaceId|workspace_id|companyId|company_id|customerId|customer_id)\b/u;
53
+ const codePaginationEvidencePattern = /\b(?:take|skip|limit|offset|page|perPage|pageSize|cursor|Pageable|PageRequest|paginate|simplePaginate|Limit|Offset|Take|Skip|TOP\s+\d+)\b/iu;
54
+ function getLineStarts(file) {
55
+ if (file.lineStarts) {
56
+ return file.lineStarts;
57
+ }
58
+ const lineStarts = [0];
59
+ for (let index = 0; index < file.content.length; index += 1) {
60
+ if (file.content[index] === '\n') {
61
+ lineStarts.push(index + 1);
62
+ }
63
+ }
64
+ file.lineStarts = lineStarts;
65
+ return lineStarts;
66
+ }
67
+ function lineNumberForOffset(file, offset) {
68
+ const lineStarts = getLineStarts(file);
69
+ let low = 0;
70
+ let high = lineStarts.length - 1;
71
+ while (low <= high) {
72
+ const middle = Math.floor((low + high) / 2);
73
+ const lineStart = lineStarts[middle] ?? 0;
74
+ if (lineStart <= offset) {
75
+ low = middle + 1;
76
+ }
77
+ else {
78
+ high = middle - 1;
79
+ }
80
+ }
81
+ return Math.max(1, high + 1);
82
+ }
83
+ function inferModuleForPath(contract, relativePath) {
84
+ for (const moduleEntry of listModulePathEntries(contract)) {
85
+ if (relativePath === moduleEntry.relativePath ||
86
+ relativePath.startsWith(`${moduleEntry.relativePath}/`)) {
87
+ return moduleEntry.module;
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ function createCodeFinding(input) {
93
+ return {
94
+ id: input.id,
95
+ severity: 'warning',
96
+ passed: false,
97
+ message: input.message,
98
+ findingType: 'file-scope',
99
+ sourceFile: input.file.relativePath,
100
+ filePath: input.file.relativePath,
101
+ sourceLine: lineNumberForOffset(input.file, input.offset),
102
+ module: input.file.module,
103
+ target: input.target,
104
+ ...(input.deductionKey ? { deductionKey: input.deductionKey } : {}),
105
+ };
106
+ }
107
+ const dqrUnboundedOrmDeductionKey = 'quality:AP-DQR-003:orm-unbounded-collection-read';
108
+ const dqrUnboundedSqlDeductionKey = 'quality:AP-DQR-003:raw-sql-unbounded-select';
109
+ function toPosixRelative(workspaceRoot, absolutePath) {
110
+ return path.relative(workspaceRoot, absolutePath).replace(/\\/g, '/');
111
+ }
112
+ function normalizeSqlTableName(raw) {
113
+ const trimmed = raw.trim().replace(/^"+|"+$/g, '').toLowerCase();
114
+ if (trimmed.includes('.')) {
115
+ const [, tableName] = trimmed.split('.').slice(-2);
116
+ return tableName ?? trimmed;
117
+ }
118
+ return trimmed;
119
+ }
120
+ async function pathExists(targetPath) {
121
+ try {
122
+ await node_fs_1.promises.access(targetPath);
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ async function readTextFileIfReasonable(filePath, sourceInventory) {
130
+ if (sourceInventory) {
131
+ return sourceInventory.readText(filePath, { maxFileSizeBytes });
132
+ }
133
+ try {
134
+ const stats = await node_fs_1.promises.stat(filePath);
135
+ if (stats.size > maxFileSizeBytes) {
136
+ return undefined;
137
+ }
138
+ return await node_fs_1.promises.readFile(filePath, { encoding: 'utf8' });
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ async function collectFilesRecursive(rootPath, includeFile, ignoredDirectoryNames) {
145
+ const collected = [];
146
+ if (!(await pathExists(rootPath))) {
147
+ return collected;
148
+ }
149
+ const stack = [rootPath];
150
+ while (stack.length > 0) {
151
+ const current = stack.pop();
152
+ if (!current) {
153
+ continue;
154
+ }
155
+ let entries;
156
+ try {
157
+ entries = await node_fs_1.promises.readdir(current, { withFileTypes: true });
158
+ }
159
+ catch {
160
+ continue;
161
+ }
162
+ for (const entry of entries) {
163
+ if (entry.name.startsWith('.')) {
164
+ if (!entry.isDirectory()) {
165
+ continue;
166
+ }
167
+ if (entry.name !== '.archpilot') {
168
+ continue;
169
+ }
170
+ }
171
+ const absoluteEntryPath = path.join(current, entry.name);
172
+ if (entry.isDirectory()) {
173
+ if (ignoredDirectoryNames.has(entry.name)) {
174
+ continue;
175
+ }
176
+ stack.push(absoluteEntryPath);
177
+ continue;
178
+ }
179
+ if (!includeFile(absoluteEntryPath)) {
180
+ continue;
181
+ }
182
+ collected.push(absoluteEntryPath);
183
+ }
184
+ }
185
+ return collected.sort((left, right) => left.localeCompare(right));
186
+ }
187
+ function stripSqlComments(sql) {
188
+ return sql
189
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
190
+ .replace(/--.*$/gm, ' ');
191
+ }
192
+ function splitSqlStatements(sql) {
193
+ const statements = [];
194
+ let start = 0;
195
+ for (let index = 0; index < sql.length; index += 1) {
196
+ if (sql[index] !== ';') {
197
+ continue;
198
+ }
199
+ const statement = sql.slice(start, index).trim();
200
+ if (statement.length > 0) {
201
+ const lower = statement.toLowerCase();
202
+ const queryType = lower.startsWith('select ')
203
+ ? 'select'
204
+ : lower.startsWith('update ')
205
+ ? 'update'
206
+ : lower.startsWith('delete ')
207
+ ? 'delete'
208
+ : 'other';
209
+ statements.push({
210
+ statement,
211
+ queryType,
212
+ startOffset: start,
213
+ });
214
+ }
215
+ start = index + 1;
216
+ }
217
+ const trailing = sql.slice(start).trim();
218
+ if (trailing.length > 0) {
219
+ const lower = trailing.toLowerCase();
220
+ const queryType = lower.startsWith('select ')
221
+ ? 'select'
222
+ : lower.startsWith('update ')
223
+ ? 'update'
224
+ : lower.startsWith('delete ')
225
+ ? 'delete'
226
+ : 'other';
227
+ statements.push({
228
+ statement: trailing,
229
+ queryType,
230
+ startOffset: start,
231
+ });
232
+ }
233
+ return statements;
234
+ }
235
+ function extractStatementTableNames(statement) {
236
+ const sql = statement.statement;
237
+ const tableNames = [];
238
+ const pushMatch = (value) => {
239
+ const normalized = normalizeSqlTableName(value);
240
+ if (normalized.length === 0) {
241
+ return;
242
+ }
243
+ if (!tableNames.includes(normalized)) {
244
+ tableNames.push(normalized);
245
+ }
246
+ };
247
+ if (statement.queryType === 'select') {
248
+ for (const match of sql.matchAll(/\bfrom\s+([a-zA-Z0-9_."-]+)/gi)) {
249
+ pushMatch(match[1]);
250
+ }
251
+ for (const match of sql.matchAll(/\bjoin\s+([a-zA-Z0-9_."-]+)/gi)) {
252
+ pushMatch(match[1]);
253
+ }
254
+ }
255
+ if (statement.queryType === 'update') {
256
+ const match = /\bupdate\s+([a-zA-Z0-9_."-]+)/i.exec(sql);
257
+ if (match?.[1]) {
258
+ pushMatch(match[1]);
259
+ }
260
+ }
261
+ if (statement.queryType === 'delete') {
262
+ const match = /\bdelete\s+from\s+([a-zA-Z0-9_."-]+)/i.exec(sql);
263
+ if (match?.[1]) {
264
+ pushMatch(match[1]);
265
+ }
266
+ }
267
+ return tableNames.sort((left, right) => left.localeCompare(right));
268
+ }
269
+ function hasTenantFilterEvidence(statement) {
270
+ const normalized = statement.toLowerCase();
271
+ if (!normalized.includes('tenant_id')) {
272
+ return false;
273
+ }
274
+ if (/\bwhere\b[\s\S]*\btenant_id\b\s*(=|in\b|is\b|!=|<>|>=|<=|>|<)/i.test(normalized)) {
275
+ return true;
276
+ }
277
+ if (/\bon\b[\s\S]*\btenant_id\b\s*=\s*[\w."`:$@?]+/i.test(normalized)) {
278
+ return true;
279
+ }
280
+ return false;
281
+ }
282
+ function hasSelectStar(statement) {
283
+ return /\bselect\s+(distinct\s+)?\*/i.test(statement);
284
+ }
285
+ function hasPaginationEvidence(statement) {
286
+ return /\blimit\s+\d+\b|\boffset\s+\d+\b|\bfetch\s+(first|next)\b|\btop\s+\d+\b/i.test(statement);
287
+ }
288
+ function isObviousPointLookup(statement) {
289
+ const normalized = statement.toLowerCase();
290
+ if (!normalized.includes('where')) {
291
+ return false;
292
+ }
293
+ if (normalized.includes(' or ')) {
294
+ return false;
295
+ }
296
+ return /\bwhere\b[\s\S]*\b([a-z_]*id)\b\s*=\s*([\w$:@?]+|'[^']+'|\d+)/i.test(normalized);
297
+ }
298
+ function listModulePathEntries(contract) {
299
+ return Object.entries(contract.modules ?? {})
300
+ .map(([moduleName, reference]) => {
301
+ const modulePath = reference.path?.replace(/\\/g, '/').trim();
302
+ if (!modulePath) {
303
+ return undefined;
304
+ }
305
+ return {
306
+ module: moduleName,
307
+ relativePath: modulePath,
308
+ };
309
+ })
310
+ .filter((entry) => !!entry)
311
+ .sort((left, right) => left.module.localeCompare(right.module));
312
+ }
313
+ function dedupeFindings(findings) {
314
+ return findings
315
+ .sort((left, right) => {
316
+ const idCompare = left.id.localeCompare(right.id);
317
+ if (idCompare !== 0) {
318
+ return idCompare;
319
+ }
320
+ const pathCompare = left.filePath.localeCompare(right.filePath);
321
+ if (pathCompare !== 0) {
322
+ return pathCompare;
323
+ }
324
+ const moduleCompare = (left.module ?? '').localeCompare(right.module ?? '');
325
+ if (moduleCompare !== 0) {
326
+ return moduleCompare;
327
+ }
328
+ const targetCompare = (left.target ?? '').localeCompare(right.target ?? '');
329
+ if (targetCompare !== 0) {
330
+ return targetCompare;
331
+ }
332
+ const tableCompare = (left.table ?? '').localeCompare(right.table ?? '');
333
+ if (tableCompare !== 0) {
334
+ return tableCompare;
335
+ }
336
+ return left.message.localeCompare(right.message);
337
+ })
338
+ .filter((entry, index, values) => {
339
+ if (index === 0) {
340
+ return true;
341
+ }
342
+ const previous = values[index - 1];
343
+ return !(entry.id === previous.id &&
344
+ entry.filePath === previous.filePath &&
345
+ (entry.module ?? '') === (previous.module ?? '') &&
346
+ (entry.target ?? '') === (previous.target ?? '') &&
347
+ (entry.table ?? '') === (previous.table ?? '') &&
348
+ entry.message === previous.message);
349
+ });
350
+ }
351
+ async function collectSqlFiles(workspaceRoot, contract, sourceInventory) {
352
+ const roots = new Set();
353
+ roots.add(path.join(workspaceRoot, 'db', 'sql'));
354
+ if (contract.structure.databaseRoot) {
355
+ roots.add(path.join(workspaceRoot, ...contract.structure.databaseRoot.split('/')));
356
+ }
357
+ for (const moduleEntry of listModulePathEntries(contract)) {
358
+ roots.add(path.join(workspaceRoot, ...moduleEntry.relativePath.split('/'), 'db', 'sql'));
359
+ roots.add(path.join(workspaceRoot, ...moduleEntry.relativePath.split('/'), 'sql'));
360
+ }
361
+ const adapter = (0, stackAdapters_1.resolvePrimaryAdapterFromContract)(contract);
362
+ const ignoredDirectoryNames = new Set([
363
+ ...adapter.ignoreDirectoryNames,
364
+ ...supplementalIgnoredDirectoryNames,
365
+ ]);
366
+ const sqlPaths = new Set();
367
+ for (const rootPath of [...roots].sort((left, right) => left.localeCompare(right))) {
368
+ const filePaths = sourceInventory
369
+ ? await sourceInventory.getFilePaths({
370
+ rootPath,
371
+ extensions: ['.sql'],
372
+ ignoredDirectoryNames: [...ignoredDirectoryNames],
373
+ useArchpilotIgnore: false,
374
+ })
375
+ : await collectFilesRecursive(rootPath, (filePath) => path.extname(filePath).toLowerCase() === '.sql', ignoredDirectoryNames);
376
+ for (const filePath of filePaths) {
377
+ sqlPaths.add(filePath);
378
+ }
379
+ }
380
+ const sqlFiles = [];
381
+ for (const absolutePath of [...sqlPaths].sort((left, right) => left.localeCompare(right))) {
382
+ const content = await readTextFileIfReasonable(absolutePath, sourceInventory);
383
+ if (!content) {
384
+ continue;
385
+ }
386
+ sqlFiles.push({
387
+ absolutePath,
388
+ relativePath: toPosixRelative(workspaceRoot, absolutePath),
389
+ content,
390
+ });
391
+ }
392
+ return sqlFiles;
393
+ }
394
+ async function collectCodeFiles(workspaceRoot, contract, sourceInventory) {
395
+ const adapter = (0, stackAdapters_1.resolvePrimaryAdapterFromContract)(contract);
396
+ const extensions = new Set([...adapter.relevantFileExtensions, ...backendCodeExtensions]);
397
+ const ignoredDirectoryNames = new Set([
398
+ ...adapter.ignoreDirectoryNames,
399
+ ...supplementalIgnoredDirectoryNames,
400
+ ]);
401
+ const roots = new Set();
402
+ for (const moduleEntry of listModulePathEntries(contract)) {
403
+ roots.add(path.join(workspaceRoot, ...moduleEntry.relativePath.split('/')));
404
+ }
405
+ if (roots.size === 0) {
406
+ roots.add(workspaceRoot);
407
+ }
408
+ const files = [];
409
+ const seen = new Set();
410
+ for (const rootPath of [...roots].sort((left, right) => left.localeCompare(right))) {
411
+ const sourceFiles = sourceInventory
412
+ ? await sourceInventory.getTextFiles({
413
+ rootPath,
414
+ extensions: [...extensions],
415
+ ignoredDirectoryNames: [...ignoredDirectoryNames],
416
+ maxFileSizeBytes,
417
+ useArchpilotIgnore: false,
418
+ })
419
+ : undefined;
420
+ const sourceFileByPath = new Map(sourceFiles?.map((file) => [file.absolutePath, file]) ?? []);
421
+ const filePaths = sourceFiles?.map((file) => file.absolutePath) ??
422
+ await collectFilesRecursive(rootPath, (filePath) => extensions.has(path.extname(filePath).toLowerCase()), ignoredDirectoryNames);
423
+ for (const absolutePath of filePaths) {
424
+ if (seen.has(absolutePath)) {
425
+ continue;
426
+ }
427
+ seen.add(absolutePath);
428
+ const cachedFile = sourceFileByPath.get(absolutePath);
429
+ const content = cachedFile?.content ?? await readTextFileIfReasonable(absolutePath, sourceInventory);
430
+ if (!content) {
431
+ continue;
432
+ }
433
+ const relativePath = toPosixRelative(workspaceRoot, absolutePath);
434
+ files.push({
435
+ absolutePath,
436
+ relativePath,
437
+ content,
438
+ extension: path.extname(absolutePath).toLowerCase(),
439
+ module: inferModuleForPath(contract, relativePath),
440
+ });
441
+ }
442
+ }
443
+ return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
444
+ }
445
+ function getNodeNameText(name) {
446
+ if (!name) {
447
+ return undefined;
448
+ }
449
+ if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) {
450
+ return name.text;
451
+ }
452
+ if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
453
+ return name.text;
454
+ }
455
+ return undefined;
456
+ }
457
+ function extractTypeScriptMethodBodies(file, astCache) {
458
+ const sourceFile = astCache
459
+ ? astCache.parseSourceFile({
460
+ filePath: file.relativePath,
461
+ sourceText: file.content,
462
+ scriptTarget: ts.ScriptTarget.Latest,
463
+ setParentNodes: true,
464
+ })
465
+ : ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
466
+ const methods = [];
467
+ const addBody = (name, body, startNode) => {
468
+ methods.push({
469
+ name,
470
+ body: file.content.slice(body.getStart(sourceFile, false), body.getEnd()),
471
+ startOffset: startNode.getStart(sourceFile, false),
472
+ file,
473
+ });
474
+ };
475
+ const visit = (node) => {
476
+ if (ts.isMethodDeclaration(node) && node.body) {
477
+ addBody(getNodeNameText(node.name) ?? 'method', node.body, node);
478
+ ts.forEachChild(node.body, visit);
479
+ return;
480
+ }
481
+ if (ts.isFunctionDeclaration(node) && node.body) {
482
+ addBody(node.name?.text ?? 'function', node.body, node);
483
+ ts.forEachChild(node.body, visit);
484
+ return;
485
+ }
486
+ if ((ts.isPropertyDeclaration(node) || ts.isVariableDeclaration(node)) &&
487
+ node.initializer &&
488
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
489
+ node.initializer.body) {
490
+ addBody(getNodeNameText(node.name) ?? 'function', node.initializer.body, node);
491
+ ts.forEachChild(node.initializer.body, visit);
492
+ return;
493
+ }
494
+ ts.forEachChild(node, visit);
495
+ };
496
+ visit(sourceFile);
497
+ return methods;
498
+ }
499
+ function findMatchingBrace(content, openBraceIndex) {
500
+ let depth = 0;
501
+ for (let index = openBraceIndex; index < content.length; index += 1) {
502
+ const char = content[index];
503
+ if (char === '{') {
504
+ depth += 1;
505
+ }
506
+ else if (char === '}') {
507
+ depth -= 1;
508
+ if (depth === 0) {
509
+ return index;
510
+ }
511
+ }
512
+ }
513
+ return Math.min(content.length - 1, openBraceIndex + 3000);
514
+ }
515
+ function findMatchingDelimiter(content, openIndex, openChar, closeChar) {
516
+ let depth = 0;
517
+ for (let index = openIndex; index < content.length; index += 1) {
518
+ const char = content[index];
519
+ if (char === openChar) {
520
+ depth += 1;
521
+ }
522
+ else if (char === closeChar) {
523
+ depth -= 1;
524
+ if (depth === 0) {
525
+ return index;
526
+ }
527
+ }
528
+ }
529
+ return -1;
530
+ }
531
+ function findNextNonWhitespace(content, startOffset) {
532
+ for (let index = startOffset; index < content.length; index += 1) {
533
+ if (!/\s/u.test(content[index] ?? '')) {
534
+ return index;
535
+ }
536
+ }
537
+ return -1;
538
+ }
539
+ function extractLoopBodyEntries(body) {
540
+ const loops = [];
541
+ for (const match of body.matchAll(/\bfor\s*\(/gu)) {
542
+ const openParen = body.indexOf('(', match.index ?? 0);
543
+ const closeParen = findMatchingDelimiter(body, openParen, '(', ')');
544
+ if (closeParen < 0) {
545
+ continue;
546
+ }
547
+ const bodyStart = findNextNonWhitespace(body, closeParen + 1);
548
+ if (bodyStart < 0) {
549
+ continue;
550
+ }
551
+ if (body[bodyStart] === '{') {
552
+ const closeBrace = findMatchingBrace(body, bodyStart);
553
+ loops.push({ body: body.slice(bodyStart, closeBrace + 1), offset: bodyStart });
554
+ }
555
+ else {
556
+ const statementEnd = body.indexOf(';', bodyStart);
557
+ if (statementEnd > bodyStart) {
558
+ loops.push({ body: body.slice(bodyStart, statementEnd + 1), offset: bodyStart });
559
+ }
560
+ }
561
+ }
562
+ for (const match of body.matchAll(/\b(?:forEach|map)\s*\(/gu)) {
563
+ const openParen = body.indexOf('(', match.index ?? 0);
564
+ const closeParen = findMatchingDelimiter(body, openParen, '(', ')');
565
+ if (closeParen > openParen) {
566
+ loops.push({ body: body.slice(openParen + 1, closeParen), offset: openParen + 1 });
567
+ }
568
+ }
569
+ return loops;
570
+ }
571
+ function extractJavaMethodBodies(file) {
572
+ const methods = [];
573
+ const pattern = /^\s*(?:@\w+(?:\([^)]*\))?\s*)*(?:public|private|protected)\s+[\w<>, ?\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/gmu;
574
+ for (const match of file.content.matchAll(pattern)) {
575
+ const openBrace = (match.index ?? 0) + match[0].lastIndexOf('{');
576
+ const end = findMatchingBrace(file.content, openBrace);
577
+ methods.push({
578
+ name: match[1] ?? 'method',
579
+ body: file.content.slice(openBrace, end + 1),
580
+ startOffset: match.index ?? 0,
581
+ file,
582
+ });
583
+ }
584
+ return methods;
585
+ }
586
+ function braceMethodPatternForExtension(extension) {
587
+ if (extension === '.java') {
588
+ return /^\s*(?:@\w+(?:\([^)]*\))?\s*)*(?:public|private|protected)\s+[\w<>, ?\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/gmu;
589
+ }
590
+ if (extension === '.cs') {
591
+ return /^\s*(?:\[[^\]]+\]\s*)*(?:public|private|protected|internal)\s+(?:async\s+)?[\w<>, ?\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/gmu;
592
+ }
593
+ if (extension === '.php') {
594
+ return /^\s*(?:(?:public|private|protected|static|final)\s+)*function\s+(\w+)\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{/gmu;
595
+ }
596
+ if (extension === '.go') {
597
+ return /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\([^)]*\)\s*(?:\([^)]*\)|[\w*.[\]]+)?\s*\{/gmu;
598
+ }
599
+ return /^\s*(?:export\s+)?(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(?:function\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{/gmu;
600
+ }
601
+ function extractBraceMethodBodies(file) {
602
+ const methods = [];
603
+ const pattern = braceMethodPatternForExtension(file.extension);
604
+ for (const match of file.content.matchAll(pattern)) {
605
+ const openBrace = (match.index ?? 0) + match[0].lastIndexOf('{');
606
+ const end = findMatchingBrace(file.content, openBrace);
607
+ methods.push({
608
+ name: match[1] ?? 'method',
609
+ body: file.content.slice(openBrace, end + 1),
610
+ startOffset: match.index ?? 0,
611
+ file,
612
+ });
613
+ }
614
+ return methods;
615
+ }
616
+ function leadingWhitespaceLength(value) {
617
+ return value.length - value.trimStart().length;
618
+ }
619
+ function extractPythonMethodBodies(file) {
620
+ const methods = [];
621
+ const lineStarts = getLineStarts(file);
622
+ for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex += 1) {
623
+ const lineStart = lineStarts[lineIndex] ?? 0;
624
+ const lineEnd = file.content.indexOf('\n', lineStart);
625
+ const line = file.content.slice(lineStart, lineEnd === -1 ? file.content.length : lineEnd);
626
+ const match = /^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\([^)]*\)\s*:/u.exec(line.trimStart());
627
+ if (!match?.[1]) {
628
+ continue;
629
+ }
630
+ const definitionIndent = leadingWhitespaceLength(line);
631
+ let bodyEnd = file.content.length;
632
+ for (let cursor = lineIndex + 1; cursor < lineStarts.length; cursor += 1) {
633
+ const cursorStart = lineStarts[cursor] ?? 0;
634
+ const cursorEnd = file.content.indexOf('\n', cursorStart);
635
+ const cursorLine = file.content.slice(cursorStart, cursorEnd === -1 ? file.content.length : cursorEnd);
636
+ if (cursorLine.trim().length === 0) {
637
+ continue;
638
+ }
639
+ if (leadingWhitespaceLength(cursorLine) <= definitionIndent) {
640
+ bodyEnd = cursorStart;
641
+ break;
642
+ }
643
+ }
644
+ methods.push({
645
+ name: match[1],
646
+ body: file.content.slice(lineStart, bodyEnd),
647
+ startOffset: lineStart,
648
+ file,
649
+ });
650
+ }
651
+ return methods;
652
+ }
653
+ function extractMethodBodies(file, astCache) {
654
+ if (file.extension === '.java') {
655
+ return extractJavaMethodBodies(file);
656
+ }
657
+ if (file.extension === '.py') {
658
+ return extractPythonMethodBodies(file);
659
+ }
660
+ if (file.extension === '.cs' || file.extension === '.php' || file.extension === '.go') {
661
+ return extractBraceMethodBodies(file);
662
+ }
663
+ return extractTypeScriptMethodBodies(file, astCache);
664
+ }
665
+ function collectTenantScopedTables(sqlFiles) {
666
+ const tables = new Set();
667
+ for (const sqlFile of sqlFiles) {
668
+ const statements = splitSqlStatements(stripSqlComments(sqlFile.content));
669
+ for (const statement of statements) {
670
+ if (!/^create\s+table\b/i.test(statement.statement)) {
671
+ continue;
672
+ }
673
+ if (!/\btenant_id\b/i.test(statement.statement)) {
674
+ continue;
675
+ }
676
+ const match = /\bcreate\s+table\s+(if\s+not\s+exists\s+)?([a-zA-Z0-9_."-]+)/i.exec(statement.statement);
677
+ if (!match?.[2]) {
678
+ continue;
679
+ }
680
+ tables.add(normalizeSqlTableName(match[2]));
681
+ }
682
+ }
683
+ return tables;
684
+ }
685
+ async function detectCrossModuleDirectDatabaseAccess(workspaceRoot, contract, sourceInventory) {
686
+ const started = Date.now();
687
+ const findings = [];
688
+ const adapter = (0, stackAdapters_1.resolvePrimaryAdapterFromContract)(contract);
689
+ const codeFileExtensions = new Set([
690
+ ...adapter.relevantFileExtensions,
691
+ ...supplementalRiskScanExtensions,
692
+ ]);
693
+ const ignoredDirectoryNames = new Set([
694
+ ...adapter.ignoreDirectoryNames,
695
+ ...supplementalIgnoredDirectoryNames,
696
+ ]);
697
+ const moduleEntries = listModulePathEntries(contract);
698
+ let scannedFiles = 0;
699
+ for (const sourceModule of moduleEntries) {
700
+ const sourceRoot = path.join(workspaceRoot, ...sourceModule.relativePath.split('/'));
701
+ const sourceFiles = sourceInventory
702
+ ? await sourceInventory.getTextFiles({
703
+ rootPath: sourceRoot,
704
+ extensions: [...codeFileExtensions],
705
+ ignoredDirectoryNames: [...ignoredDirectoryNames],
706
+ maxFileSizeBytes,
707
+ useArchpilotIgnore: false,
708
+ })
709
+ : undefined;
710
+ const sourceFilePaths = sourceFiles?.map((file) => file.absolutePath) ??
711
+ await collectFilesRecursive(sourceRoot, (filePath) => codeFileExtensions.has(path.extname(filePath).toLowerCase()), ignoredDirectoryNames);
712
+ const sourceFileByPath = new Map(sourceFiles?.map((file) => [file.absolutePath, file]) ?? []);
713
+ scannedFiles += sourceFilePaths.length;
714
+ for (const sourceFilePath of sourceFilePaths) {
715
+ const content = sourceFileByPath.get(sourceFilePath)?.content ??
716
+ await readTextFileIfReasonable(sourceFilePath, sourceInventory);
717
+ if (!content) {
718
+ continue;
719
+ }
720
+ const normalized = content.replace(/\\/g, '/').toLowerCase();
721
+ for (const targetModule of moduleEntries) {
722
+ if (targetModule.module === sourceModule.module) {
723
+ continue;
724
+ }
725
+ const targetModuleLower = targetModule.module.toLowerCase();
726
+ const targetPathLower = targetModule.relativePath.replace(/\\/g, '/').toLowerCase();
727
+ const directDbPattern = `/${targetModuleLower}/db/`;
728
+ const directSqlPattern = `/${targetModuleLower}/sql/`;
729
+ const targetPathDbPattern = `${targetPathLower}/db/`;
730
+ const targetPathSqlPattern = `${targetPathLower}/sql/`;
731
+ const hasDirectReference = normalized.includes(directDbPattern) ||
732
+ normalized.includes(directSqlPattern) ||
733
+ normalized.includes(targetPathDbPattern) ||
734
+ normalized.includes(targetPathSqlPattern);
735
+ if (!hasDirectReference) {
736
+ continue;
737
+ }
738
+ findings.push({
739
+ id: 'AP-DQR-004',
740
+ severity: 'warning',
741
+ passed: false,
742
+ message: `Module '${sourceModule.module}' references database artifacts under module '${targetModule.module}', bypassing module boundary expectations.`,
743
+ findingType: 'module-dependency',
744
+ sourceFile: toPosixRelative(workspaceRoot, sourceFilePath),
745
+ filePath: toPosixRelative(workspaceRoot, sourceFilePath),
746
+ module: sourceModule.module,
747
+ target: targetModule.module,
748
+ });
749
+ }
750
+ }
751
+ }
752
+ (0, validationProfiling_1.emitValidationProfile)('data/query risk analyzer cross-module scan', Date.now() - started, [
753
+ { name: 'files', value: scannedFiles },
754
+ { name: 'modules', value: moduleEntries.length },
755
+ ]);
756
+ return findings;
757
+ }
758
+ function normalizeQueryCall(value) {
759
+ return value
760
+ .replace(/\s+/gu, ' ')
761
+ .replace(/(["'`])[^"'`]{1,120}\1/gu, '<literal>')
762
+ .replace(/\b[a-f0-9-]{8,}\b/giu, '<id>')
763
+ .trim()
764
+ .toLowerCase();
765
+ }
766
+ function normalizeRepositoryKey(value) {
767
+ return value
768
+ .replace(/^this\./u, '')
769
+ .replace(/Repository$/u, '')
770
+ .toLowerCase();
771
+ }
772
+ function detectNPlusOneQueryRisk(method) {
773
+ if (batchingEvidencePattern.test(method.body)) {
774
+ return [];
775
+ }
776
+ const evidences = [];
777
+ for (const loop of extractLoopBodyEntries(method.body)) {
778
+ for (const match of loop.body.matchAll(queryReadPattern)) {
779
+ const repositoryName = match[2] ?? match[3] ?? 'repository';
780
+ const operation = match[4] ?? 'find';
781
+ evidences.push({
782
+ method,
783
+ offset: method.startOffset + loop.offset + (match.index ?? 0),
784
+ repositoryKey: normalizeRepositoryKey(repositoryName),
785
+ queryLabel: `${repositoryName}.${operation}`,
786
+ });
787
+ }
788
+ }
789
+ return evidences;
790
+ }
791
+ function aggregateNPlusOneFindings(evidences) {
792
+ const grouped = new Map();
793
+ for (const evidence of evidences) {
794
+ const key = evidence.method.file.relativePath;
795
+ const group = grouped.get(key);
796
+ if (group) {
797
+ group.push(evidence);
798
+ }
799
+ else {
800
+ grouped.set(key, [evidence]);
801
+ }
802
+ }
803
+ return [...grouped.values()].map((group) => {
804
+ const first = group[0];
805
+ const file = first?.method.file;
806
+ if (!first || !file) {
807
+ throw new Error('Cannot aggregate AP-DQR-005 evidence without a representative method.');
808
+ }
809
+ const affectedMethods = [...new Set(group.map((evidence) => evidence.method.name))]
810
+ .sort((left, right) => left.localeCompare(right));
811
+ const representativeCalls = [...new Set(group.map((evidence) => evidence.queryLabel))]
812
+ .sort((left, right) => left.localeCompare(right));
813
+ const repositoryKeys = [...new Set(group.map((evidence) => evidence.repositoryKey))]
814
+ .sort((left, right) => left.localeCompare(right));
815
+ const methodSummary = affectedMethods.slice(0, 6).join(', ');
816
+ const callSummary = representativeCalls.slice(0, 4).join(', ');
817
+ const repositorySummary = repositoryKeys.slice(0, 6).join(', ');
818
+ const extraMethodCount = Math.max(0, affectedMethods.length - 6);
819
+ const extraRepositoryCount = Math.max(0, repositoryKeys.length - 6);
820
+ return createCodeFinding({
821
+ id: 'AP-DQR-005',
822
+ message: `Potential N+1 query risk: ${affectedMethods.length} method(s) in '${file.relativePath}' contain ` +
823
+ `repository/database reads inside collection loops without clear batching evidence. ` +
824
+ `Affected methods: ${methodSummary}${extraMethodCount > 0 ? `, +${extraMethodCount} more` : ''}. ` +
825
+ `Repository/model families: ${repositorySummary}${extraRepositoryCount > 0 ? `, +${extraRepositoryCount} more` : ''}. ` +
826
+ `Loop query evidence: ${callSummary}. Risk instances: ${group.length}.`,
827
+ file,
828
+ offset: first.offset,
829
+ target: 'n-plus-one',
830
+ });
831
+ });
832
+ }
833
+ function detectLargeCollectionLoading(method) {
834
+ const match = unboundedLoadPattern.exec(method.body);
835
+ if (!match || boundEvidencePattern.test(match[3] ?? '')) {
836
+ return [];
837
+ }
838
+ return [
839
+ createCodeFinding({
840
+ id: 'AP-DQR-006',
841
+ message: `Large collection loading risk: method '${method.name}' loads '${match[1]}' without bounds and then processes it in memory.`,
842
+ file: method.file,
843
+ offset: method.startOffset + (match.index ?? 0),
844
+ target: method.name,
845
+ }),
846
+ ];
847
+ }
848
+ function detectMissingProjection(method) {
849
+ const findings = [];
850
+ const pattern = /\b(?:const|let)\s+(\w+)\s*=\s*await\s+((?:this\.)?(?:prisma\.(\w+)|(\w*Repository|repository))\.(?:findMany|findAll))\s*\(([\s\S]{0,700}?)\)\s*;?([\s\S]{0,900})/giu;
851
+ for (const match of method.body.matchAll(pattern)) {
852
+ const variableName = match[1];
853
+ const modelName = match[3] ?? match[4] ?? 'entity';
854
+ const queryArgs = match[5] ?? '';
855
+ const following = match[6] ?? '';
856
+ if (!variableName ||
857
+ /\b(?:select|projection|fields|attributes)\b/iu.test(queryArgs) ||
858
+ new RegExp(`\\breturn\\s+${variableName}\\s*;`, 'u').test(following)) {
859
+ continue;
860
+ }
861
+ const fieldMatches = [...following.matchAll(new RegExp(`\\b${variableName}\\.(?:map|filter)\\s*\\([\\s\\S]{0,500}?\\.([A-Za-z_]\\w*)`, 'gu'))]
862
+ .map((fieldMatch) => fieldMatch[1])
863
+ .filter((field) => !!field);
864
+ const uniqueFields = [...new Set(fieldMatches)];
865
+ if (uniqueFields.length === 0 || uniqueFields.length > 3) {
866
+ continue;
867
+ }
868
+ findings.push(createCodeFinding({
869
+ id: 'AP-DQR-007',
870
+ message: `Missing projection risk: method '${method.name}' reads '${modelName}' entities but only references ${uniqueFields.join(', ')} afterward.`,
871
+ file: method.file,
872
+ offset: method.startOffset + (match.index ?? 0),
873
+ target: method.name,
874
+ }));
875
+ }
876
+ return findings.slice(0, 1);
877
+ }
878
+ function detectRepeatedQueryPattern(method) {
879
+ const normalizedCounts = new Map();
880
+ for (const match of method.body.matchAll(repeatedQueryPattern)) {
881
+ const call = match[1];
882
+ if (!call) {
883
+ continue;
884
+ }
885
+ const normalized = normalizeQueryCall(call);
886
+ const current = normalizedCounts.get(normalized);
887
+ if (current) {
888
+ current.count += 1;
889
+ }
890
+ else {
891
+ normalizedCounts.set(normalized, {
892
+ count: 1,
893
+ index: match.index ?? 0,
894
+ label: `${match[2] ?? match[3] ?? 'repository'}.${match[4] ?? 'find'}`,
895
+ });
896
+ }
897
+ }
898
+ const repeated = [...normalizedCounts.values()].find((entry) => entry.count >= 2);
899
+ if (!repeated) {
900
+ return [];
901
+ }
902
+ return [
903
+ createCodeFinding({
904
+ id: 'AP-DQR-008',
905
+ message: `Repeated query pattern: method '${method.name}' performs the same '${repeated.label}' query ${repeated.count} times in one flow.`,
906
+ file: method.file,
907
+ offset: method.startOffset + repeated.index,
908
+ target: method.name,
909
+ }),
910
+ ];
911
+ }
912
+ function extractSqlQueryLiterals(content) {
913
+ const queries = [];
914
+ const patterns = [
915
+ /(?:\b(?:[\w$]+\.)?(?:query|execute|executeQuery|createNativeQuery|createQuery|FromSqlRaw|FromSqlInterpolated|Query|QueryContext|QueryRow|QueryRowContext|Exec|ExecContext|Raw|text)|\$queryRaw|\$executeRaw)\s*\([^'"`)]{0,180}?(['"`])([\s\S]{0,900}?)\1/giu,
916
+ /\b(?:DB::select|DB::statement|DB::raw|DB::table)\s*\([^'"`)]{0,180}?(['"`])([\s\S]{0,900}?)\1/gu,
917
+ /@Query\s*\(\s*(['"`])([\s\S]{0,900}?)\1/gu,
918
+ ];
919
+ for (const pattern of patterns) {
920
+ for (const match of content.matchAll(pattern)) {
921
+ const sql = match[2];
922
+ if (!sql || !/\b(?:select|update|delete|insert)\b/iu.test(sql)) {
923
+ continue;
924
+ }
925
+ queries.push({
926
+ sql,
927
+ offset: (match.index ?? 0) + match[0].indexOf(sql),
928
+ });
929
+ }
930
+ }
931
+ return queries;
932
+ }
933
+ function detectCodeSqlQueryShapeFindings(file, config) {
934
+ const findings = [];
935
+ for (const query of extractSqlQueryLiterals(file.content)) {
936
+ for (const statement of splitSqlStatements(stripSqlComments(query.sql))) {
937
+ if (statement.queryType === 'other') {
938
+ continue;
939
+ }
940
+ const tableNames = extractStatementTableNames(statement);
941
+ const firstTable = tableNames[0];
942
+ const offset = query.offset + statement.startOffset;
943
+ if (config.tenantModel === 'same_db_same_schema' &&
944
+ (statement.queryType === 'select' || statement.queryType === 'update' || statement.queryType === 'delete') &&
945
+ !hasTenantFilterEvidence(statement.statement) &&
946
+ !tenantPredicatePattern.test(statement.statement)) {
947
+ findings.push(createCodeFinding({
948
+ id: 'AP-DQR-001',
949
+ message: `Potential tenant-isolation risk: ${statement.queryType.toUpperCase()} query${firstTable ? ` on table '${firstTable}'` : ''} has no tenant predicate evidence.`,
950
+ file,
951
+ offset,
952
+ target: firstTable ?? 'sql-query',
953
+ }));
954
+ }
955
+ if (statement.queryType === 'select' && hasSelectStar(statement.statement)) {
956
+ findings.push(createCodeFinding({
957
+ id: 'AP-DQR-002',
958
+ message: `Broad SELECT * usage detected in code query path${firstTable ? ` (table '${firstTable}')` : ''}.`,
959
+ file,
960
+ offset,
961
+ target: firstTable ?? 'sql-query',
962
+ }));
963
+ }
964
+ if (statement.queryType === 'select' &&
965
+ !isOperationalOrBatchQueryContext(file) &&
966
+ !hasPaginationEvidence(statement.statement) &&
967
+ !isObviousPointLookup(statement.statement) &&
968
+ !/\bcount\s*\(/i.test(statement.statement)) {
969
+ findings.push(createCodeFinding({
970
+ id: 'AP-DQR-003',
971
+ message: `Potential unbounded query risk: SELECT query${firstTable ? ` on table '${firstTable}'` : ''} has no LIMIT/OFFSET/FETCH pagination evidence.`,
972
+ file,
973
+ offset,
974
+ target: firstTable ?? 'sql-query',
975
+ deductionKey: dqrUnboundedSqlDeductionKey,
976
+ }));
977
+ }
978
+ }
979
+ }
980
+ return findings;
981
+ }
982
+ function collectOrmReadEvidence(method) {
983
+ const patterns = [
984
+ [/\b(?:this\.)?prisma\.\w+\.findMany\s*\(/giu, 'prisma.findMany'],
985
+ [/\b(?:sequelize|model|[A-Z]\w*)\.findAll\s*\(/gu, 'findAll'],
986
+ [/\bsession\.query\s*\(/giu, 'sqlalchemy.query'],
987
+ [/\bselect\s*\(\s*[A-Z]\w*/gu, 'sqlalchemy.select'],
988
+ [/\b(?:DB::table|[A-Z]\w*::(?:query|where|all))\s*\(/g, 'laravel.query'],
989
+ [/->get\s*\(/g, 'laravel.get'],
990
+ [/\b(?:db|tx)\.(?:Query|QueryContext|QueryRow|QueryRowContext)\s*\(/g, 'go.sql'],
991
+ [/\b(?:db|gormDb)\.(?:Find|Raw|Where)\s*\(/g, 'go.orm'],
992
+ [/\b(?:knex\s*\(|db\.query\s*\(|dataSource\.createQueryBuilder\s*\(|createQueryBuilder\s*\()/giu, 'node.query'],
993
+ [/\b(?:this\.)?db\.[A-Z]\w*\.(?:Where|ToList|ToListAsync)\s*\(/g, 'ef.query'],
994
+ ];
995
+ const evidence = [];
996
+ for (const [pattern, label] of patterns) {
997
+ for (const match of method.body.matchAll(pattern)) {
998
+ const relativeOffset = match.index ?? 0;
999
+ evidence.push({
1000
+ offset: method.startOffset + relativeOffset,
1001
+ snippet: method.body.slice(relativeOffset, relativeOffset + 600),
1002
+ label,
1003
+ });
1004
+ }
1005
+ }
1006
+ return evidence;
1007
+ }
1008
+ function getOrmTerminalOperation(snippet) {
1009
+ const match = /\.(getMany|getRawMany|loadMany|getOne|getRawOne|loadOne|getCount|getExists|execute|findOne|findOneOrFail|findOneBy|findOneByOrFail|findUnique|findFirst|count|exists|update|delete|remove|save|insert)\s*\(/iu.exec(snippet);
1010
+ return match?.[1];
1011
+ }
1012
+ function isOperationalOrBatchQueryContext(file, methodName) {
1013
+ const pathValue = file.relativePath.toLowerCase();
1014
+ const segments = pathValue.split('/');
1015
+ const method = methodName?.toLowerCase() ?? '';
1016
+ return (segments.some((segment) => /\b(?:health|cache|job|queue|scheduler|indexer|telemetry|metrics)\b/u.test(segment.replace(/-/g, ' '))) ||
1017
+ /(^|\/)(?:health-check|healthchecks?|cache|job-queue|scheduler|search|indexer|telemetry|metrics)(?:\/|-)/u.test(pathValue) ||
1018
+ /(?:health-check|healthcheck|cache|job-queue|scheduler|search|indexer|telemetry|metrics)\.(?:ts|tsx|js|jsx|java|cs|py|php|go)$/u.test(pathValue) ||
1019
+ /\b(?:health|cache|job|queue|schedule|scheduler|index|telemetry|metrics|buffer|worker|batch)\b/u.test(method) ||
1020
+ method.includes('batch') ||
1021
+ /^on(?:moduleinit|applicationbootstrap|applicationstart|init)$/u.test(method));
1022
+ }
1023
+ function isScopedRelationLoad(snippet) {
1024
+ return (/\.relation\s*\(/iu.test(snippet) &&
1025
+ /\.of\s*\(\s*[^)]{1,180}\)\s*\.loadMany\s*\(/iu.test(snippet));
1026
+ }
1027
+ function hasAssignedBuilderBoundedTerminal(methodBody, snippet) {
1028
+ const variableMatch = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:[\s\S]{0,240}?)createQueryBuilder\s*\(/iu.exec(snippet);
1029
+ const variableName = variableMatch?.[1];
1030
+ if (!variableName) {
1031
+ return false;
1032
+ }
1033
+ return new RegExp(`\\b${variableName}\\s*\\.\\s*(?:getOne|getRawOne|loadOne|getCount|getExists)\\s*\\(`, 'iu').test(methodBody);
1034
+ }
1035
+ function isNonCollectionOrmRead(label, snippet, methodBody = snippet) {
1036
+ const terminal = getOrmTerminalOperation(snippet);
1037
+ return (/\b(?:findOne|findOneOrFail|findOneBy|findOneByOrFail|findUnique|findFirst|count|exists|aggregate)\b/iu.test(label) ||
1038
+ !!terminal && /^(?:getOne|getRawOne|loadOne|getCount|getExists|findOne|findOneOrFail|findOneBy|findOneByOrFail|findUnique|findFirst|count|exists|update|delete|remove|save|insert|execute)$/iu.test(terminal) ||
1039
+ isScopedRelationLoad(snippet) ||
1040
+ hasAssignedBuilderBoundedTerminal(methodBody, snippet));
1041
+ }
1042
+ function hasOrmBoundEvidence(snippet) {
1043
+ return (codePaginationEvidencePattern.test(snippet) ||
1044
+ /\.(?:take|limit|offset|skip)\s*\(/iu.test(snippet) ||
1045
+ /\b(?:maxResults|setMaxResults|firstResult|setFirstResult)\s*\(/iu.test(snippet) ||
1046
+ /\b(?:pageSize|perPage|limit|take)\s*[:=]\s*\d+\b/iu.test(snippet));
1047
+ }
1048
+ function getOrmQueryTarget(method, label, snippet) {
1049
+ const terminal = getOrmTerminalOperation(snippet);
1050
+ if (terminal) {
1051
+ return `${method.name}.${terminal}`;
1052
+ }
1053
+ const repositoryMatch = /\b(?:this\.)?([A-Za-z_$][\w$]*(?:Repository|Repo|Dao)|repository|prisma\.\w+)\./u.exec(snippet);
1054
+ if (repositoryMatch?.[1]) {
1055
+ return `${method.name}.${repositoryMatch[1]}`;
1056
+ }
1057
+ return method.name || label;
1058
+ }
1059
+ function getOrmQueryLabel(method, label, snippet) {
1060
+ const terminal = getOrmTerminalOperation(snippet);
1061
+ if (terminal) {
1062
+ return `${label}.${terminal}`;
1063
+ }
1064
+ return label === 'node.query' ? method.name : label;
1065
+ }
1066
+ function detectOrmQueryShapeFindings(method, config) {
1067
+ const findings = [];
1068
+ for (const evidence of collectOrmReadEvidence(method)) {
1069
+ if (config.tenantModel === 'same_db_same_schema' && !tenantPredicatePattern.test(evidence.snippet)) {
1070
+ findings.push(createCodeFinding({
1071
+ id: 'AP-DQR-001',
1072
+ message: `Potential tenant-isolation risk: ORM/query-builder read '${evidence.label}' has no tenant predicate evidence.`,
1073
+ file: method.file,
1074
+ offset: evidence.offset,
1075
+ target: evidence.label,
1076
+ }));
1077
+ }
1078
+ if (!isOperationalOrBatchQueryContext(method.file, method.name) &&
1079
+ !isNonCollectionOrmRead(evidence.label, evidence.snippet, method.body) &&
1080
+ !hasOrmBoundEvidence(evidence.snippet)) {
1081
+ const target = getOrmQueryTarget(method, evidence.label, evidence.snippet);
1082
+ const label = getOrmQueryLabel(method, evidence.label, evidence.snippet);
1083
+ findings.push(createCodeFinding({
1084
+ id: 'AP-DQR-003',
1085
+ message: `Potential unbounded query risk: ORM/query-builder read '${label}' in method '${method.name}' has no limit or pagination evidence.`,
1086
+ file: method.file,
1087
+ offset: evidence.offset,
1088
+ target,
1089
+ deductionKey: dqrUnboundedOrmDeductionKey,
1090
+ }));
1091
+ }
1092
+ }
1093
+ return findings.slice(0, 3);
1094
+ }
1095
+ function detectCodeQueryRiskFindings(files, config, astCache) {
1096
+ const findings = [];
1097
+ const nPlusOneEvidence = [];
1098
+ for (const file of files) {
1099
+ const scope = (0, sourceScopeClassification_1.classifyValidationSourceScope)(file.relativePath);
1100
+ const isProductionBackend = (0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(scope);
1101
+ if (isProductionBackend) {
1102
+ findings.push(...detectCodeSqlQueryShapeFindings(file, config));
1103
+ }
1104
+ for (const method of extractMethodBodies(file, astCache)) {
1105
+ if (!isProductionBackend) {
1106
+ continue;
1107
+ }
1108
+ nPlusOneEvidence.push(...detectNPlusOneQueryRisk(method));
1109
+ findings.push(...detectOrmQueryShapeFindings(method, config), ...detectLargeCollectionLoading(method), ...detectMissingProjection(method), ...detectRepeatedQueryPattern(method));
1110
+ }
1111
+ }
1112
+ return [
1113
+ ...aggregateNPlusOneFindings(nPlusOneEvidence),
1114
+ ...findings,
1115
+ ];
1116
+ }
1117
+ async function detectDataQueryRiskFindings(input) {
1118
+ const { workspaceRoot, config, contract, disabledRuleIds } = input;
1119
+ if (config.database === 'none') {
1120
+ return [];
1121
+ }
1122
+ const findings = [];
1123
+ const sqlStarted = Date.now();
1124
+ const sqlFiles = await collectSqlFiles(workspaceRoot, contract, input.sourceInventory);
1125
+ (0, validationProfiling_1.emitValidationProfile)('data/query risk analyzer SQL scan', Date.now() - sqlStarted, [
1126
+ { name: 'files', value: sqlFiles.length },
1127
+ ]);
1128
+ const tenantScopedTables = collectTenantScopedTables(sqlFiles);
1129
+ for (const sqlFile of sqlFiles) {
1130
+ const scope = (0, sourceScopeClassification_1.classifyValidationSourceScope)(sqlFile.relativePath);
1131
+ const isProductionBackend = (0, sourceScopeClassification_1.isProductionBackendGovernanceScope)(scope);
1132
+ const statements = splitSqlStatements(stripSqlComments(sqlFile.content));
1133
+ for (const statement of statements) {
1134
+ if (statement.queryType === 'other') {
1135
+ continue;
1136
+ }
1137
+ const tableNames = extractStatementTableNames(statement);
1138
+ const firstTable = tableNames[0];
1139
+ if (config.tenantModel === 'same_db_same_schema' &&
1140
+ !(disabledRuleIds?.has('AP-DQR-001') ?? false) &&
1141
+ (statement.queryType === 'select' ||
1142
+ statement.queryType === 'update' ||
1143
+ statement.queryType === 'delete') &&
1144
+ tableNames.some((tableName) => tenantScopedTables.has(tableName)) &&
1145
+ !hasTenantFilterEvidence(statement.statement)) {
1146
+ findings.push({
1147
+ id: 'AP-DQR-001',
1148
+ severity: 'warning',
1149
+ passed: false,
1150
+ message: `Potential tenant-isolation risk: ${statement.queryType.toUpperCase()} query on tenant-scoped table${firstTable ? ` '${firstTable}'` : ''} has no tenant_id filter evidence.`,
1151
+ findingType: 'table-scope',
1152
+ sourceFile: sqlFile.relativePath,
1153
+ filePath: sqlFile.relativePath,
1154
+ ...(firstTable ? { table: firstTable } : {}),
1155
+ });
1156
+ }
1157
+ if (statement.queryType === 'select' &&
1158
+ isProductionBackend &&
1159
+ !(disabledRuleIds?.has('AP-DQR-002') ?? false) &&
1160
+ hasSelectStar(statement.statement)) {
1161
+ findings.push({
1162
+ id: 'AP-DQR-002',
1163
+ severity: 'warning',
1164
+ passed: false,
1165
+ message: `Broad SELECT * usage detected in query path${firstTable ? ` (table '${firstTable}')` : ''}.`,
1166
+ findingType: 'table-scope',
1167
+ sourceFile: sqlFile.relativePath,
1168
+ filePath: sqlFile.relativePath,
1169
+ ...(firstTable ? { table: firstTable } : {}),
1170
+ });
1171
+ }
1172
+ if (statement.queryType === 'select' &&
1173
+ isProductionBackend &&
1174
+ !(disabledRuleIds?.has('AP-DQR-003') ?? false) &&
1175
+ !hasPaginationEvidence(statement.statement) &&
1176
+ !isObviousPointLookup(statement.statement) &&
1177
+ !/\bcount\s*\(/i.test(statement.statement)) {
1178
+ findings.push({
1179
+ id: 'AP-DQR-003',
1180
+ severity: 'warning',
1181
+ passed: false,
1182
+ message: `Potential unbounded query risk: SELECT query${firstTable ? ` on table '${firstTable}'` : ''} has no LIMIT/OFFSET/FETCH pagination evidence.`,
1183
+ findingType: 'table-scope',
1184
+ sourceFile: sqlFile.relativePath,
1185
+ filePath: sqlFile.relativePath,
1186
+ deductionKey: dqrUnboundedSqlDeductionKey,
1187
+ ...(firstTable ? { table: firstTable } : {}),
1188
+ });
1189
+ }
1190
+ }
1191
+ }
1192
+ if (!(disabledRuleIds?.has('AP-DQR-004') ?? false)) {
1193
+ findings.push(...(await detectCrossModuleDirectDatabaseAccess(workspaceRoot, contract, input.sourceInventory)));
1194
+ }
1195
+ const codeStarted = Date.now();
1196
+ const codeFiles = await collectCodeFiles(workspaceRoot, contract, input.sourceInventory);
1197
+ (0, validationProfiling_1.emitValidationProfile)('data/query risk analyzer code scan', Date.now() - codeStarted, [
1198
+ { name: 'files', value: codeFiles.length },
1199
+ ]);
1200
+ findings.push(...detectCodeQueryRiskFindings(codeFiles, config, input.astCache).filter((finding) => !(disabledRuleIds?.has(finding.id) ?? false)));
1201
+ return dedupeFindings(findings);
1202
+ }
1203
+ //# sourceMappingURL=dataQueryRiskDetection.js.map