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,153 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { log } from "#app/utils/cli";
4
+ import { retry } from "#app/utils/retry";
5
+
6
+ vi.mock("#app/utils/cli", () => ({
7
+ log: {
8
+ info: vi.fn(),
9
+ debug: vi.fn(),
10
+ warning: vi.fn(),
11
+ error: vi.fn(),
12
+ success: vi.fn(),
13
+ },
14
+ }));
15
+
16
+ vi.mock("node:timers/promises", () => ({
17
+ setTimeout: vi.fn(async () => undefined),
18
+ }));
19
+
20
+ const sleepMock = vi.mocked(sleep);
21
+ const logInfoMock = vi.mocked(log.info);
22
+
23
+ afterEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ describe("retry", () => {
28
+ it("returns the result on first success without sleeping", async () => {
29
+ const fn = vi.fn(async () => "ok");
30
+ await expect(retry(fn)).resolves.toBe("ok");
31
+ expect(fn).toHaveBeenCalledTimes(1);
32
+ expect(sleepMock).not.toHaveBeenCalled();
33
+ });
34
+
35
+ it("retries transient 'fetch failed' errors with default linear backoff", async () => {
36
+ const fn = vi
37
+ .fn<() => Promise<string>>()
38
+ .mockRejectedValueOnce(new Error("fetch failed"))
39
+ .mockRejectedValueOnce(new Error("fetch failed"))
40
+ .mockResolvedValueOnce("ok");
41
+
42
+ await expect(retry(fn)).resolves.toBe("ok");
43
+
44
+ expect(fn).toHaveBeenCalledTimes(3);
45
+ expect(sleepMock).toHaveBeenNthCalledWith(1, 1000);
46
+ expect(sleepMock).toHaveBeenNthCalledWith(2, 2000);
47
+ expect(logInfoMock).toHaveBeenCalledWith(
48
+ "» operation failed (attempt 1/3), retrying in 1000ms...",
49
+ );
50
+ });
51
+
52
+ it("retries AbortError by name (default shouldRetry)", async () => {
53
+ const abortError = new Error("aborted");
54
+ abortError.name = "AbortError";
55
+ const fn = vi
56
+ .fn<() => Promise<string>>()
57
+ .mockRejectedValueOnce(abortError)
58
+ .mockResolvedValueOnce("ok");
59
+
60
+ await expect(retry(fn)).resolves.toBe("ok");
61
+ expect(fn).toHaveBeenCalledTimes(2);
62
+ });
63
+
64
+ it("retries ECONNRESET errors (default shouldRetry)", async () => {
65
+ const fn = vi
66
+ .fn<() => Promise<string>>()
67
+ .mockRejectedValueOnce(new Error("read ECONNRESET"))
68
+ .mockResolvedValueOnce("ok");
69
+
70
+ await expect(retry(fn)).resolves.toBe("ok");
71
+ expect(fn).toHaveBeenCalledTimes(2);
72
+ });
73
+
74
+ it("does not retry non-transient errors", async () => {
75
+ const fn = vi.fn(async () => {
76
+ throw new Error("boom");
77
+ });
78
+ await expect(retry(fn)).rejects.toThrow("boom");
79
+ expect(fn).toHaveBeenCalledTimes(1);
80
+ expect(sleepMock).not.toHaveBeenCalled();
81
+ });
82
+
83
+ it("does not retry non-Error rejections (default shouldRetry)", async () => {
84
+ const fn = vi.fn(async () => {
85
+ throw "string failure";
86
+ });
87
+ await expect(retry(fn)).rejects.toBe("string failure");
88
+ expect(fn).toHaveBeenCalledTimes(1);
89
+ });
90
+
91
+ it("throws the last error after exhausting all attempts", async () => {
92
+ const fn = vi.fn(async () => {
93
+ throw new Error("fetch failed");
94
+ });
95
+ await expect(retry(fn, { maxAttempts: 2 })).rejects.toThrow("fetch failed");
96
+ expect(fn).toHaveBeenCalledTimes(2);
97
+ expect(sleepMock).toHaveBeenCalledTimes(1);
98
+ expect(sleepMock).toHaveBeenCalledWith(1000);
99
+ });
100
+
101
+ it("uses delayMs as a linear backoff base", async () => {
102
+ const fn = vi
103
+ .fn<() => Promise<string>>()
104
+ .mockRejectedValueOnce(new Error("e1"))
105
+ .mockRejectedValueOnce(new Error("e2"))
106
+ .mockResolvedValueOnce("ok");
107
+
108
+ await expect(retry(fn, { maxAttempts: 3, delayMs: 10, shouldRetry: () => true })).resolves.toBe(
109
+ "ok",
110
+ );
111
+
112
+ expect(sleepMock).toHaveBeenNthCalledWith(1, 10);
113
+ expect(sleepMock).toHaveBeenNthCalledWith(2, 20);
114
+ });
115
+
116
+ it("honors an explicit delaysMs schedule over maxAttempts", async () => {
117
+ const fn = vi.fn(async () => {
118
+ throw new Error("always");
119
+ });
120
+ await expect(
121
+ retry(fn, { delaysMs: [5, 7], maxAttempts: 99, shouldRetry: () => true }),
122
+ ).rejects.toThrow("always");
123
+
124
+ expect(fn).toHaveBeenCalledTimes(3);
125
+ expect(sleepMock).toHaveBeenNthCalledWith(1, 5);
126
+ expect(sleepMock).toHaveBeenNthCalledWith(2, 7);
127
+ });
128
+
129
+ it("uses the custom label in the retry log line", async () => {
130
+ const fn = vi
131
+ .fn<() => Promise<string>>()
132
+ .mockRejectedValueOnce(new Error("transient"))
133
+ .mockResolvedValueOnce("ok");
134
+
135
+ await retry(fn, { label: "token exchange", shouldRetry: () => true });
136
+
137
+ expect(logInfoMock).toHaveBeenCalledWith(
138
+ "» token exchange failed (attempt 1/3), retrying in 1000ms...",
139
+ );
140
+ });
141
+
142
+ it("passes the thrown error to a custom shouldRetry and stops when it returns false", async () => {
143
+ const error = new Error("fetch failed");
144
+ const shouldRetry = vi.fn(() => false);
145
+ const fn = vi.fn(async () => {
146
+ throw error;
147
+ });
148
+
149
+ await expect(retry(fn, { shouldRetry })).rejects.toBe(error);
150
+ expect(shouldRetry).toHaveBeenCalledWith(error);
151
+ expect(fn).toHaveBeenCalledTimes(1);
152
+ });
153
+ });
@@ -0,0 +1,58 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ import { log } from "#app/utils/cli";
3
+
4
+ export type RetryOptions = {
5
+ maxAttempts?: number;
6
+ delayMs?: number;
7
+ /**
8
+ * explicit delay schedule — one entry per retry (length N ⇒ N+1 attempts).
9
+ * when set, overrides `maxAttempts` and `delayMs`. e.g. `[1_000, 3_000]`
10
+ * means up to 3 attempts, sleeping 1s before retry 2 and 3s before retry 3.
11
+ */
12
+ delaysMs?: readonly number[];
13
+ shouldRetry?: (error: unknown) => boolean;
14
+ label?: string;
15
+ };
16
+
17
+ const defaultShouldRetry = (error: unknown): boolean => {
18
+ if (!(error instanceof Error)) return false;
19
+ // retry on transient network errors
20
+ return (
21
+ error.name === "AbortError" ||
22
+ error.message.includes("fetch failed") ||
23
+ error.message.includes("ECONNRESET") ||
24
+ error.message.includes("ETIMEDOUT")
25
+ );
26
+ };
27
+
28
+ export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
29
+ const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
30
+ const label = options.label ?? "operation";
31
+ const delays = options.delaysMs
32
+ ? Array.from(options.delaysMs)
33
+ : Array.from(
34
+ { length: (options.maxAttempts ?? 3) - 1 },
35
+ (_, i) => (options.delayMs ?? 1000) * (i + 1),
36
+ );
37
+ const maxAttempts = delays.length + 1;
38
+
39
+ let lastError: unknown;
40
+
41
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
42
+ try {
43
+ return await fn();
44
+ } catch (error) {
45
+ lastError = error;
46
+
47
+ if (attempt === maxAttempts || !shouldRetry(error)) {
48
+ throw error;
49
+ }
50
+
51
+ const delay = delays[attempt - 1]!;
52
+ log.info(`» ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay}ms...`);
53
+ await sleep(delay);
54
+ }
55
+ }
56
+
57
+ throw lastError;
58
+ }
@@ -0,0 +1,106 @@
1
+ import type { WriteablePayload } from "#app/external";
2
+ import { reportReviewNodeId } from "#app/mcp/review";
3
+ import type { ToolContext } from "#app/mcp/server";
4
+ import { log } from "#app/utils/cli";
5
+
6
+ const RE_REVIEW_PREAMBLE =
7
+ "Incrementally re-review the new commits on this pull request. Use the IncrementalReview mode.";
8
+
9
+ /**
10
+ * post-agent review lifecycle: runs after the agent exits (success or timeout).
11
+ *
12
+ * normally the agent handles new commits inline: create_pull_request_review
13
+ * detects HEAD movement and tells the agent to pull and review the delta.
14
+ * this dispatch is a safety net for cases where the agent couldn't handle
15
+ * it (timeout, error, etc).
16
+ *
17
+ * ordering matters: reportReviewNodeId marks this run "done" FIRST so push
18
+ * webhooks stop being suppressed by dedup. the HEAD check runs SECOND to
19
+ * catch any pushes that were suppressed while this run was in-flight.
20
+ */
21
+ export async function postReviewCleanup(ctx: ToolContext): Promise<void> {
22
+ const review = ctx.toolState.review;
23
+ if (!review) return;
24
+ delete ctx.toolState.review;
25
+
26
+ // mark review as submitted — unlocks webhook dedup for new pushes
27
+ await bestEffort(() => reportReviewNodeId(ctx, { nodeId: review.nodeId }), "reportReviewNodeId");
28
+
29
+ // dispatch follow-up if PR HEAD moved past the reviewed commit
30
+ if (review.reviewedSha) {
31
+ await bestEffort(
32
+ () => dispatchFollowUpReReview(ctx, review.reviewedSha!),
33
+ "follow-up re-review dispatch",
34
+ );
35
+ }
36
+ }
37
+
38
+ async function bestEffort(fn: () => Promise<unknown>, label: string): Promise<void> {
39
+ try {
40
+ await fn();
41
+ } catch (error) {
42
+ log.debug(`${label} failed: ${error}`);
43
+ }
44
+ }
45
+
46
+ async function dispatchFollowUpReReview(ctx: ToolContext, reviewedSha: string): Promise<void> {
47
+ const issueNumber = ctx.payload.event.issue_number;
48
+ if (!issueNumber) return;
49
+
50
+ const pr = await ctx.octokit.rest.pulls.get({
51
+ owner: ctx.repo.owner,
52
+ repo: ctx.repo.name,
53
+ pull_number: issueNumber,
54
+ });
55
+
56
+ if (pr.data.head.sha === reviewedSha) return;
57
+ if (pr.data.state !== "open") return;
58
+ if (pr.data.draft) return;
59
+
60
+ log.info(
61
+ `safety net: pr HEAD moved from ${reviewedSha.slice(0, 7)} to ${pr.data.head.sha.slice(0, 7)} ` +
62
+ `and agent did not review inline — dispatching follow-up re-review`,
63
+ );
64
+
65
+ const event: WriteablePayload["event"] = {
66
+ trigger: "pull_request_synchronize",
67
+ issue_number: issueNumber,
68
+ is_pr: true,
69
+ title: pr.data.title,
70
+ body: null,
71
+ branch: pr.data.head.ref,
72
+ before_sha: reviewedSha,
73
+ silent: true,
74
+ };
75
+ if (ctx.payload.event.authorPermission) {
76
+ event.authorPermission = ctx.payload.event.authorPermission;
77
+ }
78
+
79
+ const payload: WriteablePayload = {
80
+ "~terramend": true,
81
+ version: ctx.payload.version,
82
+ model: ctx.payload.model,
83
+ prompt: "",
84
+ eventInstructions: RE_REVIEW_PREAMBLE,
85
+ event,
86
+ };
87
+
88
+ await ctx.octokit.rest.actions.createWorkflowDispatch({
89
+ owner: ctx.repo.owner,
90
+ repo: ctx.repo.name,
91
+ workflow_id: getCurrentWorkflowFilename(),
92
+ ref: pr.data.base.repo.default_branch,
93
+ inputs: { prompt: JSON.stringify(payload) },
94
+ });
95
+ }
96
+
97
+ /**
98
+ * derive the running workflow's filename from `GITHUB_WORKFLOW_REF`, which has the form
99
+ * `<owner>/<repo>/.github/workflows/<filename>@<ref>` (e.g. `.../terramend.yaml@refs/heads/main`).
100
+ * falls back to `terramend.yml` if the env var is missing or malformed (shouldn't happen in CI).
101
+ */
102
+ function getCurrentWorkflowFilename(): string {
103
+ const ref = process.env.GITHUB_WORKFLOW_REF ?? "";
104
+ const match = ref.match(/\/([^/]+)@/);
105
+ return match?.[1] ?? "terramend.yml";
106
+ }
@@ -0,0 +1,99 @@
1
+ import type { AgentResult } from "#app/agents/shared";
2
+ import type { MainResult } from "#app/main";
3
+ import { reportProgress } from "#app/mcp/comment";
4
+ import type { ToolContext } from "#app/mcp/server";
5
+ import { log } from "#app/utils/cli";
6
+ import { reportErrorToComment } from "#app/utils/errorReport";
7
+
8
+ export interface HandleAgentResultParams {
9
+ result: AgentResult;
10
+ toolContext: ToolContext;
11
+ silent: boolean | undefined;
12
+ }
13
+
14
+ function getErrorMessage(error: unknown): string {
15
+ return error instanceof Error ? error.message : String(error);
16
+ }
17
+
18
+ export async function handleAgentResult(ctx: HandleAgentResultParams): Promise<MainResult> {
19
+ if (!ctx.result.success) {
20
+ // rendering + posting for the `!success` branch lives in
21
+ // `finalizeSuccessRun` (called immediately before this function) so the
22
+ // BYOK billing-exhausted, hang, and api-key bodies land on a single
23
+ // surface — both for runs with a pre-existing progress comment AND for
24
+ // silent triggers via `createIfMissing`. see #835.
25
+ return {
26
+ success: false,
27
+ error: ctx.result.error || "Agent execution failed",
28
+ output: ctx.result.output!,
29
+ };
30
+ }
31
+
32
+ // IncrementalReview's non-substantive path exits cleanly without
33
+ // submitting any review, so no MCP write tool flips wasUpdated and the
34
+ // strict completion check below would otherwise fail the run. The
35
+ // isReviewMode skip is load-bearing for that path: the agent's exit
36
+ // code is the completion signal, not a progress-comment write.
37
+ // (Review mode that submits a real review now flips wasUpdated via
38
+ // create_pull_request_review, so the skip is redundant for the
39
+ // substantive-review path but kept for symmetry with IncrementalReview.)
40
+ // See plans/review_progress_comment_cleanup_b0120f6c.plan.md.
41
+ const toolState = ctx.toolContext.toolState;
42
+ const mode = toolState.selectedMode;
43
+ const isReviewMode = mode === "Review" || mode === "IncrementalReview";
44
+ if (!isReviewMode && !toolState.wasUpdated && toolState.hadProgressComment && !ctx.silent) {
45
+ // the agent exited successfully but never landed a GitHub write — either it
46
+ // answered the mention in raw assistant text (which is never posted, only
47
+ // logged) or a write tool failed (e.g. report_progress hit a 401). salvage
48
+ // by delivering the content we have to the progress comment so the user
49
+ // actually gets the answer instead of a failed run. (finalizeSuccessRun
50
+ // preserves the progress comment whenever wasUpdated is false, so the write
51
+ // lands here.)
52
+ //
53
+ // stop the todo tracker first: on this path the agent used todowrite but
54
+ // never called report_progress, so a pending debounced render could still
55
+ // be queued — draining it keeps it from clobbering the salvaged answer and
56
+ // from holding the event loop open. mirrors ReportProgressTool.
57
+ const tracker = toolState.todoTracker;
58
+ if (tracker) {
59
+ tracker.cancel();
60
+ await tracker.settled();
61
+ }
62
+ // `lastProgressBody` is the body a failed report_progress already assembled
63
+ // (task-list collapsible included), so use it verbatim; otherwise fall back
64
+ // to the agent's final assistant text and append the collapsible the way
65
+ // report_progress would.
66
+ let salvage = toolState.lastProgressBody?.trim();
67
+ if (!salvage) {
68
+ const output = ctx.result.output?.trim();
69
+ const collapsible = tracker?.renderCollapsible({ completeInProgress: true });
70
+ salvage = output && collapsible ? `${output}\n\n${collapsible}` : output;
71
+ }
72
+ if (salvage) {
73
+ try {
74
+ await reportProgress(ctx.toolContext, { body: salvage });
75
+ log.success("Task complete.");
76
+ return { success: true, output: ctx.result.output || "" };
77
+ } catch (writeError) {
78
+ // the write itself is failing (auth/permissions) — surface THAT, not
79
+ // the generic "no progress" message, so the real cause isn't masked.
80
+ const error = `failed to deliver agent result: ${getErrorMessage(writeError)}`;
81
+ await reportErrorToComment({ toolState, error, title: "Error" }).catch(() => {});
82
+ return { success: false, error, output: ctx.result.output || "" };
83
+ }
84
+ }
85
+
86
+ const error = ctx.result.error || "agent completed without reporting progress";
87
+ try {
88
+ await reportErrorToComment({ toolState, error, title: "Error" });
89
+ } catch {}
90
+ return { success: false, error, output: ctx.result.output || "" };
91
+ }
92
+
93
+ log.success("Task complete.");
94
+
95
+ return {
96
+ success: true,
97
+ output: ctx.result.output || "",
98
+ };
99
+ }
@@ -0,0 +1,145 @@
1
+ import type { PushPermission, ShellPermission } from "#app/external";
2
+ import { apiFetch } from "#app/utils/apiFetch";
3
+ import type { RepoContext } from "#app/utils/github";
4
+
5
+ export interface Mode {
6
+ id: string;
7
+ name: string;
8
+ description: string;
9
+ prompt: string;
10
+ }
11
+
12
+ /**
13
+ * server-parsed TOC entry for `Repo.learnings`. depth is 1-6 (h1-h6),
14
+ * line numbers are 1-indexed against the raw body. computed by
15
+ * `parseLearningsHeadings` in `utils/learningsToc.ts` (server side) and
16
+ * shipped over the run-context JSON boundary; the canonical declaration
17
+ * lives there. duplicated here because the action runtime can't reach
18
+ * across into the proprietary root-level codebase, and the JSON wire
19
+ * means typecheck can't enforce shape equality across both sides.
20
+ */
21
+ export interface LearningsHeading {
22
+ depth: 1 | 2 | 3 | 4 | 5 | 6;
23
+ title: string;
24
+ startLine: number;
25
+ endLine: number;
26
+ }
27
+
28
+ export interface RepoSettings {
29
+ model: string | null;
30
+ modes: Mode[];
31
+ setupScript: string | null;
32
+ postCheckoutScript: string | null;
33
+ prepushScript: string | null;
34
+ stopScript: string | null;
35
+ push: PushPermission;
36
+ shell: ShellPermission;
37
+ prApproveEnabled: boolean;
38
+ modeInstructions: Record<string, string>;
39
+ learnings: string | null;
40
+ learningsHeadings: LearningsHeading[];
41
+ envAllowlist: string | null;
42
+ }
43
+
44
+ /**
45
+ * Account-level billing plan. Orthogonal to repo-level OSS status. Mirrors
46
+ * the server's `AccountPlan` in `utils/billing.ts`. `"none"` = free tier,
47
+ * `"payg"` = card on file / pay-as-you-go.
48
+ */
49
+ export type AccountPlan = "none" | "payg";
50
+
51
+ export interface RunContext {
52
+ settings: RepoSettings;
53
+ apiToken: string;
54
+ oss: boolean;
55
+ plan: AccountPlan;
56
+ }
57
+
58
+ const defaultSettings: RepoSettings = {
59
+ model: null,
60
+ modes: [],
61
+ setupScript: null,
62
+ postCheckoutScript: null,
63
+ prepushScript: null,
64
+ stopScript: null,
65
+ push: "restricted",
66
+ shell: "restricted",
67
+ prApproveEnabled: false,
68
+ modeInstructions: {},
69
+ learnings: null,
70
+ learningsHeadings: [],
71
+ envAllowlist: null,
72
+ };
73
+
74
+ const defaultRunContext: RunContext = {
75
+ settings: defaultSettings,
76
+ apiToken: "",
77
+ oss: false,
78
+ plan: "none",
79
+ };
80
+
81
+ /**
82
+ * fetch run context from Terramend API
83
+ * returns settings + API token for subsequent calls
84
+ * returns defaults if fetch fails
85
+ */
86
+ export async function fetchRunContext(params: {
87
+ token: string;
88
+ repoContext: RepoContext;
89
+ oidcToken?: string | undefined;
90
+ }): Promise<RunContext> {
91
+ const timeoutMs = 30000;
92
+ const controller = new AbortController();
93
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
94
+
95
+ try {
96
+ const headers: Record<string, string> = {
97
+ Authorization: `Bearer ${params.token}`,
98
+ };
99
+ if (params.oidcToken) {
100
+ headers["X-GitHub-OIDC-Token"] = params.oidcToken;
101
+ }
102
+
103
+ const response = await apiFetch({
104
+ path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
105
+ headers,
106
+ signal: controller.signal,
107
+ });
108
+
109
+ clearTimeout(timeoutId);
110
+
111
+ if (!response.ok) {
112
+ return defaultRunContext;
113
+ }
114
+
115
+ const data = (await response.json()) as {
116
+ settings: RepoSettings | null;
117
+ apiToken: string;
118
+ oss?: boolean;
119
+ plan?: AccountPlan;
120
+ } | null;
121
+
122
+ if (data === null) {
123
+ return defaultRunContext;
124
+ }
125
+
126
+ return {
127
+ settings: {
128
+ ...defaultSettings,
129
+ ...data.settings,
130
+ modes: data.settings?.modes ?? [],
131
+ setupScript: data.settings?.setupScript ?? null,
132
+ postCheckoutScript: data.settings?.postCheckoutScript ?? null,
133
+ prepushScript: data.settings?.prepushScript ?? null,
134
+ stopScript: data.settings?.stopScript ?? null,
135
+ learningsHeadings: data.settings?.learningsHeadings ?? [],
136
+ },
137
+ apiToken: data.apiToken,
138
+ oss: data.oss ?? false,
139
+ plan: data.plan ?? "none",
140
+ };
141
+ } catch {
142
+ clearTimeout(timeoutId);
143
+ return defaultRunContext;
144
+ }
145
+ }
@@ -0,0 +1,58 @@
1
+ import * as core from "@actions/core";
2
+ import type { Octokit } from "@octokit/rest";
3
+ import { log } from "#app/utils/cli";
4
+ import { type OctokitWithPlugins, parseRepoContext } from "#app/utils/github";
5
+ import { type AccountPlan, fetchRunContext, type RepoSettings } from "#app/utils/runContext";
6
+ import packageJson from "#package.json" with { type: "json" };
7
+
8
+ export interface RunContextData {
9
+ repo: {
10
+ owner: string;
11
+ name: string;
12
+ data: Awaited<ReturnType<Octokit["repos"]["get"]>>["data"];
13
+ };
14
+ repoSettings: RepoSettings;
15
+ apiToken: string;
16
+ oss: boolean;
17
+ plan: AccountPlan;
18
+ }
19
+
20
+ interface ResolveRunContextDataParams {
21
+ octokit: OctokitWithPlugins;
22
+ token: string;
23
+ }
24
+
25
+ /**
26
+ * initialize run context data: parse context, fetch repo info and settings
27
+ */
28
+ export async function resolveRunContextData(
29
+ params: ResolveRunContextDataParams,
30
+ ): Promise<RunContextData> {
31
+ log.info(`» running Terramend v${packageJson.version}...`);
32
+
33
+ const repoContext = parseRepoContext();
34
+
35
+ let oidcToken: string | undefined;
36
+ try {
37
+ oidcToken = await core.getIDToken("terramend-api");
38
+ } catch {
39
+ // OIDC not available (local dev, non-actions environment, fork PRs)
40
+ }
41
+
42
+ const [repoResponse, runContext] = await Promise.all([
43
+ params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }),
44
+ fetchRunContext({ token: params.token, repoContext, oidcToken }),
45
+ ]);
46
+
47
+ return {
48
+ repo: {
49
+ owner: repoContext.owner,
50
+ name: repoContext.name,
51
+ data: repoResponse.data,
52
+ },
53
+ repoSettings: runContext.settings,
54
+ apiToken: runContext.apiToken,
55
+ oss: runContext.oss,
56
+ plan: runContext.plan,
57
+ };
58
+ }