projscan 4.4.0 → 4.6.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 (611) hide show
  1. package/README.md +36 -12
  2. package/dist/analyzers/pythonDependencyRiskCheck.js +1 -1
  3. package/dist/analyzers/pythonDependencyRiskCheck.js.map +1 -1
  4. package/dist/cli/_shared.d.ts +4 -5
  5. package/dist/cli/_shared.js +41 -188
  6. package/dist/cli/_shared.js.map +1 -1
  7. package/dist/cli/architectureLayers.d.ts +2 -0
  8. package/dist/cli/architectureLayers.js +112 -0
  9. package/dist/cli/architectureLayers.js.map +1 -0
  10. package/dist/cli/bannerDisplay.d.ts +9 -0
  11. package/dist/cli/bannerDisplay.js +18 -0
  12. package/dist/cli/bannerDisplay.js.map +1 -0
  13. package/dist/cli/changedIssueFilter.d.ts +10 -0
  14. package/dist/cli/changedIssueFilter.js +24 -0
  15. package/dist/cli/changedIssueFilter.js.map +1 -0
  16. package/dist/cli/commandPath.d.ts +2 -0
  17. package/dist/cli/commandPath.js +12 -0
  18. package/dist/cli/commandPath.js.map +1 -0
  19. package/dist/cli/commands/agentBrief.js +11 -0
  20. package/dist/cli/commands/agentBrief.js.map +1 -1
  21. package/dist/cli/commands/analyze.js +17 -6
  22. package/dist/cli/commands/analyze.js.map +1 -1
  23. package/dist/cli/commands/ci.js +15 -3
  24. package/dist/cli/commands/ci.js.map +1 -1
  25. package/dist/cli/commands/coordinate.js +27 -0
  26. package/dist/cli/commands/coordinate.js.map +1 -1
  27. package/dist/cli/commands/doctor.js +16 -4
  28. package/dist/cli/commands/doctor.js.map +1 -1
  29. package/dist/cli/commands/upgrade.js +1 -1
  30. package/dist/cli/commands/upgrade.js.map +1 -1
  31. package/dist/cli/pluginReporter.d.ts +14 -0
  32. package/dist/cli/pluginReporter.js +46 -0
  33. package/dist/cli/pluginReporter.js.map +1 -0
  34. package/dist/cli/projectConfig.d.ts +12 -0
  35. package/dist/cli/projectConfig.js +41 -0
  36. package/dist/cli/projectConfig.js.map +1 -0
  37. package/dist/cli/treeSlice.d.ts +3 -0
  38. package/dist/cli/treeSlice.js +12 -0
  39. package/dist/cli/treeSlice.js.map +1 -0
  40. package/dist/core/ast.d.ts +4 -76
  41. package/dist/core/ast.js +11 -630
  42. package/dist/core/ast.js.map +1 -1
  43. package/dist/core/astBodySignals.d.ts +17 -0
  44. package/dist/core/astBodySignals.js +107 -0
  45. package/dist/core/astBodySignals.js.map +1 -0
  46. package/dist/core/astFunctionCollector.d.ts +11 -0
  47. package/dist/core/astFunctionCollector.js +140 -0
  48. package/dist/core/astFunctionCollector.js.map +1 -0
  49. package/dist/core/astFunctionNames.d.ts +2 -0
  50. package/dist/core/astFunctionNames.js +53 -0
  51. package/dist/core/astFunctionNames.js.map +1 -0
  52. package/dist/core/astFunctionNodes.d.ts +2 -0
  53. package/dist/core/astFunctionNodes.js +12 -0
  54. package/dist/core/astFunctionNodes.js.map +1 -0
  55. package/dist/core/astMembers.d.ts +13 -0
  56. package/dist/core/astMembers.js +116 -0
  57. package/dist/core/astMembers.js.map +1 -0
  58. package/dist/core/astModuleSignals.d.ts +3 -0
  59. package/dist/core/astModuleSignals.js +140 -0
  60. package/dist/core/astModuleSignals.js.map +1 -0
  61. package/dist/core/astParser.d.ts +11 -0
  62. package/dist/core/astParser.js +38 -0
  63. package/dist/core/astParser.js.map +1 -0
  64. package/dist/core/astProgramSignals.d.ts +11 -0
  65. package/dist/core/astProgramSignals.js +97 -0
  66. package/dist/core/astProgramSignals.js.map +1 -0
  67. package/dist/core/astTypes.d.ts +78 -0
  68. package/dist/core/astTypes.js +2 -0
  69. package/dist/core/astTypes.js.map +1 -0
  70. package/dist/core/codeGraph.d.ts +3 -28
  71. package/dist/core/codeGraph.js +3 -231
  72. package/dist/core/codeGraph.js.map +1 -1
  73. package/dist/core/codeGraphFanMetrics.d.ts +17 -0
  74. package/dist/core/codeGraphFanMetrics.js +89 -0
  75. package/dist/core/codeGraphFanMetrics.js.map +1 -0
  76. package/dist/core/codeGraphIndexes.d.ts +23 -0
  77. package/dist/core/codeGraphIndexes.js +57 -0
  78. package/dist/core/codeGraphIndexes.js.map +1 -0
  79. package/dist/core/codeGraphParsing.d.ts +20 -0
  80. package/dist/core/codeGraphParsing.js +104 -0
  81. package/dist/core/codeGraphParsing.js.map +1 -0
  82. package/dist/core/codeGraphTypes.d.ts +28 -0
  83. package/dist/core/codeGraphTypes.js +2 -0
  84. package/dist/core/codeGraphTypes.js.map +1 -0
  85. package/dist/core/collisionDetector.d.ts +2 -0
  86. package/dist/core/collisionDetector.js +17 -12
  87. package/dist/core/collisionDetector.js.map +1 -1
  88. package/dist/core/coordination.d.ts +4 -2
  89. package/dist/core/coordination.js +40 -4
  90. package/dist/core/coordination.js.map +1 -1
  91. package/dist/core/coordinationEvidence.d.ts +32 -0
  92. package/dist/core/coordinationEvidence.js +101 -0
  93. package/dist/core/coordinationEvidence.js.map +1 -0
  94. package/dist/core/dataflow.js +2 -1
  95. package/dist/core/dataflow.js.map +1 -1
  96. package/dist/core/fileAccess.d.ts +16 -0
  97. package/dist/core/fileAccess.js +78 -0
  98. package/dist/core/fileAccess.js.map +1 -0
  99. package/dist/core/fileExportTypes.d.ts +2 -0
  100. package/dist/core/fileExportTypes.js +16 -0
  101. package/dist/core/fileExportTypes.js.map +1 -0
  102. package/dist/core/fileGraphMetrics.d.ts +4 -0
  103. package/dist/core/fileGraphMetrics.js +34 -0
  104. package/dist/core/fileGraphMetrics.js.map +1 -0
  105. package/dist/core/fileInspectionEvidence.d.ts +13 -0
  106. package/dist/core/fileInspectionEvidence.js +14 -0
  107. package/dist/core/fileInspectionEvidence.js.map +1 -0
  108. package/dist/core/fileInspectionGraph.d.ts +5 -0
  109. package/dist/core/fileInspectionGraph.js +29 -0
  110. package/dist/core/fileInspectionGraph.js.map +1 -0
  111. package/dist/core/fileInspector.d.ts +4 -4
  112. package/dist/core/fileInspector.js +28 -215
  113. package/dist/core/fileInspector.js.map +1 -1
  114. package/dist/core/fileIssues.d.ts +1 -0
  115. package/dist/core/fileIssues.js +18 -0
  116. package/dist/core/fileIssues.js.map +1 -0
  117. package/dist/core/filePurpose.d.ts +2 -0
  118. package/dist/core/filePurpose.js +61 -0
  119. package/dist/core/filePurpose.js.map +1 -0
  120. package/dist/core/frameworkExpressSources.d.ts +4 -0
  121. package/dist/core/frameworkExpressSources.js +95 -0
  122. package/dist/core/frameworkExpressSources.js.map +1 -0
  123. package/dist/core/frameworkFastifySources.d.ts +4 -0
  124. package/dist/core/frameworkFastifySources.js +74 -0
  125. package/dist/core/frameworkFastifySources.js.map +1 -0
  126. package/dist/core/frameworkHonoSources.d.ts +4 -0
  127. package/dist/core/frameworkHonoSources.js +73 -0
  128. package/dist/core/frameworkHonoSources.js.map +1 -0
  129. package/dist/core/frameworkKoaSources.d.ts +4 -0
  130. package/dist/core/frameworkKoaSources.js +81 -0
  131. package/dist/core/frameworkKoaSources.js.map +1 -0
  132. package/dist/core/frameworkNextRouteSources.d.ts +2 -0
  133. package/dist/core/frameworkNextRouteSources.js +55 -0
  134. package/dist/core/frameworkNextRouteSources.js.map +1 -0
  135. package/dist/core/frameworkSources.d.ts +1 -1
  136. package/dist/core/frameworkSources.js +16 -125
  137. package/dist/core/frameworkSources.js.map +1 -1
  138. package/dist/core/hotspotAnalyzer.d.ts +2 -18
  139. package/dist/core/hotspotAnalyzer.js +15 -477
  140. package/dist/core/hotspotAnalyzer.js.map +1 -1
  141. package/dist/core/hotspotBuilder.d.ts +14 -0
  142. package/dist/core/hotspotBuilder.js +70 -0
  143. package/dist/core/hotspotBuilder.js.map +1 -0
  144. package/dist/core/hotspotCandidates.d.ts +9 -0
  145. package/dist/core/hotspotCandidates.js +63 -0
  146. package/dist/core/hotspotCandidates.js.map +1 -0
  147. package/dist/core/hotspotGit.d.ts +10 -0
  148. package/dist/core/hotspotGit.js +152 -0
  149. package/dist/core/hotspotGit.js.map +1 -0
  150. package/dist/core/hotspotIssues.d.ts +2 -0
  151. package/dist/core/hotspotIssues.js +83 -0
  152. package/dist/core/hotspotIssues.js.map +1 -0
  153. package/dist/core/hotspotLines.d.ts +2 -0
  154. package/dist/core/hotspotLines.js +24 -0
  155. package/dist/core/hotspotLines.js.map +1 -0
  156. package/dist/core/hotspotMemory.d.ts +2 -0
  157. package/dist/core/hotspotMemory.js +21 -0
  158. package/dist/core/hotspotMemory.js.map +1 -0
  159. package/dist/core/hotspotRanking.d.ts +13 -0
  160. package/dist/core/hotspotRanking.js +44 -0
  161. package/dist/core/hotspotRanking.js.map +1 -0
  162. package/dist/core/hotspotScoring.d.ts +23 -0
  163. package/dist/core/hotspotScoring.js +128 -0
  164. package/dist/core/hotspotScoring.js.map +1 -0
  165. package/dist/core/indexCache.js +4 -1
  166. package/dist/core/indexCache.js.map +1 -1
  167. package/dist/core/intentRouter.d.ts +3 -16
  168. package/dist/core/intentRouter.js +5 -7348
  169. package/dist/core/intentRouter.js.map +1 -1
  170. package/dist/core/intentRouterCatalog.d.ts +16 -0
  171. package/dist/core/intentRouterCatalog.js +1692 -0
  172. package/dist/core/intentRouterCatalog.js.map +1 -0
  173. package/dist/core/intentRouterCoordinationSignals.d.ts +12 -0
  174. package/dist/core/intentRouterCoordinationSignals.js +111 -0
  175. package/dist/core/intentRouterCoordinationSignals.js.map +1 -0
  176. package/dist/core/intentRouterDependencySignals.d.ts +9 -0
  177. package/dist/core/intentRouterDependencySignals.js +226 -0
  178. package/dist/core/intentRouterDependencySignals.js.map +1 -0
  179. package/dist/core/intentRouterKeywordContext.d.ts +14 -0
  180. package/dist/core/intentRouterKeywordContext.js +2 -0
  181. package/dist/core/intentRouterKeywordContext.js.map +1 -0
  182. package/dist/core/intentRouterKeywordEarlyGuards.d.ts +2 -0
  183. package/dist/core/intentRouterKeywordEarlyGuards.js +127 -0
  184. package/dist/core/intentRouterKeywordEarlyGuards.js.map +1 -0
  185. package/dist/core/intentRouterKeywordMatches.d.ts +3 -0
  186. package/dist/core/intentRouterKeywordMatches.js +31 -0
  187. package/dist/core/intentRouterKeywordMatches.js.map +1 -0
  188. package/dist/core/intentRouterKeywordSearchGuards.d.ts +2 -0
  189. package/dist/core/intentRouterKeywordSearchGuards.js +239 -0
  190. package/dist/core/intentRouterKeywordSearchGuards.js.map +1 -0
  191. package/dist/core/intentRouterKeywordTargetGuards.d.ts +2 -0
  192. package/dist/core/intentRouterKeywordTargetGuards.js +191 -0
  193. package/dist/core/intentRouterKeywordTargetGuards.js.map +1 -0
  194. package/dist/core/intentRouterKeywordToolGuards.d.ts +2 -0
  195. package/dist/core/intentRouterKeywordToolGuards.js +133 -0
  196. package/dist/core/intentRouterKeywordToolGuards.js.map +1 -0
  197. package/dist/core/intentRouterKeywordWeights.d.ts +4 -0
  198. package/dist/core/intentRouterKeywordWeights.js +1184 -0
  199. package/dist/core/intentRouterKeywordWeights.js.map +1 -0
  200. package/dist/core/intentRouterPlanningSignals.d.ts +7 -0
  201. package/dist/core/intentRouterPlanningSignals.js +268 -0
  202. package/dist/core/intentRouterPlanningSignals.js.map +1 -0
  203. package/dist/core/intentRouterPrDiffSignals.d.ts +1 -0
  204. package/dist/core/intentRouterPrDiffSignals.js +41 -0
  205. package/dist/core/intentRouterPrDiffSignals.js.map +1 -0
  206. package/dist/core/intentRouterPreflightSignals.d.ts +3 -0
  207. package/dist/core/intentRouterPreflightSignals.js +54 -0
  208. package/dist/core/intentRouterPreflightSignals.js.map +1 -0
  209. package/dist/core/intentRouterRegressionKeywordMatches.d.ts +1 -0
  210. package/dist/core/intentRouterRegressionKeywordMatches.js +176 -0
  211. package/dist/core/intentRouterRegressionKeywordMatches.js.map +1 -0
  212. package/dist/core/intentRouterRegressionSignals.d.ts +10 -0
  213. package/dist/core/intentRouterRegressionSignals.js +207 -0
  214. package/dist/core/intentRouterRegressionSignals.js.map +1 -0
  215. package/dist/core/intentRouterReleaseSignals.d.ts +8 -0
  216. package/dist/core/intentRouterReleaseSignals.js +59 -0
  217. package/dist/core/intentRouterReleaseSignals.js.map +1 -0
  218. package/dist/core/intentRouterRepoSignals.d.ts +8 -0
  219. package/dist/core/intentRouterRepoSignals.js +226 -0
  220. package/dist/core/intentRouterRepoSignals.js.map +1 -0
  221. package/dist/core/intentRouterReviewSignals.d.ts +2 -0
  222. package/dist/core/intentRouterReviewSignals.js +109 -0
  223. package/dist/core/intentRouterReviewSignals.js.map +1 -0
  224. package/dist/core/intentRouterRiskSignals.d.ts +12 -0
  225. package/dist/core/intentRouterRiskSignals.js +242 -0
  226. package/dist/core/intentRouterRiskSignals.js.map +1 -0
  227. package/dist/core/intentRouterScoring.d.ts +9 -0
  228. package/dist/core/intentRouterScoring.js +40 -0
  229. package/dist/core/intentRouterScoring.js.map +1 -0
  230. package/dist/core/intentRouterSearchApiSignals.d.ts +1 -0
  231. package/dist/core/intentRouterSearchApiSignals.js +62 -0
  232. package/dist/core/intentRouterSearchApiSignals.js.map +1 -0
  233. package/dist/core/intentRouterSearchBackgroundSignals.d.ts +1 -0
  234. package/dist/core/intentRouterSearchBackgroundSignals.js +55 -0
  235. package/dist/core/intentRouterSearchBackgroundSignals.js.map +1 -0
  236. package/dist/core/intentRouterSearchCommunicationSignals.d.ts +1 -0
  237. package/dist/core/intentRouterSearchCommunicationSignals.js +74 -0
  238. package/dist/core/intentRouterSearchCommunicationSignals.js.map +1 -0
  239. package/dist/core/intentRouterSearchDataSignals.d.ts +2 -0
  240. package/dist/core/intentRouterSearchDataSignals.js +98 -0
  241. package/dist/core/intentRouterSearchDataSignals.js.map +1 -0
  242. package/dist/core/intentRouterSearchDomainSignals.d.ts +1 -0
  243. package/dist/core/intentRouterSearchDomainSignals.js +71 -0
  244. package/dist/core/intentRouterSearchDomainSignals.js.map +1 -0
  245. package/dist/core/intentRouterSearchInfraSignals.d.ts +1 -0
  246. package/dist/core/intentRouterSearchInfraSignals.js +79 -0
  247. package/dist/core/intentRouterSearchInfraSignals.js.map +1 -0
  248. package/dist/core/intentRouterSearchIntegrationSignals.d.ts +1 -0
  249. package/dist/core/intentRouterSearchIntegrationSignals.js +117 -0
  250. package/dist/core/intentRouterSearchIntegrationSignals.js.map +1 -0
  251. package/dist/core/intentRouterSearchLookupSignals.d.ts +10 -0
  252. package/dist/core/intentRouterSearchLookupSignals.js +310 -0
  253. package/dist/core/intentRouterSearchLookupSignals.js.map +1 -0
  254. package/dist/core/intentRouterSearchNavigationSignals.d.ts +1 -0
  255. package/dist/core/intentRouterSearchNavigationSignals.js +62 -0
  256. package/dist/core/intentRouterSearchNavigationSignals.js.map +1 -0
  257. package/dist/core/intentRouterSearchOwnershipSignals.d.ts +1 -0
  258. package/dist/core/intentRouterSearchOwnershipSignals.js +15 -0
  259. package/dist/core/intentRouterSearchOwnershipSignals.js.map +1 -0
  260. package/dist/core/intentRouterSearchPageSignals.d.ts +1 -0
  261. package/dist/core/intentRouterSearchPageSignals.js +84 -0
  262. package/dist/core/intentRouterSearchPageSignals.js.map +1 -0
  263. package/dist/core/intentRouterSearchReliabilitySignals.d.ts +1 -0
  264. package/dist/core/intentRouterSearchReliabilitySignals.js +94 -0
  265. package/dist/core/intentRouterSearchReliabilitySignals.js.map +1 -0
  266. package/dist/core/intentRouterSearchStateSignals.d.ts +1 -0
  267. package/dist/core/intentRouterSearchStateSignals.js +107 -0
  268. package/dist/core/intentRouterSearchStateSignals.js.map +1 -0
  269. package/dist/core/intentRouterSearchStyleSignals.d.ts +1 -0
  270. package/dist/core/intentRouterSearchStyleSignals.js +99 -0
  271. package/dist/core/intentRouterSearchStyleSignals.js.map +1 -0
  272. package/dist/core/intentRouterSearchTestSignals.d.ts +1 -0
  273. package/dist/core/intentRouterSearchTestSignals.js +34 -0
  274. package/dist/core/intentRouterSearchTestSignals.js.map +1 -0
  275. package/dist/core/intentRouterSearchToolingSignals.d.ts +1 -0
  276. package/dist/core/intentRouterSearchToolingSignals.js +106 -0
  277. package/dist/core/intentRouterSearchToolingSignals.js.map +1 -0
  278. package/dist/core/intentRouterSearchUiSignals.d.ts +1 -0
  279. package/dist/core/intentRouterSearchUiSignals.js +77 -0
  280. package/dist/core/intentRouterSearchUiSignals.js.map +1 -0
  281. package/dist/core/intentRouterSecuritySignals.d.ts +4 -0
  282. package/dist/core/intentRouterSecuritySignals.js +235 -0
  283. package/dist/core/intentRouterSecuritySignals.js.map +1 -0
  284. package/dist/core/intentRouterTargetSignals.d.ts +5 -0
  285. package/dist/core/intentRouterTargetSignals.js +76 -0
  286. package/dist/core/intentRouterTargetSignals.js.map +1 -0
  287. package/dist/core/intentRouterTokens.d.ts +1 -0
  288. package/dist/core/intentRouterTokens.js +36 -0
  289. package/dist/core/intentRouterTokens.js.map +1 -0
  290. package/dist/core/intentRouterUnderstandSignals.d.ts +1 -0
  291. package/dist/core/intentRouterUnderstandSignals.js +171 -0
  292. package/dist/core/intentRouterUnderstandSignals.js.map +1 -0
  293. package/dist/core/intentRouterVerificationSignals.d.ts +8 -0
  294. package/dist/core/intentRouterVerificationSignals.js +119 -0
  295. package/dist/core/intentRouterVerificationSignals.js.map +1 -0
  296. package/dist/core/intentRouterWorkSignals.d.ts +4 -0
  297. package/dist/core/intentRouterWorkSignals.js +157 -0
  298. package/dist/core/intentRouterWorkSignals.js.map +1 -0
  299. package/dist/core/languages/pythonLockfiles.d.ts +11 -0
  300. package/dist/core/languages/pythonLockfiles.js +206 -0
  301. package/dist/core/languages/pythonLockfiles.js.map +1 -0
  302. package/dist/core/languages/pythonManifestText.d.ts +7 -0
  303. package/dist/core/languages/pythonManifestText.js +25 -0
  304. package/dist/core/languages/pythonManifestText.js.map +1 -0
  305. package/dist/core/languages/pythonManifests.d.ts +6 -27
  306. package/dist/core/languages/pythonManifests.js +29 -303
  307. package/dist/core/languages/pythonManifests.js.map +1 -1
  308. package/dist/core/languages/pythonPep508.d.ts +4 -0
  309. package/dist/core/languages/pythonPep508.js +14 -0
  310. package/dist/core/languages/pythonPep508.js.map +1 -0
  311. package/dist/core/languages/pythonProjectEvidence.d.ts +2 -0
  312. package/dist/core/languages/pythonProjectEvidence.js +29 -0
  313. package/dist/core/languages/pythonProjectEvidence.js.map +1 -0
  314. package/dist/core/languages/pythonProjectTypes.d.ts +31 -0
  315. package/dist/core/languages/pythonProjectTypes.js +2 -0
  316. package/dist/core/languages/pythonProjectTypes.js.map +1 -0
  317. package/dist/core/languages/pythonPyproject.d.ts +2 -0
  318. package/dist/core/languages/pythonPyproject.js +160 -0
  319. package/dist/core/languages/pythonPyproject.js.map +1 -0
  320. package/dist/core/languages/pythonRequirements.d.ts +9 -0
  321. package/dist/core/languages/pythonRequirements.js +86 -0
  322. package/dist/core/languages/pythonRequirements.js.map +1 -0
  323. package/dist/core/languages/pythonRoots.d.ts +3 -0
  324. package/dist/core/languages/pythonRoots.js +83 -0
  325. package/dist/core/languages/pythonRoots.js.map +1 -0
  326. package/dist/core/languages/pythonSetuptools.d.ts +6 -0
  327. package/dist/core/languages/pythonSetuptools.js +58 -0
  328. package/dist/core/languages/pythonSetuptools.js.map +1 -0
  329. package/dist/core/prDiff.js +12 -0
  330. package/dist/core/prDiff.js.map +1 -1
  331. package/dist/core/preflight.d.ts +3 -3
  332. package/dist/core/preflight.js +28 -542
  333. package/dist/core/preflight.js.map +1 -1
  334. package/dist/core/preflightChangedFileReasons.d.ts +14 -0
  335. package/dist/core/preflightChangedFileReasons.js +75 -0
  336. package/dist/core/preflightChangedFileReasons.js.map +1 -0
  337. package/dist/core/preflightChangedFiles.d.ts +9 -0
  338. package/dist/core/preflightChangedFiles.js +34 -0
  339. package/dist/core/preflightChangedFiles.js.map +1 -0
  340. package/dist/core/preflightContextReasons.d.ts +17 -0
  341. package/dist/core/preflightContextReasons.js +73 -0
  342. package/dist/core/preflightContextReasons.js.map +1 -0
  343. package/dist/core/preflightEvidence.d.ts +34 -0
  344. package/dist/core/preflightEvidence.js +119 -0
  345. package/dist/core/preflightEvidence.js.map +1 -0
  346. package/dist/core/preflightInputs.d.ts +15 -0
  347. package/dist/core/preflightInputs.js +31 -0
  348. package/dist/core/preflightInputs.js.map +1 -0
  349. package/dist/core/preflightIssueReasons.d.ts +2 -0
  350. package/dist/core/preflightIssueReasons.js +39 -0
  351. package/dist/core/preflightIssueReasons.js.map +1 -0
  352. package/dist/core/preflightLocalEvidence.d.ts +12 -0
  353. package/dist/core/preflightLocalEvidence.js +36 -0
  354. package/dist/core/preflightLocalEvidence.js.map +1 -0
  355. package/dist/core/preflightReleaseScale.d.ts +28 -0
  356. package/dist/core/preflightReleaseScale.js +95 -0
  357. package/dist/core/preflightReleaseScale.js.map +1 -0
  358. package/dist/core/preflightRequiredChecks.d.ts +26 -0
  359. package/dist/core/preflightRequiredChecks.js +96 -0
  360. package/dist/core/preflightRequiredChecks.js.map +1 -0
  361. package/dist/core/preflightReviewEvidence.d.ts +16 -0
  362. package/dist/core/preflightReviewEvidence.js +31 -0
  363. package/dist/core/preflightReviewEvidence.js.map +1 -0
  364. package/dist/core/preflightReviewReasons.d.ts +15 -0
  365. package/dist/core/preflightReviewReasons.js +76 -0
  366. package/dist/core/preflightReviewReasons.js.map +1 -0
  367. package/dist/core/preflightSuggestedActions.d.ts +15 -0
  368. package/dist/core/preflightSuggestedActions.js +84 -0
  369. package/dist/core/preflightSuggestedActions.js.map +1 -0
  370. package/dist/core/preflightTruncation.d.ts +6 -0
  371. package/dist/core/preflightTruncation.js +7 -0
  372. package/dist/core/preflightTruncation.js.map +1 -0
  373. package/dist/core/preflightVerdict.d.ts +3 -0
  374. package/dist/core/preflightVerdict.js +17 -0
  375. package/dist/core/preflightVerdict.js.map +1 -0
  376. package/dist/core/releaseEvidence.d.ts +4 -3
  377. package/dist/core/releaseEvidence.js +12 -263
  378. package/dist/core/releaseEvidence.js.map +1 -1
  379. package/dist/core/releaseEvidenceBaseline.d.ts +2 -0
  380. package/dist/core/releaseEvidenceBaseline.js +28 -0
  381. package/dist/core/releaseEvidenceBaseline.js.map +1 -0
  382. package/dist/core/releaseEvidencePrSummary.d.ts +13 -0
  383. package/dist/core/releaseEvidencePrSummary.js +240 -0
  384. package/dist/core/releaseEvidencePrSummary.js.map +1 -0
  385. package/dist/core/releaseTrain.js +3 -317
  386. package/dist/core/releaseTrain.js.map +1 -1
  387. package/dist/core/releaseTrainFallbacks.d.ts +3 -0
  388. package/dist/core/releaseTrainFallbacks.js +318 -0
  389. package/dist/core/releaseTrainFallbacks.js.map +1 -0
  390. package/dist/core/reportScope.d.ts +24 -0
  391. package/dist/core/reportScope.js +234 -0
  392. package/dist/core/reportScope.js.map +1 -0
  393. package/dist/core/review.d.ts +2 -25
  394. package/dist/core/review.js +34 -1034
  395. package/dist/core/review.js.map +1 -1
  396. package/dist/core/reviewBaseSnapshot.d.ts +14 -0
  397. package/dist/core/reviewBaseSnapshot.js +41 -0
  398. package/dist/core/reviewBaseSnapshot.js.map +1 -0
  399. package/dist/core/reviewChangedFiles.d.ts +8 -0
  400. package/dist/core/reviewChangedFiles.js +63 -0
  401. package/dist/core/reviewChangedFiles.js.map +1 -0
  402. package/dist/core/reviewContractChanges.d.ts +5 -0
  403. package/dist/core/reviewContractChanges.js +114 -0
  404. package/dist/core/reviewContractChanges.js.map +1 -0
  405. package/dist/core/reviewCycles.d.ts +7 -0
  406. package/dist/core/reviewCycles.js +53 -0
  407. package/dist/core/reviewCycles.js.map +1 -0
  408. package/dist/core/reviewFindings.d.ts +17 -0
  409. package/dist/core/reviewFindings.js +49 -0
  410. package/dist/core/reviewFindings.js.map +1 -0
  411. package/dist/core/reviewFlowDiffs.d.ts +4 -0
  412. package/dist/core/reviewFlowDiffs.js +99 -0
  413. package/dist/core/reviewFlowDiffs.js.map +1 -0
  414. package/dist/core/reviewGit.d.ts +7 -0
  415. package/dist/core/reviewGit.js +45 -0
  416. package/dist/core/reviewGit.js.map +1 -0
  417. package/dist/core/reviewGraphEvidence.d.ts +3 -0
  418. package/dist/core/reviewGraphEvidence.js +55 -0
  419. package/dist/core/reviewGraphEvidence.js.map +1 -0
  420. package/dist/core/reviewHeadSnapshot.d.ts +8 -0
  421. package/dist/core/reviewHeadSnapshot.js +15 -0
  422. package/dist/core/reviewHeadSnapshot.js.map +1 -0
  423. package/dist/core/reviewIntent.d.ts +2 -0
  424. package/dist/core/reviewIntent.js +18 -0
  425. package/dist/core/reviewIntent.js.map +1 -0
  426. package/dist/core/reviewManifests.d.ts +12 -0
  427. package/dist/core/reviewManifests.js +124 -0
  428. package/dist/core/reviewManifests.js.map +1 -0
  429. package/dist/core/reviewNoChanges.d.ts +9 -0
  430. package/dist/core/reviewNoChanges.js +26 -0
  431. package/dist/core/reviewNoChanges.js.map +1 -0
  432. package/dist/core/reviewPackageScope.d.ts +4 -0
  433. package/dist/core/reviewPackageScope.js +24 -0
  434. package/dist/core/reviewPackageScope.js.map +1 -0
  435. package/dist/core/reviewRefs.d.ts +4 -0
  436. package/dist/core/reviewRefs.js +65 -0
  437. package/dist/core/reviewRefs.js.map +1 -0
  438. package/dist/core/reviewRiskyFunctions.d.ts +8 -0
  439. package/dist/core/reviewRiskyFunctions.js +83 -0
  440. package/dist/core/reviewRiskyFunctions.js.map +1 -0
  441. package/dist/core/reviewState.d.ts +21 -0
  442. package/dist/core/reviewState.js +96 -0
  443. package/dist/core/reviewState.js.map +1 -0
  444. package/dist/core/reviewTier.d.ts +18 -0
  445. package/dist/core/reviewTier.js +99 -0
  446. package/dist/core/reviewTier.js.map +1 -0
  447. package/dist/core/reviewVerdict.d.ts +9 -0
  448. package/dist/core/reviewVerdict.js +121 -0
  449. package/dist/core/reviewVerdict.js.map +1 -0
  450. package/dist/core/roadmapCatalog.d.ts +1 -0
  451. package/dist/core/roadmapCatalog.js +227 -7
  452. package/dist/core/roadmapCatalog.js.map +1 -1
  453. package/dist/core/start.d.ts +1 -1
  454. package/dist/core/start.js +26 -63
  455. package/dist/core/start.js.map +1 -1
  456. package/dist/core/startAdoptionGaps.d.ts +3 -0
  457. package/dist/core/startAdoptionGaps.js +12 -0
  458. package/dist/core/startAdoptionGaps.js.map +1 -0
  459. package/dist/core/startInputs.d.ts +31 -0
  460. package/dist/core/startInputs.js +27 -0
  461. package/dist/core/startInputs.js.map +1 -0
  462. package/dist/core/startMode.js +7 -1
  463. package/dist/core/startMode.js.map +1 -1
  464. package/dist/core/startNextActions.d.ts +13 -0
  465. package/dist/core/startNextActions.js +19 -0
  466. package/dist/core/startNextActions.js.map +1 -0
  467. package/dist/core/startReportBuilder.d.ts +25 -0
  468. package/dist/core/startReportBuilder.js +44 -0
  469. package/dist/core/startReportBuilder.js.map +1 -0
  470. package/dist/core/taint.js +14 -125
  471. package/dist/core/taint.js.map +1 -1
  472. package/dist/core/taintMatching.d.ts +11 -0
  473. package/dist/core/taintMatching.js +126 -0
  474. package/dist/core/taintMatching.js.map +1 -0
  475. package/dist/core/upgradePreview.d.ts +1 -12
  476. package/dist/core/upgradePreview.js +12 -160
  477. package/dist/core/upgradePreview.js.map +1 -1
  478. package/dist/core/upgradePreviewNpmEvidence.d.ts +19 -0
  479. package/dist/core/upgradePreviewNpmEvidence.js +164 -0
  480. package/dist/core/upgradePreviewNpmEvidence.js.map +1 -0
  481. package/dist/core/upgradePreviewPython.d.ts +2 -0
  482. package/dist/core/upgradePreviewPython.js +71 -0
  483. package/dist/core/upgradePreviewPython.js.map +1 -0
  484. package/dist/index.d.ts +6 -12
  485. package/dist/index.js +2 -3
  486. package/dist/index.js.map +1 -1
  487. package/dist/mcp/server.d.ts +3 -24
  488. package/dist/mcp/server.js +32 -414
  489. package/dist/mcp/server.js.map +1 -1
  490. package/dist/mcp/serverContext.d.ts +6 -0
  491. package/dist/mcp/serverContext.js +55 -0
  492. package/dist/mcp/serverContext.js.map +1 -0
  493. package/dist/mcp/serverDispatch.d.ts +39 -0
  494. package/dist/mcp/serverDispatch.js +74 -0
  495. package/dist/mcp/serverDispatch.js.map +1 -0
  496. package/dist/mcp/serverHandlers.d.ts +15 -0
  497. package/dist/mcp/serverHandlers.js +94 -0
  498. package/dist/mcp/serverHandlers.js.map +1 -0
  499. package/dist/mcp/serverLifecycle.d.ts +14 -0
  500. package/dist/mcp/serverLifecycle.js +65 -0
  501. package/dist/mcp/serverLifecycle.js.map +1 -0
  502. package/dist/mcp/serverMessage.d.ts +11 -0
  503. package/dist/mcp/serverMessage.js +37 -0
  504. package/dist/mcp/serverMessage.js.map +1 -0
  505. package/dist/mcp/serverSession.d.ts +6 -0
  506. package/dist/mcp/serverSession.js +77 -0
  507. package/dist/mcp/serverSession.js.map +1 -0
  508. package/dist/mcp/serverStdio.d.ts +7 -0
  509. package/dist/mcp/serverStdio.js +34 -0
  510. package/dist/mcp/serverStdio.js.map +1 -0
  511. package/dist/mcp/serverTypes.d.ts +18 -0
  512. package/dist/mcp/serverTypes.js +2 -0
  513. package/dist/mcp/serverTypes.js.map +1 -0
  514. package/dist/mcp/serverVersion.d.ts +1 -0
  515. package/dist/mcp/serverVersion.js +17 -0
  516. package/dist/mcp/serverVersion.js.map +1 -0
  517. package/dist/mcp/toolCatalog.d.ts +2 -0
  518. package/dist/mcp/toolCatalog.js +93 -0
  519. package/dist/mcp/toolCatalog.js.map +1 -0
  520. package/dist/mcp/tools/upgrade.d.ts +1 -1
  521. package/dist/mcp/tools/upgrade.js +2 -16
  522. package/dist/mcp/tools/upgrade.js.map +1 -1
  523. package/dist/mcp/tools.d.ts +2 -3
  524. package/dist/mcp/tools.js +5 -97
  525. package/dist/mcp/tools.js.map +1 -1
  526. package/dist/projscan-sbom.cdx.json +6 -6
  527. package/dist/reporters/consoleUpgradeReporter.js +24 -2
  528. package/dist/reporters/consoleUpgradeReporter.js.map +1 -1
  529. package/dist/reporters/htmlReporter.d.ts +3 -2
  530. package/dist/reporters/htmlReporter.js +14 -2
  531. package/dist/reporters/htmlReporter.js.map +1 -1
  532. package/dist/reporters/jsonReporter.d.ts +4 -3
  533. package/dist/reporters/jsonReporter.js +9 -4
  534. package/dist/reporters/jsonReporter.js.map +1 -1
  535. package/dist/reporters/markdownAnalysisReporter.d.ts +2 -1
  536. package/dist/reporters/markdownAnalysisReporter.js +8 -1
  537. package/dist/reporters/markdownAnalysisReporter.js.map +1 -1
  538. package/dist/reporters/markdownArchitectureReporter.d.ts +3 -0
  539. package/dist/reporters/markdownArchitectureReporter.js +33 -0
  540. package/dist/reporters/markdownArchitectureReporter.js.map +1 -0
  541. package/dist/reporters/markdownCouplingReporter.d.ts +2 -0
  542. package/dist/reporters/markdownCouplingReporter.js +43 -0
  543. package/dist/reporters/markdownCouplingReporter.js.map +1 -0
  544. package/dist/reporters/markdownCoverageReporter.d.ts +2 -0
  545. package/dist/reporters/markdownCoverageReporter.js +40 -0
  546. package/dist/reporters/markdownCoverageReporter.js.map +1 -0
  547. package/dist/reporters/markdownExplanationReporter.d.ts +2 -0
  548. package/dist/reporters/markdownExplanationReporter.js +37 -0
  549. package/dist/reporters/markdownExplanationReporter.js.map +1 -0
  550. package/dist/reporters/markdownHealthReporter.d.ts +4 -0
  551. package/dist/reporters/markdownHealthReporter.js +66 -0
  552. package/dist/reporters/markdownHealthReporter.js.map +1 -0
  553. package/dist/reporters/markdownHotspotReporter.d.ts +2 -0
  554. package/dist/reporters/markdownHotspotReporter.js +36 -0
  555. package/dist/reporters/markdownHotspotReporter.js.map +1 -0
  556. package/dist/reporters/markdownOutdatedReporter.d.ts +2 -0
  557. package/dist/reporters/markdownOutdatedReporter.js +31 -0
  558. package/dist/reporters/markdownOutdatedReporter.js.map +1 -0
  559. package/dist/reporters/markdownPrDiffReporter.d.ts +2 -0
  560. package/dist/reporters/markdownPrDiffReporter.js +63 -0
  561. package/dist/reporters/markdownPrDiffReporter.js.map +1 -0
  562. package/dist/reporters/markdownReporter.d.ts +9 -12
  563. package/dist/reporters/markdownReporter.js +9 -288
  564. package/dist/reporters/markdownReporter.js.map +1 -1
  565. package/dist/reporters/markdownUpgradeReporter.js +19 -4
  566. package/dist/reporters/markdownUpgradeReporter.js.map +1 -1
  567. package/dist/reporters/markdownWorkspaceReporter.d.ts +2 -0
  568. package/dist/reporters/markdownWorkspaceReporter.js +25 -0
  569. package/dist/reporters/markdownWorkspaceReporter.js.map +1 -0
  570. package/dist/reporters/sarifReporter.d.ts +6 -4
  571. package/dist/reporters/sarifReporter.js +8 -7
  572. package/dist/reporters/sarifReporter.js.map +1 -1
  573. package/dist/tool-manifest.json +4 -4
  574. package/dist/types/config.d.ts +10 -0
  575. package/dist/types/dependencyHealth.d.ts +12 -0
  576. package/dist/types.d.ts +34 -34
  577. package/dist/utils/config.d.ts +2 -8
  578. package/dist/utils/config.js +14 -180
  579. package/dist/utils/config.js.map +1 -1
  580. package/dist/utils/configBasics.d.ts +5 -0
  581. package/dist/utils/configBasics.js +21 -0
  582. package/dist/utils/configBasics.js.map +1 -0
  583. package/dist/utils/configHotspots.d.ts +2 -0
  584. package/dist/utils/configHotspots.js +15 -0
  585. package/dist/utils/configHotspots.js.map +1 -0
  586. package/dist/utils/configIssueRules.d.ts +8 -0
  587. package/dist/utils/configIssueRules.js +24 -0
  588. package/dist/utils/configIssueRules.js.map +1 -0
  589. package/dist/utils/configMonorepo.d.ts +2 -0
  590. package/dist/utils/configMonorepo.js +38 -0
  591. package/dist/utils/configMonorepo.js.map +1 -0
  592. package/dist/utils/configReportPolicies.d.ts +2 -0
  593. package/dist/utils/configReportPolicies.js +32 -0
  594. package/dist/utils/configReportPolicies.js.map +1 -0
  595. package/dist/utils/configScan.d.ts +2 -0
  596. package/dist/utils/configScan.js +15 -0
  597. package/dist/utils/configScan.js.map +1 -0
  598. package/dist/utils/configSeverity.d.ts +2 -0
  599. package/dist/utils/configSeverity.js +15 -0
  600. package/dist/utils/configSeverity.js.map +1 -0
  601. package/dist/utils/configSources.d.ts +5 -0
  602. package/dist/utils/configSources.js +55 -0
  603. package/dist/utils/configSources.js.map +1 -0
  604. package/dist/utils/configTaint.d.ts +2 -0
  605. package/dist/utils/configTaint.js +15 -0
  606. package/dist/utils/configTaint.js.map +1 -0
  607. package/docs/GUIDE.md +39 -4
  608. package/docs/ROADMAP.md +54 -21
  609. package/docs/examples/adoption-workflows.md +128 -0
  610. package/docs/examples/swarm-coordination.md +120 -0
  611. package/package.json +3 -1
