infraweaver 0.1.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 (524) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +244 -0
  3. package/dist/agents/claude.d.ts +113 -0
  4. package/dist/agents/claudePretoolGate.d.ts +137 -0
  5. package/dist/agents/dispatchMetrics.d.ts +77 -0
  6. package/dist/agents/gateServer.d.ts +7 -0
  7. package/dist/agents/index.d.ts +6 -0
  8. package/dist/agents/nativeFsDenies.d.ts +46 -0
  9. package/dist/agents/opencode.d.ts +284 -0
  10. package/dist/agents/opencodePlugin.d.ts +96 -0
  11. package/dist/agents/opencodeShared.d.ts +40 -0
  12. package/dist/agents/postRun.d.ts +126 -0
  13. package/dist/agents/reviewer.d.ts +41 -0
  14. package/dist/agents/sessionLabeler.d.ts +97 -0
  15. package/dist/agents/shared.d.ts +246 -0
  16. package/dist/agents/subagentModels.d.ts +19 -0
  17. package/dist/agents/tokenQuota.d.ts +118 -0
  18. package/dist/agents/writeGateSource.d.ts +22 -0
  19. package/dist/agents/writePolicy.d.ts +79 -0
  20. package/dist/brand.d.ts +67 -0
  21. package/dist/cli.mjs +247846 -0
  22. package/dist/external.d.ts +227 -0
  23. package/dist/i18n/scaffolding.d.ts +59 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +247061 -0
  26. package/dist/internal/index.d.ts +18 -0
  27. package/dist/internal.js +2398 -0
  28. package/dist/lifecycle.d.ts +2 -0
  29. package/dist/main.d.ts +8 -0
  30. package/dist/mcp/arkConfig.d.ts +1 -0
  31. package/dist/mcp/assess.d.ts +157 -0
  32. package/dist/mcp/capabilityContext.d.ts +71 -0
  33. package/dist/mcp/changeSummary.d.ts +52 -0
  34. package/dist/mcp/checkSuite.d.ts +27 -0
  35. package/dist/mcp/checkout.d.ts +92 -0
  36. package/dist/mcp/comment.d.ts +127 -0
  37. package/dist/mcp/commitInfo.d.ts +11 -0
  38. package/dist/mcp/crosswalk.d.ts +178 -0
  39. package/dist/mcp/crosswalkDigest.d.ts +1 -0
  40. package/dist/mcp/cyberEssentials.d.ts +24 -0
  41. package/dist/mcp/dashboard.d.ts +125 -0
  42. package/dist/mcp/dependencies.d.ts +12 -0
  43. package/dist/mcp/frameworks.d.ts +74 -0
  44. package/dist/mcp/geminiSanitizer.d.ts +28 -0
  45. package/dist/mcp/git.d.ts +60 -0
  46. package/dist/mcp/guardrails.d.ts +222 -0
  47. package/dist/mcp/issue.d.ts +20 -0
  48. package/dist/mcp/issueComments.d.ts +11 -0
  49. package/dist/mcp/issueEvents.d.ts +11 -0
  50. package/dist/mcp/issueInfo.d.ts +11 -0
  51. package/dist/mcp/labels.d.ts +14 -0
  52. package/dist/mcp/localContext.d.ts +26 -0
  53. package/dist/mcp/moduleExtraction.d.ts +79 -0
  54. package/dist/mcp/moduleTests.d.ts +106 -0
  55. package/dist/mcp/modules.d.ts +198 -0
  56. package/dist/mcp/output.d.ts +16 -0
  57. package/dist/mcp/pathSafety.d.ts +14 -0
  58. package/dist/mcp/policy.d.ts +50 -0
  59. package/dist/mcp/pr.d.ts +64 -0
  60. package/dist/mcp/prInfo.d.ts +11 -0
  61. package/dist/mcp/providerSchema.d.ts +52 -0
  62. package/dist/mcp/review.d.ts +212 -0
  63. package/dist/mcp/reviewComments.d.ts +245 -0
  64. package/dist/mcp/roots.d.ts +60 -0
  65. package/dist/mcp/scope.d.ts +26 -0
  66. package/dist/mcp/selectMode.d.ts +20 -0
  67. package/dist/mcp/server.d.ts +61 -0
  68. package/dist/mcp/shared.d.ts +62 -0
  69. package/dist/mcp/shell.d.ts +58 -0
  70. package/dist/mcp/staleFix.d.ts +138 -0
  71. package/dist/mcp/terraform/azurePrices.d.ts +8 -0
  72. package/dist/mcp/terraform/baseline.d.ts +57 -0
  73. package/dist/mcp/terraform/concernResult.d.ts +38 -0
  74. package/dist/mcp/terraform/cost.d.ts +55 -0
  75. package/dist/mcp/terraform/cspm.d.ts +80 -0
  76. package/dist/mcp/terraform/currency.d.ts +110 -0
  77. package/dist/mcp/terraform/decisions.d.ts +178 -0
  78. package/dist/mcp/terraform/eidas.d.ts +100 -0
  79. package/dist/mcp/terraform/evidence.d.ts +343 -0
  80. package/dist/mcp/terraform/findings.d.ts +76 -0
  81. package/dist/mcp/terraform/fixMemory.d.ts +81 -0
  82. package/dist/mcp/terraform/governance.d.ts +56 -0
  83. package/dist/mcp/terraform/hcl.d.ts +115 -0
  84. package/dist/mcp/terraform/iacLanguages.d.ts +120 -0
  85. package/dist/mcp/terraform/idleFloor.d.ts +78 -0
  86. package/dist/mcp/terraform/keyless.d.ts +68 -0
  87. package/dist/mcp/terraform/moduleDocs.d.ts +56 -0
  88. package/dist/mcp/terraform/nativeRules.d.ts +38 -0
  89. package/dist/mcp/terraform/nativeScan.d.ts +11 -0
  90. package/dist/mcp/terraform/noiseProfile.d.ts +100 -0
  91. package/dist/mcp/terraform/normalization.d.ts +52 -0
  92. package/dist/mcp/terraform/oscal.d.ts +103 -0
  93. package/dist/mcp/terraform/packs/azurerm-3-4.d.ts +2 -0
  94. package/dist/mcp/terraform/packs/cloudflare-4-5.d.ts +2 -0
  95. package/dist/mcp/terraform/paths.d.ts +28 -0
  96. package/dist/mcp/terraform/plan.d.ts +157 -0
  97. package/dist/mcp/terraform/planDrift.d.ts +199 -0
  98. package/dist/mcp/terraform/policyAuthor.d.ts +191 -0
  99. package/dist/mcp/terraform/prowlerOcsf.d.ts +136 -0
  100. package/dist/mcp/terraform/refactor.d.ts +183 -0
  101. package/dist/mcp/terraform/registry.d.ts +108 -0
  102. package/dist/mcp/terraform/risk.d.ts +41 -0
  103. package/dist/mcp/terraform/scanSession.d.ts +116 -0
  104. package/dist/mcp/terraform/scannerCache.d.ts +15 -0
  105. package/dist/mcp/terraform/scannerEgress.d.ts +72 -0
  106. package/dist/mcp/terraform/scannerJson.d.ts +13 -0
  107. package/dist/mcp/terraform/scanners.d.ts +205 -0
  108. package/dist/mcp/terraform/signing.d.ts +61 -0
  109. package/dist/mcp/terraform/stateLocking.d.ts +18 -0
  110. package/dist/mcp/terraform/subprocess.d.ts +43 -0
  111. package/dist/mcp/terraform/suppressions.d.ts +90 -0
  112. package/dist/mcp/terraform/taxonomy.d.ts +39 -0
  113. package/dist/mcp/terraform/tfquery.d.ts +47 -0
  114. package/dist/mcp/terraform/toolchainIntegrity.d.ts +82 -0
  115. package/dist/mcp/terraform/tools/authorPolicy.d.ts +59 -0
  116. package/dist/mcp/terraform/tools/detectPlanDrift.d.ts +20 -0
  117. package/dist/mcp/terraform/tools/emitCost.d.ts +11 -0
  118. package/dist/mcp/terraform/tools/emitSarif.d.ts +14 -0
  119. package/dist/mcp/terraform/tools/fixMemory.d.ts +19 -0
  120. package/dist/mcp/terraform/tools/infracostDiff.d.ts +22 -0
  121. package/dist/mcp/terraform/tools/ingestExternalFindings.d.ts +21 -0
  122. package/dist/mcp/terraform/tools/moduleLookup.d.ts +14 -0
  123. package/dist/mcp/terraform/tools/moduleSearch.d.ts +23 -0
  124. package/dist/mcp/terraform/tools/plan.d.ts +59 -0
  125. package/dist/mcp/terraform/tools/providerUpgrade.d.ts +55 -0
  126. package/dist/mcp/terraform/tools/readFindings.d.ts +17 -0
  127. package/dist/mcp/terraform/tools/scan.d.ts +114 -0
  128. package/dist/mcp/terraform/tools/validate.d.ts +31 -0
  129. package/dist/mcp/terraform/tools/verifyRemediation.d.ts +31 -0
  130. package/dist/mcp/terraform/tools/versionCurrency.d.ts +5 -0
  131. package/dist/mcp/terraform/tools.d.ts +16 -0
  132. package/dist/mcp/terraform/transformPacks.d.ts +267 -0
  133. package/dist/mcp/terraform/types.d.ts +226 -0
  134. package/dist/mcp/terraform/verification.d.ts +81 -0
  135. package/dist/mcp/terraform/vex.d.ts +115 -0
  136. package/dist/mcp/terraform/writeOnly.d.ts +67 -0
  137. package/dist/mcp/terraform.d.ts +51 -0
  138. package/dist/mcp/terratest.d.ts +85 -0
  139. package/dist/mcp/untrustedContent.d.ts +76 -0
  140. package/dist/mcp/upload.d.ts +8 -0
  141. package/dist/models.d.ts +181 -0
  142. package/dist/modes/address-reviews.d.ts +2 -0
  143. package/dist/modes/assess.d.ts +2 -0
  144. package/dist/modes/build.d.ts +2 -0
  145. package/dist/modes/compliance-audit.d.ts +2 -0
  146. package/dist/modes/cost-optimization.d.ts +2 -0
  147. package/dist/modes/drift-detect.d.ts +2 -0
  148. package/dist/modes/fix.d.ts +2 -0
  149. package/dist/modes/incremental-review.d.ts +2 -0
  150. package/dist/modes/index.d.ts +24 -0
  151. package/dist/modes/modernize-deprecated.d.ts +2 -0
  152. package/dist/modes/plan.d.ts +2 -0
  153. package/dist/modes/policy-gate.d.ts +2 -0
  154. package/dist/modes/prFormats.d.ts +3 -0
  155. package/dist/modes/refactor.d.ts +2 -0
  156. package/dist/modes/refresh-remediation.d.ts +2 -0
  157. package/dist/modes/remediate-and-refactor.d.ts +2 -0
  158. package/dist/modes/remediate.d.ts +2 -0
  159. package/dist/modes/resolve-conflicts.d.ts +2 -0
  160. package/dist/modes/review.d.ts +2 -0
  161. package/dist/modes/summarize-pr.d.ts +2 -0
  162. package/dist/modes/task.d.ts +2 -0
  163. package/dist/modes/terraform-code-review.d.ts +2 -0
  164. package/dist/modes/types.d.ts +9 -0
  165. package/dist/modes/update-dependencies.d.ts +2 -0
  166. package/dist/prep/index.d.ts +7 -0
  167. package/dist/prep/installNodeDependencies.d.ts +2 -0
  168. package/dist/prep/installPythonDependencies.d.ts +2 -0
  169. package/dist/prep/types.d.ts +31 -0
  170. package/dist/reviewQuality.d.ts +107 -0
  171. package/dist/skills/terraform-best-practices/SKILL.md +379 -0
  172. package/dist/toolState.d.ts +123 -0
  173. package/dist/utils/activity.d.ts +40 -0
  174. package/dist/utils/agent.d.ts +47 -0
  175. package/dist/utils/agentHangReport.d.ts +38 -0
  176. package/dist/utils/annotations.d.ts +17 -0
  177. package/dist/utils/apiFetch.d.ts +11 -0
  178. package/dist/utils/apiKeys.d.ts +48 -0
  179. package/dist/utils/apiUrl.d.ts +28 -0
  180. package/dist/utils/assets.d.ts +8 -0
  181. package/dist/utils/baseRefConfig.d.ts +121 -0
  182. package/dist/utils/billingErrors.d.ts +85 -0
  183. package/dist/utils/body.d.ts +34 -0
  184. package/dist/utils/buildInfraweaverFooter.d.ts +29 -0
  185. package/dist/utils/byokFallback.d.ts +85 -0
  186. package/dist/utils/changeImpact.d.ts +101 -0
  187. package/dist/utils/claudeSubscription.d.ts +30 -0
  188. package/dist/utils/cli.d.ts +10 -0
  189. package/dist/utils/cloudClient.d.ts +33 -0
  190. package/dist/utils/cloudFetchCore.d.ts +42 -0
  191. package/dist/utils/cloudReport.d.ts +114 -0
  192. package/dist/utils/codexHome.d.ts +29 -0
  193. package/dist/utils/codexOAuth.d.ts +60 -0
  194. package/dist/utils/diffCoverage.d.ts +63 -0
  195. package/dist/utils/env.d.ts +46 -0
  196. package/dist/utils/errorReport.d.ts +17 -0
  197. package/dist/utils/exitHandler.d.ts +8 -0
  198. package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
  199. package/dist/utils/gitAuth.d.ts +84 -0
  200. package/dist/utils/gitAuthServer.d.ts +24 -0
  201. package/dist/utils/github.d.ts +104 -0
  202. package/dist/utils/globals.d.ts +3 -0
  203. package/dist/utils/infraweaverConfig.d.ts +56 -0
  204. package/dist/utils/install.d.ts +37 -0
  205. package/dist/utils/instructions.d.ts +48 -0
  206. package/dist/utils/keylessOidc.d.ts +28 -0
  207. package/dist/utils/leapingComment.d.ts +11 -0
  208. package/dist/utils/learnings.d.ts +62 -0
  209. package/dist/utils/learningsTruncate.d.ts +25 -0
  210. package/dist/utils/lifecycle.d.ts +57 -0
  211. package/dist/utils/log.d.ts +111 -0
  212. package/dist/utils/modelResolver.d.ts +63 -0
  213. package/dist/utils/moduleFetch.d.ts +39 -0
  214. package/dist/utils/normalizeEnv.d.ts +30 -0
  215. package/dist/utils/openCodeModels.d.ts +11 -0
  216. package/dist/utils/openaiCompatible.d.ts +26 -0
  217. package/dist/utils/opentofuMcp.d.ts +50 -0
  218. package/dist/utils/overrides.d.ts +40 -0
  219. package/dist/utils/packageManager.d.ts +49 -0
  220. package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
  221. package/dist/utils/payload.d.ts +249 -0
  222. package/dist/utils/prSummary.d.ts +61 -0
  223. package/dist/utils/presets.d.ts +36 -0
  224. package/dist/utils/progressComment.d.ts +146 -0
  225. package/dist/utils/providerErrors.d.ts +31 -0
  226. package/dist/utils/proxyModel.d.ts +76 -0
  227. package/dist/utils/rangeDiff.d.ts +51 -0
  228. package/dist/utils/redact.d.ts +33 -0
  229. package/dist/utils/registryAuth.d.ts +52 -0
  230. package/dist/utils/remediationCommand.d.ts +58 -0
  231. package/dist/utils/retry.d.ts +38 -0
  232. package/dist/utils/reviewCleanup.d.ts +14 -0
  233. package/dist/utils/run.d.ts +9 -0
  234. package/dist/utils/runContext.d.ts +112 -0
  235. package/dist/utils/runContextData.d.ts +42 -0
  236. package/dist/utils/runErrorRenderer.d.ts +64 -0
  237. package/dist/utils/runLifecycle.d.ts +89 -0
  238. package/dist/utils/runStartupLog.d.ts +15 -0
  239. package/dist/utils/secrets.d.ts +34 -0
  240. package/dist/utils/setup.d.ts +90 -0
  241. package/dist/utils/shell.d.ts +44 -0
  242. package/dist/utils/skills.d.ts +10 -0
  243. package/dist/utils/subprocess.d.ts +81 -0
  244. package/dist/utils/terraformMcp.d.ts +46 -0
  245. package/dist/utils/time.d.ts +15 -0
  246. package/dist/utils/timer.d.ts +23 -0
  247. package/dist/utils/todoTracking.d.ts +16 -0
  248. package/dist/utils/token.d.ts +52 -0
  249. package/dist/utils/toolLicensing.d.ts +56 -0
  250. package/dist/utils/toolSelection.d.ts +73 -0
  251. package/dist/utils/toon.d.ts +16 -0
  252. package/dist/utils/version.d.ts +2 -0
  253. package/dist/utils/versioning.d.ts +7 -0
  254. package/dist/utils/vertex.d.ts +16 -0
  255. package/dist/utils/workflow.d.ts +13 -0
  256. package/package.json +132 -0
  257. package/src/agents/claude.ts +1588 -0
  258. package/src/agents/claudePretoolGate.ts +286 -0
  259. package/src/agents/dispatchMetrics.ts +162 -0
  260. package/src/agents/gateServer.ts +178 -0
  261. package/src/agents/index.ts +10 -0
  262. package/src/agents/nativeFsDenies.ts +164 -0
  263. package/src/agents/opencode.ts +1600 -0
  264. package/src/agents/opencodePlugin.ts +304 -0
  265. package/src/agents/opencodeShared.ts +134 -0
  266. package/src/agents/postRun.ts +615 -0
  267. package/src/agents/reviewer.ts +134 -0
  268. package/src/agents/sessionLabeler.ts +185 -0
  269. package/src/agents/shared.ts +377 -0
  270. package/src/agents/subagentModels.ts +40 -0
  271. package/src/agents/tokenQuota.ts +203 -0
  272. package/src/agents/writeGateSource.ts +148 -0
  273. package/src/agents/writePolicy.ts +132 -0
  274. package/src/brand.ts +84 -0
  275. package/src/cli.ts +117 -0
  276. package/src/commands/gha.ts +188 -0
  277. package/src/commands/mcp.ts +126 -0
  278. package/src/commands/verifyEvidence.ts +100 -0
  279. package/src/entry.ts +7 -0
  280. package/src/entryPost.ts +109 -0
  281. package/src/external.ts +308 -0
  282. package/src/i18n/scaffolding.ts +191 -0
  283. package/src/index.ts +7 -0
  284. package/src/internal/index.ts +74 -0
  285. package/src/lifecycle.ts +2 -0
  286. package/src/main.ts +815 -0
  287. package/src/mcp/__fixtures__/infraweaver-scratch-pr-49-review-3485940013.json +110 -0
  288. package/src/mcp/__fixtures__/infraweaver-scratch-pr-64-review-3531000326.json +14 -0
  289. package/src/mcp/__fixtures__/infraweaver-test-repo-pr-1.diff.json +67 -0
  290. package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
  291. package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
  292. package/src/mcp/arkConfig.ts +7 -0
  293. package/src/mcp/assess.ts +723 -0
  294. package/src/mcp/capabilityContext.ts +80 -0
  295. package/src/mcp/changeSummary.ts +145 -0
  296. package/src/mcp/checkSuite.ts +261 -0
  297. package/src/mcp/checkout.ts +1015 -0
  298. package/src/mcp/comment.ts +683 -0
  299. package/src/mcp/commitInfo.ts +57 -0
  300. package/src/mcp/crosswalk.ts +1253 -0
  301. package/src/mcp/crosswalkDigest.ts +5 -0
  302. package/src/mcp/cyberEssentials.ts +60 -0
  303. package/src/mcp/dashboard.ts +232 -0
  304. package/src/mcp/dependencies.ts +190 -0
  305. package/src/mcp/frameworks.ts +236 -0
  306. package/src/mcp/geminiSanitizer.ts +212 -0
  307. package/src/mcp/git.ts +1116 -0
  308. package/src/mcp/guardrails.ts +771 -0
  309. package/src/mcp/issue.ts +74 -0
  310. package/src/mcp/issueComments.ts +48 -0
  311. package/src/mcp/issueEvents.ts +100 -0
  312. package/src/mcp/issueInfo.ts +72 -0
  313. package/src/mcp/labels.ts +35 -0
  314. package/src/mcp/localContext.ts +65 -0
  315. package/src/mcp/localServer.ts +217 -0
  316. package/src/mcp/moduleExtraction.ts +368 -0
  317. package/src/mcp/moduleTests.ts +421 -0
  318. package/src/mcp/modules.ts +752 -0
  319. package/src/mcp/output.ts +71 -0
  320. package/src/mcp/pathSafety.ts +28 -0
  321. package/src/mcp/policy.ts +226 -0
  322. package/src/mcp/pr.ts +238 -0
  323. package/src/mcp/prInfo.ts +91 -0
  324. package/src/mcp/providerSchema.ts +175 -0
  325. package/src/mcp/review.ts +1081 -0
  326. package/src/mcp/reviewComments.ts +1137 -0
  327. package/src/mcp/roots.ts +217 -0
  328. package/src/mcp/scope.ts +82 -0
  329. package/src/mcp/selectMode.ts +207 -0
  330. package/src/mcp/server.ts +498 -0
  331. package/src/mcp/shared.ts +120 -0
  332. package/src/mcp/shell.ts +631 -0
  333. package/src/mcp/staleFix.ts +557 -0
  334. package/src/mcp/terraform/__snapshots__/moduleDocs.test.ts.snap +40 -0
  335. package/src/mcp/terraform/azurePrices.ts +38 -0
  336. package/src/mcp/terraform/baseline.ts +172 -0
  337. package/src/mcp/terraform/concernResult.ts +111 -0
  338. package/src/mcp/terraform/cost.ts +175 -0
  339. package/src/mcp/terraform/cspm.ts +326 -0
  340. package/src/mcp/terraform/currency.ts +354 -0
  341. package/src/mcp/terraform/decisions.ts +590 -0
  342. package/src/mcp/terraform/eidas.ts +134 -0
  343. package/src/mcp/terraform/evidence.ts +771 -0
  344. package/src/mcp/terraform/findings.ts +383 -0
  345. package/src/mcp/terraform/fixMemory.ts +253 -0
  346. package/src/mcp/terraform/governance.ts +231 -0
  347. package/src/mcp/terraform/hcl.ts +273 -0
  348. package/src/mcp/terraform/iacLanguages.ts +431 -0
  349. package/src/mcp/terraform/idleFloor.ts +304 -0
  350. package/src/mcp/terraform/keyless.ts +123 -0
  351. package/src/mcp/terraform/moduleDocs.ts +303 -0
  352. package/src/mcp/terraform/nativeRules.ts +152 -0
  353. package/src/mcp/terraform/nativeScan.ts +67 -0
  354. package/src/mcp/terraform/noiseProfile.ts +183 -0
  355. package/src/mcp/terraform/normalization.ts +265 -0
  356. package/src/mcp/terraform/oscal.ts +283 -0
  357. package/src/mcp/terraform/packs/azurerm-3-4.ts +382 -0
  358. package/src/mcp/terraform/packs/cloudflare-4-5.ts +114 -0
  359. package/src/mcp/terraform/paths.ts +59 -0
  360. package/src/mcp/terraform/plan.ts +377 -0
  361. package/src/mcp/terraform/planDrift.ts +520 -0
  362. package/src/mcp/terraform/policyAuthor.ts +886 -0
  363. package/src/mcp/terraform/prowlerOcsf.ts +380 -0
  364. package/src/mcp/terraform/refactor.ts +879 -0
  365. package/src/mcp/terraform/registry.ts +317 -0
  366. package/src/mcp/terraform/risk.ts +99 -0
  367. package/src/mcp/terraform/scanSession.ts +242 -0
  368. package/src/mcp/terraform/scannerCache.ts +86 -0
  369. package/src/mcp/terraform/scannerEgress.ts +141 -0
  370. package/src/mcp/terraform/scannerJson.ts +22 -0
  371. package/src/mcp/terraform/scanners.ts +1134 -0
  372. package/src/mcp/terraform/signing.ts +151 -0
  373. package/src/mcp/terraform/stateLocking.ts +106 -0
  374. package/src/mcp/terraform/subprocess.ts +102 -0
  375. package/src/mcp/terraform/suppressions.ts +551 -0
  376. package/src/mcp/terraform/taxonomy.ts +0 -0
  377. package/src/mcp/terraform/tfquery.ts +159 -0
  378. package/src/mcp/terraform/toolchainIntegrity.ts +151 -0
  379. package/src/mcp/terraform/tools/authorPolicy.ts +185 -0
  380. package/src/mcp/terraform/tools/detectPlanDrift.ts +279 -0
  381. package/src/mcp/terraform/tools/emitCost.ts +130 -0
  382. package/src/mcp/terraform/tools/emitSarif.ts +108 -0
  383. package/src/mcp/terraform/tools/fixMemory.ts +82 -0
  384. package/src/mcp/terraform/tools/infracostDiff.ts +110 -0
  385. package/src/mcp/terraform/tools/ingestExternalFindings.ts +259 -0
  386. package/src/mcp/terraform/tools/moduleLookup.ts +86 -0
  387. package/src/mcp/terraform/tools/moduleSearch.ts +46 -0
  388. package/src/mcp/terraform/tools/plan.ts +384 -0
  389. package/src/mcp/terraform/tools/providerUpgrade.ts +217 -0
  390. package/src/mcp/terraform/tools/readFindings.ts +138 -0
  391. package/src/mcp/terraform/tools/scan.ts +303 -0
  392. package/src/mcp/terraform/tools/validate.ts +160 -0
  393. package/src/mcp/terraform/tools/verifyRemediation.ts +190 -0
  394. package/src/mcp/terraform/tools/versionCurrency.ts +64 -0
  395. package/src/mcp/terraform/tools.ts +62 -0
  396. package/src/mcp/terraform/transformPacks.ts +594 -0
  397. package/src/mcp/terraform/types.ts +382 -0
  398. package/src/mcp/terraform/verification.ts +150 -0
  399. package/src/mcp/terraform/vex.ts +367 -0
  400. package/src/mcp/terraform/writeOnly.ts +252 -0
  401. package/src/mcp/terraform.ts +52 -0
  402. package/src/mcp/terratest.ts +215 -0
  403. package/src/mcp/untrustedContent.ts +126 -0
  404. package/src/mcp/upload.ts +123 -0
  405. package/src/models.ts +753 -0
  406. package/src/modes/address-reviews.ts +35 -0
  407. package/src/modes/assess.ts +27 -0
  408. package/src/modes/build.ts +86 -0
  409. package/src/modes/compliance-audit.ts +25 -0
  410. package/src/modes/cost-optimization.ts +36 -0
  411. package/src/modes/drift-detect.ts +31 -0
  412. package/src/modes/fix.ts +29 -0
  413. package/src/modes/incremental-review.ts +103 -0
  414. package/src/modes/index.ts +94 -0
  415. package/src/modes/modernize-deprecated.ts +36 -0
  416. package/src/modes/plan.ts +21 -0
  417. package/src/modes/policy-gate.ts +27 -0
  418. package/src/modes/prFormats.ts +325 -0
  419. package/src/modes/refactor.ts +74 -0
  420. package/src/modes/refresh-remediation.ts +40 -0
  421. package/src/modes/remediate-and-refactor.ts +42 -0
  422. package/src/modes/remediate.ts +78 -0
  423. package/src/modes/resolve-conflicts.ts +32 -0
  424. package/src/modes/review.ts +135 -0
  425. package/src/modes/summarize-pr.ts +42 -0
  426. package/src/modes/task.ts +26 -0
  427. package/src/modes/terraform-code-review.ts +68 -0
  428. package/src/modes/types.ts +17 -0
  429. package/src/modes/update-dependencies.ts +44 -0
  430. package/src/prep/index.ts +96 -0
  431. package/src/prep/installNodeDependencies.ts +251 -0
  432. package/src/prep/installPythonDependencies.ts +235 -0
  433. package/src/prep/types.ts +38 -0
  434. package/src/reviewQuality.ts +237 -0
  435. package/src/runCli.ts +335 -0
  436. package/src/skills/terraform-best-practices/SKILL.md +379 -0
  437. package/src/toolState.ts +241 -0
  438. package/src/utils/activity.ts +210 -0
  439. package/src/utils/agent.ts +245 -0
  440. package/src/utils/agentHangReport.ts +180 -0
  441. package/src/utils/annotations.ts +56 -0
  442. package/src/utils/apiFetch.ts +19 -0
  443. package/src/utils/apiKeys.ts +305 -0
  444. package/src/utils/apiUrl.ts +42 -0
  445. package/src/utils/assets.ts +123 -0
  446. package/src/utils/baseRefConfig.ts +254 -0
  447. package/src/utils/billingErrors.ts +210 -0
  448. package/src/utils/body.ts +168 -0
  449. package/src/utils/buildInfraweaverFooter.ts +104 -0
  450. package/src/utils/byokFallback.ts +137 -0
  451. package/src/utils/changeImpact.ts +330 -0
  452. package/src/utils/claudeSubscription.ts +93 -0
  453. package/src/utils/cli.ts +36 -0
  454. package/src/utils/cloudClient.ts +57 -0
  455. package/src/utils/cloudFetchCore.ts +139 -0
  456. package/src/utils/cloudReport.ts +316 -0
  457. package/src/utils/codexHome.ts +204 -0
  458. package/src/utils/codexOAuth.ts +154 -0
  459. package/src/utils/codexRefreshDetect.ts +36 -0
  460. package/src/utils/diffCoverage.ts +404 -0
  461. package/src/utils/env.ts +84 -0
  462. package/src/utils/errorReport.ts +94 -0
  463. package/src/utils/exitHandler.ts +35 -0
  464. package/src/utils/fixDoubleEscapedString.ts +9 -0
  465. package/src/utils/ghaCore.ts +13 -0
  466. package/src/utils/gitAuth.ts +268 -0
  467. package/src/utils/gitAuthServer.ts +186 -0
  468. package/src/utils/github.ts +676 -0
  469. package/src/utils/globals.ts +9 -0
  470. package/src/utils/humanEditCapture.ts +198 -0
  471. package/src/utils/infraweaverConfig.ts +202 -0
  472. package/src/utils/install.ts +279 -0
  473. package/src/utils/instructions.ts +640 -0
  474. package/src/utils/keylessOidc.ts +47 -0
  475. package/src/utils/leapingComment.ts +20 -0
  476. package/src/utils/learnings.ts +145 -0
  477. package/src/utils/learningsTruncate.ts +42 -0
  478. package/src/utils/lifecycle.ts +198 -0
  479. package/src/utils/log.ts +452 -0
  480. package/src/utils/modelResolver.ts +105 -0
  481. package/src/utils/moduleFetch.ts +138 -0
  482. package/src/utils/normalizeEnv.ts +106 -0
  483. package/src/utils/openCodeModels.ts +86 -0
  484. package/src/utils/openaiCompatible.ts +52 -0
  485. package/src/utils/opentofuMcp.ts +61 -0
  486. package/src/utils/overrides.ts +100 -0
  487. package/src/utils/packageManager.ts +257 -0
  488. package/src/utils/patchWorkflowRunFields.ts +173 -0
  489. package/src/utils/payload.ts +1206 -0
  490. package/src/utils/prSummary.ts +148 -0
  491. package/src/utils/presets.ts +105 -0
  492. package/src/utils/progressComment.ts +266 -0
  493. package/src/utils/providerErrors.ts +189 -0
  494. package/src/utils/proxyModel.ts +239 -0
  495. package/src/utils/rangeDiff.ts +182 -0
  496. package/src/utils/redact.ts +86 -0
  497. package/src/utils/registryAuth.ts +115 -0
  498. package/src/utils/remediationCommand.ts +144 -0
  499. package/src/utils/retry.ts +180 -0
  500. package/src/utils/reviewCleanup.ts +116 -0
  501. package/src/utils/run.ts +100 -0
  502. package/src/utils/runContext.ts +276 -0
  503. package/src/utils/runContextData.ts +121 -0
  504. package/src/utils/runErrorRenderer.ts +282 -0
  505. package/src/utils/runFixture.ts +91 -0
  506. package/src/utils/runLifecycle.ts +360 -0
  507. package/src/utils/runStartupLog.ts +67 -0
  508. package/src/utils/secrets.ts +208 -0
  509. package/src/utils/setup.ts +368 -0
  510. package/src/utils/shell.ts +132 -0
  511. package/src/utils/skills.ts +67 -0
  512. package/src/utils/subprocess.ts +474 -0
  513. package/src/utils/terraformMcp.ts +99 -0
  514. package/src/utils/time.ts +59 -0
  515. package/src/utils/timer.ts +72 -0
  516. package/src/utils/todoTracking.ts +168 -0
  517. package/src/utils/token.ts +294 -0
  518. package/src/utils/toolLicensing.ts +152 -0
  519. package/src/utils/toolSelection.ts +239 -0
  520. package/src/utils/toon.ts +76 -0
  521. package/src/utils/version.ts +10 -0
  522. package/src/utils/versioning.ts +44 -0
  523. package/src/utils/vertex.ts +94 -0
  524. package/src/utils/workflow.ts +25 -0
