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,771 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { readFileSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { COST_FILE, PRODUCT_NAME } from "#app/brand";
5
+ import { groupIdFromBranch } from "#app/mcp/staleFix";
6
+ import { scanSessionOf } from "#app/mcp/terraform/scanSession";
7
+ import type { ToolState } from "#app/toolState";
8
+ import { log } from "#app/utils/cli";
9
+ import type { OctokitWithPlugins } from "#app/utils/github";
10
+ import type { ResolvedPayload } from "#app/utils/payload";
11
+ import type { RunContextData } from "#app/utils/runContextData";
12
+ import { resolveEnv } from "#app/utils/secrets";
13
+ import { $ } from "#app/utils/shell";
14
+ import { resolveToolSelection } from "#app/utils/toolSelection";
15
+
16
+ /**
17
+ * The narrow context the push-time guardrails read: the payload (allow-lists,
18
+ * tool selection, protected paths, PR caps), the tool state (selected mode,
19
+ * run-start baseline, recorded plan verdicts), and tmpdir (a gitleaks report
20
+ * lands here). No GitHub/git tokens — these gates inspect local state + the local
21
+ * git tree, so both `GitToolContext` and the GitHub write context satisfy them.
22
+ */
23
+ export interface GuardrailContext {
24
+ payload: ResolvedPayload;
25
+ toolState: ToolState;
26
+ tmpdir: string;
27
+ }
28
+
29
+ /**
30
+ * The cross-run open-PR flood guard additionally needs the GitHub client + repo
31
+ * coordinates to count PRs the run can't see in local state. Only
32
+ * {@link assertUnderOpenPrCaps} requires it; the pure push-time gates take the
33
+ * narrower {@link GuardrailContext}.
34
+ */
35
+ export interface OpenPrCapContext extends GuardrailContext {
36
+ octokit: OctokitWithPlugins;
37
+ repo: RunContextData["repo"];
38
+ }
39
+
40
+ /**
41
+ * Terraform-write guardrails — hard, code-level limits that back the prompt
42
+ * rules of the modes that write Terraform and open PRs (**Remediate** and the
43
+ * **RefreshRemediation** sweep). They only engage for those modes, so every other
44
+ * mode (Build, Fix, Review, …) is completely unaffected.
45
+ */
46
+
47
+ export const REMEDIATE_MODE = "Remediate";
48
+ /** the stale-fix self-healing sweep re-derives + force-updates remediation
49
+ * PRs, so it writes Terraform and pushes exactly like Remediate and is bounded by
50
+ * the same guardrails. */
51
+ export const REFRESH_REMEDIATION_MODE = "RefreshRemediation";
52
+ /** the `refactor` verb relocates resources behind a module boundary and pushes
53
+ * Terraform like Remediate, so it is bounded by the same allow-list / secret /
54
+ * destroy guardrails — PLUS the equivalence hard-fail below (a refactor that
55
+ * can't prove behaviour-preservation must not reach a human). */
56
+ export const REFACTOR_MODE = "Refactor";
57
+ /** the dependency / deprecation / cost verbs each write Terraform and push
58
+ * exactly like Remediate, so they are bounded by the same allow-list / secret /
59
+ * destroy / PR-cap guardrails. UpdateDependencies also runs the equivalence
60
+ * check for config-preserving bumps and so inherits the equivalence hard-fail
61
+ * below; ModernizeDeprecated and CostOptimization are behaviour-altering and
62
+ * prove via plan, so they never record an equivalence verdict and that one gate
63
+ * no-ops for them. */
64
+ export const UPDATE_DEPENDENCIES_MODE = "UpdateDependencies";
65
+ export const MODERNIZE_DEPRECATED_MODE = "ModernizeDeprecated";
66
+ export const COST_OPTIMIZATION_MODE = "CostOptimization";
67
+ /** the combined verb: it both remediates (a ✗→✓ fix, like Remediate) and
68
+ * refactors (an equivalence-proven modularisation, like Refactor) on one branch,
69
+ * so it is bounded by EVERY guardrail of both — the allow-list / secret /
70
+ * destroy / PR caps AND the equivalence hard-fail (which gates the refactor half
71
+ * once terraform_equivalence_check runs; no-op until it does). */
72
+ export const REMEDIATE_AND_REFACTOR_MODE = "RemediateAndRefactor";
73
+
74
+ /** default paths these modes may modify/create: Terraform sources only. */
75
+ export const DEFAULT_ALLOWED_PATHS = ["**/*.tf", "**/*.tfvars"] as const;
76
+
77
+ /** extra paths the Terratest scaffold writes, allowed only when the
78
+ * `terratest` input is enabled (Go test files + native `*.tftest.hcl` tests fall
79
+ * outside the Terraform-only default). */
80
+ export const TERRATEST_ALLOWED_PATHS = [
81
+ "**/*_test.go",
82
+ "**/*.tftest.hcl",
83
+ "test/**",
84
+ "tests/**",
85
+ "go.mod",
86
+ "go.sum",
87
+ ] as const;
88
+
89
+ /** doc paths writable only when the `docs` input is enabled — the
90
+ * terraform_module_docs surface. Deliberately narrow: module READMEs and the
91
+ * infraweaver docs dir only; the repo root README and arbitrary `docs/**` stay
92
+ * blocked even with the input on (fail-closed, mirroring the Terratest
93
+ * carve-out above). */
94
+ export const DOCS_ALLOWED_PATHS = [
95
+ "modules/**/README.md",
96
+ "docs/infraweaver/**/*.md",
97
+ ] as const;
98
+
99
+ /** the cost artifact, writable only by the CostOptimization mode (§5.19). A
100
+ * single exact filename rather than a glob — the same fail-closed narrowness as
101
+ * the docs carve-out, so no other markdown becomes writable by accident. */
102
+ export const COST_ALLOWED_PATHS = [COST_FILE] as const;
103
+
104
+ /** modes whose pushes/PRs are bounded by these guardrails — exported (ordered)
105
+ * so the security-model doc generator renders the real list, not a copy. */
106
+ export const GUARDED_MODE_NAMES = [
107
+ REMEDIATE_MODE,
108
+ REFRESH_REMEDIATION_MODE,
109
+ REFACTOR_MODE,
110
+ REMEDIATE_AND_REFACTOR_MODE,
111
+ UPDATE_DEPENDENCIES_MODE,
112
+ MODERNIZE_DEPRECATED_MODE,
113
+ COST_OPTIMIZATION_MODE,
114
+ ] as const;
115
+
116
+ const GUARDED_MODES: ReadonlySet<string> = new Set(GUARDED_MODE_NAMES);
117
+
118
+ /** Whether a mode NAME is one whose pushes/PRs are bounded by these guardrails.
119
+ * Pure (no ctx) so read-only tools — e.g. terraform_plan, which can't see the
120
+ * full push surface — can tell whether a destructive plan WOULD block a push. */
121
+ export function isGuardedModeName(mode: string | undefined): boolean {
122
+ return mode !== undefined && GUARDED_MODES.has(mode);
123
+ }
124
+
125
+ function isGuardedMode(ctx: GuardrailContext): boolean {
126
+ return isGuardedModeName(ctx.toolState.selectedMode);
127
+ }
128
+
129
+ export function resolveAllowedPaths(ctx: GuardrailContext): string[] {
130
+ const configured = ctx.payload.allowedPaths;
131
+ const base =
132
+ configured && configured.length > 0
133
+ ? [...configured]
134
+ : [...DEFAULT_ALLOWED_PATHS];
135
+ // when Terratest scaffolding is opted in (the `terratest` input OR the
136
+ // unified tools_enabled list), also permit the Go test + example-fixture
137
+ // paths the scaffold writes (they're outside the .tf default).
138
+ if (resolveToolSelection(ctx.payload).enabled("terratest"))
139
+ base.push(...TERRATEST_ALLOWED_PATHS);
140
+ // when the `docs` input is opted in, also permit the evidence-backed module
141
+ // README paths terraform_module_docs writes. A plain payload flag (not a
142
+ // tools_enabled token): docs generation is built-in, not an external tool.
143
+ if (ctx.payload.docs) base.push(...DOCS_ALLOWED_PATHS);
144
+ // the cost artifact is only ever written by the cost verb, so the carve-out is
145
+ // scoped to that mode rather than opened for every guarded mode.
146
+ if (ctx.toolState.selectedMode === COST_OPTIMIZATION_MODE)
147
+ base.push(...COST_ALLOWED_PATHS);
148
+ return base;
149
+ }
150
+
151
+ /**
152
+ * Compile a glob to an anchored RegExp. Supports `**` (any path segments,
153
+ * including the `**\/` "zero or more leading dirs" idiom), `*` (within a
154
+ * segment), and `?`. Sufficient for the path allow-list patterns
155
+ * (`**\/*.tf`, `modules/**`, `*.tfvars`).
156
+ */
157
+ export function globToRegex(glob: string): RegExp {
158
+ let re = "";
159
+ for (let i = 0; i < glob.length; i++) {
160
+ const c = glob[i]!;
161
+ if (c === "*") {
162
+ if (glob[i + 1] === "*") {
163
+ if (glob[i + 2] === "/") {
164
+ re += "(?:.*/)?";
165
+ i += 2;
166
+ } else {
167
+ re += ".*";
168
+ i += 1;
169
+ }
170
+ } else {
171
+ re += "[^/]*";
172
+ }
173
+ } else if (c === "?") {
174
+ re += "[^/]";
175
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: literal regex metacharacters, not a template interpolation
176
+ } else if (".+^${}()|[]\\".includes(c)) {
177
+ re += `\\${c}`;
178
+ } else {
179
+ re += c;
180
+ }
181
+ }
182
+ return new RegExp(`^${re}$`);
183
+ }
184
+
185
+ export function isPathAllowed(path: string, globs: string[]): boolean {
186
+ const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
187
+ return globs.some((g) => globToRegex(g).test(normalized));
188
+ }
189
+
190
+ /** the run-entry commit sha, used as the baseline for "what this run changed". */
191
+ function runStartSha(ctx: GuardrailContext): string | null {
192
+ const head = ctx.toolState.initialHead;
193
+ if (!head) return null;
194
+ try {
195
+ const ref = head.kind === "branch" ? head.name : head.sha;
196
+ return $("git", ["rev-parse", ref], { log: false });
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+
202
+ /** files changed on the current branch since the run started. */
203
+ function changedFilesSinceRunStart(ctx: GuardrailContext): string[] {
204
+ const base = runStartSha(ctx);
205
+ if (!base) return [];
206
+ // `$` returns trimmed stdout on success and throws on a non-zero exit (no
207
+ // onError handler) — so a genuine git failure here propagates and the caller
208
+ // fails closed (the push is refused) rather than treating an errored diff as
209
+ // "nothing changed". `git diff --name-only` exits 0 on a clean diff.
210
+ const out = $("git", ["diff", "--name-only", base, "HEAD"], { log: false });
211
+ return out
212
+ .split("\n")
213
+ .map((l) => l.trim())
214
+ .filter(Boolean);
215
+ }
216
+
217
+ /**
218
+ * Enforce the path allow-list before a Remediate-mode push. Throws if the
219
+ * branch changed any file outside the allowed globs — the choke point is
220
+ * push_branch, the only way changes reach a PR. Fails closed: if the baseline
221
+ * can't be established it refuses rather than letting an unbounded change
222
+ * through.
223
+ */
224
+ export function enforceRemediationPaths(ctx: GuardrailContext): void {
225
+ if (!isGuardedMode(ctx)) return;
226
+
227
+ const base = runStartSha(ctx);
228
+ if (!base) {
229
+ throw new Error(
230
+ "push blocked (Terraform-only guardrail): could not establish the run-start commit to verify the change is limited to Terraform files. " +
231
+ "Ensure the run started from a clean checkout.",
232
+ );
233
+ }
234
+
235
+ const allowed = resolveAllowedPaths(ctx);
236
+ const changed = changedFilesSinceRunStart(ctx);
237
+ const violations = changed.filter((f) => !isPathAllowed(f, allowed));
238
+ if (violations.length > 0) {
239
+ throw new Error(
240
+ `push blocked (Terraform-only guardrail): this run changed files outside the allowed paths [${allowed.join(", ")}]. ` +
241
+ `This mode must only touch Terraform files. Revert these and keep the change to Terraform only:\n` +
242
+ violations.map((v) => ` - ${v}`).join("\n"),
243
+ );
244
+ }
245
+ log.info(
246
+ `» Terraform-only path guardrail ok (${changed.length} file(s), all within [${allowed.join(", ")}])`,
247
+ );
248
+ }
249
+
250
+ // --- protected-resource allowlist -------------------------------------
251
+
252
+ /** glob patterns marking files the fixer must NEVER auto-modify (prod state,
253
+ * data stores, anything sensitive). The inverse of `allowed_paths`. */
254
+ export function resolveProtectedPaths(ctx: GuardrailContext): string[] {
255
+ return ctx.payload.protectedPaths ?? [];
256
+ }
257
+
258
+ /**
259
+ * Block a push that touched any file matching `protected_paths`. This is the
260
+ * inverse of the allow-list: a changed file matching a protected glob fails the
261
+ * push, even though it's a `.tf`/`.tfvars` the allow-list would otherwise permit.
262
+ * No-op when `protected_paths` is unset or outside a guarded mode. Fails closed:
263
+ * if the run-start baseline can't be established it refuses, same as
264
+ * `enforceRemediationPaths`.
265
+ */
266
+ export function enforceProtectedPaths(ctx: GuardrailContext): void {
267
+ if (!isGuardedMode(ctx)) return;
268
+ const protectedGlobs = resolveProtectedPaths(ctx);
269
+ if (protectedGlobs.length === 0) return;
270
+
271
+ const base = runStartSha(ctx);
272
+ if (!base) {
273
+ throw new Error(
274
+ "push blocked (protected-paths guardrail): could not establish the run-start commit to verify no protected path was modified. " +
275
+ "Ensure the run started from a clean checkout.",
276
+ );
277
+ }
278
+
279
+ const changed = changedFilesSinceRunStart(ctx);
280
+ const violations = changed.filter((f) => isPathAllowed(f, protectedGlobs));
281
+ if (violations.length > 0) {
282
+ throw new Error(
283
+ `push blocked (protected-paths guardrail): this run modified files matching the protected_paths globs [${protectedGlobs.join(", ")}], ` +
284
+ `which are marked never-auto-modify. Revert these and leave them for a human:\n` +
285
+ violations.map((v) => ` - ${v}`).join("\n"),
286
+ );
287
+ }
288
+ log.info(
289
+ `» protected-paths guardrail ok (no change matched [${protectedGlobs.join(", ")}])`,
290
+ );
291
+ }
292
+
293
+ // --- read-only (vendored) module guard --------------------------------
294
+
295
+ /** globs marking FETCHED (vendored) module code — a private cross-repo `git::`
296
+ * module (refactor b.1) or a third-party registry module (b.4) that `terraform
297
+ * init` resolved into the local module cache. The refactor contract is that such
298
+ * a module is READ-ONLY: a concern inside it is fixed at the CALL SITE via inputs,
299
+ * never by editing the vendored copy, and a separate-repo b.1 module must never
300
+ * receive a write change. This is the code-level backing for that rule. */
301
+ export const VENDORED_MODULE_PATHS = [
302
+ "**/.terraform/modules/**",
303
+ "**/.external_modules/**",
304
+ ] as const;
305
+
306
+ /**
307
+ * Block a push that committed an edit to FETCHED (vendored) module code. The
308
+ * Terraform-only allow-list (`**​/*.tf`) would otherwise PERMIT
309
+ * `.terraform/modules/<x>/main.tf`, so without this a refactor could silently
310
+ * vendor-and-edit a read-only module's source — exactly what the b.1/b.4 contract
311
+ * forbids ("never open a write PR against a read-only module repo; never edit
312
+ * vendored module code"). Engages only in a guarded mode; fails closed on a
313
+ * missing baseline, like {@link enforceRemediationPaths}. No-op when nothing under
314
+ * the module cache was committed (the normal case — it's usually gitignored).
315
+ */
316
+ export function assertNoVendoredModuleEdits(ctx: GuardrailContext): void {
317
+ if (!isGuardedMode(ctx)) return;
318
+ const base = runStartSha(ctx);
319
+ if (!base) {
320
+ throw new Error(
321
+ "push blocked (read-only module guardrail): could not establish the run-start commit to verify no vendored module code was edited. Ensure the run started from a clean checkout.",
322
+ );
323
+ }
324
+ const changed = changedFilesSinceRunStart(ctx);
325
+ const violations = changed.filter((f) =>
326
+ isPathAllowed(f, [...VENDORED_MODULE_PATHS]),
327
+ );
328
+ if (violations.length > 0) {
329
+ throw new Error(
330
+ "push blocked (read-only module guardrail): this run modified FETCHED module code under the local " +
331
+ "module cache. A module in a separate repo (b.1) or a third-party registry module (b.4) is READ-ONLY — " +
332
+ "fix a module-internal concern at the CALL SITE via the module's inputs, never by editing the vendored " +
333
+ "copy. Revert these:\n" +
334
+ violations.map((v) => ` - ${v}`).join("\n"),
335
+ );
336
+ }
337
+ log.info(
338
+ "» read-only module guardrail ok (no vendored module code was edited)",
339
+ );
340
+ }
341
+
342
+ // --- secrets-safe diff scan -------------------------------------------
343
+
344
+ export interface SecretHit {
345
+ file: string;
346
+ line: number;
347
+ rule: string;
348
+ }
349
+
350
+ /**
351
+ * High-signal secret detectors applied to lines a fix ADDED. Kept deliberately
352
+ * narrow (low false-positive) — the goal is to stop a "fix" that hardcodes a
353
+ * literal credential (e.g. resolving a "use a variable" finding by pasting the
354
+ * secret), not to be a general-purpose scanner. Each entry is [rule, regex].
355
+ */
356
+ const SECRET_VALUE_PATTERNS: ReadonlyArray<readonly [string, RegExp]> = [
357
+ [
358
+ "aws-access-key-id",
359
+ /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA)[0-9A-Z]{16}\b/,
360
+ ],
361
+ [
362
+ "pem-private-key",
363
+ /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/,
364
+ ],
365
+ ["gcp-api-key", /\bAIza[0-9A-Za-z_-]{35}\b/],
366
+ ["github-token", /\bgh[pousr]_[0-9A-Za-z]{36,}\b/],
367
+ ["slack-token", /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/],
368
+ ["private-key-pem-block", /-----BEGIN PRIVATE KEY-----/],
369
+ ];
370
+
371
+ // an HCL/tfvars assignment of a STRING LITERAL to a secret-named attribute, e.g.
372
+ // `password = "hunter2"` or `secret_access_key = "AKIA..."`. The captured value is
373
+ // filtered in code (see scanDiffForSecrets) to exclude empty strings and any value
374
+ // containing a Terraform interpolation `${...}` — references like `= var.x` /
375
+ // `= local.y` already fail the quoted-value match. Only a real inlined literal trips it.
376
+ const SENSITIVE_ASSIGNMENT =
377
+ /\b(?:password|passwd|secret|secret_key|secret_access_key|access_key|api_key|apikey|auth_token|access_token|private_key|client_secret|credential|connection_string)\b\s*[=:]\s*"([^"]*)"/i;
378
+
379
+ /**
380
+ * Scan a unified `git diff` for inlined secrets on ADDED lines only. Tracks the
381
+ * current file from `+++ b/<path>` headers and the new-side line number from
382
+ * `@@` hunk headers, so each hit carries an accurate `file:line`. Pure — the
383
+ * guardrail feeds it `git diff` output. Removed/context lines are ignored (a
384
+ * secret already in the base isn't this run's doing).
385
+ */
386
+ export function scanDiffForSecrets(diff: string): SecretHit[] {
387
+ const hits: SecretHit[] = [];
388
+ let file = "(unknown)";
389
+ let newLine = 0;
390
+ for (const raw of diff.split("\n")) {
391
+ if (raw.startsWith("+++ ")) {
392
+ const path = raw.slice(4).trim().replace(/^b\//, "");
393
+ file = path === "/dev/null" ? "(deleted)" : path;
394
+ continue;
395
+ }
396
+ if (
397
+ raw.startsWith("--- ") ||
398
+ raw.startsWith("diff --git") ||
399
+ raw.startsWith("index ")
400
+ )
401
+ continue;
402
+ const hunk = raw.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
403
+ if (hunk) {
404
+ newLine = Number(hunk[1]);
405
+ continue;
406
+ }
407
+ if (raw.startsWith("+")) {
408
+ const content = raw.slice(1);
409
+ for (const [rule, re] of SECRET_VALUE_PATTERNS) {
410
+ if (re.test(content)) hits.push({ file, line: newLine, rule });
411
+ }
412
+ // only a real inlined literal trips it: skip empty strings and any value
413
+ // carrying a `${...}` interpolation — including one with literal text before
414
+ // the interpolation (e.g. `connection_string = "postgres://u:${var.pw}@h"`),
415
+ // which the prior first-char-only exclusion wrongly flagged and dead-ended.
416
+ const secretValue = content.match(SENSITIVE_ASSIGNMENT)?.[1];
417
+ if (secretValue && !secretValue.includes("${")) {
418
+ hits.push({ file, line: newLine, rule: "hardcoded-secret-assignment" });
419
+ }
420
+ newLine++;
421
+ } else if (raw.startsWith("-")) {
422
+ // removed line — does not advance the new-side counter
423
+ } else {
424
+ // context line (leading space) or blank — advances the new-side counter
425
+ newLine++;
426
+ }
427
+ }
428
+ return hits;
429
+ }
430
+
431
+ /**
432
+ * Parse a `gitleaks detect --report-format json` report (an array of finding
433
+ * objects) into the shared `SecretHit` shape. Pure, so it's unit-testable
434
+ * without the binary. `gitleaks:` prefixes the rule so a hit's engine is
435
+ * obvious next to the built-in detectors. Tolerates an empty / non-array report.
436
+ */
437
+ export function parseGitleaksReport(json: string): SecretHit[] {
438
+ let parsed: unknown;
439
+ try {
440
+ parsed = JSON.parse(json || "[]");
441
+ } catch {
442
+ return [];
443
+ }
444
+ if (!Array.isArray(parsed)) return [];
445
+ const hits: SecretHit[] = [];
446
+ for (const f of parsed as Array<Record<string, unknown>>) {
447
+ const file = typeof f.File === "string" && f.File ? f.File : "(unknown)";
448
+ const line = typeof f.StartLine === "number" ? f.StartLine : 0;
449
+ const rule = typeof f.RuleID === "string" && f.RuleID ? f.RuleID : "secret";
450
+ hits.push({ file, line, rule: `gitleaks:${rule}` });
451
+ }
452
+ return hits;
453
+ }
454
+
455
+ /**
456
+ * Optional deeper secret scan via the external `gitleaks` binary, opt-in through
457
+ * the `gitleaks` action input. Best-effort: returns `null` when gitleaks isn't
458
+ * installed or can't run (the built-in scanner already provides the fail-closed
459
+ * baseline, so an absent gitleaks degrades to "built-in only" rather than
460
+ * failing the push). Scans the commits this run added (`<base>..HEAD`) and uses
461
+ * `--exit-code 0` so a leak doesn't make the process exit non-zero — we read the
462
+ * JSON report instead. Restricted env, so no secret leaks into the subprocess.
463
+ */
464
+ function scanWithGitleaks(
465
+ ctx: GuardrailContext,
466
+ base: string,
467
+ ): SecretHit[] | null {
468
+ const reportPath = join(ctx.tmpdir, `gitleaks-report-${process.pid}.json`);
469
+ const cwd = ctx.payload.cwd ?? process.cwd();
470
+ const result = spawnSync(
471
+ "gitleaks",
472
+ [
473
+ "detect",
474
+ "--source",
475
+ ".",
476
+ "--log-opts",
477
+ `${base}..HEAD`,
478
+ "--report-format",
479
+ "json",
480
+ "--report-path",
481
+ reportPath,
482
+ "--exit-code",
483
+ "0",
484
+ "--no-banner",
485
+ "--redact",
486
+ ],
487
+ {
488
+ cwd,
489
+ encoding: "utf-8",
490
+ env: resolveEnv("restricted") as NodeJS.ProcessEnv,
491
+ maxBuffer: 64 * 1024 * 1024,
492
+ },
493
+ );
494
+ if (result.error) {
495
+ const missing = (result.error as NodeJS.ErrnoException).code === "ENOENT";
496
+ log.warning(
497
+ missing
498
+ ? "» gitleaks requested but not installed — falling back to the built-in secret scanner only"
499
+ : `» gitleaks could not run (${result.error.message}) — built-in secret scanner still enforced`,
500
+ );
501
+ return null;
502
+ }
503
+ try {
504
+ const report = readFileSync(reportPath, "utf8");
505
+ return parseGitleaksReport(report);
506
+ } catch {
507
+ // no report file written usually means a clean scan; treat as no hits.
508
+ return [];
509
+ } finally {
510
+ try {
511
+ rmSync(reportPath, { force: true });
512
+ } catch {
513
+ /* best-effort cleanup */
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Block a push whose diff (since run start) inlines a secret. Reuses the same
520
+ * run-start baseline as the path guardrail. No-op outside a guarded mode. Fails
521
+ * closed on a missing baseline. The diff is read with `$` (restricted env), so
522
+ * no secret leaks into the subprocess.
523
+ *
524
+ * The built-in detectors always run (the deterministic, fail-closed baseline).
525
+ * When the operator opts in via the `gitleaks` input, gitleaks ALSO runs for
526
+ * deeper coverage and its hits are merged — but its absence never weakens the
527
+ * baseline (see scanWithGitleaks).
528
+ */
529
+ export function assertNoSecretsInDiff(ctx: GuardrailContext): void {
530
+ if (!isGuardedMode(ctx)) return;
531
+ const base = runStartSha(ctx);
532
+ if (!base) {
533
+ throw new Error(
534
+ "push blocked (secret-scan guardrail): could not establish the run-start commit to scan the diff for inlined secrets. " +
535
+ "Ensure the run started from a clean checkout.",
536
+ );
537
+ }
538
+ const diff = $("git", ["diff", base, "HEAD"], { log: false });
539
+ const hits = scanDiffForSecrets(diff);
540
+
541
+ // optional deeper engine — merged on top of the built-in baseline. Opted in
542
+ // via the `gitleaks` input OR by naming "gitleaks" in the unified tools_enabled
543
+ // list; an explicit `-gitleaks` there turns it back off.
544
+ const gitleaksEnabled = resolveToolSelection(ctx.payload).enabled("gitleaks");
545
+ if (gitleaksEnabled) {
546
+ const gitleaksHits = scanWithGitleaks(ctx, base);
547
+ if (gitleaksHits) hits.push(...gitleaksHits);
548
+ }
549
+
550
+ if (hits.length > 0) {
551
+ throw new Error(
552
+ `push blocked (secret-scan guardrail): the change appears to inline ${hits.length} secret(s) — a fix must reference a variable/secret store, never paste a literal. ` +
553
+ `Remove or parameterise these and re-push:\n` +
554
+ hits.map((h) => ` - ${h.file}:${h.line} (${h.rule})`).join("\n"),
555
+ );
556
+ }
557
+ log.info(
558
+ `» secret-scan guardrail ok (no inlined secrets in the diff${gitleaksEnabled ? ", built-in + gitleaks" : ""})`,
559
+ );
560
+ }
561
+
562
+ /** resource addresses the operator has explicitly allowed to be destroyed/replaced. */
563
+ export function resolveAllowReplace(ctx: GuardrailContext): string[] {
564
+ return ctx.payload.allowReplace ?? [];
565
+ }
566
+
567
+ export function isReplaceAllowed(
568
+ address: string,
569
+ allowList: string[],
570
+ ): boolean {
571
+ return allowList.some(
572
+ (a) =>
573
+ a === "*" || a === "all" || a === address || globToRegex(a).test(address),
574
+ );
575
+ }
576
+
577
+ /** Split a plan's stateful destroy/replace set by whether the operator's
578
+ * `allow_replace` list covers each address. `blocked` is what
579
+ * {@link assertNoBlockedDestroy} throws on at push — surfacing it at plan time
580
+ * lets a committing mode abandon BEFORE the wasted commit/push round-trip. */
581
+ export function partitionStatefulDestroy<T extends { address: string }>(
582
+ stateful: T[],
583
+ allowList: string[],
584
+ ): { blocked: T[]; allowed: T[] } {
585
+ const blocked: T[] = [];
586
+ const allowed: T[] = [];
587
+ for (const r of stateful) {
588
+ (isReplaceAllowed(r.address, allowList) ? allowed : blocked).push(r);
589
+ }
590
+ return { blocked, allowed };
591
+ }
592
+
593
+ /**
594
+ * Block a push that `terraform_plan` showed would DELETE or REPLACE a stateful
595
+ * (data-bearing) resource — RDS, S3, EBS, a SQL database, etc. A best-practice
596
+ * remediation should never destroy data; if the replacement is genuinely
597
+ * intended the operator opts in per-resource via the `allow_replace` input
598
+ * (an address, a glob, or `*`/`all`). No-op outside guarded modes. When no plan
599
+ * ran (no cloud credentials — `terraform_plan` degraded green), there is no
600
+ * evidence to act on and nothing is blocked: this gate engages only on what the
601
+ * plan actually reported, so it strengthens the run when creds are wired and is
602
+ * silent otherwise.
603
+ */
604
+ export function assertNoBlockedDestroy(ctx: GuardrailContext): void {
605
+ if (!isGuardedMode(ctx)) return;
606
+ const planned = scanSessionOf(ctx.toolState).plannedDestroy;
607
+ if (!planned || planned.stateful.length === 0) return;
608
+
609
+ const allow = resolveAllowReplace(ctx);
610
+ const { blocked } = partitionStatefulDestroy(planned.stateful, allow);
611
+ if (blocked.length === 0) {
612
+ log.info(
613
+ `» destroy-block guardrail ok (${planned.stateful.length} stateful destroy/replace allowed via allow_replace)`,
614
+ );
615
+ return;
616
+ }
617
+ throw new Error(
618
+ `push blocked (Terraform-only guardrail): terraform plan shows this change would DESTROY or REPLACE ` +
619
+ `${blocked.length} stateful resource(s), which would likely cause data loss — a best-practice ` +
620
+ `remediation should not. Abandon this change, or, ONLY if the replacement is genuinely intended, ` +
621
+ `set the \`allow_replace\` input to include the resource address(es):\n` +
622
+ blocked
623
+ .map((r) => ` - ${r.address} (${r.action}, ${r.type})`)
624
+ .join("\n"),
625
+ );
626
+ }
627
+
628
+ /**
629
+ * Block a push whose `refactor` did not PROVE behaviour-equivalence. Acts on the
630
+ * `terraform_equivalence_check` verdict recorded in tool state — most critically
631
+ * an UNCOVERED `moved {}` block, which would silently destroy and recreate live
632
+ * resources on apply (the single highest-risk refactor defect, a sin of
633
+ * commission far worse than a missed finding). Like the destroy-block gate, it
634
+ * engages only on evidence the proof tool actually produced: no equivalence check
635
+ * recorded → nothing to act on → no-op (so a remediation, which never runs the
636
+ * check, is unaffected). No-op outside a guarded mode. When the recorded verdict
637
+ * is `equivalent: false`, the push is refused with the proof's own reasons.
638
+ */
639
+ export function assertRefactorEquivalence(ctx: GuardrailContext): void {
640
+ if (!isGuardedMode(ctx)) return;
641
+ const proof = scanSessionOf(ctx.toolState).refactorEquivalence;
642
+ if (!proof || proof.equivalent) {
643
+ if (proof?.equivalent)
644
+ log.info(
645
+ "» refactor equivalence guardrail ok (behaviour-preserving proven)",
646
+ );
647
+ return;
648
+ }
649
+ const uncovered =
650
+ proof.uncovered_moves.length > 0
651
+ ? `\nUNCOVERED moves (these would be DESTROYED and RECREATED on apply — add a \`moved {}\` block for each):\n` +
652
+ proof.uncovered_moves.map((m) => ` - ${m.from} → ${m.to}`).join("\n")
653
+ : "";
654
+ throw new Error(
655
+ `push blocked (refactor equivalence guardrail): terraform_equivalence_check could NOT prove this ` +
656
+ `refactor preserves behaviour, so it must not reach a human as an equivalence claim. Resolve these and ` +
657
+ `re-prove before pushing:\n` +
658
+ proof.reasons.map((r) => ` - ${r}`).join("\n") +
659
+ uncovered,
660
+ );
661
+ }
662
+
663
+ /** maximum remediation PRs a single run may open (default 1). */
664
+ export function resolveMaxPrs(ctx: GuardrailContext): number {
665
+ return ctx.payload.maxPrs ?? 1;
666
+ }
667
+
668
+ /**
669
+ * Enforce the per-run PR cap before opening a remediation PR. Throws when the
670
+ * cap is already reached so the agent stops at the configured number of scoped
671
+ * PRs instead of fanning out.
672
+ */
673
+ export function assertUnderPrCap(ctx: GuardrailContext): void {
674
+ if (!isGuardedMode(ctx)) return;
675
+ const cap = resolveMaxPrs(ctx);
676
+ const opened = ctx.toolState.remediationPrsOpened ?? 0;
677
+ if (opened >= cap) {
678
+ throw new Error(
679
+ `PR limit reached (Terraform-only guardrail): this run is configured to open at most ${cap} PR(s) and has already opened ${opened}. ` +
680
+ `Stop here and report the remaining work for a future run.`,
681
+ );
682
+ }
683
+ }
684
+
685
+ /** record that a guarded-mode PR was opened (after create_pull_request succeeds). */
686
+ export function recordRemediationPrOpened(ctx: GuardrailContext): void {
687
+ if (!isGuardedMode(ctx)) return;
688
+ ctx.toolState.remediationPrsOpened =
689
+ (ctx.toolState.remediationPrsOpened ?? 0) + 1;
690
+ }
691
+
692
+ /** max simultaneously-open remediation PRs before new ones are held back
693
+ * (default 10; 0 disables the cap). Action-input only (see payload.ts). */
694
+ export function resolvePrConcurrentLimit(ctx: GuardrailContext): number {
695
+ return ctx.payload.prConcurrentLimit ?? 10;
696
+ }
697
+
698
+ /** max remediation PRs opened per rolling hour (default 0 = no hourly cap). */
699
+ export function resolvePrHourlyLimit(ctx: GuardrailContext): number {
700
+ return ctx.payload.prHourlyLimit ?? 0;
701
+ }
702
+
703
+ /**
704
+ * Cross-run flood guard: cap how many remediation PRs may be simultaneously OPEN
705
+ * (`prConcurrentLimit`) and how many may be CREATED per rolling hour
706
+ * (`prHourlyLimit`). Unlike {@link assertUnderPrCap} — which counts only this
707
+ * run's PRs from in-memory tool state — this queries GitHub for PRs across runs,
708
+ * so a first scan on a messy repo (or a burst of push-triggered runs) can't bury
709
+ * the maintainer under remediation PRs. Engages only in a guarded mode; a 0 limit
710
+ * disables that check. Throws with an agent-actionable message, before creation.
711
+ */
712
+ export async function assertUnderOpenPrCaps(
713
+ ctx: OpenPrCapContext,
714
+ ): Promise<void> {
715
+ if (!isGuardedMode(ctx)) return;
716
+ const concurrentLimit = resolvePrConcurrentLimit(ctx);
717
+ const hourlyLimit = resolvePrHourlyLimit(ctx);
718
+ if (concurrentLimit <= 0 && hourlyLimit <= 0) return;
719
+
720
+ const owner = ctx.repo.owner;
721
+ const repo = ctx.repo.name;
722
+
723
+ // Concurrency: count currently-open remediation PRs (`remediate/<id>`, incl.
724
+ // `remediate/batch-*`). groupIdFromBranch is non-null exactly for those.
725
+ if (concurrentLimit > 0) {
726
+ const open = await ctx.octokit.paginate(ctx.octokit.rest.pulls.list, {
727
+ owner,
728
+ repo,
729
+ state: "open",
730
+ per_page: 100,
731
+ });
732
+ const openRemediation = open.filter(
733
+ (pr) => groupIdFromBranch(pr.head.ref) !== null,
734
+ );
735
+ if (openRemediation.length >= concurrentLimit) {
736
+ throw new Error(
737
+ `Open remediation PR cap reached (Terraform-only guardrail): ${openRemediation.length} ${PRODUCT_NAME} ` +
738
+ `remediation PR(s) are already open and the cap is ${concurrentLimit}. Don't open another — stop here ` +
739
+ `and report the remaining work so it lands once existing PRs are merged or closed (or raise ` +
740
+ `\`pr_concurrent_limit\`).`,
741
+ );
742
+ }
743
+ }
744
+
745
+ // Hourly: count remediation PRs CREATED in the last rolling hour (any state — a
746
+ // PR merged or closed within the hour still counts toward creation velocity).
747
+ // The 100 most-recently-created PRs cover an hour in any realistic repo.
748
+ if (hourlyLimit > 0) {
749
+ const cutoffMs = Date.now() - 60 * 60 * 1000;
750
+ const recent = await ctx.octokit.rest.pulls.list({
751
+ owner,
752
+ repo,
753
+ state: "all",
754
+ sort: "created",
755
+ direction: "desc",
756
+ per_page: 100,
757
+ });
758
+ const recentRemediation = recent.data.filter(
759
+ (pr) =>
760
+ groupIdFromBranch(pr.head.ref) !== null &&
761
+ new Date(pr.created_at).getTime() >= cutoffMs,
762
+ );
763
+ if (recentRemediation.length >= hourlyLimit) {
764
+ throw new Error(
765
+ `Hourly remediation PR cap reached (Terraform-only guardrail): ${recentRemediation.length} remediation ` +
766
+ `PR(s) were opened in the last hour and the cap is ${hourlyLimit}. Stop here and report the remaining ` +
767
+ `work for a later run (or raise \`pr_hourly_limit\`).`,
768
+ );
769
+ }
770
+ }
771
+ }