@@ -1,25 +1,11 @@
1
- import { spawn } from 'node:child_process';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { scanRepository } from './repositoryScanner.js';
6
- import { collectIssues } from './issueEngine.js';
7
- import { buildCodeGraph } from './codeGraph.js';
8
- import { analyzeHotspots } from './hotspotAnalyzer.js';
9
- import { computeCoupling } from './couplingAnalyzer.js';
10
1
  import { diffGraphs } from './prDiff.js';
11
- import { computeTaint } from './taint.js';
12
- import { computeDataflow } from './dataflow.js';
13
- import { detectWorkspaces, filterFilesByPackage } from './monorepo.js';
14
- import { isReviewBlockingDataflowRisk, isReviewBlockingFlow } from './reviewDataflow.js';
15
- import { buildSemanticGraph } from './semanticGraph.js';
16
- import { annotateReviewWithIntent, appendIntentToSummary, parseIntent } from './intent.js';
17
- import { buildPublicExportFileSet, readEntrypointFiles, } from './reviewPublicSurface.js';
18
- import { loadConfig } from '../utils/config.js';
19
- const HIGH_CC_THRESHOLD = 10;
20
- const CC_JUMP_THRESHOLD = 5;
21
- const RISK_VERDICT_BLOCK_SCORE = 80;
22
- const RISK_VERDICT_REVIEW_SCORE = 40;
2
+ import { buildReviewBaseSnapshot } from './reviewBaseSnapshot.js';
3
+ import { buildReviewHeadSnapshot } from './reviewHeadSnapshot.js';
4
+ import { readManifests } from './reviewManifests.js';
5
+ import { buildReviewFindings } from './reviewFindings.js';
6
+ import { applyReviewIntent } from './reviewIntent.js';
7
+ import { resolveReviewState, unavailableReviewReport } from './reviewState.js';
8
+ export { selectReviewTier, shapeReviewForTier } from './reviewTier.js';
23
9
  /**
24
10
  * Compose a one-shot PR review. Builds head + base graphs (worktree dance),
25
11
  * joins the structural diff with hotspot risk scores, surfaces cycles
@@ -34,1031 +20,45 @@ const RISK_VERDICT_REVIEW_SCORE = 40;
34
20
  * ok - otherwise
35
21
  */