@@ -0,0 +1,326 @@
1
+ import { type } from "arktype";
2
+ import {
3
+ buildConcern,
4
+ type Concern,
5
+ type Severity,
6
+ } from "#app/mcp/terraform/types";
7
+ import { neutralizeOptional } from "#app/mcp/untrustedContent";
8
+
9
+ /**
10
+ * Ingest EXTERNAL findings — a CSPM / cloud-security alert feed the customer already
11
+ * produces (Wiz, Orca, Prisma, or any tool) — and tie each alert back to the exact
12
+ * Terraform resource in the repo, so a runtime cloud finding can enter the normal
13
+ * remediation loop WITHOUT Infraweaver ever holding a cloud credential. The customer's
14
+ * CSPM holds the creds and emits the alerts; we only consume its output.
15
+ *
16
+ * The crux is the mapping: a CSPM alert is keyed by a live cloud resource (an ARN /
17
+ * resource id), not a `file:line`. This module builds a line-aware index of the
18
+ * repo's `resource` blocks and correlates each alert to one of them, so the alert
19
+ * becomes a `Concern` with a real `location`. Alerts that can't be tied to a resource
20
+ * in code are reported separately (never silently dropped, never fabricated).
21
+ *
22
+ * Provenance: a mapped alert becomes a `reviewer`-source concern (the existing class
23
+ * for findings whose originating tool Infraweaver does not re-run). It is therefore
24
+ * honestly `not-code-verifiable` by the `✗→✓` re-scan — the same treatment a
25
+ * reviewer/tfsec finding gets — unless its misconfig class also fires a local scanner
26
+ * rule. Pure: no filesystem, no network (the tool wrapper supplies the file bodies).
27
+ */
28
+
29
+ // --- external-findings file schema -------------------------------------------
30
+
31
+ const ExternalResource = type({
32
+ /** A direct Terraform address, e.g. `aws_s3_bucket.logs` (module prefixes are
33
+ * tolerated — only the final `type.name` is matched). Highest-confidence key. */
34
+ "address?": "string",
35
+ "type?": "string",
36
+ "name?": "string",
37
+ /** A cloud resource id / name (e.g. the bucket name) used to correlate by value. */
38
+ "id?": "string",
39
+ /** A cloud ARN — correlated by looking for a resource whose identifier is a suffix. */
40
+ "arn?": "string",
41
+ /** Cloud TAG VALUES (lower-cased) offered as a last-resort correlation hint,
42
+ * e.g. a `Name` tag that also appears in the resource block. Lowest priority
43
+ * and deliberately strict: see the tag pass in `matchResource`. */
44
+ "tags?": "string[]",
45
+ });
46
+
47
+ const ExternalFinding = type({
48
+ /** The external tool's rule / policy id — provenance is preserved in `rule_id`. */
49
+ rule_id: "string",
50
+ /** The CSPM / tool name (wiz | orca | prisma | ...), prefixed onto the rule. */
51
+ "provider?": "string",
52
+ "severity?": "string",
53
+ "title?": "string",
54
+ "message?": "string",
55
+ "remediation?": "string",
56
+ "resource?": ExternalResource,
57
+ });
58
+
59
+ export const ExternalFindingsFile = type({
60
+ "schema?": "string",
61
+ findings: ExternalFinding.array(),
62
+ });
63
+
64
+ export type ExternalFinding = typeof ExternalFinding.infer;
65
+
66
+ /** True for a parsed object that looks like an external-findings feed (vs a SARIF or
67
+ * reviewer findings.json), so a dispatcher can route it. */
68
+ export function isExternalFindings(parsed: unknown): boolean {
69
+ if (typeof parsed !== "object" || parsed === null) return false;
70
+ const findings = (parsed as { findings?: unknown }).findings;
71
+ if (!Array.isArray(findings)) return false;
72
+ // A reviewer findings.json also has `findings`; disambiguate on the external shape
73
+ // (an explicit schema tag, or a `resource`/`provider`/`rule_id` on the first item).
74
+ const schema = (parsed as { schema?: unknown }).schema;
75
+ if (
76
+ typeof schema === "string" &&
77
+ schema.startsWith("infraweaver.external-findings")
78
+ )
79
+ return true;
80
+ const first = findings[0] as Record<string, unknown> | undefined;
81
+ if (!first) return false;
82
+ return "resource" in first || "provider" in first || "rule_id" in first;
83
+ }
84
+
85
+ // --- line-aware Terraform resource index -------------------------------------
86
+
87
+ export interface TfResourceEntry {
88
+ /** `type.name` — the Terraform address of the block. */
89
+ address: string;
90
+ type: string;
91
+ name: string;
92
+ file: string;
93
+ /** 1-based line of the `resource "type" "name" {` header. */
94
+ line: number;
95
+ /** String-literal values inside the block body (bucket names, tags, ids) — the
96
+ * values a CSPM alert's cloud id / ARN is correlated against. Lower-cased. */
97
+ identifiers: string[];
98
+ }
99
+
100
+ const RESOURCE_HEADER = /(^|\n)([ \t]*)resource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g;
101
+
102
+ /** Collect the string literals inside the block that opens at `openBraceIdx`, scanning
103
+ * to the matching close brace (approximate — good enough to correlate an id/ARN). */
104
+ function collectBlockIdentifiers(hcl: string, openBraceIdx: number): string[] {
105
+ const ids: string[] = [];
106
+ let depth = 0;
107
+ for (let i = openBraceIdx; i < hcl.length; i++) {
108
+ const c = hcl[i];
109
+ if (c === "{") depth++;
110
+ else if (c === "}") {
111
+ depth--;
112
+ if (depth === 0) break;
113
+ } else if (c === '"') {
114
+ let j = i + 1;
115
+ let value = "";
116
+ while (j < hcl.length && hcl[j] !== '"') {
117
+ if (hcl[j] === "\\") j++;
118
+ value += hcl[j] ?? "";
119
+ j++;
120
+ }
121
+ if (value) ids.push(value.toLowerCase());
122
+ i = j;
123
+ }
124
+ }
125
+ return ids;
126
+ }
127
+
128
+ /** Build a line-aware index of every `resource` block across the given `.tf` files. */
129
+ export function indexTfResources(
130
+ files: ReadonlyArray<{ file: string; hcl: string }>,
131
+ ): TfResourceEntry[] {
132
+ const index: TfResourceEntry[] = [];
133
+ for (const { file, hcl } of files) {
134
+ RESOURCE_HEADER.lastIndex = 0;
135
+ let m: RegExpExecArray | null;
136
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex-exec iteration
137
+ while ((m = RESOURCE_HEADER.exec(hcl)) !== null) {
138
+ const type = m[3];
139
+ const name = m[4];
140
+ if (type === undefined || name === undefined) continue;
141
+ const headerStart = m.index + (m[1] ? m[1].length : 0);
142
+ const line = hcl.slice(0, headerStart).split("\n").length;
143
+ const openBraceIdx = hcl.indexOf("{", m.index);
144
+ const identifiers =
145
+ openBraceIdx >= 0 ? collectBlockIdentifiers(hcl, openBraceIdx) : [];
146
+ index.push({
147
+ address: `${type}.${name}`,
148
+ type,
149
+ name,
150
+ file,
151
+ line,
152
+ identifiers,
153
+ });
154
+ }
155
+ }
156
+ return index;
157
+ }
158
+
159
+ // --- mapping -----------------------------------------------------------------
160
+
161
+ /** The final `type.name` of a possibly module-qualified address. */
162
+ function bareAddress(address: string): string {
163
+ const parts = address.split(".");
164
+ // `module.x.aws_s3_bucket.logs` → `aws_s3_bucket.logs`; `aws_s3_bucket.logs` → same.
165
+ return parts.length >= 2 ? parts.slice(-2).join(".") : address;
166
+ }
167
+
168
+ /** Correlate one external finding to a resource block, best key first. Returns the
169
+ * matched entry, or null when the alert can't be tied to code. */
170
+ export function matchResource(
171
+ finding: ExternalFinding,
172
+ index: TfResourceEntry[],
173
+ ): TfResourceEntry | null {
174
+ const r = finding.resource;
175
+ if (!r) return null;
176
+
177
+ // 1. Explicit Terraform address.
178
+ if (r.address) {
179
+ const want = bareAddress(r.address);
180
+ const hit = index.find((e) => e.address === want);
181
+ if (hit) return hit;
182
+ }
183
+
184
+ // 2. type + name.
185
+ if (r.type && r.name) {
186
+ const hit = index.find((e) => e.type === r.type && e.name === r.name);
187
+ if (hit) return hit;
188
+ }
189
+
190
+ // 3. type + a cloud id / ARN correlated against the block's identifier values.
191
+ // ARN matching is SEGMENT-exact (split on `:` and `/`), never substring — an
192
+ // unanchored `includes` would let an incidental literal like `"private"` in one
193
+ // block claim `arn:aws:s3:::private-bucket` and pin the alert to the wrong
194
+ // resource. Wrong-file mapping is worse than honest `unmapped`.
195
+ const idNeedle = r.id?.toLowerCase();
196
+ const arn = r.arn?.toLowerCase();
197
+ const arnSegments = arn ? new Set(arn.split(/[:/]/).filter(Boolean)) : null;
198
+ if (idNeedle || arnSegments) {
199
+ const candidates = r.type ? index.filter((e) => e.type === r.type) : index;
200
+ const hit = candidates.find((e) =>
201
+ e.identifiers.some(
202
+ (v) => (idNeedle && v === idNeedle) || arnSegments?.has(v) === true,
203
+ ),
204
+ );
205
+ if (hit) return hit;
206
+ }
207
+
208
+ // 4. Tag hints — lowest priority, for feeds (Prowler) whose alerts carry cloud
209
+ // tags but no ARN our source happens to contain. Callers must supply only
210
+ // IDENTITY-bearing tag values (a `Name` tag), never descriptive ones: if
211
+ // "production" happens to appear in exactly one block, a descriptive-tag pass
212
+ // would pin every finding tagged `env=production` to that one resource. A value
213
+ // must match a block's string literal EXACTLY, and the hits across ALL tags
214
+ // must resolve to exactly one resource — deciding per-tag would make the answer
215
+ // depend on label order when two tags each uniquely hit different resources.
216
+ // Any ambiguity yields `unmapped`, never a coin-flip between candidates.
217
+ const tags =
218
+ r.tags?.map((t) => t.toLowerCase()).filter((t) => t.length >= 4) ?? [];
219
+ if (tags.length > 0) {
220
+ const hits = new Set<TfResourceEntry>();
221
+ for (const tag of tags) {
222
+ for (const e of index) if (e.identifiers.includes(tag)) hits.add(e);
223
+ }
224
+ if (hits.size === 1) return [...hits][0] as TfResourceEntry;
225
+ }
226
+
227
+ return null;
228
+ }
229
+
230
+ /** Why an alert couldn't be mapped to a resource in code (for honest reporting). */
231
+ export interface UnmappedFinding {
232
+ rule_id: string;
233
+ provider: string | null;
234
+ resource: ExternalFinding["resource"] | null;
235
+ reason: string;
236
+ }
237
+
238
+ export interface MappedExternalFindings {
239
+ concerns: Concern[];
240
+ unmapped: UnmappedFinding[];
241
+ }
242
+
243
+ const SEVERITY_WORDS: Record<string, Severity> = {
244
+ critical: "critical",
245
+ high: "high",
246
+ medium: "medium",
247
+ moderate: "medium",
248
+ low: "low",
249
+ info: "info",
250
+ informational: "info",
251
+ none: "info",
252
+ };
253
+
254
+ /** Normalise an external severity string (word or numeric CVSS) to our scale. */
255
+ export function normalizeExternalSeverity(raw: string | undefined): Severity {
256
+ if (!raw) return "medium";
257
+ const word = SEVERITY_WORDS[raw.trim().toLowerCase()];
258
+ if (word) return word;
259
+ const num = Number(raw);
260
+ if (Number.isFinite(num)) {
261
+ if (num >= 9) return "critical";
262
+ if (num >= 7) return "high";
263
+ if (num >= 4) return "medium";
264
+ if (num > 0) return "low";
265
+ }
266
+ return "medium";
267
+ }
268
+
269
+ /** A safe bare-rule token: `<provider>-<external rule id>`, provider omitted when absent. */
270
+ function ruleToken(finding: ExternalFinding): string {
271
+ const provider = finding.provider?.trim().toLowerCase();
272
+ const base = finding.rule_id.trim();
273
+ const combined = provider ? `${provider}-${base}` : base;
274
+ // Keep it a clean rule token (no `:` so `reviewer:<rule>` stays unambiguous).
275
+ return combined.replace(/[:\s]+/g, "-");
276
+ }
277
+
278
+ /**
279
+ * Turn a batch of external findings into `reviewer`-source concerns by mapping each
280
+ * to its Terraform resource. Unmappable alerts are returned separately so the caller
281
+ * can report them honestly rather than drop them.
282
+ */
283
+ export function mapExternalFindings(
284
+ findings: ReadonlyArray<ExternalFinding>,
285
+ index: TfResourceEntry[],
286
+ ): MappedExternalFindings {
287
+ const concerns: Concern[] = [];
288
+ const unmapped: UnmappedFinding[] = [];
289
+ for (const finding of findings) {
290
+ const match = matchResource(finding, index);
291
+ if (!match) {
292
+ unmapped.push({
293
+ rule_id: finding.rule_id,
294
+ provider: finding.provider ?? null,
295
+ resource: finding.resource ?? null,
296
+ reason: finding.resource
297
+ ? "no Terraform resource in the repo matched this alert's cloud resource"
298
+ : "the alert carries no resource identifier to map",
299
+ });
300
+ continue;
301
+ }
302
+ // LLM01: `title` / `message` / `remediation` are free text from a
303
+ // third-party feed — the least-obvious attacker-influenced channel into the
304
+ // agent's context, and from there into PR bodies, review comments and
305
+ // SARIF, all of which render Markdown and some HTML. Neutralise the markup
306
+ // where the text ENTERS our data model, so every downstream surface is
307
+ // covered once instead of each having to remember. See mcp/untrustedContent.ts.
308
+ const evidence =
309
+ neutralizeOptional(finding.title) ??
310
+ neutralizeOptional(finding.message) ??
311
+ finding.rule_id;
312
+ concerns.push(
313
+ buildConcern({
314
+ source: "reviewer",
315
+ rule: ruleToken(finding),
316
+ file: match.file,
317
+ line: match.line,
318
+ severity: normalizeExternalSeverity(finding.severity),
319
+ category: "security",
320
+ evidence,
321
+ remediation_hint: neutralizeOptional(finding.remediation),
322
+ }),
323
+ );
324
+ }
325
+ return { concerns, unmapped };
326
+ }
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Version currency (provider currency / module upgrades).
3
+ *
4
+ * Answers one question the scanners can't: "is a NEWER version available?"
5
+ * tflint checks that versions are *pinned*; nothing we run checks that they're
6
+ * *current*. This module compares the workspace's pinned provider requirements
7
+ * and registry-module pins against the Terraform Registry's published versions.
8
+ *
9
+ * Deliberately NOT a scanner source: results are advisory intelligence the
10
+ * Remediate mode turns into `chore(deps)` upgrade PRs, not `Concern`s — the
11
+ * finding baseline stays scanner-owned (see docs/workplan/04-implementation-plan.md).
12
+ *
13
+ * Degrades green everywhere: a per-source lookup failure is reported on that
14
+ * row (`lookup: "error" | "not_found" | "unsupported_source"`), and only a
15
+ * fully-unreachable registry (every lookup failed) becomes a tool-level skip.
16
+ */
17
+
18
+ import semver from "semver";
19
+ import { collectModuleGraph, splitModuleSource } from "#app/mcp/modules";
20
+ import { collectProviderRequirements } from "#app/mcp/terraform/scanners";
21
+
22
+ export const DEFAULT_REGISTRY_BASE_URL = "https://registry.terraform.io";
23
+ const FETCH_TIMEOUT_MS = 5_000;
24
+
25
+ // --- constraint parsing (pure) ----------------------------------------------
26
+
27
+ /** one comparator of a Terraform constraint: `~> 5.0`, `>= 1.2`, `5.1.0`. */
28
+ const COMPARATOR_RE = /^(~>|>=|<=|!=|=|>|<)?\s*v?(\d+(?:\.\d+){0,2})$/;
29
+
30
+ /** pad a 1-3 part version to full semver: `5` → `5.0.0`, `5.1` → `5.1.0`. */
31
+ function padVersion(version: string): string {
32
+ const parts = version.split(".");
33
+ while (parts.length < 3) parts.push("0");
34
+ return parts.join(".");
35
+ }
36
+
37
+ /**
38
+ * Convert ONE Terraform version constraint string (comma-separated comparators,
39
+ * AND semantics) into an npm semver range, or null when any comparator is
40
+ * unparseable. `~>` is Terraform's pessimistic operator: the RIGHTMOST given
41
+ * component may float (`~> 5.0` → `>=5.0.0 <6.0.0`, `~> 5.1.2` → `>=5.1.2 <5.2.0`,
42
+ * `~> 5` → `>=5.0.0 <6.0.0`). `!=` comparators have no single-range npm
43
+ * equivalent and are skipped — acceptable here because the result only ranks
44
+ * candidate versions, it never selects what gets installed.
45
+ */
46
+ export function terraformConstraintToRange(constraint: string): string | null {
47
+ const parts = constraint
48
+ .split(",")
49
+ .map((p) => p.trim())
50
+ .filter(Boolean);
51
+ if (parts.length === 0) return null;
52
+
53
+ const ranges: string[] = [];
54
+ for (const part of parts) {
55
+ const m = part.match(COMPARATOR_RE);
56
+ if (!m) return null;
57
+ const op = m[1] ?? "=";
58
+ const raw = m[2];
59
+ if (raw === undefined) return null;
60
+ if (op === "!=") continue; // see docstring — skipped, never fatal
61
+ const full = padVersion(raw);
62
+ if (op === "~>") {
63
+ const given = raw.split(".").length;
64
+ // bump the component LEFT of the floating one: `~> 5.1.2` floats patch
65
+ // (< 5.2.0); `~> 5.0` and `~> 5` float minor/major-rightmost (< 6.0.0).
66
+ const upper =
67
+ given >= 3
68
+ ? `${semver.major(full)}.${semver.minor(full) + 1}.0`
69
+ : `${semver.major(full) + 1}.0.0`;
70
+ ranges.push(`>=${full} <${upper}`);
71
+ } else if (op === "=") {
72
+ ranges.push(full);
73
+ } else {
74
+ ranges.push(`${op}${full}`);
75
+ }
76
+ }
77
+ return ranges.length > 0 ? ranges.join(" ") : null;
78
+ }
79
+
80
+ // --- currency classification (pure) -----------------------------------------
81
+
82
+ export interface CurrencyVerdict {
83
+ /** newest stable version the registry publishes (null: nothing published). */
84
+ latest: string | null;
85
+ /** newest published version the written constraint admits (null: no
86
+ * constraint, unparseable constraint, or nothing satisfies). */
87
+ newestSatisfying: string | null;
88
+ /** true when the registry has a stable version the constraint does NOT admit
89
+ * — i.e. an upgrade PR is available. Always false without a constraint. */
90
+ outdated: boolean;
91
+ /** how many MAJORs the constraint's best version trails the latest by —
92
+ * >0 signals an interface-risk upgrade that must be `needs-human`. */
93
+ majorsBehind: number;
94
+ }
95
+
96
+ /** drop non-semver tokens and prereleases; registries publish plain versions
97
+ * but git refs (`v1.2.0-rc1`, branch names) also land here via module pins.
98
+ * Valid-but-prerelease versions must be dropped BEFORE any coercion — coercing
99
+ * `7.0.0-beta1` would strip the suffix and misreport a beta as stable. */
100
+ function stableVersions(available: string[]): string[] {
101
+ const out: string[] = [];
102
+ for (const raw of available) {
103
+ const exact = semver.valid(raw);
104
+ if (exact !== null) {
105
+ if (semver.prerelease(exact) === null) out.push(exact);
106
+ continue;
107
+ }
108
+ const coerced = semver.coerce(raw);
109
+ if (coerced !== null) out.push(coerced.version);
110
+ }
111
+ return out;
112
+ }
113
+
114
+ export function classifyCurrency(params: {
115
+ constraint: string | null;
116
+ available: string[];
117
+ }): CurrencyVerdict {
118
+ const versions = stableVersions(params.available);
119
+ const latest = versions.length > 0 ? (versions.sort(semver.rcompare)[0] ?? null) : null;
120
+ if (latest === null) {
121
+ return { latest: null, newestSatisfying: null, outdated: false, majorsBehind: 0 };
122
+ }
123
+ if (params.constraint === null) {
124
+ return { latest, newestSatisfying: null, outdated: false, majorsBehind: 0 };
125
+ }
126
+ const range = terraformConstraintToRange(params.constraint);
127
+ if (range === null) {
128
+ return { latest, newestSatisfying: null, outdated: false, majorsBehind: 0 };
129
+ }
130
+ const newestSatisfying = semver.maxSatisfying(versions, range);
131
+ const outdated = newestSatisfying === null || semver.lt(newestSatisfying, latest);
132
+ const majorsBehind = newestSatisfying
133
+ ? Math.max(0, semver.major(latest) - semver.major(newestSatisfying))
134
+ : Math.max(0, semver.major(latest));
135
+ return { latest, newestSatisfying, outdated, majorsBehind };
136
+ }
137
+
138
+ // --- registry lookups (I/O) --------------------------------------------------
139
+
140
+ export type LookupStatus = "ok" | "not_found" | "error" | "unsupported_source";
141
+
142
+ /** strip a leading default-registry host; reject other hosts (private/TFE
143
+ * registries use different auth + paths — out of scope for the public check). */
144
+ export function normalizeRegistryPath(base: string, expectedSegments: number): string | null {
145
+ const segments = base.split("/").filter(Boolean);
146
+ if (segments.length === expectedSegments + 1 && segments[0]?.includes(".")) {
147
+ if (segments[0] !== "registry.terraform.io") return null;
148
+ segments.shift();
149
+ }
150
+ return segments.length === expectedSegments ? segments.join("/") : null;
151
+ }
152
+
153
+ /**
154
+ * One registry GET that returns parsed JSON or a per-lookup status. Shared by
155
+ * the version-currency lookups here and the registry module-detail/search
156
+ * lookups in `./registry.ts` so every registry call degrades green identically:
157
+ * 404 → `not_found`, network/non-2xx/parse failure → `error`. `headers` carries
158
+ * the `Authorization: Bearer …` token for a private-registry call (public default
159
+ * registry calls pass none).
160
+ */
161
+ export async function fetchRegistryJson(
162
+ url: string,
163
+ headers?: Record<string, string>,
164
+ ): Promise<{ status: LookupStatus; body: unknown }> {
165
+ let response: Response;
166
+ try {
167
+ response = await fetch(url, {
168
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
169
+ ...(headers ? { headers } : {}),
170
+ });
171
+ } catch {
172
+ return { status: "error", body: null };
173
+ }
174
+ if (response.status === 404) return { status: "not_found", body: null };
175
+ if (!response.ok) return { status: "error", body: null };
176
+ try {
177
+ return { status: "ok", body: await response.json() };
178
+ } catch {
179
+ return { status: "error", body: null };
180
+ }
181
+ }
182
+
183
+ async function fetchVersionList(
184
+ url: string,
185
+ extract: (body: unknown) => string[] | null,
186
+ ): Promise<{ status: LookupStatus; versions: string[] }> {
187
+ const { status, body } = await fetchRegistryJson(url);
188
+ if (status !== "ok") return { status, versions: [] };
189
+ const versions = extract(body);
190
+ if (versions === null) return { status: "error", versions: [] };
191
+ return { status: "ok", versions };
192
+ }
193
+
194
+ export function isRecord(value: unknown): value is Record<string, unknown> {
195
+ return typeof value === "object" && value !== null && !Array.isArray(value);
196
+ }
197
+
198
+ /** pull `[{version}, …]` out of a `{ …, versions: [...] }` payload. */
199
+ function extractVersionsArray(container: unknown): string[] | null {
200
+ if (!isRecord(container) || !Array.isArray(container.versions)) return null;
201
+ const out: string[] = [];
202
+ for (const entry of container.versions) {
203
+ if (isRecord(entry) && typeof entry.version === "string") out.push(entry.version);
204
+ }
205
+ return out;
206
+ }
207
+
208
+ /** GET /v1/providers/{namespace}/{type}/versions — provider release list. */
209
+ export async function fetchProviderVersions(
210
+ source: string,
211
+ opts: { baseUrl?: string } = {},
212
+ ): Promise<{ status: LookupStatus; versions: string[] }> {
213
+ const path = normalizeRegistryPath(source, 2);
214
+ if (path === null) return { status: "unsupported_source", versions: [] };
215
+ const base = opts.baseUrl ?? DEFAULT_REGISTRY_BASE_URL;
216
+ return fetchVersionList(`${base}/v1/providers/${path}/versions`, extractVersionsArray);
217
+ }
218
+
219
+ /** GET /v1/modules/{namespace}/{name}/{provider}/versions — registry-module
220
+ * release list (the response nests per-module records; the first is the
221
+ * requested module, the rest are dependency records we don't want). */
222
+ export async function fetchModuleVersions(
223
+ sourceBase: string,
224
+ opts: { baseUrl?: string } = {},
225
+ ): Promise<{ status: LookupStatus; versions: string[] }> {
226
+ const path = normalizeRegistryPath(sourceBase, 3);
227
+ if (path === null) return { status: "unsupported_source", versions: [] };
228
+ const base = opts.baseUrl ?? DEFAULT_REGISTRY_BASE_URL;
229
+ return fetchVersionList(`${base}/v1/modules/${path}/versions`, (body) => {
230
+ if (!isRecord(body) || !Array.isArray(body.modules)) return null;
231
+ return extractVersionsArray(body.modules[0]);
232
+ });
233
+ }
234
+
235
+ // --- workspace report (orchestration) ----------------------------------------
236
+
237
+ export interface ProviderCurrencyRow {
238
+ name: string;
239
+ source: string;
240
+ constraint: string | null;
241
+ latest: string | null;
242
+ newest_satisfying: string | null;
243
+ outdated: boolean;
244
+ majors_behind: number;
245
+ lookup: LookupStatus;
246
+ }
247
+
248
+ export interface ModuleCurrencyRow {
249
+ name: string;
250
+ source: string;
251
+ version: string | null;
252
+ latest: string | null;
253
+ newest_satisfying: string | null;
254
+ outdated: boolean;
255
+ /** registry module with no `version` attribute — pin it (to `latest`). */
256
+ unpinned: boolean;
257
+ lookup: LookupStatus;
258
+ declared_in: string;
259
+ }
260
+
261
+ export interface CurrencyReport {
262
+ providers: ProviderCurrencyRow[];
263
+ modules: ModuleCurrencyRow[];
264
+ outdated_count: number;
265
+ unpinned_count: number;
266
+ lookups_attempted: number;
267
+ lookups_failed: number;
268
+ }
269
+
270
+ /** bare `aws` in a legacy required_providers block means `hashicorp/aws`. */
271
+ function providerSource(name: string, source: string | null): string {
272
+ return source ?? `hashicorp/${name}`;
273
+ }
274
+
275
+ export async function checkVersionCurrency(
276
+ cwd: string,
277
+ opts: { baseUrl?: string } = {},
278
+ ): Promise<CurrencyReport> {
279
+ const providerReqs = collectProviderRequirements(cwd);
280
+ const registryModules = collectModuleGraph(cwd).modules.filter((m) => m.kind === "registry");
281
+
282
+ // one lookup per distinct source — a workspace redeclaring `hashicorp/aws`
283
+ // in every root must not turn into N identical registry calls.
284
+ const providerLookups = new Map<string, Promise<{ status: LookupStatus; versions: string[] }>>();
285
+ for (const req of providerReqs) {
286
+ const source = providerSource(req.name, req.source);
287
+ if (!providerLookups.has(source)) {
288
+ providerLookups.set(source, fetchProviderVersions(source, opts));
289
+ }
290
+ }
291
+ const moduleLookups = new Map<string, Promise<{ status: LookupStatus; versions: string[] }>>();
292
+ for (const mod of registryModules) {
293
+ const base = splitModuleSource(mod.source).base;
294
+ if (!moduleLookups.has(base)) {
295
+ moduleLookups.set(base, fetchModuleVersions(base, opts));
296
+ }
297
+ }
298
+ // resolve every lookup up front so the row loops below read settled results
299
+ // (and so a missing map entry can degrade to an error row, not a crash).
300
+ const noLookup = { status: "error" as LookupStatus, versions: [] as string[] };
301
+ const providerResults = new Map(
302
+ await Promise.all([...providerLookups].map(async ([source, p]) => [source, await p] as const)),
303
+ );
304
+ const moduleResults = new Map(
305
+ await Promise.all([...moduleLookups].map(async ([base, p]) => [base, await p] as const)),
306
+ );
307
+
308
+ const providers: ProviderCurrencyRow[] = [];
309
+ for (const req of providerReqs) {
310
+ const source = providerSource(req.name, req.source);
311
+ const result = providerResults.get(source) ?? noLookup;
312
+ const verdict = classifyCurrency({ constraint: req.version, available: result.versions });
313
+ providers.push({
314
+ name: req.name,
315
+ source,
316
+ constraint: req.version,
317
+ latest: verdict.latest,
318
+ newest_satisfying: verdict.newestSatisfying,
319
+ outdated: result.status === "ok" && verdict.outdated,
320
+ majors_behind: result.status === "ok" ? verdict.majorsBehind : 0,
321
+ lookup: result.status,
322
+ });
323
+ }
324
+
325
+ const modules: ModuleCurrencyRow[] = [];
326
+ for (const mod of registryModules) {
327
+ const base = splitModuleSource(mod.source).base;
328
+ const result = moduleResults.get(base) ?? noLookup;
329
+ const verdict = classifyCurrency({ constraint: mod.version, available: result.versions });
330
+ modules.push({
331
+ name: mod.name,
332
+ source: mod.source,
333
+ version: mod.version,
334
+ latest: verdict.latest,
335
+ newest_satisfying: verdict.newestSatisfying,
336
+ outdated: result.status === "ok" && verdict.outdated,
337
+ unpinned: mod.version === null,
338
+ lookup: result.status,
339
+ declared_in: mod.declaredIn,
340
+ });
341
+ }
342
+
343
+ const failed = [...providers, ...modules].filter(
344
+ (row) => row.lookup === "error" || row.lookup === "not_found",
345
+ ).length;
346
+ return {
347
+ providers,
348
+ modules,
349
+ outdated_count: [...providers, ...modules].filter((r) => r.outdated).length,
350
+ unpinned_count: modules.filter((m) => m.unpinned).length,
351
+ lookups_attempted: providers.length + modules.length,
352
+ lookups_failed: failed,
353
+ };
354
+ }