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,582 @@
1
+ import { type } from "arktype";
2
+ import { assertTargetInScope } from "#app/mcp/scope";
3
+ import type { ToolContext } from "#app/mcp/server";
4
+ import { execute, tool } from "#app/mcp/shared";
5
+ import { getApiUrl } from "#app/utils/apiUrl";
6
+ import { buildTerramendFooter, stripExistingFooter } from "#app/utils/buildTerramendFooter";
7
+ import { log } from "#app/utils/cli";
8
+ import { fixDoubleEscapedString } from "#app/utils/fixDoubleEscapedString";
9
+ import { patchWorkflowRunFields } from "#app/utils/patchWorkflowRunFields";
10
+ import {
11
+ createLeapingProgressComment,
12
+ deleteProgressCommentApi,
13
+ updateProgressComment,
14
+ } from "#app/utils/progressComment";
15
+
16
+ // re-export for backward compat with anything importing the leaping helpers from mcp/comment
17
+ export {
18
+ isLeapingIntoActionCommentBody,
19
+ LEAPING_INTO_ACTION_PREFIX,
20
+ } from "#app/utils/leapingComment";
21
+
22
+ function isNotFoundError(error: unknown): boolean {
23
+ return error instanceof Error && error.message.includes("Not Found");
24
+ }
25
+
26
+ function buildCommentFooter(ctx: ToolContext, customParts?: string[]): string {
27
+ return buildTerramendFooter({
28
+ triggeredBy: true,
29
+ customParts,
30
+ model: ctx.toolState.model,
31
+ fallbackFrom: ctx.toolState.modelFallback?.from,
32
+ });
33
+ }
34
+
35
+ function buildImplementPlanLink(ctx: ToolContext, issueNumber: number, commentId: number): string {
36
+ const apiUrl = getApiUrl();
37
+ return `[Implement plan ➔](${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${issueNumber}?action=implement&comment_id=${commentId})`;
38
+ }
39
+
40
+ export function addFooter(ctx: ToolContext, body: string): string {
41
+ if (/<br\s*\/?>[ \t]*\n(?!\s*\n)/i.test(body)) {
42
+ throw new Error(
43
+ "body contains <br/> followed by a non-blank line, which breaks GitHub markdown rendering. always add a blank line after <br/> tags.",
44
+ );
45
+ }
46
+ const bodyWithoutFooter = stripExistingFooter(fixDoubleEscapedString(body));
47
+ const footer = buildCommentFooter(ctx);
48
+ return `${bodyWithoutFooter}${footer}`;
49
+ }
50
+
51
+ export const Comment = type({
52
+ issueNumber: type.number.describe("the issue number to comment on"),
53
+ body: type.string.describe("the comment body content"),
54
+ type: type
55
+ .enumerated("Plan", "Comment")
56
+ .describe("Plan: record as the plan for this run. Comment: regular comment (default).")
57
+ .optional(),
58
+ });
59
+
60
+ export function CreateCommentTool(ctx: ToolContext) {
61
+ return tool({
62
+ name: "create_issue_comment",
63
+ description:
64
+ "Create a comment on a GitHub issue or PR. " +
65
+ 'Example: `create_issue_comment({ issueNumber: 1234, body: "Thanks for the report." })`. ' +
66
+ "For progress/plan updates on the current run use report_progress instead — plan output (initial post AND revisions) is always posted via report_progress, never via this tool.",
67
+ parameters: Comment,
68
+ execute: execute(async ({ issueNumber, body, type: commentType }) => {
69
+ assertTargetInScope(ctx, issueNumber, "comment on");
70
+ const bodyWithFooter = addFooter(ctx, body);
71
+
72
+ const result = await ctx.octokit.rest.issues.createComment({
73
+ owner: ctx.repo.owner,
74
+ repo: ctx.repo.name,
75
+ issue_number: issueNumber,
76
+ body: bodyWithFooter,
77
+ });
78
+
79
+ ctx.toolState.wasUpdated = true;
80
+ log.info(`» created comment ${result.data.id}`);
81
+
82
+ if (commentType === "Plan") {
83
+ if (result.data.node_id) {
84
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: result.data.node_id });
85
+ }
86
+ // add "Implement plan" link (needs comment ID, so create-then-update)
87
+ const customParts = [buildImplementPlanLink(ctx, issueNumber, result.data.id)];
88
+ const footer = buildCommentFooter(ctx, customParts);
89
+ const bodyWithPlanLink = `${stripExistingFooter(body)}${footer}`;
90
+
91
+ const updateResult = await ctx.octokit.rest.issues.updateComment({
92
+ owner: ctx.repo.owner,
93
+ repo: ctx.repo.name,
94
+ comment_id: result.data.id,
95
+ body: bodyWithPlanLink,
96
+ });
97
+ log.info(`» updated comment ${updateResult.data.id}`);
98
+
99
+ return {
100
+ success: true,
101
+ commentId: updateResult.data.id,
102
+ url: updateResult.data.html_url,
103
+ body: updateResult.data.body,
104
+ };
105
+ }
106
+
107
+ return {
108
+ success: true,
109
+ commentId: result.data.id,
110
+ url: result.data.html_url,
111
+ body: result.data.body,
112
+ };
113
+ }),
114
+ });
115
+ }
116
+
117
+ export const EditComment = type({
118
+ commentId: type.number.describe("the ID of the comment to edit"),
119
+ body: type.string.describe("the new comment body content"),
120
+ });
121
+
122
+ /** parse the issue/PR number a comment belongs to from its API `issue_url`
123
+ * (`https://api.github.com/repos/o/r/issues/123`). undefined when unparseable. */
124
+ function issueNumberFromUrl(url: string | undefined): number | undefined {
125
+ const m = url?.match(/\/issues\/(\d+)(?:$|[/?#])/);
126
+ return m ? Number(m[1]) : undefined;
127
+ }
128
+
129
+ /**
130
+ * SECURITY: bind edit_issue_comment to the run's scoped issue/PR. A comment id
131
+ * is repo-global, so resolve it to the issue/PR it lives on first (an injected
132
+ * agent must not be able to overwrite a comment on an UNRELATED issue/PR).
133
+ * Skips for standalone runs and, deliberately, fails OPEN when the lookup can't
134
+ * determine the issue — editing a comment is reversible and low-risk, so a
135
+ * transient API/permission hiccup must not block a legitimate edit. A definite
136
+ * out-of-scope match still throws.
137
+ */
138
+ async function assertCommentInScope(ctx: ToolContext, commentId: number): Promise<void> {
139
+ if (ctx.payload?.event?.issue_number === undefined) return;
140
+ let issueNumber: number | undefined;
141
+ try {
142
+ const { data } = await ctx.octokit.rest.issues.getComment({
143
+ owner: ctx.repo.owner,
144
+ repo: ctx.repo.name,
145
+ comment_id: commentId,
146
+ });
147
+ issueNumber = issueNumberFromUrl(data.issue_url);
148
+ } catch (e) {
149
+ log.debug(
150
+ `edit_issue_comment scope lookup failed (allowing): ${e instanceof Error ? e.message : String(e)}`,
151
+ );
152
+ return;
153
+ }
154
+ if (issueNumber !== undefined) {
155
+ assertTargetInScope(ctx, issueNumber, "edit a comment on");
156
+ }
157
+ }
158
+
159
+ export function EditCommentTool(ctx: ToolContext) {
160
+ return tool({
161
+ name: "edit_issue_comment",
162
+ description: "Edit a GitHub issue comment by its ID",
163
+ parameters: EditComment,
164
+ execute: execute(async ({ commentId, body }) => {
165
+ await assertCommentInScope(ctx, commentId);
166
+ const bodyWithFooter = addFooter(ctx, body);
167
+
168
+ const result = await ctx.octokit.rest.issues.updateComment({
169
+ owner: ctx.repo.owner,
170
+ repo: ctx.repo.name,
171
+ comment_id: commentId,
172
+ body: bodyWithFooter,
173
+ });
174
+ log.info(`» updated comment ${result.data.id}`);
175
+
176
+ return {
177
+ success: true,
178
+ commentId: result.data.id,
179
+ url: result.data.html_url,
180
+ body: result.data.body,
181
+ updatedAt: result.data.updated_at,
182
+ };
183
+ }),
184
+ });
185
+ }
186
+
187
+ export const ReportProgress = type({
188
+ body: type.string.describe("the progress update content to share"),
189
+ "target_plan_comment?": type("boolean").describe(
190
+ "for revising an existing plan comment ONLY. set to true only when the PlanEdit checklist from select_mode tells you to (i.e. a prior plan comment was found for this issue). NEVER set on the initial plan post — the initial plan reuses the run's progress comment and is posted by calling report_progress without this flag.",
191
+ ),
192
+ });
193
+
194
+ /**
195
+ * Report progress to a GitHub comment.
196
+ *
197
+ * progressComment has three states:
198
+ * - undefined: no comment yet — will create one if an issue/PR target exists
199
+ * - object: active comment — will update it in place via the right REST endpoint for its type
200
+ * - null: deliberately deleted (e.g. after submitting a PR review) — skips silently
201
+ *
202
+ * The body is tracked in lastProgressBody for the job summary regardless of comment state,
203
+ * EXCEPT for `liveProgress` (todo-tracker) writes — see the param note below.
204
+ *
205
+ * The "existing plan comment" path always targets a top-level issue comment (plan comments are
206
+ * created by create_issue_comment with type:"Plan", never as review-thread replies).
207
+ */
208
+ export async function reportProgress(
209
+ ctx: ToolContext,
210
+ params: { body: string; target_plan_comment?: boolean; liveProgress?: boolean },
211
+ ): Promise<{
212
+ commentId?: number;
213
+ url?: string;
214
+ body: string;
215
+ action: "created" | "updated" | "skipped";
216
+ }> {
217
+ const { body, target_plan_comment } = params;
218
+ // `liveProgress` marks the automatic todo-tracker checklist render — a live
219
+ // progress update, NOT the agent's deliberate final answer. such writes must
220
+ // not record `lastProgressBody` or flip `wasUpdated`: both signal "a real
221
+ // user-facing answer landed", and letting an auto checklist trip them masks
222
+ // the #868 salvage (it would post the checklist instead of the real output,
223
+ // or skip salvage entirely) and triggers stranded-comment deletion.
224
+ if (!params.liveProgress) {
225
+ ctx.toolState.lastProgressBody = body;
226
+ }
227
+
228
+ // silent events (e.g., auto-label, pr-summary Task) should never create or update progress comments.
229
+ // the body is still tracked above for the GitHub Actions job summary.
230
+ if (ctx.payload.event.silent) {
231
+ return { body, action: "skipped" };
232
+ }
233
+
234
+ const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber;
235
+ const isPlanMode = ctx.toolState.selectedMode === "Plan";
236
+ const apiCtx = { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name };
237
+
238
+ // when editing existing plan: update the plan comment from tool state (set by select_mode)
239
+ if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === undefined) {
240
+ log.warning("target_plan_comment requested but no existingPlanCommentId in tool state");
241
+ }
242
+ if (target_plan_comment === true && ctx.toolState.existingPlanCommentId !== undefined) {
243
+ const commentId = ctx.toolState.existingPlanCommentId;
244
+ const customParts =
245
+ issueNumber !== undefined ? [buildImplementPlanLink(ctx, issueNumber, commentId)] : undefined;
246
+ const bodyWithoutFooter = stripExistingFooter(body);
247
+ const footer = buildCommentFooter(ctx, customParts);
248
+ const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
249
+
250
+ const result = await updateProgressComment(
251
+ apiCtx,
252
+ { id: commentId, type: "issue" },
253
+ bodyWithFooter,
254
+ );
255
+
256
+ if (!params.liveProgress) ctx.toolState.wasUpdated = true;
257
+
258
+ if (isPlanMode && result.node_id) {
259
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
260
+ }
261
+
262
+ return {
263
+ commentId: result.id,
264
+ url: result.html_url,
265
+ body: result.body || "",
266
+ action: "updated",
267
+ };
268
+ }
269
+
270
+ const existingComment = ctx.toolState.progressComment;
271
+
272
+ // if we already have a progress comment, update it
273
+ if (existingComment) {
274
+ const customParts =
275
+ isPlanMode && issueNumber !== undefined
276
+ ? [buildImplementPlanLink(ctx, issueNumber, existingComment.id)]
277
+ : undefined;
278
+
279
+ const bodyWithoutFooter = stripExistingFooter(body);
280
+ const footer = buildCommentFooter(ctx, customParts);
281
+ const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
282
+
283
+ // a review-reply progress comment (seeded by the dispatch path) can become
284
+ // stale before final delivery — the thread is deleted or otherwise
285
+ // unreachable, so updateProgressComment 404s. rather than fail an
286
+ // already-completed run, fall back to a fresh top-level comment on the PR
287
+ // and retarget future writes there.
288
+ let result: Awaited<ReturnType<typeof updateProgressComment>>;
289
+ try {
290
+ result = await updateProgressComment(apiCtx, existingComment, bodyWithFooter);
291
+ } catch (error) {
292
+ // only a deliberate write to a stale review-reply comment falls back. a
293
+ // liveProgress (todo-tracker) 404 rethrows — it must never create a
294
+ // user-facing comment, and the next deliberate report_progress recovers.
295
+ if (
296
+ params.liveProgress ||
297
+ existingComment.type !== "review" ||
298
+ !isNotFoundError(error) ||
299
+ issueNumber === undefined
300
+ ) {
301
+ throw error;
302
+ }
303
+ log.warning(
304
+ `progress review comment ${existingComment.id} is gone (404); posting a top-level comment on #${issueNumber} instead`,
305
+ );
306
+ const created = await createLeapingProgressComment(
307
+ apiCtx,
308
+ { kind: "issue", issueNumber },
309
+ bodyWithFooter,
310
+ );
311
+ ctx.toolState.progressComment = created.comment;
312
+ if (!params.liveProgress) ctx.toolState.wasUpdated = true;
313
+ return {
314
+ commentId: created.comment.id,
315
+ url: created.html_url,
316
+ body: created.body || "",
317
+ action: "created",
318
+ };
319
+ }
320
+
321
+ if (!params.liveProgress) ctx.toolState.wasUpdated = true;
322
+
323
+ if (isPlanMode && result.node_id) {
324
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
325
+ }
326
+
327
+ return {
328
+ commentId: result.id,
329
+ url: result.html_url,
330
+ body: result.body || "",
331
+ action: "updated",
332
+ };
333
+ }
334
+
335
+ // null = progress comment was deleted by stranded-comment cleanup in main.ts
336
+ if (existingComment === null) {
337
+ return { body, action: "skipped" };
338
+ }
339
+
340
+ // no existing comment - need an issue/PR to create one on
341
+ // use fallback chain: dynamically set context > event payload
342
+ if (issueNumber === undefined) {
343
+ // no-op: no comment target (e.g., workflow_dispatch events)
344
+ // body is already tracked for job summary
345
+ return { body, action: "skipped" };
346
+ }
347
+
348
+ // for new comments, we need to create first, then update with Plan link if in Plan mode
349
+ // self-created progress comments are always top-level issue comments — review-reply
350
+ // progress comments only originate from the dispatch path and arrive pre-created.
351
+ const initialBody = addFooter(ctx, body);
352
+ const created = await createLeapingProgressComment(
353
+ apiCtx,
354
+ { kind: "issue", issueNumber },
355
+ initialBody,
356
+ );
357
+
358
+ ctx.toolState.progressComment = created.comment;
359
+ if (!params.liveProgress) ctx.toolState.wasUpdated = true;
360
+
361
+ // if Plan mode, update the comment to add the "Implement plan" link
362
+ if (isPlanMode) {
363
+ const customParts = [buildImplementPlanLink(ctx, issueNumber, created.comment.id)];
364
+ const bodyWithoutFooter = stripExistingFooter(body);
365
+ const footer = buildCommentFooter(ctx, customParts);
366
+ const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
367
+
368
+ const updateResult = await updateProgressComment(apiCtx, created.comment, bodyWithPlanLink);
369
+
370
+ if (updateResult.node_id) {
371
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.node_id });
372
+ }
373
+
374
+ return {
375
+ commentId: updateResult.id,
376
+ url: updateResult.html_url,
377
+ body: updateResult.body || "",
378
+ action: "created",
379
+ };
380
+ }
381
+
382
+ return {
383
+ commentId: created.comment.id,
384
+ url: created.html_url,
385
+ body: created.body || "",
386
+ action: "created",
387
+ };
388
+ }
389
+
390
+ export function ReportProgressTool(ctx: ToolContext) {
391
+ return tool({
392
+ name: "report_progress",
393
+ description:
394
+ "Share progress on the associated GitHub issue/PR. The first call creates a comment; subsequent calls update it in place. " +
395
+ 'Example: `report_progress({ body: "Implemented the auth check and added tests." })`. ' +
396
+ "Call this at the end of every run with a brief final summary (1-3 sentences) unless the mode guidance instructs otherwise. The current task list is automatically appended in a collapsible section — do not restate individual steps.",
397
+ parameters: ReportProgress,
398
+ execute: execute(async (params) => {
399
+ let body = params.body;
400
+
401
+ // for non-plan calls: stop auto-updates, wait for in-flight writes to settle,
402
+ // then append completed task list collapsible
403
+ if (!params.target_plan_comment && ctx.toolState.todoTracker) {
404
+ ctx.toolState.todoTracker.cancel();
405
+ await ctx.toolState.todoTracker.settled();
406
+ const collapsible = ctx.toolState.todoTracker.renderCollapsible({
407
+ completeInProgress: true,
408
+ });
409
+ if (collapsible) {
410
+ body = `${body}\n\n${collapsible}`;
411
+ }
412
+ }
413
+
414
+ const reportParams: { body: string; target_plan_comment?: boolean } = { body };
415
+ if (params.target_plan_comment !== undefined) {
416
+ reportParams.target_plan_comment = params.target_plan_comment;
417
+ }
418
+ const result = await reportProgress(ctx, reportParams);
419
+
420
+ if (result.action === "skipped") {
421
+ return {
422
+ success: true,
423
+ message:
424
+ "progress recorded (no GitHub comment created - this may occur for workflow_dispatch events or when there is no associated issue/PR)",
425
+ };
426
+ }
427
+
428
+ if (result.commentId !== undefined) {
429
+ log.info(`» ${result.action} comment ${result.commentId}`);
430
+ }
431
+
432
+ if (!params.target_plan_comment) {
433
+ ctx.toolState.finalSummaryWritten = true;
434
+ }
435
+
436
+ return {
437
+ success: true,
438
+ ...result,
439
+ };
440
+ }),
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Delete the progress comment if it exists.
446
+ * Used by main.ts for stranded-comment cleanup (orphaned "Leaping into action" or
447
+ * checklist left by the todo tracker when the agent didn't call report_progress).
448
+ * Sets progressComment to null so subsequent report_progress calls are no-ops.
449
+ */
450
+ export async function deleteProgressComment(ctx: ToolContext): Promise<boolean> {
451
+ const existing = ctx.toolState.progressComment;
452
+ if (!existing) {
453
+ return false;
454
+ }
455
+
456
+ try {
457
+ await deleteProgressCommentApi(
458
+ { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name },
459
+ existing,
460
+ );
461
+ } catch (error) {
462
+ // ignore 404 - comment already deleted
463
+ if (!isNotFoundError(error)) throw error;
464
+ }
465
+
466
+ // set to null (not undefined) so report_progress skips instead of creating a new comment
467
+ ctx.toolState.progressComment = null;
468
+
469
+ return true;
470
+ }
471
+
472
+ export const ReplyToReviewComment = type({
473
+ pull_number: type.number.describe("the pull request number"),
474
+ comment_id: type.number.describe("the ID of the review comment to reply to"),
475
+ body: type.string.describe(
476
+ "extremely brief reply (1 sentence max) explaining what was fixed, e.g. 'Fixed by renaming to X' or 'Added null check'",
477
+ ),
478
+ });
479
+
480
+ /**
481
+ * decision returned by `duplicateReplyDecision` when a session has already
482
+ * posted an identical reply to the same parent review comment.
483
+ */
484
+ export interface DuplicateReplyDecision {
485
+ kind: "already-replied";
486
+ commentId: number;
487
+ url: string | undefined;
488
+ reason: string;
489
+ }
490
+
491
+ /**
492
+ * decide whether a second reply_to_review_comment call in the same session
493
+ * is a duplicate of an earlier reply to the same parent comment.
494
+ *
495
+ * the agent is instructed to call reply_to_review_comment exactly once per
496
+ * parent comment per AddressReviews session, but in practice it sometimes
497
+ * emits the same call twice. PR #610 reproduced this with Kimi K2:
498
+ * identical body posted 3 seconds apart, only one tool_use event in the
499
+ * agent log. the second post is always redundant and clutters the PR thread.
500
+ *
501
+ * we key on (comment_id, bodyWithFooter) so a legitimate follow-up reply
502
+ * with different content still goes through. within a single run the
503
+ * footer is constant (workflow run + model + jobId), so byte-equal bodies
504
+ * catch the stutter without blocking real follow-ups.
505
+ *
506
+ * mirrors the shape of `duplicateReviewDecision` in mcp/review.ts.
507
+ */
508
+ export function duplicateReplyDecision(params: {
509
+ existing: { commentId: number; url: string | undefined; bodyWithFooter: string } | undefined;
510
+ bodyWithFooter: string;
511
+ }): DuplicateReplyDecision | null {
512
+ const existing = params.existing;
513
+ if (!existing) return null;
514
+ if (existing.bodyWithFooter !== params.bodyWithFooter) return null;
515
+ return {
516
+ kind: "already-replied",
517
+ commentId: existing.commentId,
518
+ url: existing.url,
519
+ reason: `reply ${existing.commentId} with identical body was already posted in this session; ignoring duplicate call`,
520
+ };
521
+ }
522
+
523
+ export function ReplyToReviewCommentTool(ctx: ToolContext) {
524
+ return tool({
525
+ name: "reply_to_review_comment",
526
+ description:
527
+ "Reply to a PR review comment thread (NOT issue comments — this only works for inline review comments on PR diffs). " +
528
+ 'Example: `reply_to_review_comment({ pull_number: 1234, comment_id: 567890, body: "Fixed by adding a null check." })`. ' +
529
+ "Call exactly ONCE per parent comment you address in AddressReviews mode — duplicate calls with the same body are a no-op. Keep replies extremely brief (1 sentence max).",
530
+ parameters: ReplyToReviewComment,
531
+ execute: execute(async ({ pull_number, comment_id, body }) => {
532
+ assertTargetInScope(ctx, pull_number, "reply to a review comment on");
533
+ const bodyWithFooter = addFooter(ctx, body);
534
+
535
+ // guard against duplicate reply submissions in the same session.
536
+ // see duplicateReplyDecision for the rationale.
537
+ const dup = duplicateReplyDecision({
538
+ existing: ctx.toolState.reviewReplies?.get(comment_id),
539
+ bodyWithFooter,
540
+ });
541
+ if (dup) {
542
+ log.info(`skipping duplicate review reply: ${dup.reason}`);
543
+ return {
544
+ success: true,
545
+ skipped: true,
546
+ reason: dup.reason,
547
+ commentId: dup.commentId,
548
+ url: dup.url,
549
+ };
550
+ }
551
+
552
+ const result = await ctx.octokit.rest.pulls.createReplyForReviewComment({
553
+ owner: ctx.repo.owner,
554
+ repo: ctx.repo.name,
555
+ pull_number,
556
+ comment_id,
557
+ body: bodyWithFooter,
558
+ });
559
+ log.info(`» created review comment ${result.data.id} (in reply to ${comment_id})`);
560
+
561
+ // mark progress as updated so error reporting + run-result handling know
562
+ // a substantive write happened (used by reportErrorToComment / handleAgentResult)
563
+ ctx.toolState.wasUpdated = true;
564
+
565
+ // record this reply for in-session dedupe of subsequent identical calls.
566
+ ctx.toolState.reviewReplies ??= new Map();
567
+ ctx.toolState.reviewReplies.set(comment_id, {
568
+ commentId: result.data.id,
569
+ url: result.data.html_url,
570
+ bodyWithFooter,
571
+ });
572
+
573
+ return {
574
+ success: true,
575
+ commentId: result.data.id,
576
+ url: result.data.html_url,
577
+ body: result.data.body,
578
+ in_reply_to_id: result.data.in_reply_to_id,
579
+ };
580
+ }, "reply_to_review_comment"),
581
+ });
582
+ }