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,198 @@
1
+ /**
2
+ * Human-edit capture — the data flywheel.
3
+ *
4
+ * When a human edits or rejects a Infraweaver remediation PR, the delta between
5
+ * what Infraweaver originally committed and what was actually merged is the single
6
+ * most valuable training/eval signal we can collect: it tells us exactly where a
7
+ * fix was wrong, over-reaching, or stylistically off. This module computes that
8
+ * delta as a structured record AND persists it (when a backend is configured).
9
+ *
10
+ * SCOPE / SEAM: the pure record-building (`computeHumanEditDelta`,
11
+ * `deriveRemediationOutcome`) lives here and is unit-tested. PERSISTENCE
12
+ * (`persistHumanEditRecord`) mirrors the `learnings` seam: it POSTs the record
13
+ * to the open-core backend ONLY when `API_URL` is set — a standalone BYOK run
14
+ * has nowhere to persist, so it no-ops cleanly instead of POSTing into the
15
+ * marketing host. The remaining external dependency is the `pull_request`
16
+ * `closed`/`merged` EVENT that triggers a capture run; that event wiring is the
17
+ * hosted product's (it dispatches the action on PR-close). `captureRemediationOutcome`
18
+ * is the single entry point an event handler calls: build → persist.
19
+ */
20
+ import { cloudRequest } from "#app/utils/cloudClient";
21
+ import { isBackendConfigured } from "#app/utils/apiUrl";
22
+ import { log } from "#app/utils/cli";
23
+
24
+ export interface RemediationOutcome {
25
+ /** the concern id(s) the original Infraweaver PR targeted. */
26
+ concernIds: string[];
27
+ /** Infraweaver's original committed patch (unified diff of its commit). */
28
+ originalFixDiff: string;
29
+ /** the patch that was actually merged (HEAD of the PR at merge), unified diff. */
30
+ mergedDiff: string;
31
+ /** how the PR ended. */
32
+ outcome: "merged_clean" | "merged_with_edits" | "rejected";
33
+ }
34
+
35
+ export interface HumanEditRecord {
36
+ concernIds: string[];
37
+ outcome: RemediationOutcome["outcome"];
38
+ /** true when the human changed Infraweaver's fix before merging (or rejected). */
39
+ humanIntervened: boolean;
40
+ /** the lines the human ADDED on top of / instead of Infraweaver's fix. */
41
+ humanAddedLines: string[];
42
+ /** the lines from Infraweaver's original fix the human REMOVED/replaced. */
43
+ removedFromOriginal: string[];
44
+ }
45
+
46
+ /** added (`+`, not `+++`) content lines of a unified diff, sans the leading `+`. */
47
+ function addedLines(diff: string): string[] {
48
+ return diff
49
+ .split("\n")
50
+ .filter((l) => l.startsWith("+") && !l.startsWith("+++"))
51
+ .map((l) => l.slice(1));
52
+ }
53
+
54
+ /**
55
+ * Build the structured human-edit record from a finished remediation PR. Pure:
56
+ * the caller supplies Infraweaver's original diff and the merged diff; this diffs
57
+ * the two added-line sets to isolate what the human changed. `rejected` (PR
58
+ * closed unmerged) is always an intervention; `merged_clean` never is; for a
59
+ * merge, intervention is inferred from whether the merged added-lines differ
60
+ * from Infraweaver's.
61
+ */
62
+ export function computeHumanEditDelta(o: RemediationOutcome): HumanEditRecord {
63
+ const original = addedLines(o.originalFixDiff);
64
+ const merged = addedLines(o.mergedDiff);
65
+ const originalSet = new Set(original);
66
+ const mergedSet = new Set(merged);
67
+
68
+ const humanAddedLines = merged.filter((l) => !originalSet.has(l));
69
+ const removedFromOriginal = original.filter((l) => !mergedSet.has(l));
70
+
71
+ const humanIntervened =
72
+ o.outcome === "rejected" ||
73
+ (o.outcome === "merged_with_edits" &&
74
+ (humanAddedLines.length > 0 || removedFromOriginal.length > 0));
75
+
76
+ return {
77
+ concernIds: o.concernIds,
78
+ outcome: o.outcome,
79
+ humanIntervened,
80
+ humanAddedLines,
81
+ removedFromOriginal,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Derive the PR outcome from the raw close event: an unmerged close is
87
+ * `rejected`; a merge whose final added-lines differ from Infraweaver's original
88
+ * is `merged_with_edits`; an identical merge is `merged_clean`. Pure — the same
89
+ * added-line comparison `computeHumanEditDelta` uses, lifted so a caller can
90
+ * classify the event before (or without) building the full delta.
91
+ */
92
+ export function deriveRemediationOutcome(
93
+ merged: boolean,
94
+ originalFixDiff: string,
95
+ mergedDiff: string,
96
+ ): RemediationOutcome["outcome"] {
97
+ if (!merged) return "rejected";
98
+ const original = addedLines(originalFixDiff);
99
+ const mergedSet = new Set(addedLines(mergedDiff));
100
+ const originalSet = new Set(original);
101
+ const addedByHuman = addedLines(mergedDiff).some((l) => !originalSet.has(l));
102
+ const removedInfraweaver = original.some((l) => !mergedSet.has(l));
103
+ return addedByHuman || removedInfraweaver
104
+ ? "merged_with_edits"
105
+ : "merged_clean";
106
+ }
107
+
108
+ /** the input an event handler hands to `captureRemediationOutcome`: the closed
109
+ * remediation PR plus the two diffs needed to compute the human-edit delta. */
110
+ export interface RemediationPrClose {
111
+ prNumber: number;
112
+ /** whether the PR was merged (vs closed unmerged → rejected). */
113
+ merged: boolean;
114
+ /** the concern id(s) the original Infraweaver PR targeted. */
115
+ concernIds: string[];
116
+ /** Infraweaver's original committed patch (unified diff of its first commit). */
117
+ originalFixDiff: string;
118
+ /** the patch that was actually merged (HEAD of the PR at merge). */
119
+ mergedDiff: string;
120
+ }
121
+
122
+ /**
123
+ * Persist a built human-edit record to the open-core backend. Best-effort and
124
+ * dormant by default: with no `API_URL` (standalone BYOK) there's nowhere to
125
+ * persist, so it no-ops and reports `{ persisted: false, reason }` rather than
126
+ * POSTing into the marketing host. Mirrors `persistLearnings`. Any network
127
+ * failure is logged, never thrown — capture must never fail a run.
128
+ */
129
+ export async function persistHumanEditRecord(params: {
130
+ repo: { owner: string; name: string };
131
+ apiToken: string;
132
+ prNumber: number;
133
+ record: HumanEditRecord;
134
+ }): Promise<{ persisted: boolean; reason?: string }> {
135
+ if (!isBackendConfigured()) {
136
+ log.debug(
137
+ "no backend configured (API_URL unset) — skipping human-edit capture persist",
138
+ );
139
+ return { persisted: false, reason: "no_backend" };
140
+ }
141
+ try {
142
+ const response = await cloudRequest({
143
+ path: `/api/repo/${params.repo.owner}/${params.repo.name}/remediation-outcomes`,
144
+ method: "POST",
145
+ credential: params.apiToken,
146
+ json: { pr_number: params.prNumber, ...params.record },
147
+ timeoutMs: 10_000,
148
+ });
149
+ if (!response.ok) {
150
+ const error = await response.text().catch(() => "(no body)");
151
+ log.warning(
152
+ `human-edit capture persist failed (${response.status}): ${error}`,
153
+ );
154
+ return { persisted: false, reason: `http_${response.status}` };
155
+ }
156
+ log.info(
157
+ `» human-edit record captured for PR #${params.prNumber} (${params.record.outcome})`,
158
+ );
159
+ return { persisted: true };
160
+ } catch (err) {
161
+ log.warning(
162
+ `human-edit capture persist failed: ${err instanceof Error ? err.message : String(err)}`,
163
+ );
164
+ return { persisted: false, reason: "error" };
165
+ }
166
+ }
167
+
168
+ /**
169
+ * The single entry point for human-edit capture: classify the close event, build the
170
+ * structured delta, and persist it (no-op without a backend). Returns the record
171
+ * and whether it was persisted, so the caller can log/surface it. Pure parts are
172
+ * separately testable (`deriveRemediationOutcome` / `computeHumanEditDelta`);
173
+ * this just composes them with the dormant persistence seam.
174
+ */
175
+ export async function captureRemediationOutcome(params: {
176
+ repo: { owner: string; name: string };
177
+ apiToken: string;
178
+ event: RemediationPrClose;
179
+ }): Promise<{ record: HumanEditRecord; persisted: boolean; reason?: string }> {
180
+ const outcome = deriveRemediationOutcome(
181
+ params.event.merged,
182
+ params.event.originalFixDiff,
183
+ params.event.mergedDiff,
184
+ );
185
+ const record = computeHumanEditDelta({
186
+ concernIds: params.event.concernIds,
187
+ originalFixDiff: params.event.originalFixDiff,
188
+ mergedDiff: params.event.mergedDiff,
189
+ outcome,
190
+ });
191
+ const result = await persistHumanEditRecord({
192
+ repo: params.repo,
193
+ apiToken: params.apiToken,
194
+ prNumber: params.event.prNumber,
195
+ record,
196
+ });
197
+ return { record, ...result };
198
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Repo-committed `.infraweaver.yml` / `.infraweaver.json` config (the follow-up
3
+ * to the unified `tools_enabled` input). A thin layer that sits **under** the
4
+ * action inputs: an
5
+ * explicit workflow input always wins; the file only fills the gaps. It versions
6
+ * the toolchain + scoping policy *with the code* (and doubles as the auditable
7
+ * record of which non-permissive tools the repo owner opted into), so the
8
+ * workflow file can stay minimal and the policy lives next to the Terraform.
9
+ *
10
+ * Scope is deliberate — only repo-level **policy** knobs live here. Each maps
11
+ * 1:1 to the matching action input and flows through the SAME parser, so the file
12
+ * and the input validate identically. Secrets (`module_fetch_token`) and
13
+ * per-run/workflow knobs (`mode`, `max_prs`, `base_branch`, `allow_replace`) are
14
+ * intentionally NOT read from the file: a committed file is the wrong place for a
15
+ * credential, and the run's shape belongs to the workflow that dispatches it.
16
+ *
17
+ * Trust boundary: `.infraweaver.yml` is controlled by whoever can push to the repo
18
+ * — the same surface as the Terraform being remediated. It can only RELAX within
19
+ * the licence gate's structure (naming a non-permissive tool is the repo owner's
20
+ * licence acknowledgement, exactly as on the input) and it can never disable the
21
+ * required substrate. A workflow author who needs to *enforce* a policy sets the
22
+ * action input, which wins over the file.
23
+ *
24
+ * Degrade-green: a missing file is silent; malformed YAML, a non-mapping
25
+ * document, an unknown key, or a value of the wrong shape yields a warning and is
26
+ * ignored — never a hard failure.
27
+ */
28
+
29
+ import { readFileSync } from "node:fs";
30
+ import { join } from "node:path";
31
+ import { parse as parseYaml } from "yaml";
32
+ import { CONFIG_FILE, CONFIG_FILE_JSON, CONFIG_FILE_YAML } from "#app/brand";
33
+ import { log } from "#app/utils/cli";
34
+ import { normalizeExtends, resolvePresets } from "#app/utils/presets";
35
+
36
+ /** filenames checked, in order; the first that exists wins. `.infraweaver.json` is
37
+ * listed first so the JSON form takes precedence if a repo (mis)commits both. */
38
+ export const INFRAWEAVER_CONFIG_FILENAMES = [
39
+ CONFIG_FILE_JSON,
40
+ CONFIG_FILE,
41
+ CONFIG_FILE_YAML,
42
+ ] as const;
43
+
44
+ /** the repo-level keys `.infraweaver.yml` may set. Each is the snake_case name of
45
+ * the matching action input, so the file value can be fed straight through the
46
+ * input's own parser in `resolvePayload`. */
47
+ export const INFRAWEAVER_CONFIG_KEYS = [
48
+ "tools_enabled",
49
+ "protected_paths",
50
+ "allowed_paths",
51
+ "scan_scope",
52
+ "iac_languages",
53
+ "language",
54
+ "severity_threshold",
55
+ "grouping",
56
+ "autonomy_threshold",
57
+ "module_catalogue",
58
+ "required_controls",
59
+ ] as const;
60
+
61
+ export type InfraweaverConfigKey = (typeof INFRAWEAVER_CONFIG_KEYS)[number];
62
+
63
+ /** normalized string values (lists newline-joined), keyed by input name. */
64
+ export type InfraweaverFileValues = Partial<
65
+ Record<InfraweaverConfigKey, string>
66
+ >;
67
+
68
+ export interface ParsedInfraweaverConfig {
69
+ values: InfraweaverFileValues;
70
+ /** non-fatal problems (malformed value, unknown key) for the caller to log. */
71
+ warnings: string[];
72
+ }
73
+
74
+ const KEY_SET: ReadonlySet<string> = new Set(INFRAWEAVER_CONFIG_KEYS);
75
+ // Keys parsed by a dedicated path, not the scalar value pipeline: `suppressions`
76
+ // (the suppression-policy block) and `extends` (shareable presets).
77
+ const STRUCTURED_KEYS: ReadonlySet<string> = new Set([
78
+ "suppressions",
79
+ "extends",
80
+ ]);
81
+
82
+ /**
83
+ * Normalize a YAML value to the string shape the action-input parsers expect:
84
+ * a scalar → its trimmed string; a list → newline-joined (the tool-selection,
85
+ * glob, and module-catalogue parsers all split on newlines *or* commas). Returns
86
+ * null for an unusable value (a mapping, an empty/blank string, null) so the
87
+ * caller can warn and skip it.
88
+ */
89
+ function normalizeValue(value: unknown): string | null {
90
+ if (value === null || value === undefined) return null;
91
+ if (typeof value === "string") return value.trim() || null;
92
+ if (typeof value === "number" || typeof value === "boolean")
93
+ return String(value);
94
+ if (Array.isArray(value)) {
95
+ const items = value
96
+ .filter((v) => v !== null && v !== undefined && typeof v !== "object")
97
+ .map((v) => String(v).trim())
98
+ .filter(Boolean);
99
+ return items.length > 0 ? items.join("\n") : null;
100
+ }
101
+ return null; // a nested mapping isn't a valid value for any of these keys.
102
+ }
103
+
104
+ /**
105
+ * Parse raw `.infraweaver.yml`/`.infraweaver.json` text into normalized string values
106
+ * + warnings. Pure — no I/O, no logging — so the parsing/validation is
107
+ * unit-testable. `format` selects the syntax (and shapes the warning wording);
108
+ * everything after the document is parsed is identical, since JSON and YAML both
109
+ * yield a plain object here.
110
+ */
111
+ export function parseInfraweaverConfig(
112
+ raw: string,
113
+ format: "yaml" | "json" = "yaml",
114
+ ): ParsedInfraweaverConfig {
115
+ const label = format === "json" ? CONFIG_FILE_JSON : CONFIG_FILE;
116
+ let doc: unknown;
117
+ if (format === "json") {
118
+ // An empty/whitespace-only file is a valid no-op; JSON.parse would throw on it.
119
+ if (raw.trim() === "") return { values: {}, warnings: [] };
120
+ try {
121
+ doc = JSON.parse(raw);
122
+ } catch (err) {
123
+ const detail = err instanceof Error ? err.message : String(err);
124
+ return {
125
+ values: {},
126
+ warnings: [`${label} is not valid JSON — ignored (${detail})`],
127
+ };
128
+ }
129
+ } else {
130
+ try {
131
+ doc = parseYaml(raw);
132
+ } catch (err) {
133
+ const detail = err instanceof Error ? err.message : String(err);
134
+ return {
135
+ values: {},
136
+ warnings: [`${label} is not valid YAML — ignored (${detail})`],
137
+ };
138
+ }
139
+ }
140
+ // an empty file / only comments parses to null|undefined — a valid no-op.
141
+ if (doc === null || doc === undefined) return { values: {}, warnings: [] };
142
+ if (typeof doc !== "object" || Array.isArray(doc)) {
143
+ const shape =
144
+ format === "json" ? "JSON object" : "YAML mapping (key: value)";
145
+ return { values: {}, warnings: [`${label} must be a ${shape} — ignored`] };
146
+ }
147
+
148
+ const record = doc as Record<string, unknown>;
149
+ const warnings: string[] = [];
150
+
151
+ // Resolve `extends` presets FIRST so the file's own keys (handled below) can
152
+ // override them. Precedence: file key > later preset > earlier preset.
153
+ const presets = resolvePresets(normalizeExtends(record.extends));
154
+ warnings.push(...presets.warnings);
155
+ const values: InfraweaverFileValues = { ...presets.values };
156
+
157
+ for (const [key, value] of Object.entries(record)) {
158
+ if (STRUCTURED_KEYS.has(key)) continue;
159
+ if (!KEY_SET.has(key)) {
160
+ warnings.push(`${label}: ignoring unrecognised key "${key}"`);
161
+ continue;
162
+ }
163
+ const normalized = normalizeValue(value);
164
+ if (normalized === null) {
165
+ warnings.push(
166
+ `${label}: ignoring "${key}" — expected a string or a list of strings`,
167
+ );
168
+ continue;
169
+ }
170
+ values[key as InfraweaverConfigKey] = normalized;
171
+ }
172
+ return { values, warnings };
173
+ }
174
+
175
+ /**
176
+ * Read + parse the repo's `.infraweaver.yml` (first of the supported filenames
177
+ * found under `cwd`). Returns the normalized values, logging any warnings. A
178
+ * missing file resolves to `{}` silently — the common, valid case.
179
+ */
180
+ export function loadInfraweaverConfig(
181
+ cwd: string | undefined,
182
+ ): InfraweaverFileValues {
183
+ const root = cwd ?? process.cwd();
184
+ for (const name of INFRAWEAVER_CONFIG_FILENAMES) {
185
+ let raw: string;
186
+ try {
187
+ raw = readFileSync(join(root, name), "utf-8");
188
+ } catch {
189
+ continue; // not this filename — try the next, or fall through to {}.
190
+ }
191
+ const { values, warnings } = parseInfraweaverConfig(
192
+ raw,
193
+ name.endsWith(".json") ? "json" : "yaml",
194
+ );
195
+ for (const w of warnings) log.warning(`» ${w}`);
196
+ if (Object.keys(values).length > 0) {
197
+ log.info(`» loaded ${name} (${Object.keys(values).join(", ")})`);
198
+ }
199
+ return values;
200
+ }
201
+ return {};
202
+ }
@@ -0,0 +1,279 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ chmodSync,
5
+ createWriteStream,
6
+ existsSync,
7
+ readFileSync,
8
+ } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { pipeline } from "node:stream/promises";
11
+ import semver from "semver";
12
+ import { log } from "#app/utils/cli";
13
+ import { getTempDir } from "#app/utils/env";
14
+
15
+ // generous ceiling sized for the biggest artifact (a full CLI tarball on a
16
+ // slow runner) — a stalled CDN must fail the install instead of hanging the
17
+ // whole action with no timeout at all.
18
+ const INSTALL_FETCH_TIMEOUT_MS = 120_000;
19
+
20
+ export interface InstallFromNpmTarballParams {
21
+ packageName: string;
22
+ version: string;
23
+ executablePath: string;
24
+ installDependencies?: boolean;
25
+ }
26
+
27
+ /**
28
+ * The `dist` block npm returns for a published version. `integrity` is a
29
+ * Subresource-Integrity string (`sha512-<base64>`); `shasum` is the legacy hex
30
+ * SHA-1. Every public-registry version carries at least one.
31
+ */
32
+ export interface NpmDist {
33
+ integrity?: string;
34
+ shasum?: string;
35
+ }
36
+
37
+ interface NpmVersionManifest {
38
+ dist?: NpmDist;
39
+ }
40
+
41
+ interface NpmRegistryData {
42
+ "dist-tags": { latest: string };
43
+ versions: Record<string, NpmVersionManifest | undefined>;
44
+ }
45
+
46
+ /** npm path-encodes a scoped name's slash (`@scope/name` → `@scope%2Fname`). */
47
+ function encodePackageName(packageName: string): string {
48
+ return packageName.startsWith("@")
49
+ ? `@${packageName.slice(1).split("/").join("%2F")}`
50
+ : packageName;
51
+ }
52
+
53
+ /**
54
+ * Verify a downloaded tarball against the digest the registry published for
55
+ * that exact version, and throw if it doesn't match.
56
+ *
57
+ * This is what makes a consumer's `uses: infraweaver@<sha>` pin mean something:
58
+ * the action itself is pinned by SHA, but it then fetches an agent CLI at
59
+ * runtime. Without this check a compromised registry/CDN — or anything able to
60
+ * intercept the download — could serve arbitrary code into a run that reads the
61
+ * customer's repository. The version is already pinned by `package.json` (see
62
+ * `getDevDependencyVersion`, which refuses a range); this pins the *bytes*.
63
+ *
64
+ * Fails **closed**: no digest published ⇒ we refuse to install, rather than
65
+ * silently degrading to unverified. A registry that serves no digest is exactly
66
+ * the case worth stopping on.
67
+ */
68
+ export function verifyTarballIntegrity(
69
+ tarballPath: string,
70
+ dist: NpmDist | undefined,
71
+ descriptor: string,
72
+ ): void {
73
+ const integrity = dist?.integrity;
74
+ const shasum = dist?.shasum;
75
+
76
+ if (!integrity && !shasum) {
77
+ throw new Error(
78
+ `Refusing to install ${descriptor}: the registry published no integrity digest ` +
79
+ `(neither dist.integrity nor dist.shasum), so the download cannot be verified.`,
80
+ );
81
+ }
82
+
83
+ const bytes = readFileSync(tarballPath);
84
+
85
+ // prefer SRI (sha512/sha384/sha256) over the legacy SHA-1 shasum
86
+ if (integrity) {
87
+ // an SRI string may carry several space-separated digests; any one matching
88
+ // is a pass, which is how SRI is specified.
89
+ const candidates = integrity.trim().split(/\s+/).filter(Boolean);
90
+ for (const candidate of candidates) {
91
+ const separator = candidate.indexOf("-");
92
+ if (separator === -1) continue;
93
+ const algorithm = candidate.slice(0, separator);
94
+ const expected = candidate.slice(separator + 1);
95
+ let actual: string;
96
+ try {
97
+ actual = createHash(algorithm).update(bytes).digest("base64");
98
+ } catch {
99
+ continue; // unknown algorithm — try the next candidate
100
+ }
101
+ if (actual === expected) return;
102
+ }
103
+ throw new Error(
104
+ `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match ` +
105
+ `the registry's published digest (${integrity}). Refusing to execute it.`,
106
+ );
107
+ }
108
+
109
+ const actualShasum = createHash("sha1").update(bytes).digest("hex");
110
+ if (actualShasum !== shasum) {
111
+ throw new Error(
112
+ `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match ` +
113
+ `the registry's published shasum (expected ${shasum}, got ${actualShasum}). ` +
114
+ `Refusing to execute it.`,
115
+ );
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Install a CLI tool from an npm package tarball
121
+ * Downloads the tarball, extracts it to a temp directory, and returns the path to the CLI executable
122
+ * The temp directory will be cleaned up by the OS automatically
123
+ */
124
+ export async function installFromNpmTarball(
125
+ params: InstallFromNpmTarballParams,
126
+ ): Promise<string> {
127
+ const tempDir = getTempDir();
128
+
129
+ const extractedDir = join(tempDir, "package");
130
+ const cliPath = join(extractedDir, params.executablePath);
131
+
132
+ if (existsSync(cliPath)) {
133
+ log.debug(`» using cached binary at ${cliPath}`);
134
+ return cliPath;
135
+ }
136
+
137
+ const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
138
+ const encodedName = encodePackageName(params.packageName);
139
+
140
+ // Resolve version if it's a range or "latest", capturing the published digest
141
+ // from the same document so the common path costs one request, not two.
142
+ let resolvedVersion = params.version;
143
+ let dist: NpmDist | undefined;
144
+ if (
145
+ params.version.startsWith("^") ||
146
+ params.version.startsWith("~") ||
147
+ params.version === "latest"
148
+ ) {
149
+ log.debug(`» resolving version for ${params.version}...`);
150
+ try {
151
+ const registryResponse = await fetch(`${npmRegistry}/${encodedName}`, {
152
+ signal: AbortSignal.timeout(INSTALL_FETCH_TIMEOUT_MS),
153
+ });
154
+ if (!registryResponse.ok) {
155
+ throw new Error(`Failed to query registry: ${registryResponse.status}`);
156
+ }
157
+ const registryData = (await registryResponse.json()) as NpmRegistryData;
158
+ if (params.version === "latest") {
159
+ resolvedVersion = registryData["dist-tags"].latest;
160
+ } else {
161
+ // honour the range. previously this took `dist-tags.latest` for ANY
162
+ // range, so `^1.2.3` could install 3.x — a silent major bump.
163
+ const available = Object.keys(registryData.versions ?? {});
164
+ const best = semver.maxSatisfying(available, params.version);
165
+ if (!best) {
166
+ throw new Error(
167
+ `No published version of ${params.packageName} satisfies "${params.version}"`,
168
+ );
169
+ }
170
+ resolvedVersion = best;
171
+ }
172
+ dist = registryData.versions?.[resolvedVersion]?.dist;
173
+ log.debug(`» resolved to version ${resolvedVersion}`);
174
+ } catch (error) {
175
+ log.warning(
176
+ `Failed to resolve version from registry: ${error instanceof Error ? error.message : String(error)}`,
177
+ );
178
+ throw error;
179
+ }
180
+ }
181
+
182
+ // Exact pin: fetch just that version's manifest for its digest.
183
+ if (!dist) {
184
+ const manifestUrl = `${npmRegistry}/${encodedName}/${resolvedVersion}`;
185
+ log.debug(`» fetching integrity metadata from ${manifestUrl}...`);
186
+ const manifestResponse = await fetch(manifestUrl, {
187
+ signal: AbortSignal.timeout(INSTALL_FETCH_TIMEOUT_MS),
188
+ });
189
+ if (!manifestResponse.ok) {
190
+ throw new Error(
191
+ `Failed to fetch integrity metadata for ${params.packageName}@${resolvedVersion}: ${manifestResponse.status}`,
192
+ );
193
+ }
194
+ dist = ((await manifestResponse.json()) as NpmVersionManifest).dist;
195
+ }
196
+
197
+ log.debug(`» installing ${params.packageName}@${resolvedVersion}...`);
198
+
199
+ const tarballPath = join(tempDir, "package.tgz");
200
+
201
+ // Download tarball from npm. Scoped names keep the encoded form in the path
202
+ // but use the bare name in the filename (@scope/name -> name-1.2.3.tgz).
203
+ const tarballFileName = params.packageName.startsWith("@")
204
+ ? params.packageName.split("/")[1]
205
+ : params.packageName;
206
+ const tarballUrl = `${npmRegistry}/${encodedName}/-/${tarballFileName}-${resolvedVersion}.tgz`;
207
+
208
+ log.debug(`» downloading from ${tarballUrl}...`);
209
+ const response = await fetch(tarballUrl, {
210
+ signal: AbortSignal.timeout(INSTALL_FETCH_TIMEOUT_MS),
211
+ });
212
+ if (!response.ok) {
213
+ throw new Error(
214
+ `Failed to download tarball: ${response.status} ${response.statusText}`,
215
+ );
216
+ }
217
+
218
+ // Write tarball to file
219
+ if (!response.body) throw new Error("Response body is null");
220
+ const fileStream = createWriteStream(tarballPath);
221
+ await pipeline(response.body, fileStream);
222
+ log.debug(`» downloaded tarball to ${tarballPath}`);
223
+
224
+ // Verify BEFORE extracting — an unverified tarball is never unpacked, let
225
+ // alone executed. Throws (fail-closed) on mismatch or missing digest.
226
+ verifyTarballIntegrity(
227
+ tarballPath,
228
+ dist,
229
+ `${params.packageName}@${resolvedVersion}`,
230
+ );
231
+ log.debug(
232
+ `» integrity verified for ${params.packageName}@${resolvedVersion}`,
233
+ );
234
+
235
+ // Extract tarball
236
+ log.debug(`» extracting tarball...`);
237
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
238
+ stdio: "pipe",
239
+ encoding: "utf-8",
240
+ });
241
+ if (extractResult.status !== 0) {
242
+ throw new Error(
243
+ `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`,
244
+ );
245
+ }
246
+
247
+ if (!existsSync(cliPath)) {
248
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
249
+ }
250
+
251
+ // Install dependencies if requested
252
+ if (params.installDependencies) {
253
+ log.debug(`» installing dependencies for ${params.packageName}...`);
254
+ const installResult = spawnSync("npm", ["install", "--production"], {
255
+ cwd: extractedDir,
256
+ stdio: "pipe",
257
+ encoding: "utf-8",
258
+ // @anthropic-ai/claude-code (2.1.113+) guards its `prepare` script with an
259
+ // `AUTHORIZED` env check that hard-fails any non-release install. we need
260
+ // its `postinstall` (which copies the native binary from the platform
261
+ // optionalDependency over the bin/claude.exe stub) to run, so we set the
262
+ // flag to no-op the guard. harmless for packages without such a guard.
263
+ env: { ...process.env, AUTHORIZED: "1" },
264
+ });
265
+ if (installResult.status !== 0) {
266
+ throw new Error(
267
+ `Failed to install dependencies: ${installResult.stderr || installResult.stdout || "Unknown error"}`,
268
+ );
269
+ }
270
+ log.debug(`» dependencies installed`);
271
+ }
272
+
273
+ // Make the file executable
274
+ chmodSync(cliPath, 0o755);
275
+
276
+ log.debug(`» ${params.packageName} installed at ${cliPath}`);
277
+
278
+ return cliPath;
279
+ }