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,151 @@
1
+ /**
2
+ * Detached Ed25519 signing for the compliance artefacts (the evidence bundle in
3
+ * [[evidence]] and the CycloneDX VEX in [[vex]]) — the "signed evidence" story.
4
+ *
5
+ * Design constraints, in order:
6
+ * - **Sign the exact bytes written to disk.** The builders are already
7
+ * deterministic (same inputs ⇒ same bytes), so no JSON canonicaliser is
8
+ * needed — the detached `.sig` covers the file verbatim.
9
+ * - **`node:crypto` only** (`sign`/`verify` with a `null` digest — Ed25519 is
10
+ * a pure signature scheme); no new dependency.
11
+ * - **Honest when unsigned.** The private key arrives via env only
12
+ * (`INFRAWEAVER_SIGNING_KEY`, a base64-encoded PKCS#8 PEM). Unset ⇒ the
13
+ * artefact records an explicit `"signature": null` — never a fake or
14
+ * self-referential signature. A key that is SET but malformed throws (a
15
+ * mis-configured signer must fail loudly, not silently emit unsigned).
16
+ * - The key name matches the `_KEY` suffix in `utils/secrets.ts`
17
+ * SENSITIVE_PATTERNS, so it is redacted from logs and stripped from every
18
+ * subprocess env by the default-deny filter.
19
+ *
20
+ * Verify side: `infraweaver verify-evidence <bundle> <sig> <pubkey>` (src/cli.ts →
21
+ * commands/verifyEvidence.ts) re-checks a bundle offline with only the public key.
22
+ */
23
+ import {
24
+ createHash,
25
+ createPrivateKey,
26
+ createPublicKey,
27
+ sign as cryptoSign,
28
+ verify as cryptoVerify,
29
+ type KeyObject,
30
+ } from "node:crypto";
31
+
32
+ export const SIGNING_KEY_ENV = "INFRAWEAVER_SIGNING_KEY";
33
+ export const SIGNATURE_ALGORITHM = "Ed25519" as const;
34
+
35
+ export interface SigningKey {
36
+ privateKey: KeyObject;
37
+ /** the public half, SPKI PEM — embedded in artefact metadata so a consumer
38
+ * can verify without any other channel. */
39
+ publicKeyPem: string;
40
+ /** sha256 of the SPKI DER, hex — the short handle a PR body / log names. */
41
+ fingerprint: string;
42
+ }
43
+
44
+ /** metadata embedded in a signed artefact (never the signature itself — the
45
+ * signature covers these bytes, so it must live in the detached `.sig`). */
46
+ export interface SignatureMetadata {
47
+ algorithm: typeof SIGNATURE_ALGORITHM;
48
+ public_key: string;
49
+ fingerprint_sha256: string;
50
+ /** sibling file carrying the base64 detached signature. */
51
+ detached_signature: string;
52
+ }
53
+
54
+ /**
55
+ * Load the signing key from the env (base64-encoded PKCS#8 PEM, Ed25519).
56
+ * Returns null when unset/empty (the honest-unsigned path); throws when a key
57
+ * is present but not a valid Ed25519 PKCS#8 PEM — a configured signer that
58
+ * can't sign is an operator error, never a silent downgrade.
59
+ */
60
+ export function loadSigningKey(
61
+ raw: string | undefined = process.env[SIGNING_KEY_ENV],
62
+ ): SigningKey | null {
63
+ const b64 = raw?.trim();
64
+ if (!b64) return null;
65
+ const pem = Buffer.from(b64, "base64").toString("utf8");
66
+ if (!pem.includes("-----BEGIN")) {
67
+ throw new Error(
68
+ `${SIGNING_KEY_ENV} is set but does not decode to a PEM — supply the PKCS#8 private key PEM, base64-encoded`,
69
+ );
70
+ }
71
+ let privateKey: KeyObject;
72
+ try {
73
+ privateKey = createPrivateKey(pem);
74
+ } catch (e) {
75
+ throw new Error(
76
+ `${SIGNING_KEY_ENV} decodes to a PEM node:crypto cannot parse as a private key: ${e instanceof Error ? e.message : String(e)}`,
77
+ );
78
+ }
79
+ if (privateKey.asymmetricKeyType !== "ed25519") {
80
+ throw new Error(
81
+ `${SIGNING_KEY_ENV} must be an Ed25519 key (got ${privateKey.asymmetricKeyType ?? "unknown"})`,
82
+ );
83
+ }
84
+ // derive the public half via a PEM round-trip (the KeyObject overload isn't
85
+ // in the current @types/node surface; the bytes are identical either way).
86
+ const publicKey = createPublicKey(
87
+ privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
88
+ );
89
+ const spkiDer = publicKey.export({ type: "spki", format: "der" });
90
+ const publicKeyPem = publicKey
91
+ .export({ type: "spki", format: "pem" })
92
+ .toString();
93
+ return {
94
+ privateKey,
95
+ publicKeyPem,
96
+ fingerprint: createHash("sha256").update(spkiDer).digest("hex"),
97
+ };
98
+ }
99
+
100
+ /** detached Ed25519 signature over the exact bytes, base64. */
101
+ export function signBytes(bytes: string | Buffer, key: SigningKey): string {
102
+ const data = typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes;
103
+ // Ed25519 is a pure scheme: the digest argument MUST be null.
104
+ return cryptoSign(null, data, key.privateKey).toString("base64");
105
+ }
106
+
107
+ /** verify a detached base64 signature against the exact bytes + an SPKI PEM
108
+ * public key. Returns false (never throws) on a malformed signature/key, so a
109
+ * verifier can report FAILED rather than crash on garbage input. */
110
+ export function verifyBytes(
111
+ bytes: string | Buffer,
112
+ signatureB64: string,
113
+ publicKeyPem: string,
114
+ ): boolean {
115
+ try {
116
+ const data = typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes;
117
+ const publicKey = createPublicKey(publicKeyPem);
118
+ return cryptoVerify(
119
+ null,
120
+ data,
121
+ publicKey,
122
+ Buffer.from(signatureB64.trim(), "base64"),
123
+ );
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /** sha256 fingerprint (hex) of an SPKI PEM public key — the same handle
130
+ * {@link loadSigningKey} derives, computable by a verifier from the PEM alone. */
131
+ export function publicKeyFingerprint(publicKeyPem: string): string {
132
+ const der = createPublicKey(publicKeyPem).export({
133
+ type: "spki",
134
+ format: "der",
135
+ });
136
+ return createHash("sha256").update(der).digest("hex");
137
+ }
138
+
139
+ /** the metadata block an artefact embeds when this key signs it. `sigFile` is
140
+ * the sibling `.sig` filename (relative — beside the artefact). */
141
+ export function signatureMetadata(
142
+ key: SigningKey,
143
+ sigFile: string,
144
+ ): SignatureMetadata {
145
+ return {
146
+ algorithm: SIGNATURE_ALGORITHM,
147
+ public_key: key.publicKeyPem,
148
+ fingerprint_sha256: key.fingerprint,
149
+ detached_signature: sigFile,
150
+ };
151
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * S3 backend state-locking currency.
3
+ *
4
+ * HashiCorp deprecated DynamoDB-based locking for the `s3` backend in favour of
5
+ * native S3 lockfiles (`use_lockfile = true`), and says it "will be removed in a
6
+ * future minor version". The two may be set together — the documented migration
7
+ * path is to add `use_lockfile` while leaving `dynamodb_table` in place, so
8
+ * older Terraform versions in the same team keep locking against DynamoDB until
9
+ * everyone has moved.
10
+ *
11
+ * WHY THIS IS OURS TO REPORT. Checkov and Trivy answer "is this setting wrong?"
12
+ * and this setting is not wrong — it is *ending*. tflint checks that versions
13
+ * are pinned, not that arguments are current. Nobody else reports a working
14
+ * configuration whose mechanism has an announced end date, and that is exactly
15
+ * the class of thing a Terraform-currency product exists to see.
16
+ *
17
+ * WHY ADVISORY, NOT A FAILURE. DynamoDB locking works today; a repo using it is
18
+ * not exposed. And unlike a resource argument, a backend block cannot be
19
+ * corrected by a pull request alone — changing it requires an operator to run
20
+ * `terraform init -migrate-state` against real state. Presenting that as a
21
+ * fixable violation would be inviting a PR that breaks `init` for everyone.
22
+ *
23
+ * DELIBERATELY NO VERSION CLAIM in the user-facing text. `use_lockfile` needs a
24
+ * recent Terraform, but the backend docs do not pin the version that introduced
25
+ * it, and a wrong minimum in remediation prose is worse than none — it would
26
+ * send someone to upgrade for the wrong reason. The finding names the
27
+ * deprecation, which is checkable, and leaves the version floor to `terraform
28
+ * init`, which enforces it precisely.
29
+ *
30
+ * Pure: HCL text in, findings out.
31
+ */
32
+ import { parseBlocks, stripTrailingComment, topLevelAssignments } from "#app/mcp/terraform/hcl";
33
+ import {
34
+ BAND_SEVERITY,
35
+ DEPRECATED_STATE_LOCKING,
36
+ NATIVE_SOURCE,
37
+ } from "#app/mcp/terraform/nativeRules";
38
+ import { buildConcern, type Concern } from "#app/mcp/terraform/types";
39
+
40
+ export interface DeprecatedLockingSite {
41
+ /** 1-based line of the `backend "s3"` block. */
42
+ line: number;
43
+ /** the `dynamodb_table` value as written (a literal, or an expression). */
44
+ table: string;
45
+ }
46
+
47
+ /** `use_lockfile = true` — the only value that actually enables native locking.
48
+ * `false`, or an expression we cannot resolve, leaves DynamoDB as the sole
49
+ * mechanism, so the finding still stands. */
50
+ function enablesLockfile(value: string | undefined): boolean {
51
+ return value !== undefined && stripTrailingComment(value) === "true";
52
+ }
53
+
54
+ /**
55
+ * Every `terraform { backend "s3" { … } }` that locks via `dynamodb_table`
56
+ * without `use_lockfile = true`.
57
+ *
58
+ * Walks `terraform` → `backend` explicitly rather than matching `dynamodb_table`
59
+ * anywhere: `aws_dynamodb_table` resources and module inputs of the same name
60
+ * are common, and a currency finding fabricated from one of those would be
61
+ * indistinguishable from a real one to whoever reads the report.
62
+ */
63
+ export function findDeprecatedStateLocking(hcl: string): DeprecatedLockingSite[] {
64
+ const out: DeprecatedLockingSite[] = [];
65
+ for (const tf of parseBlocks(hcl, ["terraform"])) {
66
+ for (const backend of parseBlocks(tf.body, ["backend"])) {
67
+ if (backend.labels[0] !== "s3") continue;
68
+ const args = topLevelAssignments(backend.body);
69
+ const get = (key: string): string | undefined => args.find((a) => a.name === key)?.value;
70
+ const table = get("dynamodb_table");
71
+ if (table === undefined) continue;
72
+ if (enablesLockfile(get("use_lockfile"))) continue;
73
+ out.push({
74
+ line: tf.bodyLine + backend.line - 1,
75
+ table: stripTrailingComment(table),
76
+ });
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+
82
+ export function deprecatedStateLockingToConcerns(
83
+ sites: DeprecatedLockingSite[],
84
+ file: string,
85
+ ): Concern[] {
86
+ return sites.map((site) =>
87
+ buildConcern({
88
+ source: NATIVE_SOURCE,
89
+ rule: DEPRECATED_STATE_LOCKING,
90
+ file,
91
+ line: site.line,
92
+ severity: BAND_SEVERITY.advisory,
93
+ category: "correctness",
94
+ evidence:
95
+ `backend "s3" locks state via dynamodb_table = ${site.table} with no use_lockfile. ` +
96
+ "DynamoDB-based locking is deprecated and HashiCorp will remove it in a future release",
97
+ remediation_hint:
98
+ "Add `use_lockfile = true` to the backend block and keep `dynamodb_table` for now — the two are " +
99
+ "supported together precisely so a team can migrate without a flag day, and older Terraform " +
100
+ "versions still locking through DynamoDB keep working. Drop `dynamodb_table` (and the table) " +
101
+ "only once every consumer of this state has moved. ⚠️ A backend change is NOT a code-only fix: " +
102
+ "it needs `terraform init -migrate-state` run by someone with access to the real state, so this " +
103
+ "belongs in a human-owned change, not an automated remediation PR.",
104
+ }),
105
+ );
106
+ }
@@ -0,0 +1,102 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { log } from "#app/utils/cli";
3
+ import { resolveEnv } from "#app/utils/secrets";
4
+
5
+ export type RunResult = { status: number; stdout: string; stderr: string; missing: boolean };
6
+
7
+ /**
8
+ * Hard wall-clock cap on any single scanner/terraform invocation. Bounds a hung
9
+ * subprocess (e.g. `terraform init`/`plan` stalling on a private registry, a
10
+ * tflint plugin fetch that never returns) so it can't block the run forever.
11
+ * Generous (5 min) so it only ever fires on a genuine hang, never a slow-but-
12
+ * progressing plan. A timeout surfaces as a non-zero/`-1` status the caller
13
+ * already treats as "this scanner did not produce results".
14
+ */
15
+ export const SUBPROCESS_TIMEOUT_MS = 300_000;
16
+
17
+ /**
18
+ * Run a scanner without throwing. Terraform tools exit non-zero when they FIND
19
+ * issues, so a non-zero status is normal, not an error. `missing` is set when
20
+ * the binary isn't on PATH (ENOENT) — the scanner then degrades to "skipped"
21
+ * rather than failing the run (a tool being absent must not block).
22
+ *
23
+ * `extraEnv` opts a specific command back into a needed credential: the scanners
24
+ * run with a restricted env that strips every `*_KEY`/secret, but infracost
25
+ * legitimately needs its `INFRACOST_API_KEY`. resolveEnv(object) merges the
26
+ * restricted base with the explicit vars, so only the named keys get through.
27
+ */
28
+ export function run(
29
+ cmd: string,
30
+ args: string[],
31
+ cwd: string,
32
+ extraEnv?: Record<string, string>,
33
+ ): RunResult {
34
+ const result = spawnSync(cmd, args, {
35
+ cwd,
36
+ encoding: "utf-8",
37
+ // restricted env: keeps PATH/HOME, strips secrets so a scanner (or a tflint
38
+ // plugin) can't exfiltrate credentials. `extraEnv` re-admits only the named vars.
39
+ env: resolveEnv(extraEnv ?? "restricted") as NodeJS.ProcessEnv,
40
+ stdio: ["ignore", "pipe", "pipe"],
41
+ maxBuffer: 64 * 1024 * 1024,
42
+ timeout: SUBPROCESS_TIMEOUT_MS,
43
+ });
44
+ if (result.error) {
45
+ // ENOENT = binary absent (degrade to "skipped"); a timeout (ETIMEDOUT, or a
46
+ // SIGTERM kill on timeout) is a real failure surfaced as status -1, which the
47
+ // scanner callers already treat as "no results from this tool".
48
+ const missing = (result.error as NodeJS.ErrnoException).code === "ENOENT";
49
+ return { status: -1, stdout: "", stderr: result.error.message, missing };
50
+ }
51
+ return {
52
+ status: result.status ?? -1,
53
+ stdout: result.stdout ?? "",
54
+ stderr: result.stderr ?? "",
55
+ missing: false,
56
+ };
57
+ }
58
+
59
+ // which Terraform-compatible CLI answered, resolved lazily from the first real
60
+ // invocation (not a separate probe) and memoised so the fallback is attempted at
61
+ // most once per process. `undefined` = not yet resolved.
62
+ let resolvedTfCli: "terraform" | "tofu" | undefined;
63
+
64
+ /** test seam — forget the resolved CLI so a test can exercise both branches. */
65
+ export function resetTerraformCliCache(): void {
66
+ resolvedTfCli = undefined;
67
+ }
68
+
69
+ /**
70
+ * Run a Terraform CLI command, preferring `terraform` and transparently falling
71
+ * back to OpenTofu (`tofu`) when `terraform` is ABSENT from PATH (ENOENT). The two
72
+ * share the exact surface the engine uses (fmt / validate / init / plan / show),
73
+ * and the licence posture already prefers OpenTofu (Terraform is BUSL) — so an
74
+ * OpenTofu-only runner keeps full fmt/validate/plan coverage instead of reporting
75
+ * everything skipped. The fallback is probed via the FIRST real command (no
76
+ * separate `version` call, so a mocked runner that answers `fmt`/`init`/`plan`
77
+ * needs no extra stubbing) and the chosen binary is memoised for the rest of the
78
+ * process — the one-time resolution to `tofu` is logged so the operator sees which
79
+ * CLI ran. When BOTH are absent the terraform result (missing) is returned so the
80
+ * caller degrades to "skipped" exactly as before. `runner` is injectable for tests.
81
+ */
82
+ export function runTf(
83
+ args: string[],
84
+ cwd: string,
85
+ extraEnv?: Record<string, string>,
86
+ runner: typeof run = run,
87
+ ): RunResult {
88
+ if (resolvedTfCli) return runner(resolvedTfCli, args, cwd, extraEnv);
89
+ const tf = runner("terraform", args, cwd, extraEnv);
90
+ if (!tf.missing) {
91
+ resolvedTfCli = "terraform";
92
+ return tf;
93
+ }
94
+ const tofu = runner("tofu", args, cwd, extraEnv);
95
+ if (!tofu.missing) {
96
+ resolvedTfCli = "tofu";
97
+ log.info("» terraform not found on PATH — using OpenTofu (tofu) for fmt/validate/plan");
98
+ return tofu;
99
+ }
100
+ // neither installed — return terraform's missing result (caller skips).
101
+ return tf;
102
+ }