terramend 0.2.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 (406) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +145 -0
  3. package/dist/agents/claude.d.ts +73 -0
  4. package/dist/agents/claudePretoolGate.d.ts +99 -0
  5. package/dist/agents/gateServer.d.ts +7 -0
  6. package/dist/agents/index.d.ts +6 -0
  7. package/dist/agents/nativeFsDenies.d.ts +28 -0
  8. package/dist/agents/opencode.d.ts +231 -0
  9. package/dist/agents/opencodePlugin.d.ts +85 -0
  10. package/dist/agents/opencodeShared.d.ts +40 -0
  11. package/dist/agents/postRun.d.ts +132 -0
  12. package/dist/agents/reviewer.d.ts +38 -0
  13. package/dist/agents/sessionLabeler.d.ts +97 -0
  14. package/dist/agents/shared.d.ts +189 -0
  15. package/dist/agents/subagentModels.d.ts +19 -0
  16. package/dist/agents/subagentToolGates.d.ts +55 -0
  17. package/dist/cli.mjs +197426 -0
  18. package/dist/external.d.ts +227 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +196783 -0
  21. package/dist/internal/index.d.ts +18 -0
  22. package/dist/internal.js +1714 -0
  23. package/dist/lifecycle.d.ts +2 -0
  24. package/dist/main.d.ts +8 -0
  25. package/dist/mcp/arkConfig.d.ts +1 -0
  26. package/dist/mcp/checkSuite.d.ts +25 -0
  27. package/dist/mcp/checkout.d.ts +77 -0
  28. package/dist/mcp/comment.d.ts +119 -0
  29. package/dist/mcp/commitInfo.d.ts +9 -0
  30. package/dist/mcp/crosswalk.d.ts +105 -0
  31. package/dist/mcp/dependencies.d.ts +8 -0
  32. package/dist/mcp/geminiSanitizer.d.ts +28 -0
  33. package/dist/mcp/git.d.ts +46 -0
  34. package/dist/mcp/guardrails.d.ts +104 -0
  35. package/dist/mcp/issue.d.ts +18 -0
  36. package/dist/mcp/issueComments.d.ts +9 -0
  37. package/dist/mcp/issueEvents.d.ts +9 -0
  38. package/dist/mcp/issueInfo.d.ts +9 -0
  39. package/dist/mcp/labels.d.ts +12 -0
  40. package/dist/mcp/localContext.d.ts +19 -0
  41. package/dist/mcp/moduleExtraction.d.ts +71 -0
  42. package/dist/mcp/moduleTests.d.ts +104 -0
  43. package/dist/mcp/modules.d.ts +179 -0
  44. package/dist/mcp/output.d.ts +12 -0
  45. package/dist/mcp/pathSafety.d.ts +14 -0
  46. package/dist/mcp/policy.d.ts +48 -0
  47. package/dist/mcp/pr.d.ts +49 -0
  48. package/dist/mcp/prInfo.d.ts +9 -0
  49. package/dist/mcp/providerSchema.d.ts +50 -0
  50. package/dist/mcp/review.d.ts +199 -0
  51. package/dist/mcp/reviewComments.d.ts +178 -0
  52. package/dist/mcp/roots.d.ts +58 -0
  53. package/dist/mcp/scope.d.ts +15 -0
  54. package/dist/mcp/selectMode.d.ts +18 -0
  55. package/dist/mcp/server.d.ts +48 -0
  56. package/dist/mcp/shared.d.ts +47 -0
  57. package/dist/mcp/shell.d.ts +37 -0
  58. package/dist/mcp/staleFix.d.ts +51 -0
  59. package/dist/mcp/terraform/cost.d.ts +55 -0
  60. package/dist/mcp/terraform/currency.d.ts +94 -0
  61. package/dist/mcp/terraform/decisions.d.ts +178 -0
  62. package/dist/mcp/terraform/findings.d.ts +75 -0
  63. package/dist/mcp/terraform/plan.d.ts +157 -0
  64. package/dist/mcp/terraform/scanners.d.ts +131 -0
  65. package/dist/mcp/terraform/tools.d.ts +63 -0
  66. package/dist/mcp/terraform/types.d.ts +172 -0
  67. package/dist/mcp/terraform.d.ts +22 -0
  68. package/dist/mcp/terratest.d.ts +83 -0
  69. package/dist/mcp/upload.d.ts +6 -0
  70. package/dist/models.d.ts +171 -0
  71. package/dist/modes.d.ts +26 -0
  72. package/dist/prep/index.d.ts +7 -0
  73. package/dist/prep/installNodeDependencies.d.ts +2 -0
  74. package/dist/prep/installPythonDependencies.d.ts +2 -0
  75. package/dist/prep/types.d.ts +31 -0
  76. package/dist/reviewQuality.d.ts +64 -0
  77. package/dist/skills/terraform-best-practices/SKILL.md +369 -0
  78. package/dist/toolState.d.ts +135 -0
  79. package/dist/utils/activity.d.ts +40 -0
  80. package/dist/utils/agent.d.ts +20 -0
  81. package/dist/utils/agentHangReport.d.ts +38 -0
  82. package/dist/utils/apiFetch.d.ts +19 -0
  83. package/dist/utils/apiKeys.d.ts +41 -0
  84. package/dist/utils/apiUrl.d.ts +20 -0
  85. package/dist/utils/assets.d.ts +8 -0
  86. package/dist/utils/billingErrors.d.ts +85 -0
  87. package/dist/utils/body.d.ts +34 -0
  88. package/dist/utils/buildTerramendFooter.d.ts +25 -0
  89. package/dist/utils/byokFallback.d.ts +85 -0
  90. package/dist/utils/claudeSubscription.d.ts +30 -0
  91. package/dist/utils/cli.d.ts +10 -0
  92. package/dist/utils/codexHome.d.ts +29 -0
  93. package/dist/utils/codexOAuth.d.ts +60 -0
  94. package/dist/utils/diffCoverage.d.ts +63 -0
  95. package/dist/utils/errorReport.d.ts +17 -0
  96. package/dist/utils/exitHandler.d.ts +8 -0
  97. package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
  98. package/dist/utils/gitAuth.d.ts +84 -0
  99. package/dist/utils/gitAuthServer.d.ts +24 -0
  100. package/dist/utils/github.d.ts +78 -0
  101. package/dist/utils/globals.d.ts +3 -0
  102. package/dist/utils/install.d.ts +60 -0
  103. package/dist/utils/instructions.d.ts +48 -0
  104. package/dist/utils/leapingComment.d.ts +11 -0
  105. package/dist/utils/learnings.d.ts +62 -0
  106. package/dist/utils/learningsTruncate.d.ts +25 -0
  107. package/dist/utils/lifecycle.d.ts +57 -0
  108. package/dist/utils/log.d.ts +111 -0
  109. package/dist/utils/normalizeEnv.d.ts +30 -0
  110. package/dist/utils/openCodeModels.d.ts +11 -0
  111. package/dist/utils/overrides.d.ts +40 -0
  112. package/dist/utils/packageManager.d.ts +49 -0
  113. package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
  114. package/dist/utils/payload.d.ts +105 -0
  115. package/dist/utils/prSummary.d.ts +61 -0
  116. package/dist/utils/progressComment.d.ts +146 -0
  117. package/dist/utils/providerErrors.d.ts +31 -0
  118. package/dist/utils/rangeDiff.d.ts +51 -0
  119. package/dist/utils/remediationCommand.d.ts +55 -0
  120. package/dist/utils/retry.d.ts +13 -0
  121. package/dist/utils/reviewCleanup.d.ts +14 -0
  122. package/dist/utils/run.d.ts +9 -0
  123. package/dist/utils/runContext.d.ts +60 -0
  124. package/dist/utils/runContextData.d.ts +23 -0
  125. package/dist/utils/runErrorRenderer.d.ts +64 -0
  126. package/dist/utils/runLifecycle.d.ts +86 -0
  127. package/dist/utils/runStartupLog.d.ts +15 -0
  128. package/dist/utils/secrets.d.ts +22 -0
  129. package/dist/utils/setup.d.ts +90 -0
  130. package/dist/utils/shell.d.ts +32 -0
  131. package/dist/utils/skills.d.ts +10 -0
  132. package/dist/utils/subprocess.d.ts +80 -0
  133. package/dist/utils/terraformMcp.d.ts +42 -0
  134. package/dist/utils/time.d.ts +15 -0
  135. package/dist/utils/timer.d.ts +23 -0
  136. package/dist/utils/todoTracking.d.ts +16 -0
  137. package/dist/utils/token.d.ts +39 -0
  138. package/dist/utils/version.d.ts +2 -0
  139. package/dist/utils/versioning.d.ts +7 -0
  140. package/dist/utils/vertex.d.ts +16 -0
  141. package/dist/utils/workflow.d.ts +13 -0
  142. package/package.json +119 -0
  143. package/src/agents/claude.test.ts +1016 -0
  144. package/src/agents/claude.ts +1246 -0
  145. package/src/agents/claudePretoolGate.test.ts +28 -0
  146. package/src/agents/claudePretoolGate.ts +173 -0
  147. package/src/agents/gateServer.test.ts +204 -0
  148. package/src/agents/gateServer.ts +124 -0
  149. package/src/agents/index.ts +10 -0
  150. package/src/agents/nativeFsDenies.ts +82 -0
  151. package/src/agents/opencode.test.ts +1440 -0
  152. package/src/agents/opencode.ts +1312 -0
  153. package/src/agents/opencodePlugin.ts +222 -0
  154. package/src/agents/opencodeShared.test.ts +34 -0
  155. package/src/agents/opencodeShared.ts +121 -0
  156. package/src/agents/postRun.test.ts +549 -0
  157. package/src/agents/postRun.ts +535 -0
  158. package/src/agents/reviewer.ts +104 -0
  159. package/src/agents/sessionLabeler.test.ts +247 -0
  160. package/src/agents/sessionLabeler.ts +178 -0
  161. package/src/agents/shared.test.ts +76 -0
  162. package/src/agents/shared.ts +292 -0
  163. package/src/agents/subagentModels.test.ts +113 -0
  164. package/src/agents/subagentModels.ts +40 -0
  165. package/src/agents/subagentRegistration.test.ts +41 -0
  166. package/src/agents/subagentToolGates.ts +114 -0
  167. package/src/cli.test.ts +129 -0
  168. package/src/cli.ts +105 -0
  169. package/src/commands/gha.test.ts +192 -0
  170. package/src/commands/gha.ts +188 -0
  171. package/src/commands/mcp.ts +122 -0
  172. package/src/config.ts +1 -0
  173. package/src/entry.ts +7 -0
  174. package/src/entryPost.stdlibOnly.test.ts +109 -0
  175. package/src/entryPost.ts +99 -0
  176. package/src/external.test.ts +16 -0
  177. package/src/external.ts +302 -0
  178. package/src/index.ts +11 -0
  179. package/src/internal/index.ts +71 -0
  180. package/src/lifecycle.ts +2 -0
  181. package/src/main.test.ts +873 -0
  182. package/src/main.ts +712 -0
  183. package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
  184. package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
  185. package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
  186. package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
  187. package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
  188. package/src/mcp/arkConfig.ts +7 -0
  189. package/src/mcp/checkSuite.test.ts +245 -0
  190. package/src/mcp/checkSuite.ts +255 -0
  191. package/src/mcp/checkout.test.ts +752 -0
  192. package/src/mcp/checkout.ts +886 -0
  193. package/src/mcp/comment.test.ts +772 -0
  194. package/src/mcp/comment.ts +582 -0
  195. package/src/mcp/commitInfo.test.ts +127 -0
  196. package/src/mcp/commitInfo.ts +61 -0
  197. package/src/mcp/crosswalk.test.ts +106 -0
  198. package/src/mcp/crosswalk.ts +339 -0
  199. package/src/mcp/dependencies.test.ts +309 -0
  200. package/src/mcp/dependencies.ts +189 -0
  201. package/src/mcp/geminiSanitizer.test.ts +287 -0
  202. package/src/mcp/geminiSanitizer.ts +207 -0
  203. package/src/mcp/git.test.ts +1083 -0
  204. package/src/mcp/git.ts +890 -0
  205. package/src/mcp/guardrails.test.ts +705 -0
  206. package/src/mcp/guardrails.ts +465 -0
  207. package/src/mcp/issue.test.ts +113 -0
  208. package/src/mcp/issue.ts +73 -0
  209. package/src/mcp/issueComments.test.ts +69 -0
  210. package/src/mcp/issueComments.ts +48 -0
  211. package/src/mcp/issueEvents.test.ts +134 -0
  212. package/src/mcp/issueEvents.ts +100 -0
  213. package/src/mcp/issueInfo.test.ts +104 -0
  214. package/src/mcp/issueInfo.ts +72 -0
  215. package/src/mcp/labels.test.ts +52 -0
  216. package/src/mcp/labels.ts +34 -0
  217. package/src/mcp/localContext.ts +28 -0
  218. package/src/mcp/localServer.test.ts +75 -0
  219. package/src/mcp/localServer.ts +131 -0
  220. package/src/mcp/moduleExtraction.test.ts +261 -0
  221. package/src/mcp/moduleExtraction.ts +313 -0
  222. package/src/mcp/moduleTests.test.ts +269 -0
  223. package/src/mcp/moduleTests.ts +421 -0
  224. package/src/mcp/modules.test.ts +640 -0
  225. package/src/mcp/modules.ts +696 -0
  226. package/src/mcp/output.test.ts +96 -0
  227. package/src/mcp/output.ts +70 -0
  228. package/src/mcp/pathSafety.test.ts +44 -0
  229. package/src/mcp/pathSafety.ts +28 -0
  230. package/src/mcp/policy.test.ts +282 -0
  231. package/src/mcp/policy.ts +199 -0
  232. package/src/mcp/pr.test.ts +387 -0
  233. package/src/mcp/pr.ts +194 -0
  234. package/src/mcp/prInfo.test.ts +96 -0
  235. package/src/mcp/prInfo.ts +91 -0
  236. package/src/mcp/providerSchema.test.ts +85 -0
  237. package/src/mcp/providerSchema.ts +175 -0
  238. package/src/mcp/review.test.ts +936 -0
  239. package/src/mcp/review.ts +923 -0
  240. package/src/mcp/reviewComments.test.ts +549 -0
  241. package/src/mcp/reviewComments.ts +896 -0
  242. package/src/mcp/roots.test.ts +175 -0
  243. package/src/mcp/roots.ts +217 -0
  244. package/src/mcp/scope.test.ts +59 -0
  245. package/src/mcp/scope.ts +65 -0
  246. package/src/mcp/security.test.ts +720 -0
  247. package/src/mcp/selectMode.test.ts +210 -0
  248. package/src/mcp/selectMode.ts +181 -0
  249. package/src/mcp/server.test.ts +292 -0
  250. package/src/mcp/server.ts +403 -0
  251. package/src/mcp/shared.ts +100 -0
  252. package/src/mcp/shell.test.ts +520 -0
  253. package/src/mcp/shell.ts +505 -0
  254. package/src/mcp/staleFix.test.ts +237 -0
  255. package/src/mcp/staleFix.ts +277 -0
  256. package/src/mcp/terraform/cost.ts +163 -0
  257. package/src/mcp/terraform/currency.test.ts +338 -0
  258. package/src/mcp/terraform/currency.ts +336 -0
  259. package/src/mcp/terraform/decisions.ts +527 -0
  260. package/src/mcp/terraform/findings.ts +333 -0
  261. package/src/mcp/terraform/plan.ts +348 -0
  262. package/src/mcp/terraform/scanners.ts +809 -0
  263. package/src/mcp/terraform/tools.test.ts +1071 -0
  264. package/src/mcp/terraform/tools.ts +908 -0
  265. package/src/mcp/terraform/types.ts +305 -0
  266. package/src/mcp/terraform.test.ts +1957 -0
  267. package/src/mcp/terraform.ts +23 -0
  268. package/src/mcp/terratest.test.ts +105 -0
  269. package/src/mcp/terratest.ts +196 -0
  270. package/src/mcp/toolFiltering.test.ts +85 -0
  271. package/src/mcp/upload.test.ts +180 -0
  272. package/src/mcp/upload.ts +112 -0
  273. package/src/models.test.ts +300 -0
  274. package/src/models.ts +708 -0
  275. package/src/modes.test.ts +107 -0
  276. package/src/modes.ts +880 -0
  277. package/src/prep/index.ts +43 -0
  278. package/src/prep/installNodeDependencies.test.ts +298 -0
  279. package/src/prep/installNodeDependencies.ts +196 -0
  280. package/src/prep/installPythonDependencies.test.ts +268 -0
  281. package/src/prep/installPythonDependencies.ts +199 -0
  282. package/src/prep/types.ts +38 -0
  283. package/src/reviewQuality.test.ts +63 -0
  284. package/src/reviewQuality.ts +134 -0
  285. package/src/runCli.test.ts +214 -0
  286. package/src/runCli.ts +282 -0
  287. package/src/skills/terraform-best-practices/SKILL.md +369 -0
  288. package/src/toolState.test.ts +45 -0
  289. package/src/toolState.ts +252 -0
  290. package/src/utils/activity.test.ts +188 -0
  291. package/src/utils/activity.ts +210 -0
  292. package/src/utils/agent.test.ts +251 -0
  293. package/src/utils/agent.ts +139 -0
  294. package/src/utils/agentHangReport.test.ts +203 -0
  295. package/src/utils/agentHangReport.ts +170 -0
  296. package/src/utils/apiFetch.test.ts +115 -0
  297. package/src/utils/apiFetch.ts +62 -0
  298. package/src/utils/apiKeys.test.ts +344 -0
  299. package/src/utils/apiKeys.ts +206 -0
  300. package/src/utils/apiUrl.test.ts +30 -0
  301. package/src/utils/apiUrl.ts +59 -0
  302. package/src/utils/assets.test.ts +153 -0
  303. package/src/utils/assets.ts +107 -0
  304. package/src/utils/billingErrors.test.ts +121 -0
  305. package/src/utils/billingErrors.ts +189 -0
  306. package/src/utils/body.test.ts +217 -0
  307. package/src/utils/body.ts +168 -0
  308. package/src/utils/buildTerramendFooter.test.ts +38 -0
  309. package/src/utils/buildTerramendFooter.ts +82 -0
  310. package/src/utils/byokFallback.test.ts +205 -0
  311. package/src/utils/byokFallback.ts +128 -0
  312. package/src/utils/claudeSubscription.test.ts +179 -0
  313. package/src/utils/claudeSubscription.ts +93 -0
  314. package/src/utils/cli.ts +31 -0
  315. package/src/utils/codexHome.test.ts +190 -0
  316. package/src/utils/codexHome.ts +191 -0
  317. package/src/utils/codexOAuth.ts +147 -0
  318. package/src/utils/codexRefreshDetect.test.ts +85 -0
  319. package/src/utils/codexRefreshDetect.ts +35 -0
  320. package/src/utils/diffCoverage.test.ts +468 -0
  321. package/src/utils/diffCoverage.ts +404 -0
  322. package/src/utils/errorReport.test.ts +135 -0
  323. package/src/utils/errorReport.ts +83 -0
  324. package/src/utils/exitHandler.ts +35 -0
  325. package/src/utils/fixDoubleEscapedString.ts +9 -0
  326. package/src/utils/ghaCore.ts +13 -0
  327. package/src/utils/gitAuth.test.ts +322 -0
  328. package/src/utils/gitAuth.ts +263 -0
  329. package/src/utils/gitAuthServer.test.ts +260 -0
  330. package/src/utils/gitAuthServer.ts +182 -0
  331. package/src/utils/github.test.ts +615 -0
  332. package/src/utils/github.ts +538 -0
  333. package/src/utils/globals.ts +9 -0
  334. package/src/utils/humanEditCapture.test.ts +100 -0
  335. package/src/utils/humanEditCapture.ts +193 -0
  336. package/src/utils/install.test.ts +768 -0
  337. package/src/utils/install.ts +492 -0
  338. package/src/utils/instructions.test.ts +240 -0
  339. package/src/utils/instructions.ts +543 -0
  340. package/src/utils/leapingComment.test.ts +51 -0
  341. package/src/utils/leapingComment.ts +18 -0
  342. package/src/utils/learnings.test.ts +87 -0
  343. package/src/utils/learnings.ts +138 -0
  344. package/src/utils/learningsTocRender.test.ts +116 -0
  345. package/src/utils/learningsTruncate.test.ts +39 -0
  346. package/src/utils/learningsTruncate.ts +42 -0
  347. package/src/utils/lifecycle.test.ts +195 -0
  348. package/src/utils/lifecycle.ts +198 -0
  349. package/src/utils/log.test.ts +402 -0
  350. package/src/utils/log.ts +432 -0
  351. package/src/utils/normalizeEnv.test.ts +91 -0
  352. package/src/utils/normalizeEnv.ts +106 -0
  353. package/src/utils/openCodeModels.ts +82 -0
  354. package/src/utils/overrides.test.ts +89 -0
  355. package/src/utils/overrides.ts +98 -0
  356. package/src/utils/packageManager.test.ts +321 -0
  357. package/src/utils/packageManager.ts +257 -0
  358. package/src/utils/patchWorkflowRunFields.test.ts +92 -0
  359. package/src/utils/patchWorkflowRunFields.ts +150 -0
  360. package/src/utils/payload.test.ts +497 -0
  361. package/src/utils/payload.ts +371 -0
  362. package/src/utils/postApiFetch.ts +51 -0
  363. package/src/utils/prSummary.test.ts +224 -0
  364. package/src/utils/prSummary.ts +147 -0
  365. package/src/utils/progressComment.ts +261 -0
  366. package/src/utils/providerErrors.test.ts +315 -0
  367. package/src/utils/providerErrors.ts +172 -0
  368. package/src/utils/rangeDiff.test.ts +236 -0
  369. package/src/utils/rangeDiff.ts +182 -0
  370. package/src/utils/remediationCommand.test.ts +163 -0
  371. package/src/utils/remediationCommand.ts +119 -0
  372. package/src/utils/retry.test.ts +153 -0
  373. package/src/utils/retry.ts +58 -0
  374. package/src/utils/reviewCleanup.ts +106 -0
  375. package/src/utils/run.ts +99 -0
  376. package/src/utils/runContext.ts +145 -0
  377. package/src/utils/runContextData.ts +58 -0
  378. package/src/utils/runErrorRenderer.test.ts +95 -0
  379. package/src/utils/runErrorRenderer.ts +259 -0
  380. package/src/utils/runFixture.ts +76 -0
  381. package/src/utils/runLifecycle.ts +237 -0
  382. package/src/utils/runStartupLog.ts +60 -0
  383. package/src/utils/secrets.test.ts +103 -0
  384. package/src/utils/secrets.ts +177 -0
  385. package/src/utils/setup.test.ts +509 -0
  386. package/src/utils/setup.ts +352 -0
  387. package/src/utils/shell.ts +103 -0
  388. package/src/utils/skills.test.ts +46 -0
  389. package/src/utils/skills.ts +67 -0
  390. package/src/utils/subprocess.test.ts +170 -0
  391. package/src/utils/subprocess.ts +438 -0
  392. package/src/utils/terraformMcp.test.ts +63 -0
  393. package/src/utils/terraformMcp.ts +83 -0
  394. package/src/utils/time.test.ts +105 -0
  395. package/src/utils/time.ts +59 -0
  396. package/src/utils/timer.test.ts +91 -0
  397. package/src/utils/timer.ts +72 -0
  398. package/src/utils/todoTracking.test.ts +223 -0
  399. package/src/utils/todoTracking.ts +167 -0
  400. package/src/utils/token.test.ts +239 -0
  401. package/src/utils/token.ts +186 -0
  402. package/src/utils/version.ts +10 -0
  403. package/src/utils/versioning.test.ts +34 -0
  404. package/src/utils/versioning.ts +44 -0
  405. package/src/utils/vertex.ts +85 -0
  406. package/src/utils/workflow.ts +25 -0