36
22
  export async function computeReview(rootPath, options = {}) {
37
- const isRepo = await isGitRepository(rootPath);
38
- if (!isRepo) {
39
- return unavailable('Not a git repository - PR review requires git history.', options);
40
- }
41
- const headRef = options.head ?? 'HEAD';
42
- const baseRef = options.base ?? (await pickDefaultBase(rootPath));
43
- const headSha = await resolveSha(rootPath, headRef);
44
- const baseSha = await resolveSha(rootPath, baseRef);
45
- if (!baseSha) {
46
- return unavailable(`Could not resolve base ref "${baseRef}".`, options, baseRef, headRef, headSha);
47
- }
48
- if (headSha && headSha === baseSha && (await isWorktreeClean(rootPath))) {
49
- const report = {
50
- available: true,
51
- base: { ref: baseRef, resolvedSha: baseSha },
52
- head: { ref: headRef, resolvedSha: headSha },
53
- prDiff: {
54
- available: true,
55
- base: { ref: baseRef, resolvedSha: baseSha },
56
- head: { ref: headRef, resolvedSha: headSha },
57
- filesAdded: [],
58
- filesRemoved: [],
59
- filesModified: [],
60
- totalFilesChanged: 0,
61
- },
62
- changedFiles: [],
63
- newCycles: [],
64
- riskyFunctions: [],
65
- dependencyChanges: [],
66
- contractChanges: [],
67
- newTaintFlows: [],
68
- newDataflowRisks: [],
69
- verdict: 'ok',
70
- summary: ['No structural changes detected between base and head.'],
71
- };
72
- applyIntent(report, options.intent);
73
- return report;
74
- }
75
- // Head-side data: scan + graph + issues + hotspots.
76
- const headScan = await scanRepository(rootPath);
77
- const headGraph = await buildCodeGraph(rootPath, headScan.files);
78
- const headIssues = await collectIssues(rootPath, headScan.files);
79
- const headHotspots = await analyzeHotspots(rootPath, headScan.files, headIssues, {
80
- limit: 200,
81
- graph: headGraph,
82
- });
83
- // Base-side: spin up a worktree, scan, build graph. Best-effort cleanup.
84
- const worktreeDir = await mkTempWorktreeDir();
85
- let baseGraph;
86
- let basePackageManifests;
87
- try {
88
- // `--` separator before positional args. baseSha is verified through
89
- // `rev-parse --verify ... ^{commit}` upstream so it's already sha-shaped,
90
- // but the separator is a defense-in-depth: if a future caller pipes a
91
- // user-supplied ref here without that verification, refs starting with
92
- // '-' (e.g. `--upload-pack=evil`) won't be parsed as flags.
93
- const addWorktree = await runGit(rootPath, [
94
- 'worktree',
95
- 'add',
96
- '--detach',
97
- '--',
98
- worktreeDir,
99
- baseSha,
100
- ]);
101
- if (addWorktree.code !== 0) {
102
- return unavailable(`Could not check out base ref "${baseRef}" for review: ${gitFailureSummary(addWorktree)}`, options, baseRef, headRef, headSha);
103
- }
104
- const baseScan = await scanRepository(worktreeDir);
105
- baseGraph = await buildCodeGraph(worktreeDir, baseScan.files);
106
- basePackageManifests = await readManifests(worktreeDir);
107
- }
108
- finally {
109
- await runGit(rootPath, ['worktree', 'remove', '--force', worktreeDir]).catch(() => { });
110
- await fs.rm(worktreeDir, { recursive: true, force: true }).catch(() => { });
111
- }
23
+ const state = await resolveReviewState(rootPath, options);
24
+ if (state.kind === 'unavailable')
25
+ return state.report;
26
+ if (state.kind === 'no-change') {
27
+ applyReviewIntent(state.report, options.intent);
28
+ return state.report;
29
+ }
30
+ const { baseRef, baseSha, headRef, headSha } = state;
31
+ const { graph: headGraph, hotspots: headHotspots } = await buildReviewHeadSnapshot(rootPath);
32
+ const baseSnapshot = await buildReviewBaseSnapshot(rootPath, baseRef, baseSha);
33
+ if (!baseSnapshot.available) {
34
+ return unavailableReviewReport(baseSnapshot.reason, options, baseRef, headRef, headSha, baseSha);
35
+ }
36
+ const baseGraph = baseSnapshot.graph;
37
+ const basePackageManifests = baseSnapshot.packageManifests;
112
38
  const headPackageManifests = await readManifests(rootPath);
113
39
  const prDiff = diffGraphs(baseRef, baseSha, headRef, headSha, baseGraph, headGraph);
114
- await scopePrDiffToPackage(rootPath, prDiff, options.package);
115
- const graphScopeFiles = await resolvePackageScopeFiles(rootPath, headGraph, options.package);
116
- // Build the per-file enriched view. Index head hotspots by path for O(1) lookup.
117
- const hotspotByPath = new Map();
118
- for (const h of headHotspots.hotspots)
119
- hotspotByPath.set(h.relativePath, h.riskScore);
120
- const changedFiles = [];
121
- for (const f of prDiff.filesAdded) {
122
- const headFile = headGraph.files.get(f);
123
- changedFiles.push({
124
- relativePath: f,
125
- status: 'added',
126
- riskScore: hotspotByPath.get(f) ?? null,
127
- cyclomaticComplexity: headFile?.parseOk ? headFile.cyclomaticComplexity : null,
128
- cyclomaticDelta: null,
129
- exportsAdded: headFile?.exports.length ?? 0,
130
- exportsRemoved: 0,
131
- importsAdded: headFile?.imports.length ?? 0,
132
- importsRemoved: 0,
133
- });
134
- }
135
- for (const f of prDiff.filesRemoved) {
136
- const baseFile = baseGraph.files.get(f);
137
- changedFiles.push({
138
- relativePath: f,
139
- status: 'removed',
140
- riskScore: null,
141
- cyclomaticComplexity: null,
142
- cyclomaticDelta: null,
143
- exportsAdded: 0,
144
- exportsRemoved: baseFile?.exports.length ?? 0,
145
- importsAdded: 0,
146
- importsRemoved: baseFile?.imports.length ?? 0,
147
- });
148
- }
149
- for (const f of prDiff.filesModified) {
150
- const headFile = headGraph.files.get(f.relativePath);
151
- changedFiles.push({
152
- relativePath: f.relativePath,
153
- status: 'modified',
154
- riskScore: hotspotByPath.get(f.relativePath) ?? null,
155
- cyclomaticComplexity: headFile?.parseOk ? headFile.cyclomaticComplexity : null,
156
- cyclomaticDelta: f.cyclomaticDelta,
157
- exportsAdded: f.exportsAdded.length,
158
- exportsRemoved: f.exportsRemoved.length,
159
- importsAdded: f.importsAdded.length,
160
- importsRemoved: f.importsRemoved.length,
161
- });
162
- }
163
- changedFiles.sort((a, b) => (b.riskScore ?? 0) - (a.riskScore ?? 0));
164
- // Cycles: compute on both sides; classify head cycles as new/expanded based
165
- // on overlap with base cycles.
166
- const headCoupling = computeCoupling(headGraph);
167
- const baseCoupling = computeCoupling(baseGraph);
168
- const newCycles = scopeCyclesToFiles(classifyNewCycles(baseCoupling.cycles, headCoupling.cycles, prDiff.filesAdded), graphScopeFiles);
169
- // Risky functions: compare per-file function lists between base and head.
170
- const riskyFunctions = findRiskyFunctions(baseGraph, headGraph, prDiff);
171
- // Dependency changes across root + workspaces.
172
- const dependencyChanges = scopeDependencyChanges(diffManifests(basePackageManifests, headPackageManifests), options.package);
173
- const contractChanges = buildContractChanges(prDiff, baseGraph, headGraph, basePackageManifests, headPackageManifests, options.package);
174
- // 1.6+ — taint flows newly introduced at head. A flow is "new" iff
175
- // (a) the (sourceFn, sinkFn, source, sink) flow didn't exist at base, AND
176
- // (b) at least one file along the flow's path is in the PR diff.
177
- // (b) prevents a base-graph parse failure from avalanching every
178
- // pre-existing head flow into a false "new" verdict. Project config
179
- // adds user-declared sources/sinks on top of the built-in defaults.
180
- const touchedFiles = new Set([
181
- ...prDiff.filesAdded,
182
- ...prDiff.filesRemoved,
183
- ...prDiff.filesModified.map((f) => f.relativePath),
184
- ]);
185
- const newTaintFlows = await computeNewTaintFlows(rootPath, baseGraph, headGraph, touchedFiles);
186
- const newDataflowRisks = await computeNewDataflowRisks(rootPath, baseGraph, headGraph, touchedFiles);
187
- const graphEvidence = buildReviewGraphEvidence(headGraph, touchedFiles, newDataflowRisks.length, graphScopeFiles);
188
- // Verdict.
189
- const { verdict, summary } = decideVerdict(changedFiles, newCycles, riskyFunctions, dependencyChanges, contractChanges, newTaintFlows, newDataflowRisks);
40
+ const findings = await buildReviewFindings({
41
+ rootPath,
42
+ packageName: options.package,
43
+ prDiff,
44
+ baseGraph,
45
+ headGraph,
46
+ headHotspots,
47
+ basePackageManifests,
48
+ headPackageManifests,
49
+ });
190
50
  const report = {
191
51
  available: true,
192
52
  base: { ref: baseRef, resolvedSha: baseSha },
193
53
  head: { ref: headRef, resolvedSha: headSha },
194
54
  prDiff,
195
- changedFiles,
196
- newCycles,
197
- riskyFunctions,
198
- dependencyChanges,
199
- contractChanges,
200
- newTaintFlows,
201
- newDataflowRisks,
202
- graphEvidence,
203
- verdict,
204
- summary,
55
+ ...findings,
205
56
  };
206
57
  // 1.9+ — intent grounding. Parse the agent-supplied description,
207
58
  // annotate each finding with an alignment label, and append a
208
59
  // small intent summary to the verdict bullets. Does NOT change the
209
60
  // verdict — verdict stays structural.
210
- applyIntent(report, options.intent);
61
+ applyReviewIntent(report, options.intent);
211
62
  return report;
212
63
  }
