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,384 @@
1
+ import { type } from "arktype";
2
+ import {
3
+ isGuardedModeName,
4
+ partitionStatefulDestroy,
5
+ } from "#app/mcp/guardrails";
6
+ import type { LocalToolContext } from "#app/mcp/localContext";
7
+ import { execute, tool, toolOk } from "#app/mcp/shared";
8
+ import { resolveRoots } from "#app/mcp/terraform/paths";
9
+ import {
10
+ aggregatePlans,
11
+ classifyDestructive,
12
+ comparePlanStability,
13
+ computeBlastRadius,
14
+ isPureMovePlan,
15
+ type PlanSummary,
16
+ parseTerraformPlanJson,
17
+ type RootPlan,
18
+ } from "#app/mcp/terraform/plan";
19
+ import { scanSessionOf } from "#app/mcp/terraform/scanSession";
20
+ import { runTf } from "#app/mcp/terraform/subprocess";
21
+ import { skipResult } from "#app/mcp/terraform/types";
22
+ import { log } from "#app/utils/cli";
23
+ import { resolveModuleCredentialEnv } from "#app/utils/registryAuth";
24
+
25
+ const PLAN_JSON_ARGS = [
26
+ "plan",
27
+ "-input=false",
28
+ "-no-color",
29
+ "-lock=false",
30
+ "-json",
31
+ ];
32
+
33
+ interface RootPlanOutcome {
34
+ ran: boolean;
35
+ skipReason?: string | undefined;
36
+ summary?: PlanSummary | undefined;
37
+ stable?: boolean | undefined;
38
+ stabilityReason?: string | undefined;
39
+ planText?: string | undefined;
40
+ }
41
+
42
+ /** init + plan (+ a stability re-plan + a human-readable plan) in a SINGLE root.
43
+ * Used by terraform_plan once per discovered root. */
44
+ function planOneRoot(
45
+ absDir: string,
46
+ creds: Record<string, string>,
47
+ ): RootPlanOutcome {
48
+ // `terraform` when installed, else `tofu` (identical plan CLI, resolved by
49
+ // runTf) — an OpenTofu-only runner keeps the plan gate instead of skipping it.
50
+ const init = runTf(["init", "-input=false", "-no-color"], absDir, creds);
51
+ if (init.missing)
52
+ return { ran: false, skipReason: "terraform (or tofu) not installed" };
53
+ if (init.status !== 0) {
54
+ return {
55
+ ran: false,
56
+ skipReason: `terraform init failed: ${init.stderr.trim().slice(0, 200) || "unknown error"}`,
57
+ };
58
+ }
59
+ const plan = runTf(PLAN_JSON_ARGS, absDir, creds);
60
+ if (plan.status !== 0) {
61
+ return {
62
+ ran: false,
63
+ skipReason: `terraform plan failed: ${plan.stderr.trim().slice(0, 200) || "unknown error"}`,
64
+ };
65
+ }
66
+ const summary = parseTerraformPlanJson(plan.stdout);
67
+ const hasChanges =
68
+ summary.add + summary.change + summary.destroy > 0 ||
69
+ summary.changed.length > 0;
70
+
71
+ // stability: re-plan once and compare (only when there's a change).
72
+ let stable = true;
73
+ let stabilityReason: string | undefined;
74
+ if (hasChanges) {
75
+ const plan2 = runTf(PLAN_JSON_ARGS, absDir, creds);
76
+ if (plan2.status === 0) {
77
+ const s = comparePlanStability(
78
+ summary,
79
+ parseTerraformPlanJson(plan2.stdout),
80
+ );
81
+ stable = s.stable;
82
+ stabilityReason = s.reason;
83
+ }
84
+ }
85
+ // human-readable plan for the PR <details> block (separate non-json run).
86
+ let planText: string | undefined;
87
+ if (hasChanges) {
88
+ const readable = runTf(
89
+ ["plan", "-input=false", "-no-color", "-lock=false"],
90
+ absDir,
91
+ creds,
92
+ );
93
+ if (readable.status === 0 && readable.stdout.trim())
94
+ planText = readable.stdout.trim().slice(0, 12_000);
95
+ }
96
+ return { ran: true, summary, stable, stabilityReason, planText };
97
+ }
98
+
99
+ // env vars that signal a cloud provider credential is present — terraform plan
100
+ // needs live provider/backend access, so we only attempt it when one is set.
101
+ const CLOUD_CRED_SIGNALS = [
102
+ "AWS_ACCESS_KEY_ID",
103
+ "AWS_SECRET_ACCESS_KEY",
104
+ "AWS_PROFILE",
105
+ "AWS_ROLE_ARN",
106
+ "AWS_WEB_IDENTITY_TOKEN_FILE",
107
+ "ARM_CLIENT_ID",
108
+ "ARM_USE_OIDC",
109
+ "AZURE_CLIENT_ID",
110
+ "GOOGLE_CREDENTIALS",
111
+ "GOOGLE_APPLICATION_CREDENTIALS",
112
+ "GOOGLE_OAUTH_ACCESS_TOKEN",
113
+ ] as const;
114
+
115
+ function hasCloudCredentials(): boolean {
116
+ return CLOUD_CRED_SIGNALS.some((k) => !!process.env[k]);
117
+ }
118
+
119
+ // env vars terraform/providers legitimately consume, re-admitted past the
120
+ // secret-stripping `run()` env for the plan invocation. Infraweaver is BYOK
121
+ // across providers (Anthropic / OpenAI / Google Gemini / …), so NONE of those
122
+ // LLM keys may leak into the terraform subprocess. PREFIXES are only ones that
123
+ // can't collide with a provider key (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
124
+ // `GEMINI_API_KEY` carry no cloud prefix; the bare `GOOGLE_` prefix is
125
+ // deliberately NOT used — it would re-admit `GOOGLE_GENERATIVE_AI_API_KEY`).
126
+ // GCP creds are matched by exact NAME / the safe `GOOGLE_CLOUD_` prefix instead.
127
+ const CLOUD_CRED_PREFIXES = [
128
+ "AWS_",
129
+ "ARM_",
130
+ "AZURE_",
131
+ "GCLOUD_",
132
+ "GOOGLE_CLOUD_",
133
+ "TF_VAR_",
134
+ "TF_TOKEN_",
135
+ "TF_CLI_",
136
+ ];
137
+ const CLOUD_CRED_NAMES = new Set([
138
+ "GOOGLE_CREDENTIALS",
139
+ "GOOGLE_APPLICATION_CREDENTIALS",
140
+ "GOOGLE_CLOUD_KEYFILE_JSON",
141
+ "GOOGLE_OAUTH_ACCESS_TOKEN",
142
+ "GOOGLE_PROJECT",
143
+ "GOOGLE_REGION",
144
+ "GOOGLE_ZONE",
145
+ "GOOGLE_IMPERSONATE_SERVICE_ACCOUNT",
146
+ ]);
147
+
148
+ // LLM/model-provider credentials that collide with a cloud PREFIX above and so
149
+ // must be explicitly denied — otherwise a BYOK key would leak into the terraform
150
+ // subprocess. `AWS_BEARER_TOKEN_BEDROCK` (Amazon Bedrock) matches `AWS_`;
151
+ // `AZURE_OPENAI_*` matches `AZURE_`. NB `AWS_REGION` (also a Bedrock env var) is
152
+ // a legitimate cloud/terraform setting and is intentionally NOT denied. Keep in
153
+ // sync with the provider `envVars` in src/models.ts.
154
+ const LLM_CRED_DENY = new Set([
155
+ "AWS_BEARER_TOKEN_BEDROCK",
156
+ "AZURE_OPENAI_API_KEY",
157
+ "AZURE_OPENAI_ENDPOINT",
158
+ ]);
159
+
160
+ export function collectCloudCredentials(): Record<string, string> {
161
+ const env: Record<string, string> = {};
162
+ for (const [k, v] of Object.entries(process.env)) {
163
+ if (v === undefined) continue;
164
+ if (LLM_CRED_DENY.has(k)) continue; // never leak a model-provider key, even if it matches a cloud prefix
165
+ if (
166
+ CLOUD_CRED_PREFIXES.some((p) => k.startsWith(p)) ||
167
+ CLOUD_CRED_NAMES.has(k)
168
+ )
169
+ env[k] = v;
170
+ }
171
+ return env;
172
+ }
173
+
174
+ export const TerraformPlanParams = type({});
175
+
176
+ export interface PlanRunners {
177
+ planOneRoot: (
178
+ absDir: string,
179
+ creds: Record<string, string>,
180
+ ) => RootPlanOutcome;
181
+ hasCloudCredentials: () => boolean;
182
+ }
183
+
184
+ const realRunners: PlanRunners = { planOneRoot, hasCloudCredentials };
185
+
186
+ export async function runPlan(
187
+ ctx: LocalToolContext,
188
+ runners: PlanRunners = realRunners,
189
+ ) {
190
+ const cwd = ctx.payload.cwd ?? process.cwd();
191
+ if (!runners.hasCloudCredentials()) {
192
+ return skipResult(
193
+ "no_cloud_credentials",
194
+ "no cloud credentials detected — terraform plan needs provider/backend access; skipped (add AWS/Azure/GCP creds or an OIDC role to enable it)",
195
+ );
196
+ }
197
+ // fold the optional module credentials into the plan env so `terraform init`
198
+ // can resolve a private cross-repo `git::` module (GIT_CONFIG_*) or a private
199
+ // registry module (TF_TOKEN_<host>). Neither key namespace collides with the
200
+ // cloud creds; the operator-supplied registry token is added here because
201
+ // collectCloudCredentials() only sees a TF_TOKEN_* that's already in the env.
202
+ const creds = {
203
+ ...collectCloudCredentials(),
204
+ ...(resolveModuleCredentialEnv(ctx.payload, {
205
+ sshKeyDir: ctx.tmpdir,
206
+ }) ?? {}),
207
+ };
208
+
209
+ // multi-root: plan EACH root (hepcare: terraform/ + terraform/core/) and
210
+ // aggregate. resolveRoots falls back to [cwd] for a single-root repo, so
211
+ // behaviour there is identical to before.
212
+ const roots = resolveRoots(cwd);
213
+ const perRoot = roots.map((r) => ({
214
+ dir: r.relDir || ".",
215
+ outcome: runners.planOneRoot(r.absDir, creds),
216
+ }));
217
+ const ran = perRoot.filter((p) => p.outcome.ran);
218
+ if (ran.length === 0) {
219
+ // every root skipped — surface the first reason (e.g. not installed / init failed).
220
+ const reason =
221
+ perRoot[0]?.outcome.skipReason ?? "no terraform root could be planned";
222
+ const code = reason.includes("not installed")
223
+ ? "terraform_not_installed"
224
+ : reason.includes("init failed")
225
+ ? "terraform_init_failed"
226
+ : "terraform_plan_failed";
227
+ return skipResult(code, `terraform plan skipped — ${reason}`);
228
+ }
229
+
230
+ const rootPlans: RootPlan[] = ran.map((p) => ({
231
+ dir: p.dir,
232
+ summary: p.outcome.summary!,
233
+ stable: p.outcome.stable!,
234
+ }));
235
+ const agg = aggregatePlans(rootPlans);
236
+ const classified = classifyDestructive(agg.destructive);
237
+ const blastRadius = computeBlastRadius(agg.changed);
238
+ // record the UNION across roots so the push-time destroy-block guardrail
239
+ // blocks if ANY root would destroy/replace a stateful resource.
240
+ const session = scanSessionOf(ctx.toolState);
241
+ session.plannedDestroy = {
242
+ stateful: classified.stateful,
243
+ ephemeral: classified.ephemeral,
244
+ };
245
+ // Blast-radius pre-flight: surface, at PLAN time, which stateful
246
+ // destroy/replaces are NOT covered by the operator's `allow_replace` —
247
+ // i.e. what the push-time destroy guardrail (assertNoBlockedDestroy) would
248
+ // block. A committing mode can then abandon BEFORE the wasted commit/push
249
+ // round-trip rather than discovering the block at push. `push_will_block`
250
+ // is the same predicate the guardrail uses (guarded mode + uncovered
251
+ // stateful destroy); read-only modes never push, so it is false there.
252
+ const allowReplace = ctx.payload.allowReplace ?? [];
253
+ const { blocked: blockedDestructive } = partitionStatefulDestroy(
254
+ classified.stateful,
255
+ allowReplace,
256
+ );
257
+ const pushWillBlock =
258
+ isGuardedModeName(ctx.toolState.selectedMode) &&
259
+ blockedDestructive.length > 0;
260
+ // record the blast tier + idempotency for the confidence label.
261
+ session.lastBlastTier = blastRadius.tier;
262
+ session.lastIdempotent = agg.idempotent;
263
+ // raw totals, for the TC-2.2 upgrade evidence statement — the signed bundle
264
+ // has to say what the plan SHOWED, not merely that one ran.
265
+ session.lastPlanTotals = {
266
+ to_add: agg.add,
267
+ to_change: agg.change,
268
+ to_destroy: agg.destroy,
269
+ destructive: agg.destructive.length,
270
+ };
271
+ // Ordering is what makes this plan proof OF the upgrade rather than merely
272
+ // contemporaneous with it: a plan produced before terraform_provider_upgrade
273
+ // ran describes the pre-migration configuration and proves nothing about it.
274
+ session.planFollowedUpgradeDetection = session.upgradeIntent !== undefined;
275
+
276
+ // per-root human-readable plans, headed by root, for the PR <details>.
277
+ const planTextParts = ran
278
+ .filter((p) => p.outcome.planText)
279
+ .map((p) =>
280
+ ran.length > 1
281
+ ? `### Root: ${p.dir}\n${p.outcome.planText}`
282
+ : p.outcome.planText,
283
+ );
284
+ const planText = planTextParts.length
285
+ ? planTextParts.join("\n\n").slice(0, 20_000)
286
+ : undefined;
287
+ const idempotencyWarning = ran.find((p) => !p.outcome.stable)?.outcome
288
+ .stabilityReason;
289
+
290
+ // deterministic escalation to human review.
291
+ const escalationReasons: string[] = [];
292
+ if (blastRadius.tier === "high") {
293
+ escalationReasons.push(
294
+ `high blast radius (${blastRadius.resourceCount} resources / ${blastRadius.modules.length} modules)`,
295
+ );
296
+ }
297
+ if (classified.stateful.length > 0) {
298
+ escalationReasons.push(
299
+ `${classified.stateful.length} stateful resource(s) would be destroyed/replaced`,
300
+ );
301
+ }
302
+ if (!agg.idempotent)
303
+ escalationReasons.push("non-deterministic plan (perpetual-diff smell)");
304
+
305
+ log.info(
306
+ `» terraform_plan: +${agg.add} ~${agg.change} -${agg.destroy} across ${ran.length} root(s) ` +
307
+ `[blast: ${blastRadius.tier}, ${blastRadius.resourceCount} res / ${blastRadius.modules.length} mod]` +
308
+ (agg.hasDestroyOrReplace
309
+ ? ` (DESTRUCTIVE: ${agg.destructive.length}, stateful: ${classified.stateful.length})`
310
+ : "") +
311
+ (agg.idempotent ? "" : " ⚠ UNSTABLE (non-deterministic plan)") +
312
+ (escalationReasons.length ? " ⚠ needs-human" : ""),
313
+ );
314
+ return toolOk({
315
+ ran: true,
316
+ roots_planned: ran.map((p) => p.dir),
317
+ to_add: agg.add,
318
+ to_change: agg.change,
319
+ to_destroy: agg.destroy,
320
+ has_destroy_or_replace: agg.hasDestroyOrReplace,
321
+ destructive: agg.destructive,
322
+ // data-bearing resources that would be lost — these block the push
323
+ // unless allowed via `allow_replace`.
324
+ stateful_destructive: classified.stateful,
325
+ // blast-radius pre-flight: the stateful destroy/replaces NOT covered by
326
+ // `allow_replace` (what the push-time guardrail would block), and whether
327
+ // a push in this mode would actually be blocked — so a committing mode can
328
+ // abandon before the wasted commit/push instead of failing at push.
329
+ ...(blockedDestructive.length
330
+ ? { blocked_destructive: blockedDestructive }
331
+ : {}),
332
+ push_will_block: pushWillBlock,
333
+ // how much this fix touches; `high` should force human review.
334
+ blast_radius: blastRadius,
335
+ // false when any root's second plan disagreed (perpetual-diff smell).
336
+ idempotent: agg.idempotent,
337
+ idempotency_warning: idempotencyWarning,
338
+ // state-only moves and the modularization no-op gate.
339
+ moved_count: agg.moved.length,
340
+ ...(agg.moved.length ? { moved: agg.moved.slice(0, 50) } : {}),
341
+ refactor_safe: isPureMovePlan(agg),
342
+ // deterministic escalation to human review.
343
+ needs_human: escalationReasons.length > 0,
344
+ ...(escalationReasons.length
345
+ ? { needs_human_reasons: escalationReasons }
346
+ : {}),
347
+ // full human-readable plan(s) for a collapsed <details> block.
348
+ ...(planText ? { plan_text: planText } : {}),
349
+ // roots where plan couldn't run (no backend creds for that root, etc.).
350
+ ...(perRoot.some((p) => !p.outcome.ran)
351
+ ? {
352
+ roots_skipped: perRoot
353
+ .filter((p) => !p.outcome.ran)
354
+ .map((p) => ({ dir: p.dir, reason: p.outcome.skipReason })),
355
+ }
356
+ : {}),
357
+ });
358
+ }
359
+
360
+ export function TerraformPlanTool(ctx: LocalToolContext) {
361
+ return tool({
362
+ name: "terraform_plan",
363
+ description:
364
+ "Run `terraform plan` and report the planned change summary (resources to add / change / destroy), " +
365
+ "any resource that would be DESTROYED or REPLACED, a blast-radius score (how much the fix touches), " +
366
+ "and a plan-stability check (a second plan must match the first — a perpetual-diff smell otherwise). " +
367
+ "Opt-in and degrades green — it auto-skips (returns `ran: false`, never fails the run) when no cloud " +
368
+ "credentials are detected, terraform is not installed, or init/plan can't complete (plan needs live " +
369
+ "provider/backend access). Call it after a fix to attach the real-world effect to the PR and surface " +
370
+ "destructive changes for human review. **Multi-root aware:** it plans EVERY Terraform root in the repo " +
371
+ "(`roots_planned`) and aggregates — counts summed, destructive/blast unioned — so you don't loop " +
372
+ "yourself. Returns `plan_text` (the full human-readable plan(s), for a collapsed <details> block) and " +
373
+ "`needs_human` (true when a high blast radius, a stateful destroy/replace, or a non-deterministic plan " +
374
+ "means a human must review). Blast-radius pre-flight: `blocked_destructive` lists the stateful " +
375
+ "destroy/replaces NOT covered by `allow_replace`, and `push_will_block: true` means a push in this " +
376
+ "committing mode WOULD be blocked by the destroy guardrail — abandon now, before committing/pushing, " +
377
+ "rather than wasting the round-trip. For an M2 modularization refactor, `refactor_safe: true` is the gate: " +
378
+ "the plan is purely `moved` state operations (every resource address preserved via `moved {}` blocks, " +
379
+ "zero add/change/destroy) — if a modularization PR's plan is NOT refactor_safe, fix the moved blocks " +
380
+ "rather than accepting resource churn.",
381
+ parameters: TerraformPlanParams,
382
+ execute: execute(async () => runPlan(ctx)),
383
+ });
384
+ }
@@ -0,0 +1,217 @@
1
+ import { type } from "arktype";
2
+ import type { LocalToolContext } from "#app/mcp/localContext";
3
+ import { loadProvidersSchema } from "#app/mcp/providerSchema";
4
+ import { execute, tool, toolOk } from "#app/mcp/shared";
5
+ import { collectProviderRequirements, collectResourceArguments } from "#app/mcp/terraform/scanners";
6
+ import { scanSessionOf } from "#app/mcp/terraform/scanSession";
7
+ import {
8
+ BUILTIN_TRANSFORM_PACKS,
9
+ indexProvenTransforms,
10
+ matchTransforms,
11
+ packsForRequirements,
12
+ type SchemaLookup,
13
+ type TransformMatch,
14
+ type TransformPack,
15
+ } from "#app/mcp/terraform/transformPacks";
16
+
17
+ export const TerraformProviderUpgradeParams = type({});
18
+
19
+ /**
20
+ * Provider-upgrade transform detection (TC-2.2).
21
+ *
22
+ * Reads the pinned provider majors, selects the built-in packs that apply, and
23
+ * reports every codified breaking change that this configuration actually
24
+ * contains — with the file it is in and, crucially, whether it can be applied
25
+ * unattended.
26
+ *
27
+ * Read-only. It edits nothing, plans nothing and pushes nothing.
28
+ */
29
+
30
+ /** Bound the tool result: a repo pinning an old major can match hundreds of
31
+ * sites, and a prompt is not a report. Truncation is always stated. */
32
+ const MAX_REPORTED = 60;
33
+
34
+ /**
35
+ * Bind the installed provider schema to the pure matcher's lookup seam.
36
+ *
37
+ * Degrades honestly: when the schema can't be loaded (terraform absent, or the
38
+ * directory was never init-ed) every entry lands as `unverified` rather than
39
+ * being presented as corroborated. A pack claim nobody could check must not read
40
+ * the same as one that was checked.
41
+ */
42
+ function schemaLookupFor(cwd: string): SchemaLookup {
43
+ const schema = loadProvidersSchema(cwd);
44
+ if (!schema) return () => null;
45
+ return (resourceType: string) => {
46
+ const entry = schema.get(resourceType);
47
+ if (!entry) return null;
48
+ return new Set([...entry.attributes, ...entry.blocks]);
49
+ };
50
+ }
51
+
52
+ interface PackResult {
53
+ pack: TransformPack;
54
+ matches: TransformMatch[];
55
+ }
56
+
57
+ export function runProviderUpgrade(ctx: LocalToolContext) {
58
+ const cwd = ctx.payload.cwd ?? process.cwd();
59
+ const requirements = collectProviderRequirements(cwd);
60
+ const packs = packsForRequirements(BUILTIN_TRANSFORM_PACKS, requirements);
61
+
62
+ const pinned = requirements
63
+ .filter((r) => r.major !== null)
64
+ .map((r) => ({ provider: r.name, major: r.major, constraint: r.version }));
65
+
66
+ if (packs.length === 0) {
67
+ // Clear any earlier detection. The natural way to reach this branch is the
68
+ // agent bumping the pin and re-running to check its work: without this, the
69
+ // session still holds the PRE-edit intent and match set, and a later plan
70
+ // would sign a `plan-proven` verdict over the full original transform list
71
+ // — including ones the agent deliberately deferred.
72
+ const stale = scanSessionOf(ctx.toolState);
73
+ delete stale.upgradeIntent;
74
+ delete stale.transformMatches;
75
+ delete stale.planFollowedUpgradeDetection;
76
+ return toolOk({
77
+ applicable: false,
78
+ pinned_providers: pinned,
79
+ transforms: [],
80
+ note:
81
+ "No transform pack applies. A pack is selected only when a provider's pinned LOWER-BOUND major " +
82
+ "matches the major the pack migrates FROM; an unpinned provider matches nothing, because guessing " +
83
+ "which major it resolves to is how you get handed a migration you don't need. Packs available: " +
84
+ `${BUILTIN_TRANSFORM_PACKS.map((p) => `${p.provider} v${p.from_major}→v${p.to_major}`).join(", ")}.`,
85
+ });
86
+ }
87
+
88
+ const resources = collectResourceArguments(cwd);
89
+ const lookup = schemaLookupFor(cwd);
90
+ const results: PackResult[] = packs.map((pack) => ({
91
+ pack,
92
+ matches: matchTransforms(pack, resources, lookup),
93
+ }));
94
+
95
+ const session = scanSessionOf(ctx.toolState);
96
+ const fleet = indexProvenTransforms(session.provenTransformLibrary ?? []);
97
+ const allMatches = results.flatMap((r) => r.matches);
98
+ session.transformMatches = allMatches;
99
+
100
+ // The intent is recorded for the FIRST applicable pack. A repo mid-upgrade on
101
+ // two providers at once is possible but is not a thing to encourage, and an
102
+ // evidence statement that averaged two providers' verdicts would be unreadable.
103
+ const primary = results[0];
104
+ if (primary) {
105
+ const ids = [...new Set(primary.matches.map((m) => m.entry.id))].sort();
106
+ session.upgradeIntent = {
107
+ provider: primary.pack.provider,
108
+ from_major: primary.pack.from_major,
109
+ to_major: primary.pack.to_major,
110
+ pack_version: primary.pack.version,
111
+ pack_source: primary.pack.source,
112
+ pack_exhaustive: primary.pack.exhaustive,
113
+ transform_ids: ids,
114
+ needing_human: new Set(primary.matches.filter((m) => m.needs_human).map((m) => m.entry.id))
115
+ .size,
116
+ };
117
+ }
118
+
119
+ const needsHuman = allMatches.filter((m) => m.needs_human);
120
+ // Sort needs-human first, THEN truncate. Match order is pack-entry order, so
121
+ // a zone with 200 renamed DNS records would otherwise fill every slot and
122
+ // push the four state-moving transforms off the end — leaving the agent told
123
+ // "4 transforms move state, name them in the PR body" with no way to know
124
+ // which four. The dangerous ones are exactly the ones that must survive a cut.
125
+ const reported = [...allMatches]
126
+ .sort((a, b) => Number(b.needs_human) - Number(a.needs_human))
127
+ .slice(0, MAX_REPORTED);
128
+
129
+ return toolOk({
130
+ applicable: true,
131
+ pinned_providers: pinned,
132
+ packs: results.map((r) => ({
133
+ provider: r.pack.provider,
134
+ from_major: r.pack.from_major,
135
+ to_major: r.pack.to_major,
136
+ pack_version: r.pack.version,
137
+ source: r.pack.source,
138
+ exhaustive: r.pack.exhaustive,
139
+ ...(r.pack.official_tool ? { official_tool: r.pack.official_tool } : {}),
140
+ matched: r.matches.length,
141
+ })),
142
+ // Sites, not transforms: one entry firing on twelve resources is twelve
143
+ // matches. The signed bundle's `transforms_detected` counts DEDUPED entry
144
+ // ids, so the two are named differently rather than disagreeing under one
145
+ // name — for a big repo they differ by an order of magnitude.
146
+ matched_sites: allMatches.length,
147
+ transforms_detected: new Set(allMatches.map((m) => m.entry.id)).size,
148
+ transforms_needing_human: new Set(needsHuman.map((m) => m.entry.id)).size,
149
+ transforms: reported.map((m) => {
150
+ const proven = fleet.get(m.entry.id);
151
+ return {
152
+ id: m.entry.id,
153
+ kind: m.entry.kind,
154
+ ...(m.entry.resource_type ? { resource_type: m.entry.resource_type } : {}),
155
+ from: m.entry.from,
156
+ ...(m.entry.to ? { to: m.entry.to } : {}),
157
+ ...(m.file ? { file: m.file } : {}),
158
+ ...(m.resource_name ? { resource_name: m.resource_name } : {}),
159
+ confidence: m.confidence,
160
+ schema_agreement: m.schema_agreement,
161
+ state_move: m.entry.state_move,
162
+ needs_human: m.needs_human,
163
+ ...(m.needs_human_reason ? { needs_human_reason: m.needs_human_reason } : {}),
164
+ ...(m.entry.value_change ? { value_change: m.entry.value_change } : {}),
165
+ note: m.entry.note,
166
+ // fleet reuse (§5.12-shaped): counters only, never another repo's config.
167
+ ...(proven?.times_proven ? { fleet_times_proven: proven.times_proven } : {}),
168
+ ...(proven?.repos_count ? { fleet_repos_count: proven.repos_count } : {}),
169
+ };
170
+ }),
171
+ ...(allMatches.length > MAX_REPORTED
172
+ ? {
173
+ truncated: `showing ${MAX_REPORTED} of ${allMatches.length} matched sites, needs-human first — fix these, re-run, and the rest will surface.`,
174
+ }
175
+ : {}),
176
+ note: buildNote(results, new Set(needsHuman.map((m) => m.entry.id)).size),
177
+ });
178
+ }
179
+
180
+ function buildNote(results: PackResult[], needsHuman: number): string {
181
+ const parts: string[] = [];
182
+ const tool = results.find((r) => r.pack.official_tool)?.pack.official_tool;
183
+ if (tool) {
184
+ parts.push(
185
+ `The provider vendor ships its own migration tool for this upgrade — ${tool}. Run it FIRST and review its diff; these entries are for judging what it produced, not for redoing its work.`,
186
+ );
187
+ }
188
+ if (needsHuman > 0) {
189
+ parts.push(
190
+ `${needsHuman} transform(s) MOVE STATE or force replacement. Do not apply them autonomously: a config-only edit there destroys and recreates live resources. Split them into a separate PR, label it \`needs-human\`, and say in the body exactly which resources are affected.`,
191
+ );
192
+ }
193
+ parts.push(
194
+ "A pack entry is a LEAD, not a patch. Apply the edit yourself, then prove it: `terraform_validate` (its `unknown_arguments` reads the installed schema and is the authoritative check for anything this pack missed — the packs are explicitly not exhaustive), then `terraform_plan`. An entry marked `unverified` was NOT corroborated by the installed provider schema — treat it as a hint to check, never as fact.",
195
+ );
196
+ return parts.join(" ");
197
+ }
198
+
199
+ export function TerraformProviderUpgradeTool(ctx: LocalToolContext) {
200
+ return tool({
201
+ name: "terraform_provider_upgrade",
202
+ description:
203
+ "Codified provider-major migrations (transform packs). Reads the pinned provider majors from " +
204
+ "`required_providers`, selects the built-in pack for that major boundary (azurerm v3→v4, " +
205
+ "cloudflare v4→v5), and reports every documented breaking change this configuration actually " +
206
+ "contains: renamed and removed arguments, removed resource types, newly-required arguments, and " +
207
+ "provider-block changes — each with the file it is in. Call it BEFORE editing when raising a " +
208
+ "provider major. Two things it tells you that nothing else does: which changes MOVE STATE (those " +
209
+ "destroy and recreate live resources unless a human runs `state rm` + `import`, and must never be " +
210
+ "applied autonomously), and which changes are NOT pure renames (a value that must be inverted or " +
211
+ "retyped). Entries are cross-checked against the INSTALLED provider schema; one the schema does not " +
212
+ "corroborate is returned as `unverified`. Packs are not exhaustive — `terraform_validate`'s " +
213
+ "`unknown_arguments` remains the authoritative completeness check. Read-only.",
214
+ parameters: TerraformProviderUpgradeParams,
215
+ execute: execute(() => Promise.resolve(runProviderUpgrade(ctx))),
216
+ });
217
+ }