@@ -0,0 +1,421 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { type } from "arktype";
4
+ import type { LocalToolContext } from "#app/mcp/localContext";
5
+ import { collectModuleInterface } from "#app/mcp/modules";
6
+ import { resolveWithinCwd } from "#app/mcp/pathSafety";
7
+ import { execute, tool, toolOk } from "#app/mcp/shared";
8
+ import { log } from "#app/utils/cli";
9
+
10
+ /**
11
+ * §28 (remediation slice) — keep a reusable module's EXISTING tests/examples
12
+ * consistent with a fix. `scaffold_terratest` covers the GENERATION direction
13
+ * (new module → new plan-only tests). This is the inverse: when Remediate fixes
14
+ * an existing local/house module and the fix changes the module's public
15
+ * interface (adds a required `variable`, renames or removes one, tightens a
16
+ * type), the module's `examples/` fixtures and `terraform test` / Terratest
17
+ * files that CALL it can silently go stale — a missing required variable breaks
18
+ * `terraform test`/`plan`, and a reference to a removed variable is an error.
19
+ *
20
+ * This module finds those test/example assets for a module dir and computes the
21
+ * DRIFT between what each asset passes and the module's CURRENT interface, so the
22
+ * agent updates exactly the assets that need it (and only those) after a fix —
23
+ * never weakening an assertion to go green. The parsing is pure + unit-tested;
24
+ * the collector just reads the conventional locations (no full-repo walk).
25
+ */
26
+
27
+ export type ModuleTestAssetKind = "example" | "native-test" | "go-test";
28
+
29
+ export interface ModuleTestAsset {
30
+ /** repo-relative path: a dir for examples, a file for native/go tests. */
31
+ path: string;
32
+ kind: ModuleTestAssetKind;
33
+ /** variable names the asset passes to the module (best-effort for go tests). */
34
+ set_variables: string[];
35
+ }
36
+
37
+ export interface ModuleTestDrift {
38
+ path: string;
39
+ kind: ModuleTestAssetKind;
40
+ /** required module variables the asset does NOT set (would break plan/test). */
41
+ missing_required: string[];
42
+ /** variables the asset sets that are NOT in the module interface — the fix
43
+ * renamed or removed them, so the reference is now stale/an error. */
44
+ unknown_set: string[];
45
+ }
46
+
47
+ export interface ModuleTestReport {
48
+ module_dir: string;
49
+ /** the module's CURRENT interface, the truth the assets must match. */
50
+ required_variables: string[];
51
+ variable_names: string[];
52
+ assets: ModuleTestAsset[];
53
+ drift: ModuleTestDrift[];
54
+ }
55
+
56
+ // module-block meta-arguments that are not module variables — excluded when
57
+ // reading which variables an example's `module` block sets.
58
+ const MODULE_META_ARGS = new Set([
59
+ "source",
60
+ "version",
61
+ "providers",
62
+ "count",
63
+ "for_each",
64
+ "depends_on",
65
+ "lifecycle",
66
+ ]);
67
+
68
+ /** collapse every nested `{…}` span (to a fixpoint) so an attribute name INSIDE
69
+ * an object value or a nested block isn't read as a top-level attribute. Mirrors
70
+ * the helper in modules.ts (kept local to avoid widening that module's surface). */
71
+ function stripNestedBraces(s: string): string {
72
+ let prev: string;
73
+ let cur = s;
74
+ do {
75
+ prev = cur;
76
+ cur = cur.replace(/\{[^{}]*\}/g, " ");
77
+ } while (cur !== prev);
78
+ return cur;
79
+ }
80
+
81
+ /**
82
+ * The TOP-LEVEL attribute names assigned (`name = …`) directly in an HCL block
83
+ * body — nested-block / object-field keys are excluded by collapsing `{…}` spans
84
+ * first. Pure. Used for both a `module` block's arguments and a `variables {}`
85
+ * block's entries. `==`/`>=`/`!=` etc. are excluded via the `=(?!=)` guard and
86
+ * by requiring the name to start a line.
87
+ */
88
+ export function topLevelAttributeNames(body: string): string[] {
89
+ const flat = stripNestedBraces(body);
90
+ const names: string[] = [];
91
+ const re = /(?:^|\n)\s*([A-Za-z_][A-Za-z0-9_-]*)\s*=(?!=)/g;
92
+ let m: RegExpExecArray | null;
93
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex-exec iteration
94
+ while ((m = re.exec(flat)) !== null) names.push(m[1]!);
95
+ return names;
96
+ }
97
+
98
+ /** brace-match the body of every `<header>{` occurrence. `headerRe` must end its
99
+ * match at the `{` of the block. Returns each block's inner body. Pure. */
100
+ function eachBlockBody(hcl: string, headerRe: RegExp): string[] {
101
+ const out: string[] = [];
102
+ // exec advances headerRe.lastIndex to just past the `{`; the capture groups are
103
+ // unused here (we only need the brace position), so we don't bind the match.
104
+ while (headerRe.exec(hcl) !== null) {
105
+ const braceStart = headerRe.lastIndex - 1;
106
+ let depth = 0;
107
+ let end = -1;
108
+ for (let i = braceStart; i < hcl.length; i++) {
109
+ if (hcl[i] === "{") depth++;
110
+ else if (hcl[i] === "}") {
111
+ depth--;
112
+ if (depth === 0) {
113
+ end = i;
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ if (end === -1) break;
119
+ out.push(hcl.slice(braceStart + 1, end));
120
+ headerRe.lastIndex = end + 1;
121
+ }
122
+ return out;
123
+ }
124
+
125
+ /** the `source = "…"` of a module block body (for matching an example back to a
126
+ * module dir), or null. */
127
+ function moduleBlockSource(body: string): string | null {
128
+ return body.match(/(?:^|\n)\s*source\s*=\s*"([^"]+)"/)?.[1] ?? null;
129
+ }
130
+
131
+ /**
132
+ * Variables an example sets on the module(s) whose `source` satisfies
133
+ * `sourceMatches` — the union of the matching `module` blocks' top-level
134
+ * arguments (minus meta-args like `source`/`version`/`count`). Pure. Returns []
135
+ * when no module block in the HCL targets the module.
136
+ */
137
+ export function parseExampleModuleVariables(
138
+ hcl: string,
139
+ sourceMatches: (source: string) => boolean,
140
+ ): string[] {
141
+ const set = new Set<string>();
142
+ for (const body of eachBlockBody(hcl, /module\s+"[^"]+"\s*\{/g)) {
143
+ const source = moduleBlockSource(body);
144
+ if (!source || !sourceMatches(source)) continue;
145
+ for (const name of topLevelAttributeNames(body)) {
146
+ if (!MODULE_META_ARGS.has(name)) set.add(name);
147
+ }
148
+ }
149
+ return [...set];
150
+ }
151
+
152
+ /**
153
+ * Variables a Terraform-native test (`*.tftest.hcl`) sets — the union of every
154
+ * `variables { … }` block's top-level entries (the top-level block plus any
155
+ * inside `run "…" { … }`). When the test lives in the module's own dir it tests
156
+ * the module in place, so these are the module's variables. Pure.
157
+ */
158
+ export function parseNativeTestVariables(hcl: string): string[] {
159
+ const set = new Set<string>();
160
+ for (const body of eachBlockBody(hcl, /(?:^|\n)\s*variables\s*\{/g)) {
161
+ for (const name of topLevelAttributeNames(body)) set.add(name);
162
+ }
163
+ return [...set];
164
+ }
165
+
166
+ /**
167
+ * Variable keys a Go Terratest sets — the `"<key>":` entries inside a
168
+ * `Vars: map[string]interface{}{ … }` (or `map[string]any{ … }`) literal.
169
+ * Best-effort (Go isn't parsed structurally); advisory only. Pure.
170
+ */
171
+ export function parseGoTestVariables(go: string): string[] {
172
+ const set = new Set<string>();
173
+ const varsBlock = go.match(/Vars\s*:\s*map\[string\](?:interface\{\}|any)\s*\{/);
174
+ if (!varsBlock || varsBlock.index === undefined) return [];
175
+ const start = go.indexOf("{", varsBlock.index + varsBlock[0].length - 1);
176
+ if (start === -1) return [];
177
+ let depth = 0;
178
+ let end = -1;
179
+ for (let i = start; i < go.length; i++) {
180
+ if (go[i] === "{") depth++;
181
+ else if (go[i] === "}") {
182
+ depth--;
183
+ if (depth === 0) {
184
+ end = i;
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ if (end === -1) return [];
190
+ const body = go.slice(start + 1, end);
191
+ // only KEYS that begin a map entry: a quoted string followed by ':'. A commented
192
+ // line ('// "x": nil') still counts as a referenced variable — that's the TODO
193
+ // placeholder the scaffold writes, and it should track the interface too.
194
+ const re = /"([A-Za-z_][A-Za-z0-9_-]*)"\s*:/g;
195
+ let m: RegExpExecArray | null;
196
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex-exec iteration
197
+ while ((m = re.exec(body)) !== null) set.add(m[1]!);
198
+ return [...set];
199
+ }
200
+
201
+ /**
202
+ * Drift between what an asset passes and the module's CURRENT interface:
203
+ * `missing_required` (required vars the asset never sets) and `unknown_set`
204
+ * (vars the asset sets that the module no longer declares). Pure.
205
+ */
206
+ export function computeInterfaceDrift(input: {
207
+ setVariables: string[];
208
+ requiredVariables: string[];
209
+ variableNames: string[];
210
+ }): { missing_required: string[]; unknown_set: string[] } {
211
+ const set = new Set(input.setVariables);
212
+ const declared = new Set(input.variableNames);
213
+ const missing_required = input.requiredVariables.filter((v) => !set.has(v));
214
+ const unknown_set = input.setVariables.filter((v) => !declared.has(v));
215
+ return { missing_required, unknown_set };
216
+ }
217
+
218
+ /** normalize a POSIX path, resolving `.`/`..` segments (local copy of the
219
+ * modules.ts helper, kept private to avoid widening that module's API). */
220
+ function normalizeRel(path: string): string {
221
+ const parts: string[] = [];
222
+ for (const seg of path.replace(/\\/g, "/").split("/")) {
223
+ if (!seg || seg === ".") continue;
224
+ if (seg === "..") parts.pop();
225
+ else parts.push(seg);
226
+ }
227
+ return parts.join("/");
228
+ }
229
+
230
+ /** resolve a module-block `source` (relative to the dir that DECLARES it) to a
231
+ * repo-relative dir, for matching against the module under test. */
232
+ function resolveSourceDir(fromDir: string, source: string): string {
233
+ const raw = source.replace(/\\/g, "/");
234
+ return normalizeRel(fromDir ? `${fromDir}/${raw}` : raw);
235
+ }
236
+
237
+ function isDir(p: string): boolean {
238
+ try {
239
+ return existsSync(p) && statSync(p).isDirectory();
240
+ } catch {
241
+ return false;
242
+ }
243
+ }
244
+
245
+ function listFiles(absDir: string, suffix: string): string[] {
246
+ try {
247
+ return readdirSync(absDir)
248
+ .filter((f) => f.endsWith(suffix))
249
+ .sort();
250
+ } catch {
251
+ return [];
252
+ }
253
+ }
254
+
255
+ function listSubdirs(absDir: string): string[] {
256
+ try {
257
+ return readdirSync(absDir, { withFileTypes: true })
258
+ .filter((e) => e.isDirectory())
259
+ .map((e) => e.name)
260
+ .sort();
261
+ } catch {
262
+ return [];
263
+ }
264
+ }
265
+
266
+ function readAllTf(cwd: string, relDir: string): string {
267
+ let text = "";
268
+ for (const f of listFiles(join(cwd, relDir), ".tf")) {
269
+ try {
270
+ text += `${readFileSync(join(cwd, relDir, f), "utf8")}\n`;
271
+ } catch {
272
+ /* skip unreadable */
273
+ }
274
+ }
275
+ return text;
276
+ }
277
+
278
+ /**
279
+ * Find a module's existing test/example assets in the conventional locations and
280
+ * record which of the module's variables each one passes. Reads only the module's
281
+ * own `examples/`/`tests/`/`test/` dirs and the repo-root `examples/` (no
282
+ * full-tree walk). `moduleDir` is repo-relative POSIX. Side-effecting (reads fs).
283
+ */
284
+ export function discoverModuleTestAssets(cwd: string, moduleDir: string): ModuleTestAsset[] {
285
+ const md = normalizeRel(moduleDir);
286
+ const assets: ModuleTestAsset[] = [];
287
+ const matchesModule = (source: string, fromDir: string): boolean =>
288
+ resolveSourceDir(fromDir, source) === md;
289
+
290
+ // examples: each subdir of `<moduleDir>/examples` or the repo-root `examples`
291
+ // whose `module` block sources resolve back to this module dir.
292
+ for (const examplesRoot of [`${md}/examples`, "examples"]) {
293
+ if (!isDir(join(cwd, examplesRoot))) continue;
294
+ for (const sub of listSubdirs(join(cwd, examplesRoot))) {
295
+ const exDir = `${examplesRoot}/${sub}`;
296
+ const hcl = readAllTf(cwd, exDir);
297
+ if (!hcl) continue;
298
+ const vars = parseExampleModuleVariables(hcl, (s) => matchesModule(s, exDir));
299
+ // only count it as THIS module's example when a module block actually
300
+ // targets it (an `examples/` tree can hold fixtures for sibling modules).
301
+ const targets = eachBlockBody(hcl, /module\s+"[^"]+"\s*\{/g).some((b) => {
302
+ const src = moduleBlockSource(b);
303
+ return src !== null && matchesModule(src, exDir);
304
+ });
305
+ if (targets) assets.push({ path: exDir, kind: "example", set_variables: vars });
306
+ }
307
+ }
308
+
309
+ // native tests: `*.tftest.hcl` in the module's own dir or its `tests/` dir —
310
+ // these run the module in place, so their `variables {}` set its variables.
311
+ for (const testDir of [`${md}/tests`, md]) {
312
+ if (!isDir(join(cwd, testDir))) continue;
313
+ for (const f of listFiles(join(cwd, testDir), ".tftest.hcl")) {
314
+ const rel = `${testDir}/${f}`;
315
+ let hcl: string;
316
+ try {
317
+ hcl = readFileSync(join(cwd, rel), "utf8");
318
+ } catch {
319
+ continue;
320
+ }
321
+ assets.push({ path: rel, kind: "native-test", set_variables: parseNativeTestVariables(hcl) });
322
+ }
323
+ }
324
+
325
+ // go Terratest co-located with the module (`<moduleDir>/test` or `/tests`).
326
+ for (const goDir of [`${md}/test`, `${md}/tests`]) {
327
+ if (!isDir(join(cwd, goDir))) continue;
328
+ for (const f of listFiles(join(cwd, goDir), "_test.go")) {
329
+ const rel = `${goDir}/${f}`;
330
+ let go: string;
331
+ try {
332
+ go = readFileSync(join(cwd, rel), "utf8");
333
+ } catch {
334
+ continue;
335
+ }
336
+ assets.push({ path: rel, kind: "go-test", set_variables: parseGoTestVariables(go) });
337
+ }
338
+ }
339
+
340
+ return assets;
341
+ }
342
+
343
+ /**
344
+ * Build the module's test/example consistency report: its current interface, the
345
+ * discovered assets, and the per-asset drift against that interface. Side-effecting
346
+ * (reads fs). The agent reads `drift` to know which assets to update after a fix.
347
+ */
348
+ export function analyzeModuleTests(cwd: string, moduleDir: string): ModuleTestReport {
349
+ const iface = collectModuleInterface(cwd, moduleDir);
350
+ const requiredVariables = iface.variables.filter((v) => v.required).map((v) => v.name);
351
+ const variableNames = iface.variables.map((v) => v.name);
352
+ const assets = discoverModuleTestAssets(cwd, moduleDir);
353
+
354
+ const drift: ModuleTestDrift[] = [];
355
+ for (const a of assets) {
356
+ const d = computeInterfaceDrift({
357
+ setVariables: a.set_variables,
358
+ requiredVariables,
359
+ variableNames,
360
+ });
361
+ if (d.missing_required.length > 0 || d.unknown_set.length > 0) {
362
+ drift.push({ path: a.path, kind: a.kind, ...d });
363
+ }
364
+ }
365
+
366
+ return {
367
+ module_dir: normalizeRel(moduleDir),
368
+ required_variables: requiredVariables,
369
+ variable_names: variableNames,
370
+ assets,
371
+ drift,
372
+ };
373
+ }
374
+
375
+ export const TerraformModuleTestsParams = type({
376
+ module_dir: type.string.describe(
377
+ "the reusable module's repo-relative dir (e.g. 'modules/vpc') — typically a `local_module_dir` from terraform_module_graph that you just fixed.",
378
+ ),
379
+ });
380
+
381
+ export function TerraformModuleTestsTool(ctx: LocalToolContext) {
382
+ return tool({
383
+ name: "terraform_module_tests",
384
+ description:
385
+ "§28 — after FIXING a reusable module, check its EXISTING `examples/` fixtures and `terraform test` " +
386
+ "(`*.tftest.hcl`) / Go Terratest files for consistency with the module's CURRENT interface. Returns " +
387
+ "the module's `required_variables` + `variable_names`, the discovered test/example `assets` (with the " +
388
+ "variables each one passes to the module), and `drift` — per asset, the `missing_required` variables it " +
389
+ "fails to set (a fix that added a required `variable` breaks these) and the `unknown_set` variables it " +
390
+ "passes that the module no longer declares (the fix renamed/removed them). Update exactly the drifting " +
391
+ "assets to match the new interface; NEVER weaken or delete an assertion just to make a test pass. " +
392
+ "Complements `scaffold_terratest` (which GENERATES tests for a new module). Read-only — it reports what " +
393
+ "to change; you edit the files with your own tools. Returns empty `assets` when the module ships none.",
394
+ parameters: TerraformModuleTestsParams,
395
+ execute: execute(async ({ module_dir }) => {
396
+ const cwd = ctx.payload.cwd ?? process.cwd();
397
+ // SECURITY: confine the agent-supplied dir to the workspace so it can't be
398
+ // used to read `*.tf`/test files from outside the repo (e.g. '../../etc').
399
+ resolveWithinCwd(cwd, module_dir);
400
+ const report = analyzeModuleTests(cwd, module_dir);
401
+ log.info(
402
+ `» terraform_module_tests(${report.module_dir}): ${report.assets.length} asset(s), ` +
403
+ `${report.drift.length} drifting`,
404
+ );
405
+ return toolOk({
406
+ module_dir: report.module_dir,
407
+ required_variables: report.required_variables,
408
+ variable_names: report.variable_names,
409
+ has_assets: report.assets.length > 0,
410
+ assets: report.assets,
411
+ drift: report.drift,
412
+ note:
413
+ report.assets.length === 0
414
+ ? "This module ships no examples/ or tests — nothing to keep consistent (consider scaffold_terratest if generating coverage is in scope)."
415
+ : report.drift.length === 0
416
+ ? "All examples/tests still match the module interface — no update needed."
417
+ : "Update the drifting assets to match the new interface; never weaken an assertion to go green.",
418
+ });
419
+ }),
420
+ });
421
+ }