213
- async function resolvePackageScopeFiles(rootPath, graph, packageName) {
214
- if (!packageName)
215
- return undefined;
216
- const workspaces = await detectWorkspaces(rootPath);
217
- return new Set(filterFilesByPackage(workspaces, packageName, [...graph.files.keys()]));
218
- }
219
- async function scopePrDiffToPackage(rootPath, prDiff, packageName) {
220
- if (!packageName)
221
- return;
222
- const workspaces = await detectWorkspaces(rootPath);
223
- const allChangedPaths = [
224
- ...prDiff.filesAdded,
225
- ...prDiff.filesRemoved,
226
- ...prDiff.filesModified.map((file) => file.relativePath),
227
- ];
228
- const allowed = new Set(filterFilesByPackage(workspaces, packageName, allChangedPaths));
229
- prDiff.filesAdded = prDiff.filesAdded.filter((file) => allowed.has(file));
230
- prDiff.filesRemoved = prDiff.filesRemoved.filter((file) => allowed.has(file));
231
- prDiff.filesModified = prDiff.filesModified.filter((file) => allowed.has(file.relativePath));
232
- prDiff.totalFilesChanged =
233
- prDiff.filesAdded.length + prDiff.filesRemoved.length + prDiff.filesModified.length;
234
- }
235
- function scopeDependencyChanges(changes, packageName) {
236
- if (!packageName)
237
- return changes;
238
- return changes.filter((change) => change.workspace === packageName);
239
- }
240
- function applyIntent(report, rawIntent) {
241
- const intent = parseIntent(rawIntent);
242
- if (intent) {
243
- const analysis = annotateReviewWithIntent(report, intent);
244
- report.intent = {
245
- raw: intent.raw,
246
- action: intent.action,
247
- scopeTokens: intent.scopeTokens,
248
- };
249
- report.intentAnalysis = {
250
- totals: analysis.totals,
251
- notable: analysis.notable,
252
- };
253
- appendIntentToSummary(report.summary, analysis);
254
- }
255
- }
256
- async function computeNewTaintFlows(rootPath, baseGraph, headGraph, touchedFiles) {
257
- const { config } = await loadConfig(rootPath);
258
- const sources = config.taint?.sources ?? [];
259
- const sinks = config.taint?.sinks ?? [];
260
- const baseReport = computeTaint(baseGraph, { sources, sinks });
261
- const headReport = computeTaint(headGraph, { sources, sinks });
262
- const filterContext = { customSources: new Set(sources), customSinks: new Set(sinks) };
263
- if (!headReport.available)
264
- return [];
265
- const baseFlowKeys = new Set(baseReport.available ? baseReport.flows.map(reviewTaintFlowKey) : []);
266
- const out = [];
267
- for (const flow of headReport.flows) {
268
- const key = reviewTaintFlowKey(flow);
269
- if (baseFlowKeys.has(key))
270
- continue;
271
- // Restrict to flows the PR actually had a hand in: at least one file
272
- // along the path must be in the change set. A genuinely-introduced flow
273
- // necessarily touches a modified file (the new source-fn, sink-fn, or
274
- // intermediate hop), so this is a strict refinement — never drops a
275
- // real flow. Without it, a base-graph parse failure would surface every
276
- // pre-existing head flow as "new" and avalanche the verdict to block.
277
- if (!flow.files.some((f) => touchedFiles.has(f)))
278
- continue;
279
- if (!isReviewBlockingFlow(flow, filterContext))
280
- continue;
281
- out.push({
282
- sourceFn: flow.sourceFn,
283
- sinkFn: flow.sinkFn,
284
- source: flow.source,
285
- sink: flow.sink,
286
- pathLength: flow.path.length,
287
- files: flow.files,
288
- });
289
- }
290
- out.sort((a, b) => {
291
- if (a.pathLength !== b.pathLength)
292
- return a.pathLength - b.pathLength;
293
- return a.sourceFn.localeCompare(b.sourceFn);
294
- });
295
- return out;
296
- }
297
- function reviewTaintFlowKey(flow) {
298
- return `${flow.sourceFn}:${flow.sinkFn}:${flow.source}:${flow.sink}`;
299
- }
300
- function buildReviewGraphEvidence(graph, touchedFiles, dataflowRisks, scopeFiles) {
301
- const evidenceGraph = scopeFiles ? scopeGraphToFiles(graph, scopeFiles) : graph;
302
- const semantic = buildSemanticGraph(evidenceGraph, { maxNodes: 5_000, maxEdges: 10_000 });
303
- const changedFunctions = [...touchedFiles].reduce((count, file) => {
304
- return count + (evidenceGraph.files.get(file)?.functions?.length ?? 0);
305
- }, 0);
306
- return {
307
- schemaVersion: 1,
308
- changedFiles: touchedFiles.size,
309
- changedFunctions,
310
- totalFunctions: semantic.metrics.totalFunctions,
311
- totalPackages: semantic.metrics.totalPackages,
312
- totalCallEdges: semantic.edges.filter((edge) => edge.kind === 'calls').length,
313
- dataflowRisks,
314
- topPackages: topPackages(evidenceGraph),
315
- };
316
- }
317
- function scopeGraphToFiles(graph, files) {
318
- const scopedFiles = new Map([...graph.files.entries()].filter(([file]) => files.has(file)));
319
- const packageImporters = filterImporterMap(graph.packageImporters, files);
320
- const localImporters = new Map();
321
- for (const [target, importers] of graph.localImporters) {
322
- if (!files.has(target))
323
- continue;
324
- const scopedImporters = new Set([...importers].filter((file) => files.has(file)));
325
- if (scopedImporters.size > 0)
326
- localImporters.set(target, scopedImporters);
327
- }
328
- const symbolDefs = filterImporterMap(graph.symbolDefs, files);
329
- return {
330
- files: scopedFiles,
331
- packageImporters,
332
- localImporters,
333
- symbolDefs,
334
- scannedFiles: scopedFiles.size,
335
- };
336
- }
337
- function filterImporterMap(source, files) {
338
- const filtered = new Map();
339
- for (const [name, importers] of source) {
340
- const scopedImporters = new Set([...importers].filter((file) => files.has(file)));
341
- if (scopedImporters.size > 0)
342
- filtered.set(name, scopedImporters);
343
- }
344
- return filtered;
345
- }
346
- function topPackages(graph) {
347
- return [...graph.packageImporters.entries()]
348
- .map(([name, importers]) => ({ name, count: importers.size }))
349
- .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name))
350
- .slice(0, 5)
351
- .map((entry) => entry.name);
352
- }
353
- async function computeNewDataflowRisks(rootPath, baseGraph, headGraph, touchedFiles) {
354
- const { config } = await loadConfig(rootPath);
355
- const sources = config.taint?.sources ?? [];
356
- const sinks = config.taint?.sinks ?? [];
357
- const baseReport = computeDataflow(baseGraph, { sources, sinks });
358
- const headReport = computeDataflow(headGraph, { sources, sinks });
359
- const filterContext = { customSources: new Set(sources), customSinks: new Set(sinks) };
360
- if (!headReport.available)
361
- return [];
362
- const baseRiskKeys = new Set(baseReport.available ? baseReport.risks.map(reviewDataflowRiskKey) : []);
363
- const out = [];
364
- for (const risk of headReport.risks) {
365
- // Legacy taint flows already have their own stable review field. Keep
366
- // this additive review list focused on deeper 3.0 dataflow findings.
367
- if (risk.kind !== 'bridge')
368
- continue;
369
- const key = reviewDataflowRiskKey(risk);
370
- if (baseRiskKeys.has(key))
371
- continue;
372
- if (!risk.files.some((f) => touchedFiles.has(f)))
373
- continue;
374
- if (!isReviewBlockingDataflowRisk(risk, filterContext))
375
- continue;
376
- out.push({
377
- kind: risk.kind,
378
- sourceFn: risk.sourceFn,
379
- sinkFn: risk.sinkFn,
380
- bridgeFn: risk.bridgeFn,
381
- source: risk.source,
382
- sink: risk.sink,
383
- pathLength: risk.pathLength,
384
- files: risk.files,
385
- severity: risk.severity,
386
- confidence: risk.confidence,
387
- });
388
- }
389
- out.sort((a, b) => {
390
- if (a.pathLength !== b.pathLength)
391
- return a.pathLength - b.pathLength;
392
- return `${a.bridgeFn ?? ''}:${a.sourceFn}:${a.sinkFn}`.localeCompare(`${b.bridgeFn ?? ''}:${b.sourceFn}:${b.sinkFn}`);
393
- });
394
- return out;
395
- }
396
- function reviewDataflowRiskKey(risk) {
397
- return `${risk.kind}:${risk.bridgeFn ?? ''}:${risk.sourceFn}:${risk.sinkFn}:${risk.source}:${risk.sink}:${risk.files?.join('|') ?? ''}`;
398
- }
399
- // ── cycle classification ──────────────────────────────────
400
- function scopeCyclesToFiles(cycles, scopeFiles) {
401
- if (!scopeFiles)
402
- return cycles;
403
- return cycles.filter((cycle) => cycle.files.some((file) => scopeFiles.has(file)));
404
- }
405
- function classifyNewCycles(baseCycles, headCycles, filesAddedInPr) {
406
- const added = new Set(filesAddedInPr);
407
- const out = [];
408
- for (const head of headCycles) {
409
- const headSet = new Set(head.files);
410
- let bestOverlap = 0;
411
- for (const base of baseCycles) {
412
- let overlap = 0;
413
- for (const f of base.files)
414
- if (headSet.has(f))
415
- overlap++;
416
- if (overlap > bestOverlap)
417
- bestOverlap = overlap;
418
- }
419
- if (bestOverlap === 0) {
420
- out.push({ files: [...head.files].sort(), size: head.files.length, classification: 'new' });
421
- }
422
- else if (bestOverlap < head.files.length) {
423
- // cycle existed but grew
424
- out.push({
425
- files: [...head.files].sort(),
426
- size: head.files.length,
427
- classification: 'expanded',
428
- });
429
- }
430
- // bestOverlap === head.files.length means the cycle is identical at base.
431
- }
432
- // Bump cycles where any file is newly added to the very front.
433
- out.sort((a, b) => {
434
- const aTouchesAdded = a.files.some((f) => added.has(f)) ? 0 : 1;
435
- const bTouchesAdded = b.files.some((f) => added.has(f)) ? 0 : 1;
436
- if (aTouchesAdded !== bTouchesAdded)
437
- return aTouchesAdded - bTouchesAdded;
438
- return b.size - a.size;
439
- });
440
- return out;
441
- }
442
- // ── risky function detection ──────────────────────────────
443
- function findRiskyFunctions(baseGraph, headGraph, prDiff) {
444
- const out = [];
445
- for (const file of prDiff.filesAdded) {
446
- const head = headGraph.files.get(file);
447
- if (!head)
448
- continue;
449
- for (const fn of head.functions ?? []) {
450
- if (fn.cyclomaticComplexity >= HIGH_CC_THRESHOLD) {
451
- out.push({
452
- file,
453
- name: fn.name,
454
- line: fn.line,
455
- endLine: fn.endLine,
456
- cyclomaticComplexity: fn.cyclomaticComplexity,
457
- baseCc: null,
458
- reason: 'added',
459
- });
460
- }
461
- }
462
- }
463
- for (const f of prDiff.filesModified) {
464
- const head = headGraph.files.get(f.relativePath);
465
- const base = baseGraph.files.get(f.relativePath);
466
- if (!head || !base)
467
- continue;
468
- // Group BOTH sides by name; we need to know the count on each side, not
469
- // just on base. Many functions can share a name within a file — the
470
- // dominant case is anonymous arrow callbacks all named '<anonymous>' by
471
- // ast.ts. A flat Map<name,cc> would collapse them and compare every head
472
- // <anonymous> to the LAST base <anonymous>, producing false-positive
473
- // crossed-threshold / jumped rows for every file with ≥2 anonymous arrows
474
- // of differing CC. Even a 1-base / N-head asymmetry is unsafe: a head
475
- // arrow that's actually NEWLY ADDED would be paired with the single base
476
- // arrow's CC and reported as 'crossed-threshold' instead of 'added'.
477
- const baseByName = new Map();
478
- for (const fn of base.functions ?? []) {
479
- let list = baseByName.get(fn.name);
480
- if (!list) {
481
- list = [];
482
- baseByName.set(fn.name, list);
483
- }
484
- list.push(fn.cyclomaticComplexity);
485
- }
486
- const headCountByName = new Map();
487
- for (const fn of head.functions ?? []) {
488
- headCountByName.set(fn.name, (headCountByName.get(fn.name) ?? 0) + 1);
489
- }
490
- for (const fn of head.functions ?? []) {
491
- const candidates = baseByName.get(fn.name);
492
- if (!candidates || candidates.length === 0) {
493
- // Truly added (no base function with this name). Flag if high CC.
494
- if (fn.cyclomaticComplexity >= HIGH_CC_THRESHOLD) {
495
- out.push({
496
- file: f.relativePath,
497
- name: fn.name,
498
- line: fn.line,
499
- endLine: fn.endLine,
500
- cyclomaticComplexity: fn.cyclomaticComplexity,
501
- baseCc: null,
502
- reason: 'added',
503
- });
504
- }
505
- continue;
506
- }
507
- // Pair head-vs-base only when the name is unambiguous on BOTH sides
508
- // (1 ↔ 1). Any other ratio (1↔N, N↔1, N↔M) means we can't reliably
509
- // tell which head fn corresponds to which base fn — typically
510
- // <anonymous> arrow callbacks with no stable identity. We skip the
511
- // crossed-threshold / jumped checks in those cases. Regressions that
512
- // legitimately make one of these high-CC can still surface via other
513
- // signals (the file's overall risk score and `projscan_hotspots
514
- // view: functions`).
515
- if (candidates.length > 1 || (headCountByName.get(fn.name) ?? 0) > 1) {
516
- continue;
517
- }
518
- const baseCc = candidates[0];
519
- // Existed: flag if it newly crossed the threshold.
520
- if (baseCc < HIGH_CC_THRESHOLD && fn.cyclomaticComplexity >= HIGH_CC_THRESHOLD) {
521
- out.push({
522
- file: f.relativePath,
523
- name: fn.name,
524
- line: fn.line,
525
- endLine: fn.endLine,
526
- cyclomaticComplexity: fn.cyclomaticComplexity,
527
- baseCc,
528
- reason: 'crossed-threshold',
529
- });
530
- continue;
531
- }
532
- // Or: jumped by JUMP threshold even if both sides under HIGH_CC_THRESHOLD.
533
- if (fn.cyclomaticComplexity - baseCc >= CC_JUMP_THRESHOLD) {
534
- out.push({
535
- file: f.relativePath,
536
- name: fn.name,
537
- line: fn.line,
538
- endLine: fn.endLine,
539
- cyclomaticComplexity: fn.cyclomaticComplexity,
540
- baseCc,
541
- reason: 'jumped',
542
- });
543
- }
544
- }
545
- }
546
- out.sort((a, b) => b.cyclomaticComplexity - a.cyclomaticComplexity);
547
- return out;
548
- }
549
- async function readManifests(rootPath) {
550
- // Use detectWorkspaces to enumerate; if no workspaces just read the root.
551
- const { detectWorkspaces } = await import('./monorepo.js');
552
- const ws = await detectWorkspaces(rootPath);
553
- const out = new Map();
554
- const all = ws.kind === 'none' ? [] : ws.packages;
555
- if (all.length === 0) {
556
- const root = await readOneManifest(rootPath, 'package.json', '');
557
- if (root)
558
- out.set('package.json', root);
559
- return out;
560
- }
561
- for (const p of all) {
562
- const manifestRel = p.relativePath ? `${p.relativePath}/package.json` : 'package.json';
563
- const dir = path.join(rootPath, p.relativePath);
564
- const m = await readOneManifest(dir, manifestRel, p.name);
565
- if (m)
566
- out.set(manifestRel, m);
567
- }
568
- return out;
569
- }
570
- async function readOneManifest(dir, manifestFile, workspaceName) {
571
- const p = path.join(dir, 'package.json');
572
- let raw;
573
- try {
574
- raw = await fs.readFile(p, 'utf-8');
575
- }
576
- catch {
577
- return null;
578
- }
579
- let parsed;
580
- try {
581
- parsed = JSON.parse(raw);
582
- }
583
- catch {
584
- return null;
585
- }
586
- const entrypoints = readEntrypoints(parsed);
587
- return {
588
- workspace: workspaceName,
589
- manifestFile,
590
- dependencies: parsed.dependencies ?? {},
591
- devDependencies: parsed.devDependencies ?? {},
592
- entrypoints,
593
- entrypointFiles: readEntrypointFiles(parsed),
594
- };
595
- }
596
- function readEntrypoints(parsed) {
597
- const out = {};
598
- for (const field of ['main', 'module', 'types', 'exports', 'bin']) {
599
- const value = parsed[field];
600
- if (value === undefined)
601
- continue;
602
- out[field] = entrypointValue(value);
603
- }
604
- return out;
605
- }
606
- function entrypointValue(value) {
607
- return typeof value === 'string' ? value : JSON.stringify(value);
608
- }
609
- function diffManifests(base, head) {
610
- const out = [];
611
- const allManifests = new Set([...base.keys(), ...head.keys()]);
612
- for (const manifestFile of allManifests) {
613
- const b = base.get(manifestFile);
614
- const h = head.get(manifestFile);
615
- if (!b && !h)
616
- continue;
617
- const change = diffOneManifest(b, h, manifestFile);
618
- if (change.added.length || change.removed.length || change.bumped.length) {
619
- out.push(change);
620
- }
621
- }
622
- out.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
623
- return out;
624
- }
625
- function buildContractChanges(prDiff, baseGraph, headGraph, baseManifests, headManifests, packageName) {
626
- const changes = [];
627
- const scopedBaseManifests = scopeManifestsByPackage(baseManifests, packageName);
628
- const scopedHeadManifests = scopeManifestsByPackage(headManifests, packageName);
629
- const publicExportFiles = buildPublicExportFileSet(scopedBaseManifests.values(), scopedHeadManifests.values(), baseGraph, headGraph);
630
- for (const file of prDiff.filesAdded) {
631
- if (!publicExportFiles.has(file))
632
- continue;
633
- const entry = headGraph.files.get(file);
634
- for (const exp of entry?.exports ?? []) {
635
- changes.push(exportContractChange('export-added', file, exp.name));
636
- }
637
- }
638
- for (const file of prDiff.filesRemoved) {
639
- if (!publicExportFiles.has(file))
640
- continue;
641
- const entry = baseGraph.files.get(file);
642
- for (const exp of entry?.exports ?? []) {
643
- changes.push(exportContractChange('export-removed', file, exp.name));
644
- }
645
- }
646
- for (const file of prDiff.filesModified) {
647
- if (!publicExportFiles.has(file.relativePath))
648
- continue;
649
- for (const symbol of file.exportsAdded) {
650
- changes.push(exportContractChange('export-added', file.relativePath, symbol));
651
- }
652
- for (const symbol of file.exportsRemoved) {
653
- changes.push(exportContractChange('export-removed', file.relativePath, symbol));
654
- }
655
- for (const rename of file.exportsRenamed) {
656
- changes.push({
657
- kind: 'export-renamed',
658
- file: file.relativePath,
659
- symbol: rename.to,
660
- before: rename.from,
661
- after: rename.to,
662
- confidence: 'high',
663
- why: `Export "${rename.from}" was renamed to "${rename.to}" in ${file.relativePath}; downstream imports of the old name can fail at compile time or runtime.`,
664
- });
665
- }
666
- }
667
- changes.push(...entrypointContractChanges(scopedBaseManifests, scopedHeadManifests));
668
- return changes;
669
- }
670
- function scopeManifestsByPackage(manifests, packageName) {
671
- if (!packageName)
672
- return manifests;
673
- return new Map([...manifests].filter(([, manifest]) => manifest.workspace === packageName));
674
- }
675
- function exportContractChange(kind, file, symbol) {
676
- return {
677
- kind,
678
- file,
679
- symbol,
680
- confidence: 'high',
681
- why: kind === 'export-added'
682
- ? `Export "${symbol}" was added in ${file}; downstream code may start depending on a new public API.`
683
- : `Export "${symbol}" was removed from ${file}; downstream imports can fail at compile time or runtime.`,
684
- };
685
- }
686
- function entrypointContractChanges(base, head) {
687
- const out = [];
688
- const allManifests = new Set([...base.keys(), ...head.keys()]);
689
- for (const manifestFile of allManifests) {
690
- const baseEntrypoints = base.get(manifestFile)?.entrypoints ?? {};
691
- const headEntrypoints = head.get(manifestFile)?.entrypoints ?? {};
692
- const fields = new Set([
693
- ...Object.keys(baseEntrypoints),
694
- ...Object.keys(headEntrypoints),
695
- ]);
696
- for (const field of fields) {
697
- const before = baseEntrypoints[field];
698
- const after = headEntrypoints[field];
699
- if (before === after)
700
- continue;
701
- const kind = field === 'exports' ? 'public-export-changed' : 'entrypoint-changed';
702
- out.push({
703
- kind,
704
- file: manifestFile,
705
- symbol: field,
706
- ...(before !== undefined ? { before } : {}),
707
- ...(after !== undefined ? { after } : {}),
708
- confidence: 'high',
709
- why: kind === 'public-export-changed'
710
- ? `${manifestFile} package "exports" changed; consumers may resolve different public modules.`
711
- : `${manifestFile} package "${field}" changed from ${before ?? '<unset>'} to ${after ?? '<unset>'}; package consumers may load a different entrypoint.`,
712
- });
713
- }
714
- }
715
- return out.sort((a, b) => `${a.file}:${a.symbol ?? ''}`.localeCompare(`${b.file}:${b.symbol ?? ''}`));
716
- }
717
- function diffOneManifest(base, head, manifestFile) {
718
- const workspace = head?.workspace ?? base?.workspace ?? '';
719
- const baseDeps = base?.dependencies ?? {};
720
- const baseDev = base?.devDependencies ?? {};
721
- const headDeps = head?.dependencies ?? {};
722
- const headDev = head?.devDependencies ?? {};
723
- const added = [];
724
- const removed = [];
725
- const bumped = [];
726
- for (const [name, version] of Object.entries(headDeps)) {
727
- if (!(name in baseDeps))
728
- added.push({ name, version, kind: 'dep' });
729
- else if (baseDeps[name] !== version)
730
- bumped.push({ name, from: baseDeps[name], to: version, kind: 'dep' });
731
- }
732
- for (const [name, version] of Object.entries(baseDeps)) {
733
- if (!(name in headDeps))
734
- removed.push({ name, version, kind: 'dep' });
735
- }
736
- for (const [name, version] of Object.entries(headDev)) {
737
- if (!(name in baseDev))
738
- added.push({ name, version, kind: 'dev' });
739
- else if (baseDev[name] !== version)
740
- bumped.push({ name, from: baseDev[name], to: version, kind: 'dev' });
741
- }
742
- for (const [name, version] of Object.entries(baseDev)) {
743
- if (!(name in headDev))
744
- removed.push({ name, version, kind: 'dev' });
745
- }
746
- added.sort((a, b) => a.name.localeCompare(b.name));
747
- removed.sort((a, b) => a.name.localeCompare(b.name));
748
- bumped.sort((a, b) => a.name.localeCompare(b.name));
749
- return { workspace, manifestFile, added, removed, bumped };
750
- }
751
- // ── verdict ───────────────────────────────────────────────
752
- function decideVerdict(changedFiles, newCycles, riskyFunctions, depChanges, contractChanges, newTaintFlows, newDataflowRisks) {
753
- const summary = [];
754
- let verdict = 'ok';
755
- const maxRisk = Math.max(0, ...changedFiles.map((f) => f.riskScore ?? 0));
756
- if (maxRisk >= RISK_VERDICT_BLOCK_SCORE) {
757
- verdict = 'block';
758
- summary.push(`Maximum changed-file risk score is ${maxRisk.toFixed(1)} (>= ${RISK_VERDICT_BLOCK_SCORE}).`);
759
- }
760
- else if (maxRisk >= RISK_VERDICT_REVIEW_SCORE) {
761
- verdict = bumpTo(verdict, 'review');
762
- summary.push(`Maximum changed-file risk score is ${maxRisk.toFixed(1)} (>= ${RISK_VERDICT_REVIEW_SCORE}).`);
763
- }
764
- if (newCycles.length > 0) {
765
- const newOnly = newCycles.filter((c) => c.classification === 'new');
766
- if (newOnly.length > 0) {
767
- verdict = 'block';
768
- summary.push(`${newOnly.length} new import cycle(s) introduced.`);
769
- }
770
- else {
771
- verdict = bumpTo(verdict, 'review');
772
- summary.push(`${newCycles.length} cycle(s) expanded.`);
773
- }
774
- }
775
- if (riskyFunctions.length > 0) {
776
- verdict = bumpTo(verdict, 'review');
777
- summary.push(`${riskyFunctions.length} function(s) flagged: high CC added or jumped.`);
778
- }
779
- if (newTaintFlows.length > 0) {
780
- verdict = 'block';
781
- const sample = newTaintFlows
782
- .slice(0, 3)
783
- .map((f) => `${f.source}→${f.sink} (${f.sourceFn}${f.pathLength > 1 ? '…' : ''})`)
784
- .join(', ');
785
- summary.push(`${newTaintFlows.length} new taint flow(s) detected: ${sample}${newTaintFlows.length > 3 ? ', …' : ''}.`);
786
- }
787
- if (newDataflowRisks.length > 0) {
788
- verdict = 'block';
789
- const sample = newDataflowRisks
790
- .slice(0, 3)
791
- .map((risk) => `${risk.source}→${risk.sink} (${risk.bridgeFn ?? risk.sourceFn})`)
792
- .join(', ');
793
- summary.push(`${newDataflowRisks.length} new dataflow risk(s) detected: ${sample}${newDataflowRisks.length > 3 ? ', …' : ''}.`);
794
- }
795
- if (depChanges.length > 0) {
796
- const totals = depChanges.reduce((acc, d) => {
797
- acc.added += d.added.length;
798
- acc.removed += d.removed.length;
799
- acc.bumped += d.bumped.length;
800
- return acc;
801
- }, { added: 0, removed: 0, bumped: 0 });
802
- if (totals.added + totals.removed + totals.bumped > 0) {
803
- summary.push(`Dependency changes: +${totals.added} -${totals.removed} ~${totals.bumped}.`);
804
- }
805
- }
806
- if (isManualReleaseSignOffBlock(verdict, maxRisk, newCycles, riskyFunctions, contractChanges, newTaintFlows, newDataflowRisks)) {
807
- summary.push('Manual release sign-off required: review blocks on release-scale risk signals, not concrete cycle, risky-function, contract, taint, or dataflow defects.');
808
- }
809
- if (changedFiles.length === 0 && summary.length === 0) {
810
- summary.push('No structural changes detected between base and head.');
811
- }
812
- else if (verdict === 'ok' && summary.length === 0) {
813
- summary.push(`${changedFiles.length} file(s) changed; no risk signals.`);
814
- }
815
- return { verdict, summary };
816
- }
817
- function isManualReleaseSignOffBlock(verdict, maxRisk, newCycles, riskyFunctions, contractChanges, newTaintFlows, newDataflowRisks) {
818
- const concreteSignals = [
819
- newCycles,
820
- riskyFunctions,
821
- contractChanges,
822
- newTaintFlows,
823
- newDataflowRisks,
824
- ];
825
- return (verdict === 'block' &&
826
- maxRisk >= RISK_VERDICT_BLOCK_SCORE &&
827
- concreteSignals.every((signals) => signals.length === 0));
828
- }
829
- function bumpTo(current, target) {
830
- const order = { ok: 0, review: 1, block: 2 };
831
- return order[target] > order[current] ? target : current;
832
- }
833
- // ── git helpers (mirror prDiff.ts; kept private to keep coupling low) ──
834
- function unavailable(reason, options, baseRef = options.base ?? '', headRef = options.head ?? 'HEAD', headSha = null) {
835
- return {
836
- available: false,
837
- reason,
838
- base: { ref: baseRef, resolvedSha: null },
839
- head: { ref: headRef, resolvedSha: headSha },
840
- prDiff: {
841
- available: false,
842
- reason,
843
- base: { ref: baseRef, resolvedSha: null },
844
- head: { ref: headRef, resolvedSha: headSha },
845
- filesAdded: [],
846
- filesRemoved: [],
847
- filesModified: [],
848
- totalFilesChanged: 0,
849
- },
850
- changedFiles: [],
851
- newCycles: [],
852
- riskyFunctions: [],
853
- dependencyChanges: [],
854
- newTaintFlows: [],
855
- newDataflowRisks: [],
856
- verdict: 'ok',
857
- summary: [reason],
858
- };
859
- }
860
- async function isGitRepository(rootPath) {
861
- const { code } = await runGit(rootPath, ['rev-parse', '--is-inside-work-tree']).catch(() => ({
862
- code: 1,
863
- stdout: '',
864
- stderr: '',
865
- }));
866
- return code === 0;
867
- }
868
- async function isWorktreeClean(rootPath) {
869
- const unstaged = await runGit(rootPath, ['diff', '--quiet', '--ignore-submodules', '--']).catch(() => ({
870
- code: 1,
871
- stdout: '',
872
- stderr: '',
873
- }));
874
- if (unstaged.code !== 0)
875
- return false;
876
- const staged = await runGit(rootPath, [
877
- 'diff',
878
- '--cached',
879
- '--quiet',
880
- '--ignore-submodules',
881
- '--',
882
- ]).catch(() => ({
883
- code: 1,
884
- stdout: '',
885
- stderr: '',
886
- }));
887
- if (staged.code !== 0)
888
- return false;
889
- const untracked = await runGit(rootPath, ['ls-files', '--others', '--exclude-standard']).catch(() => ({
890
- code: 1,
891
- stdout: '',
892
- stderr: '',
893
- }));
894
- return untracked.code === 0 && untracked.stdout.trim().length === 0;
895
- }
896
- async function resolveSha(rootPath, ref) {
897
- const { code, stdout } = await runGit(rootPath, [
898
- 'rev-parse',
899
- '--verify',
900
- `${ref}^{commit}`,
901
- ]).catch(() => ({ code: 1, stdout: '', stderr: '' }));
902
- if (code !== 0)
903
- return null;
904
- const sha = stdout.trim();
905
- return sha || null;
906
- }
907
- async function pickDefaultBase(rootPath) {
908
- for (const candidate of ['origin/main', 'main', 'origin/master', 'master']) {
909
- if (await resolveSha(rootPath, candidate))
910
- return candidate;
911
- }
912
- return 'HEAD~1';
913
- }
914
- async function mkTempWorktreeDir() {
915
- return fs.mkdtemp(path.join(os.tmpdir(), 'projscan-review-'));
916
- }
917
- function gitFailureSummary(result) {
918
- const message = (result.stderr || result.stdout).trim().replace(/\s+/g, ' ');
919
- return message || `git exited with code ${result.code}`;
920
- }
921
- /**
922
- * 1.9+ — Default cap on any single `git` invocation made from the
923
- * review pipeline (worktree add/remove, rev-parse, etc.). Without it
924
- * a hung git operation (credential prompt, blocking hook, dead remote)
925
- * would hang the MCP server until kill. Mirror of prDiff.ts's same
926
- * default; kept as a sibling rather than shared because runGit here
927
- * is intentionally minimal and `prDiff.runGit` carries extra
928
- * timeoutMs override plumbing that this caller doesn't need.
929
- */
930
- const DEFAULT_GIT_TIMEOUT_MS = 30_000;
931
- function runGit(cwd, args) {
932
- return new Promise((resolve, reject) => {
933
- // Detach stdin so credential prompts / interactive hooks see EOF
934
- // and exit instead of waiting forever.
935
- const child = spawn('git', args, { cwd, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
936
- let stdout = '';
937
- let stderr = '';
938
- let settled = false;
939
- const timer = setTimeout(() => {
940
- if (settled)
941
- return;
942
- settled = true;
943
- child.kill('SIGKILL');
944
- reject(new Error(`git command timed out after ${DEFAULT_GIT_TIMEOUT_MS}ms`));
945
- }, DEFAULT_GIT_TIMEOUT_MS);
946
- child.stdout.on('data', (d) => (stdout += d.toString()));
947
- child.stderr.on('data', (d) => (stderr += d.toString()));
948
- child.on('error', (err) => {
949
- if (settled)
950
- return;
951
- settled = true;
952
- clearTimeout(timer);
953
- reject(err);
954
- });
955
- child.on('close', (code) => {
956
- if (settled)
957
- return;
958
- settled = true;
959
- clearTimeout(timer);
960
- resolve({ code: code ?? 1, stdout, stderr });
961
- });
962
- });
963
- }
964
- /**
965
- * 1.5+ — pick a review tier based on the caller's token budget.
966
- *
967
- * <3000 → 'verdict-only' (verdict + summary + totals)
968
- * <7000 → 'summary' (verdict + summary + top files / top cycles / etc.)
969
- * else → 'full' (everything)
970
- *
971
- * `0`, `undefined`, and any non-positive value all mean "no budget given"
972
- * — the caller wants the full report. The tier names are stable (clients
973
- * can read them off the response and key behavior off them).
974
- */
975
- export function selectReviewTier(maxCostTokens) {
976
- if (typeof maxCostTokens !== 'number' || !Number.isFinite(maxCostTokens) || maxCostTokens <= 0) {
977
- return 'full';
978
- }
979
- if (maxCostTokens < 3000)
980
- return 'verdict-only';
981
- if (maxCostTokens < 7000)
982
- return 'summary';
983
- return 'full';
984
- }
985
- /**
986
- * Reshape a full ReviewReport for the chosen tier. The caller passes a
987
- * fully-populated report from `computeReview`; we return a plain object
988
- * sized for the tier. Returning `Record<string, unknown>` (rather than
989
- * narrowing the ReviewReport type) keeps the type contract simple for
990
- * the dispatcher and avoids an over-engineered union.
991
- *
992
- * `unavailable` reports (no diff, missing base, etc.) pass through as-is
993
- * — there's nothing to shape; the verdict + reason already convey
994
- * everything the agent needs.
995
- */
996
- export function shapeReviewForTier(report, tier) {
997
- if (!report.available || tier === 'full') {
998
- return { ...report, tier };
999
- }
1000
- const filesChanged = report.changedFiles.length;
1001
- const cyclesAdded = report.newCycles.length;
1002
- const riskyFunctionsAdded = report.riskyFunctions.length;
1003
- const depsChanged = report.dependencyChanges.length;
1004
- const taintFlowsAdded = report.newTaintFlows?.length ?? 0;
1005
- const dataflowRisksAdded = report.newDataflowRisks?.length ?? 0;
1006
- const contractChanges = report.contractChanges?.length ?? 0;
1007
- const totals = {
1008
- filesChanged,
1009
- cyclesAdded,
1010
- riskyFunctionsAdded,
1011
- depsChanged,
1012
- taintFlowsAdded,
1013
- dataflowRisksAdded,
1014
- contractChanges,
1015
- };
1016
- if (tier === 'verdict-only') {
1017
- return {
1018
- available: report.available,
1019
- base: report.base,
1020
- head: report.head,
1021
- verdict: report.verdict,
1022
- summary: report.summary,
1023
- totals,
1024
- graphEvidence: report.graphEvidence,
1025
- tier,
1026
- };
1027
- }
1028
- // summary tier: keep the verdict, the top-N of each list, and aggregate totals.
1029
- // Drop per-file expansion lists in prDiff that bloat the response.
1030
- const TOP = 5;
1031
- const trimmedPrDiff = {
1032
- available: report.prDiff.available,
1033
- base: report.prDiff.base,
1034
- head: report.prDiff.head,
1035
- totalFilesChanged: report.prDiff.totalFilesChanged,
1036
- filesAdded: report.prDiff.filesAdded.slice(0, TOP),
1037
- filesRemoved: report.prDiff.filesRemoved.slice(0, TOP),
1038
- filesModified: report.prDiff.filesModified.slice(0, TOP).map((f) => ({
1039
- relativePath: f.relativePath,
1040
- // Keep the deltas; drop the heavy added/removed export & import arrays.
1041
- cyclomaticDelta: f.cyclomaticDelta,
1042
- fanInDelta: f.fanInDelta,
1043
- })),
1044
- };
1045
- return {
1046
- available: report.available,
1047
- base: report.base,
1048
- head: report.head,
1049
- prDiff: trimmedPrDiff,
1050
- changedFiles: report.changedFiles.slice(0, TOP),
1051
- newCycles: report.newCycles.slice(0, 3),
1052
- riskyFunctions: report.riskyFunctions.slice(0, 3),
1053
- dependencyChanges: report.dependencyChanges.slice(0, 3),
1054
- contractChanges: report.contractChanges?.slice(0, TOP) ?? [],
1055
- newTaintFlows: report.newTaintFlows?.slice(0, 5) ?? [],
1056
- newDataflowRisks: report.newDataflowRisks?.slice(0, 5) ?? [],
1057
- graphEvidence: report.graphEvidence,
1058
- verdict: report.verdict,
1059
- summary: report.summary,
1060
- totals,
1061
- tier,
1062
- };
1063
- }
1064
64
  //# sourceMappingURL=review.js.map