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,886 @@
1
+ /**
2
+ * §5.2 — plain-language standard → REVIEWED policy-as-code.
3
+ *
4
+ * The feature a compliance officer asks for is "paste our internal standard, get
5
+ * a policy". The feature that is safe to ship is narrower, and the difference is
6
+ * the whole design.
7
+ *
8
+ * WHY NOT "MODEL WRITES REGO". Rego's worst failure mode is silent: a rule that
9
+ * references a field name the plan JSON does not have is not an error, it is
10
+ * *vacuously satisfied* — it never denies. A generated policy that looks
11
+ * enforced and passes everything is strictly worse than no policy, because a
12
+ * control owner will tick the box. Free-form generation cannot rule that out,
13
+ * and neither can reading the diff.
14
+ *
15
+ * WHAT THIS DOES INSTEAD. The model's job is reduced to filling in a CLOSED
16
+ * intermediate representation ([[PolicyRuleSpec]]) — one resource type, one
17
+ * attribute path, one operator drawn from a fixed set. Everything downstream is
18
+ * deterministic:
19
+ *
20
+ * 1. `emitRego` / `emitPolicyHcl` / `emitPolicyTestHcl` are pure functions of
21
+ * the spec. The same spec always produces the same bytes.
22
+ * 2. `selfTestSpec` evaluates the spec IN PROCESS against three generated
23
+ * fixtures — violating, compliant, and attribute-absent — and REJECTS a
24
+ * spec that does not deny the violating case. That is the vacuous-rule
25
+ * check, and it runs before a human is ever shown the policy.
26
+ * 3. The crosswalk mapping is not generated at all: the spec names one of the
27
+ * existing themes, so the control citation comes from the reviewed
28
+ * [[crosswalk]] table by the same mechanical hop every other finding uses.
29
+ *
30
+ * WHAT IT DELIBERATELY DOES NOT CLAIM. We do not ship OPA or tfpolicy, so this
31
+ * module cannot assert that the EMITTED text behaves like the spec — only that
32
+ * the spec itself is non-vacuous and that the emission is deterministic. The
33
+ * caller reports that distinction (`rego_verified`, `policy_hcl_fidelity`)
34
+ * rather than papering over it; `policy_check` (conftest) and the customer's own
35
+ * `tfpolicy validate`/`test` are where the emitted artifacts get executed. The
36
+ * `.policytest.hcl` exists precisely so that step is one command, not a project.
37
+ *
38
+ * ONE MORE HONESTY POINT, AND IT IS THE SUBTLE ONE. "The attribute is missing"
39
+ * is not a verdict a policy engine can guess. `acl != "public-read"` on a bucket
40
+ * with no `acl` is compliant (the default is private); `sse_algorithm == "AES256"`
41
+ * on a bucket with no encryption block is a violation. Same shape, opposite
42
+ * answer. So `when_absent` is a REQUIRED field with no default — the author
43
+ * states it, the self-test exercises it, and the reviewing human sees it.
44
+ */
45
+ import { PRODUCT_NAME } from "#app/brand";
46
+ import { THEME_TO_NIST } from "#app/mcp/crosswalk";
47
+ import { SEVERITY_RANK, type Severity } from "#app/mcp/terraform/types";
48
+
49
+ /**
50
+ * The namespace an authored rule's id carries. Deliberately NOT the `infraweaver:`
51
+ * native-source prefix: these findings are produced by the customer's conftest
52
+ * running the customer's committed policy. We helped write it; we did not find
53
+ * it, and the label should not imply otherwise.
54
+ */
55
+ export const AUTHORED_RULE_PREFIX = "policy";
56
+
57
+ /** The fully-qualified rule id for an authored spec. */
58
+ export function authoredRuleId(id: string): string {
59
+ return `${AUTHORED_RULE_PREFIX}:${id}`;
60
+ }
61
+
62
+ /** The closed predicate set. Anything not expressible here is not authorable —
63
+ * which is the point: an operator we cannot evaluate in process is an operator
64
+ * whose generated policy we cannot self-test. */
65
+ export const POLICY_OPERATORS = [
66
+ "equals",
67
+ "not_equals",
68
+ "in",
69
+ "not_in",
70
+ "present",
71
+ "absent",
72
+ "is_true",
73
+ "is_false",
74
+ "matches",
75
+ "gte",
76
+ "lte",
77
+ "non_empty",
78
+ ] as const;
79
+
80
+ export type PolicyOperator = (typeof POLICY_OPERATORS)[number];
81
+
82
+ /** Operators that compare against a scalar operand. */
83
+ const SCALAR_OPERAND: ReadonlySet<PolicyOperator> = new Set([
84
+ "equals",
85
+ "not_equals",
86
+ "matches",
87
+ "gte",
88
+ "lte",
89
+ ]);
90
+
91
+ /** Operators that compare against a list operand. */
92
+ const LIST_OPERAND: ReadonlySet<PolicyOperator> = new Set(["in", "not_in"]);
93
+
94
+ /** Operators that take no operand at all. */
95
+ const NO_OPERAND: ReadonlySet<PolicyOperator> = new Set([
96
+ "present",
97
+ "absent",
98
+ "is_true",
99
+ "is_false",
100
+ "non_empty",
101
+ ]);
102
+
103
+ /** Operators whose operand must be numeric. */
104
+ const NUMERIC_OPERAND: ReadonlySet<PolicyOperator> = new Set(["gte", "lte"]);
105
+
106
+ export type PolicyScalar = string | number | boolean;
107
+
108
+ /**
109
+ * What the rule concludes when the attribute path resolves to nothing.
110
+ * Required, and deliberately not defaulted — see the module note.
111
+ */
112
+ export type WhenAbsent = "violation" | "compliant";
113
+
114
+ /**
115
+ * The reviewed intermediate representation. This — not Rego — is what the model
116
+ * produces from prose, and what a human reviews.
117
+ */
118
+ export interface PolicyRuleSpec {
119
+ /** kebab-case, unique within the authored pack. Becomes the rule id in every emitted artifact. */
120
+ id: string;
121
+ /** one-line human title. */
122
+ title: string;
123
+ /** the sentence of the SOURCE STANDARD this rule was derived from, quoted. The
124
+ * audit trail from prose to policy; carried into every emitted artifact. */
125
+ source_requirement: string;
126
+ /** the Terraform resource type the rule applies to, e.g. `aws_s3_bucket`. */
127
+ resource_type: string;
128
+ /** dotted path into the resource's planned values, e.g.
129
+ * `server_side_encryption_configuration.rule.apply_server_side_encryption_by_default.sse_algorithm`.
130
+ * A segment that resolves to an array is flattened — every element contributes
131
+ * a candidate value, and the rule must hold for ALL of them. */
132
+ attribute: string;
133
+ operator: PolicyOperator;
134
+ /** the operand. Scalar for equals/not_equals/matches/gte/lte, a list for
135
+ * in/not_in, omitted for the rest. */
136
+ value?: PolicyScalar | PolicyScalar[];
137
+ when_absent: WhenAbsent;
138
+ severity: Severity;
139
+ /** one of the reviewed crosswalk themes — this is what earns the control citation. */
140
+ theme: string;
141
+ /** the violation message a reviewer and an engineer both read. */
142
+ message: string;
143
+ /** a compliant value for the generated fixtures. REQUIRED for `matches`, where
144
+ * no value can be synthesised from the pattern; optional elsewhere. */
145
+ example_compliant?: PolicyScalar;
146
+ /** a violating value for the generated fixtures. REQUIRED for `matches`. */
147
+ example_violating?: PolicyScalar;
148
+ }
149
+
150
+ // ── validation ───────────────────────────────────────────────────────────────
151
+
152
+ const ID_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
153
+ const RESOURCE_TYPE_RE = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
154
+ const ATTRIBUTE_RE = /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/;
155
+
156
+ /**
157
+ * Structural validation of a spec. Returns the list of problems — empty means
158
+ * valid. Kept separate from the self-test: this catches "the spec is malformed",
159
+ * the self-test catches "the spec is well-formed but says nothing".
160
+ */
161
+ export function validateSpec(spec: PolicyRuleSpec): string[] {
162
+ const errs: string[] = [];
163
+ if (!ID_RE.test(spec.id))
164
+ errs.push(`id '${spec.id}' must be kebab-case (a-z, 0-9, hyphens)`);
165
+ if (!spec.title.trim()) errs.push("title is empty");
166
+ if (!spec.source_requirement.trim())
167
+ errs.push(
168
+ "source_requirement is empty — every rule must cite the prose it came from",
169
+ );
170
+ if (!spec.message.trim()) errs.push("message is empty");
171
+ if (!RESOURCE_TYPE_RE.test(spec.resource_type))
172
+ errs.push(
173
+ `resource_type '${spec.resource_type}' is not a Terraform resource type`,
174
+ );
175
+ if (!ATTRIBUTE_RE.test(spec.attribute))
176
+ errs.push(`attribute '${spec.attribute}' is not a dotted attribute path`);
177
+ if (!(spec.theme in THEME_TO_NIST))
178
+ errs.push(
179
+ `theme '${spec.theme}' is not a crosswalk theme — pick one of: ${Object.keys(THEME_TO_NIST).sort().join(", ")}`,
180
+ );
181
+ if (!(spec.severity in SEVERITY_RANK))
182
+ errs.push(`severity '${spec.severity}' is not a severity`);
183
+
184
+ const op = spec.operator;
185
+ if (!POLICY_OPERATORS.includes(op)) {
186
+ errs.push(`operator '${op}' is not supported`);
187
+ return errs;
188
+ }
189
+ const v = spec.value;
190
+ if (NO_OPERAND.has(op)) {
191
+ if (v !== undefined) errs.push(`operator '${op}' takes no value`);
192
+ } else if (LIST_OPERAND.has(op)) {
193
+ if (!Array.isArray(v) || v.length === 0)
194
+ errs.push(`operator '${op}' needs a non-empty list value`);
195
+ } else if (SCALAR_OPERAND.has(op)) {
196
+ if (v === undefined || Array.isArray(v))
197
+ errs.push(`operator '${op}' needs a scalar value`);
198
+ else if (NUMERIC_OPERAND.has(op) && typeof v !== "number")
199
+ errs.push(`operator '${op}' needs a numeric value`);
200
+ else if (op === "matches") {
201
+ if (typeof v !== "string")
202
+ errs.push("operator 'matches' needs a string regex");
203
+ else {
204
+ try {
205
+ new RegExp(v);
206
+ } catch (e) {
207
+ errs.push(
208
+ `operator 'matches' regex does not compile: ${(e as Error).message}`,
209
+ );
210
+ }
211
+ }
212
+ // No value can be synthesised from an arbitrary pattern, so the author
213
+ // supplies both sides. Without them the fixtures — and therefore the
214
+ // vacuity check — would be guesswork.
215
+ if (
216
+ spec.example_compliant === undefined ||
217
+ spec.example_violating === undefined
218
+ )
219
+ errs.push(
220
+ "operator 'matches' requires both example_compliant and example_violating (a fixture value cannot be derived from a regex)",
221
+ );
222
+ }
223
+ }
224
+ return errs;
225
+ }
226
+
227
+ // ── evaluation (the semantics every emitter mirrors) ─────────────────────────
228
+
229
+ /**
230
+ * Resolve a dotted path against a planned-values object, flattening arrays: at
231
+ * each segment, an array contributes each of its elements as a candidate parent.
232
+ * Returns every leaf value the path reaches. An empty array means the path
233
+ * resolved to nothing — which is what `when_absent` decides on.
234
+ *
235
+ * `null` leaves are dropped: Terraform writes an unset optional argument as
236
+ * `null`, and "present but null" is the same fact as "absent" to a reader.
237
+ */
238
+ export function resolvePath(root: unknown, path: string): unknown[] {
239
+ let level: unknown[] = [root];
240
+ for (const segment of path.split(".")) {
241
+ const next: unknown[] = [];
242
+ for (const node of level) {
243
+ for (const member of node !== null && Array.isArray(node)
244
+ ? node
245
+ : [node]) {
246
+ if (member === null || typeof member !== "object") continue;
247
+ const child = (member as Record<string, unknown>)[segment];
248
+ if (child === undefined) continue;
249
+ next.push(child);
250
+ }
251
+ }
252
+ level = next;
253
+ }
254
+ // flatten a terminal array (e.g. a list attribute) into its elements, and drop nulls
255
+ const out: unknown[] = [];
256
+ for (const node of level) {
257
+ for (const member of Array.isArray(node) ? node : [node]) {
258
+ if (member === null) continue;
259
+ out.push(member);
260
+ }
261
+ }
262
+ return out;
263
+ }
264
+
265
+ /** Does one resolved value satisfy the rule? Pure; mirrored by the emitted Rego. */
266
+ export function valueSatisfies(spec: PolicyRuleSpec, value: unknown): boolean {
267
+ const v = spec.value;
268
+ switch (spec.operator) {
269
+ case "equals":
270
+ return value === v;
271
+ case "not_equals":
272
+ return value !== v;
273
+ case "in":
274
+ return Array.isArray(v) && v.some((x) => x === value);
275
+ case "not_in":
276
+ return Array.isArray(v) && !v.some((x) => x === value);
277
+ // `present` is satisfied by any resolved value — `resolvePath` has already
278
+ // dropped nulls, so reaching here means something real is there.
279
+ case "present":
280
+ return true;
281
+ // ...and `absent` is therefore violated by any resolved value.
282
+ case "absent":
283
+ return false;
284
+ case "is_true":
285
+ return value === true;
286
+ case "is_false":
287
+ return value === false;
288
+ case "matches":
289
+ return (
290
+ typeof value === "string" &&
291
+ typeof v === "string" &&
292
+ new RegExp(v).test(value)
293
+ );
294
+ case "gte":
295
+ return typeof value === "number" && typeof v === "number" && value >= v;
296
+ case "lte":
297
+ return typeof value === "number" && typeof v === "number" && value <= v;
298
+ case "non_empty":
299
+ if (typeof value === "string" || Array.isArray(value))
300
+ return value.length > 0;
301
+ if (value !== null && typeof value === "object")
302
+ return Object.keys(value as object).length > 0;
303
+ return false;
304
+ }
305
+ }
306
+
307
+ export interface SpecVerdict {
308
+ violates: boolean;
309
+ /** why — the sentence a test failure prints. */
310
+ reason: string;
311
+ }
312
+
313
+ /**
314
+ * Evaluate a spec against ONE resource's planned values. Universal over the
315
+ * resolved values: every matched value must satisfy the rule, so a list where
316
+ * one element is wrong is a violation.
317
+ */
318
+ export function evaluateSpec(
319
+ spec: PolicyRuleSpec,
320
+ values: unknown,
321
+ ): SpecVerdict {
322
+ const resolved = resolvePath(values, spec.attribute);
323
+ if (resolved.length === 0) {
324
+ return spec.when_absent === "violation"
325
+ ? {
326
+ violates: true,
327
+ reason: `${spec.attribute} is absent and when_absent=violation`,
328
+ }
329
+ : {
330
+ violates: false,
331
+ reason: `${spec.attribute} is absent and when_absent=compliant`,
332
+ };
333
+ }
334
+ const bad = resolved.filter((v) => !valueSatisfies(spec, v));
335
+ if (bad.length === 0)
336
+ return {
337
+ violates: false,
338
+ reason: `all ${resolved.length} value(s) satisfy ${spec.operator}`,
339
+ };
340
+ return {
341
+ violates: true,
342
+ reason: `${bad.length} of ${resolved.length} value(s) fail ${spec.operator}: ${bad
343
+ .map((v) => JSON.stringify(v))
344
+ .join(", ")}`,
345
+ };
346
+ }
347
+
348
+ // ── fixtures ─────────────────────────────────────────────────────────────────
349
+
350
+ /** A sentinel that no real configuration produces, used where any non-matching
351
+ * value will do. Distinctive on purpose: if it ever shows up in a customer's
352
+ * plan output, something generated has leaked into real config. */
353
+ const SENTINEL = "__infraweaver_fixture_noncompliant__";
354
+
355
+ /** A compliant value for the spec's operator, or the author's override. */
356
+ function compliantValue(spec: PolicyRuleSpec): PolicyScalar {
357
+ if (spec.example_compliant !== undefined) return spec.example_compliant;
358
+ const v = spec.value;
359
+ switch (spec.operator) {
360
+ case "equals":
361
+ return v as PolicyScalar;
362
+ case "not_equals":
363
+ return SENTINEL;
364
+ case "in":
365
+ return (v as PolicyScalar[])[0] as PolicyScalar;
366
+ case "not_in":
367
+ return SENTINEL;
368
+ case "present":
369
+ case "non_empty":
370
+ return "infraweaver-fixture-value";
371
+ case "is_true":
372
+ return true;
373
+ case "is_false":
374
+ return false;
375
+ case "gte":
376
+ return v as number;
377
+ case "lte":
378
+ return v as number;
379
+ // `absent` has no compliant *value* — the compliant fixture is the empty one.
380
+ // `matches` is unreachable: validateSpec requires the author's example.
381
+ default:
382
+ return SENTINEL;
383
+ }
384
+ }
385
+
386
+ /** A violating value for the spec's operator, or the author's override. */
387
+ function violatingValue(spec: PolicyRuleSpec): PolicyScalar {
388
+ if (spec.example_violating !== undefined) return spec.example_violating;
389
+ const v = spec.value;
390
+ switch (spec.operator) {
391
+ case "equals":
392
+ return SENTINEL;
393
+ case "not_equals":
394
+ return v as PolicyScalar;
395
+ case "in":
396
+ return SENTINEL;
397
+ case "not_in":
398
+ return (v as PolicyScalar[])[0] as PolicyScalar;
399
+ case "present":
400
+ // handled by the absent fixture; keep the shape total
401
+ return SENTINEL;
402
+ case "absent":
403
+ return "infraweaver-fixture-value";
404
+ case "is_true":
405
+ return false;
406
+ case "is_false":
407
+ return true;
408
+ case "gte":
409
+ return (v as number) - 1;
410
+ case "lte":
411
+ return (v as number) + 1;
412
+ case "non_empty":
413
+ return "";
414
+ default:
415
+ return SENTINEL;
416
+ }
417
+ }
418
+
419
+ /** Build the nested object a dotted path implies, with `leaf` at the end. */
420
+ export function nestValue(
421
+ path: string,
422
+ leaf: unknown,
423
+ ): Record<string, unknown> {
424
+ const segs = path.split(".");
425
+ let node: unknown = leaf;
426
+ for (let i = segs.length - 1; i >= 0; i--)
427
+ node = { [segs[i] as string]: node };
428
+ return node as Record<string, unknown>;
429
+ }
430
+
431
+ export interface PolicyFixture {
432
+ /** `compliant` | `violating` | `absent` — the mock's role in the self-test. */
433
+ kind: "compliant" | "violating" | "absent";
434
+ /** the resource's planned values. */
435
+ values: Record<string, unknown>;
436
+ /** what the rule MUST conclude about it. */
437
+ expect_violation: boolean;
438
+ }
439
+
440
+ /**
441
+ * The three fixtures every authored rule is tested against. The `absent` case is
442
+ * the one that matters most: it is both the commonest real shape (an optional
443
+ * block simply not written) and the case a hand-written policy most often gets
444
+ * wrong.
445
+ */
446
+ export function buildFixtures(spec: PolicyRuleSpec): PolicyFixture[] {
447
+ const fixtures: PolicyFixture[] = [];
448
+ // `absent` as an OPERATOR means "this must not be set" — no present value can
449
+ // satisfy it, so there is no compliant-value fixture to build.
450
+ if (spec.operator !== "absent") {
451
+ fixtures.push({
452
+ kind: "compliant",
453
+ values: nestValue(spec.attribute, compliantValue(spec)),
454
+ expect_violation: false,
455
+ });
456
+ }
457
+ // Symmetrically, `present` is satisfied by ANY value, so no violating-value
458
+ // fixture exists — its only failure mode is the absent case below.
459
+ if (spec.operator !== "present") {
460
+ fixtures.push({
461
+ kind: "violating",
462
+ values: nestValue(spec.attribute, violatingValue(spec)),
463
+ expect_violation: true,
464
+ });
465
+ }
466
+ fixtures.push({
467
+ kind: "absent",
468
+ values: {},
469
+ expect_violation: spec.when_absent === "violation",
470
+ });
471
+ return fixtures;
472
+ }
473
+
474
+ export interface SelfTestCase {
475
+ kind: PolicyFixture["kind"];
476
+ expected: boolean;
477
+ actual: boolean;
478
+ passed: boolean;
479
+ reason: string;
480
+ }
481
+
482
+ export interface SelfTestReport {
483
+ /** every case behaved as its fixture demanded AND the rule denies something.
484
+ * Both, deliberately: a rule that agrees with all three fixtures by never
485
+ * firing has passed nothing. */
486
+ passed: boolean;
487
+ cases: SelfTestCase[];
488
+ /** true when the rule denied at least one fixture — the vacuity check. A rule
489
+ * that denies nothing is not a rule. */
490
+ denies_something: boolean;
491
+ }
492
+
493
+ /**
494
+ * Run the spec against its own fixtures. A spec that fails here is never
495
+ * emitted: the caller refuses it rather than hand a human a policy that does
496
+ * not do what its own description says.
497
+ */
498
+ export function selfTestSpec(spec: PolicyRuleSpec): SelfTestReport {
499
+ const cases: SelfTestCase[] = [];
500
+ for (const fixture of buildFixtures(spec)) {
501
+ const verdict = evaluateSpec(spec, fixture.values);
502
+ cases.push({
503
+ kind: fixture.kind,
504
+ expected: fixture.expect_violation,
505
+ actual: verdict.violates,
506
+ passed: verdict.violates === fixture.expect_violation,
507
+ reason: verdict.reason,
508
+ });
509
+ }
510
+ const denies_something = cases.some((c) => c.actual);
511
+ return {
512
+ passed: cases.every((c) => c.passed) && denies_something,
513
+ cases,
514
+ denies_something,
515
+ };
516
+ }
517
+
518
+ // ── Rego emission ────────────────────────────────────────────────────────────
519
+
520
+ /** A Rego identifier derived from the rule id. */
521
+ function regoIdent(id: string): string {
522
+ return id.replace(/-/g, "_");
523
+ }
524
+
525
+ /** A Rego (JSON-compatible) literal. */
526
+ function lit(v: unknown): string {
527
+ return JSON.stringify(v);
528
+ }
529
+
530
+ /**
531
+ * The shared helper every emitted policy imports by inclusion. Kept as ONE
532
+ * reviewed block rather than inlined per rule, because the array-flattening
533
+ * semantics are the part a reader must check — and they should only have to
534
+ * check it once.
535
+ *
536
+ * Rego forbids recursion, so per-rule path traversal is unrolled at generation
537
+ * time (see `pathComprehension`); `_members` is the non-recursive primitive that
538
+ * makes each step uniform.
539
+ */
540
+ export const REGO_PRELUDE = `# Shared helpers for ${PRODUCT_NAME}-authored policies. Reviewed once, used by every rule.
541
+ # _members normalises a node to a list so an array-valued block and a single
542
+ # block traverse identically — the same rule ${PRODUCT_NAME}'s own evaluator applies.
543
+ _members(x) := x if is_array(x)
544
+ _members(x) := [x] if not is_array(x)
545
+
546
+ # Only plan actions that produce or change configuration are judged. A delete
547
+ # leaves no "after" to assert on, and a no-op has already been judged.
548
+ _relevant(rc) if {
549
+ some action in rc.change.actions
550
+ action in {"create", "update"}
551
+ }
552
+ `;
553
+
554
+ /** The unrolled comprehension resolving `path` against `root`, mirroring `resolvePath`. */
555
+ function pathComprehension(path: string, root: string): string {
556
+ const segs = path.split(".");
557
+ const lines: string[] = [];
558
+ let cur = root;
559
+ for (let i = 0; i < segs.length; i++) {
560
+ const parent = `p${i}`;
561
+ lines.push(`\tsome ${parent} in _members(${cur})`);
562
+ lines.push(`\tis_object(${parent})`);
563
+ const child = `c${i}`;
564
+ lines.push(`\t${child} := ${parent}[${lit(segs[i])}]`);
565
+ cur = child;
566
+ }
567
+ // terminal flatten + null drop, matching resolvePath's tail
568
+ lines.push(`\tsome leaf in _members(${cur})`);
569
+ lines.push(`\tleaf != null`);
570
+ return `[leaf |\n${lines.join("\n")}\n]`;
571
+ }
572
+
573
+ /**
574
+ * The alternative bodies of the per-value predicate, each a list of Rego
575
+ * expressions ANDed together. Multiple bodies become multiple definitions of the
576
+ * same function, which Rego ORs.
577
+ *
578
+ * The TYPE GUARDS are not decoration. Rego's comparison operators order ACROSS
579
+ * types rather than erroring — `"5" >= 1` is true, because numbers sort before
580
+ * strings — so an unguarded `gte` would pass a string where the in-process
581
+ * evaluator (and any reasonable reading of the rule) fails it. Every operator
582
+ * whose meaning depends on the operand's type states that type explicitly, so
583
+ * the emitted Rego and `valueSatisfies` cannot disagree.
584
+ */
585
+ function regoPredicateBodies(spec: PolicyRuleSpec, v: string): string[][] {
586
+ const val = spec.value;
587
+ switch (spec.operator) {
588
+ case "equals":
589
+ return [[`${v} == ${lit(val)}`]];
590
+ case "not_equals":
591
+ return [[`${v} != ${lit(val)}`]];
592
+ case "in":
593
+ return [[`${v} in ${lit(val)}`]];
594
+ case "not_in":
595
+ return [[`not ${v} in ${lit(val)}`]];
596
+ case "present":
597
+ return [[`${v} != null`]];
598
+ case "absent":
599
+ return [[`${v} == null`]];
600
+ case "is_true":
601
+ return [[`${v} == true`]];
602
+ case "is_false":
603
+ return [[`${v} == false`]];
604
+ case "matches":
605
+ return [[`is_string(${v})`, `regex.match(${lit(val)}, ${v})`]];
606
+ case "gte":
607
+ return [[`is_number(${v})`, `${v} >= ${lit(val)}`]];
608
+ case "lte":
609
+ return [[`is_number(${v})`, `${v} <= ${lit(val)}`]];
610
+ // `count` accepts strings, arrays and objects and errors on anything else;
611
+ // one body per accepted type keeps a number or a boolean from being "empty".
612
+ case "non_empty":
613
+ return [
614
+ [`is_string(${v})`, `count(${v}) > 0`],
615
+ [`is_array(${v})`, `count(${v}) > 0`],
616
+ [`is_object(${v})`, `count(${v}) > 0`],
617
+ ];
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Emit conftest-ready Rego for one spec. Pure and byte-stable — the same spec
623
+ * always yields the same file, so a re-run produces no diff and a diff means the
624
+ * spec changed.
625
+ *
626
+ * Targets `terraform show -json` output (`input.resource_changes`), which is what
627
+ * `policy_check` already feeds conftest.
628
+ */
629
+ export function emitRego(
630
+ spec: PolicyRuleSpec,
631
+ opts: { packageName?: string } = {},
632
+ ): string {
633
+ const pkg = opts.packageName ?? "main";
634
+ const ident = regoIdent(spec.id);
635
+ const ruleId = authoredRuleId(spec.id);
636
+ const header = [
637
+ `# ${spec.title}`,
638
+ `# rule: ${ruleId} severity: ${spec.severity} theme: ${spec.theme}`,
639
+ `# source requirement: ${spec.source_requirement.replace(/\s+/g, " ").trim()}`,
640
+ "#",
641
+ "# GENERATED BY INFRAWEAVER — review before committing. The generator self-tested",
642
+ "# this rule's SEMANTICS against violating/compliant/absent fixtures; it did not",
643
+ `# execute this Rego. Run \`conftest test\` (or ${PRODUCT_NAME}'s policy_check) to do that.`,
644
+ ].join("\n");
645
+
646
+ const okRule = regoPredicateBodies(spec, "v")
647
+ .map(
648
+ (body) =>
649
+ `_ok_${ident}(v) if {\n${body.map((line) => `\t${line}`).join("\n")}\n}`,
650
+ )
651
+ .join("\n\n");
652
+ const valsRule = `_vals_${ident}(root) := ${pathComprehension(spec.attribute, "root")}`;
653
+
654
+ const msg = `sprintf("[%s] %s: %s", [${lit(ruleId)}, rc.address, ${lit(spec.message)}])`;
655
+
656
+ const denyValue = [
657
+ "deny contains msg if {",
658
+ "\tsome rc in input.resource_changes",
659
+ `\trc.type == ${lit(spec.resource_type)}`,
660
+ "\t_relevant(rc)",
661
+ `\tvals := _vals_${ident}(rc.change.after)`,
662
+ "\tcount(vals) > 0",
663
+ "\tsome v in vals",
664
+ `\tnot _ok_${ident}(v)`,
665
+ `\tmsg := ${msg}`,
666
+ "}",
667
+ ].join("\n");
668
+
669
+ const denyAbsent =
670
+ spec.when_absent === "violation"
671
+ ? [
672
+ "",
673
+ "# when_absent = violation: nothing to assert on is itself the finding.",
674
+ "deny contains msg if {",
675
+ "\tsome rc in input.resource_changes",
676
+ `\trc.type == ${lit(spec.resource_type)}`,
677
+ "\t_relevant(rc)",
678
+ `\tcount(_vals_${ident}(rc.change.after)) == 0`,
679
+ `\tmsg := ${msg}`,
680
+ "}",
681
+ ].join("\n")
682
+ : "";
683
+
684
+ return `${header}\n\npackage ${pkg}\n\nimport rego.v1\n\n${REGO_PRELUDE}\n${valsRule}\n\n${okRule}\n\n${denyValue}\n${denyAbsent}\n`;
685
+ }
686
+
687
+ // ── tfpolicy (.policy.hcl) emission ──────────────────────────────────────────
688
+
689
+ /**
690
+ * How faithful the `.policy.hcl` translation is to the spec.
691
+ *
692
+ * `exact` — a single-segment attribute path, where `attrs.<name>` means exactly
693
+ * what our resolver means.
694
+ * `approximate` — a nested path. tfpolicy's `attrs` traversal does not flatten
695
+ * arrays the way our resolver does, so a nested block that Terraform represents
696
+ * as a list needs `[0]` indexing and the translation asserts on the FIRST
697
+ * element rather than all of them. We emit it, we say so in the file, and the
698
+ * generated `.policytest.hcl` is how the customer confirms it before relying
699
+ * on it.
700
+ *
701
+ * This axis is about PATH SHAPE only. A second, unavoidable difference applies
702
+ * to every rule and is stated in the emitted header instead: HCL converts
703
+ * between types where it can, so a comparison there can succeed on an operand
704
+ * the Rego — which is explicitly type-guarded — rejects.
705
+ */
706
+ export type PolicyHclFidelity = "exact" | "approximate";
707
+
708
+ export function policyHclFidelity(spec: PolicyRuleSpec): PolicyHclFidelity {
709
+ return spec.attribute.includes(".") ? "approximate" : "exact";
710
+ }
711
+
712
+ /** An HCL literal for a scalar or list operand. */
713
+ function hcl(v: unknown): string {
714
+ if (Array.isArray(v)) return `[${v.map(hcl).join(", ")}]`;
715
+ return JSON.stringify(v);
716
+ }
717
+
718
+ /** The tfpolicy accessor for the spec's path — `core::try` guarded, single line. */
719
+ function hclAccessor(spec: PolicyRuleSpec): string {
720
+ return `core::try(attrs.${spec.attribute}, null)`;
721
+ }
722
+
723
+ /** The tfpolicy predicate over the accessor, as a single-line boolean expression. */
724
+ function hclPredicate(spec: PolicyRuleSpec, acc: string): string {
725
+ const v = spec.value;
726
+ switch (spec.operator) {
727
+ case "equals":
728
+ return `${acc} == ${hcl(v)}`;
729
+ case "not_equals":
730
+ return `${acc} != ${hcl(v)}`;
731
+ case "in":
732
+ return `core::contains(${hcl(v)}, ${acc})`;
733
+ case "not_in":
734
+ return `!core::contains(${hcl(v)}, ${acc})`;
735
+ case "present":
736
+ return `${acc} != null`;
737
+ case "absent":
738
+ return `${acc} == null`;
739
+ case "is_true":
740
+ return `${acc} == true`;
741
+ case "is_false":
742
+ return `${acc} == false`;
743
+ case "matches":
744
+ return `core::try(core::regex(${hcl(v)}, ${acc}) != null, false)`;
745
+ case "gte":
746
+ return `${acc} >= ${hcl(v)}`;
747
+ case "lte":
748
+ return `${acc} <= ${hcl(v)}`;
749
+ case "non_empty":
750
+ return `core::length(core::try(${acc}, [])) > 0`;
751
+ }
752
+ }
753
+
754
+ /** Severity → tfpolicy enforcement level. Only a code-verified `critical`/`high`
755
+ * earns `mandatory`; the rest advise, matching the band doctrine in
756
+ * [[nativeRules]] — a generated rule does not get to hard-block on day one. */
757
+ function enforcementLevel(severity: Severity): "mandatory" | "advisory" {
758
+ return severity === "critical" || severity === "high"
759
+ ? "mandatory"
760
+ : "advisory";
761
+ }
762
+
763
+ /**
764
+ * Emit a HashiCorp tfpolicy `.policy.hcl` for one spec.
765
+ *
766
+ * We AUTHOR this dialect; we do not enforce it. `tfpolicy` has no
767
+ * credential-free local plan evaluation and its binary is source-closed, so it
768
+ * cannot replace the OPA gate — the value is that an HCP customer gets a native
769
+ * artifact they can validate and run in their own pipeline.
770
+ */
771
+ export function emitPolicyHcl(spec: PolicyRuleSpec): string {
772
+ const acc = hclAccessor(spec);
773
+ const predicate = hclPredicate(spec, acc);
774
+ // `when_absent` is expressed structurally rather than left to core::try's
775
+ // default, so the absent case reads the same way it does in the spec.
776
+ const condition =
777
+ spec.when_absent === "compliant"
778
+ ? `${acc} == null || (${predicate})`
779
+ : `${acc} != null && (${predicate})`;
780
+ const fidelity = policyHclFidelity(spec);
781
+ const caveat =
782
+ fidelity === "approximate"
783
+ ? [
784
+ "# FIDELITY: approximate. This rule's attribute path is nested, and tfpolicy's",
785
+ `# attrs traversal does not flatten list-shaped blocks the way ${PRODUCT_NAME}'s`,
786
+ "# resolver does — a block Terraform represents as a list may need [0] indexing,",
787
+ "# and this condition then asserts on the first element only. Run the generated",
788
+ "# .policytest.hcl with `tfpolicy test` before relying on it.",
789
+ ].join("\n")
790
+ : "# FIDELITY: exact. Single-segment attribute path — no traversal ambiguity.";
791
+
792
+ return `# ${spec.title}
793
+ # rule: ${authoredRuleId(spec.id)} severity: ${spec.severity} theme: ${spec.theme}
794
+ # source requirement: ${spec.source_requirement.replace(/\s+/g, " ").trim()}
795
+ #
796
+ # GENERATED BY INFRAWEAVER — review before committing. ${PRODUCT_NAME} authors this
797
+ # dialect but does not execute it: validate with \`tfpolicy validate\` and run the
798
+ # generated .policytest.hcl with \`tfpolicy test\`.
799
+ #
800
+ # TYPES: HCL converts between types where it can, so this condition may accept an
801
+ # operand the companion Rego rejects — that one is explicitly type-guarded. Where
802
+ # the two must agree exactly, the Rego is the reference.
803
+ ${caveat}
804
+
805
+ resource_policy ${hcl(spec.resource_type)} ${hcl(regoIdent(spec.id))} {
806
+ enforcement_level = ${hcl(enforcementLevel(spec.severity))}
807
+
808
+ enforce {
809
+ condition = ${condition}
810
+ error_message = ${hcl(spec.message)}
811
+ }
812
+ }
813
+ `;
814
+ }
815
+
816
+ /** Render a fixture's values as a tfpolicy `attrs = { … }` body. */
817
+ function hclAttrs(values: Record<string, unknown>, indent: string): string {
818
+ const entries = Object.entries(values);
819
+ if (entries.length === 0) return "{}";
820
+ const inner = entries
821
+ .map(([k, v]) => {
822
+ const rendered =
823
+ v !== null && typeof v === "object" && !Array.isArray(v)
824
+ ? hclAttrs(v as Record<string, unknown>, `${indent} `)
825
+ : hcl(v);
826
+ return `${indent} ${k} = ${rendered}`;
827
+ })
828
+ .join("\n");
829
+ return `{\n${inner}\n${indent}}`;
830
+ }
831
+
832
+ /**
833
+ * Emit the `.policytest.hcl` companion — the same three fixtures the in-process
834
+ * self-test used, expressed as tfpolicy mocks. This is what turns "we authored
835
+ * it" into something the customer can verify in one command.
836
+ */
837
+ export function emitPolicyTestHcl(spec: PolicyRuleSpec): string {
838
+ const blocks = buildFixtures(spec).map((f) => {
839
+ const expect = f.expect_violation ? "\n expect_failure = true" : "";
840
+ return `resource ${hcl(spec.resource_type)} ${hcl(f.kind)} {${expect}\n attrs = ${hclAttrs(f.values, " ")}\n}`;
841
+ });
842
+ return `# Fixtures for ${authoredRuleId(spec.id)} — the same violating / compliant / absent
843
+ # cases ${PRODUCT_NAME} self-tested the rule's semantics against. Run with:
844
+ # tfpolicy test --policies=. --tests=.
845
+
846
+ policytest {
847
+ targets = [${hcl(policyFileName(spec))}]
848
+ }
849
+
850
+ ${blocks.join("\n\n")}
851
+ `;
852
+ }
853
+
854
+ // ── plan-JSON fixture (for conftest / policy_check) ──────────────────────────
855
+
856
+ /**
857
+ * The generated fixtures as a `terraform show -json`-shaped document, so the
858
+ * emitted Rego can be executed against them by conftest without a real plan.
859
+ * One resource per fixture, named for its role.
860
+ */
861
+ export function emitPlanFixture(spec: PolicyRuleSpec): string {
862
+ const resource_changes = buildFixtures(spec).map((f) => ({
863
+ address: `${spec.resource_type}.${f.kind}`,
864
+ mode: "managed",
865
+ type: spec.resource_type,
866
+ name: f.kind,
867
+ provider_name: `registry.terraform.io/hashicorp/${spec.resource_type.split("_")[0]}`,
868
+ change: { actions: ["create"], before: null, after: f.values },
869
+ }));
870
+ return `${JSON.stringify({ format_version: "1.2", resource_changes }, null, 2)}\n`;
871
+ }
872
+
873
+ // ── file naming ──────────────────────────────────────────────────────────────
874
+
875
+ export function regoFileName(spec: PolicyRuleSpec): string {
876
+ return `${spec.id}.rego`;
877
+ }
878
+ export function policyFileName(spec: PolicyRuleSpec): string {
879
+ return `${spec.id}.policy.hcl`;
880
+ }
881
+ export function policyTestFileName(spec: PolicyRuleSpec): string {
882
+ return `${spec.id}.policytest.hcl`;
883
+ }
884
+ export function planFixtureFileName(spec: PolicyRuleSpec): string {
885
+ return `${spec.id}.fixture.plan.json`;
886
+ }