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,43 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import { installNodeDependencies } from "#app/prep/installNodeDependencies";
3
+ import { installPythonDependencies } from "#app/prep/installPythonDependencies";
4
+ import type { PrepDefinition, PrepOptions, PrepResult } from "#app/prep/types";
5
+ import { log } from "#app/utils/cli";
6
+
7
+ export type { PrepOptions, PrepResult } from "#app/prep/types";
8
+
9
+ // register all prep steps here
10
+ const prepSteps: PrepDefinition[] = [installNodeDependencies, installPythonDependencies];
11
+
12
+ /**
13
+ * run all prep steps sequentially.
14
+ * failures are logged as warnings but don't stop the run.
15
+ */
16
+ export async function runPrepPhase(options: PrepOptions): Promise<PrepResult[]> {
17
+ log.debug("» starting prep phase...");
18
+ const startTime = performance.now();
19
+ const results: PrepResult[] = [];
20
+
21
+ for (const step of prepSteps) {
22
+ const shouldRun = await step.shouldRun();
23
+ if (!shouldRun) {
24
+ log.debug(`» skipping ${step.name} (not applicable)`);
25
+ continue;
26
+ }
27
+
28
+ log.debug(`» running ${step.name}...`);
29
+ const result = await step.run(options);
30
+ results.push(result);
31
+
32
+ if (result.dependenciesInstalled) {
33
+ log.debug(`» ${step.name}: dependencies installed`);
34
+ } else if (result.issues.length > 0) {
35
+ log.warning(`» ${step.name}: ${result.issues[0]}`);
36
+ }
37
+ }
38
+
39
+ const totalDurationMs = performance.now() - startTime;
40
+ log.debug(`» prep phase completed (${Math.round(totalDurationMs)}ms)`);
41
+
42
+ return results;
43
+ }
@@ -0,0 +1,298 @@
1
+ import { join } from "node:path";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { installNodeDependencies } from "#app/prep/installNodeDependencies";
4
+ import type { PrepOptions } from "#app/prep/types";
5
+
6
+ vi.mock("node:fs", () => ({
7
+ existsSync: vi.fn(() => false),
8
+ }));
9
+
10
+ vi.mock("package-manager-detector", () => ({
11
+ detect: vi.fn(async () => null),
12
+ }));
13
+
14
+ vi.mock("package-manager-detector/commands", () => ({
15
+ resolveCommand: vi.fn(),
16
+ }));
17
+
18
+ vi.mock("#app/utils/cli", () => ({
19
+ log: {
20
+ info: vi.fn(),
21
+ debug: vi.fn(),
22
+ warning: vi.fn(),
23
+ error: vi.fn(),
24
+ success: vi.fn(),
25
+ startGroup: vi.fn(),
26
+ endGroup: vi.fn(),
27
+ },
28
+ }));
29
+
30
+ vi.mock("#app/utils/packageManager", () => ({
31
+ ensurePackageManager: vi.fn(async () => false),
32
+ resolvePackageManagerSpec: vi.fn(async () => null),
33
+ }));
34
+
35
+ vi.mock("#app/utils/subprocess", () => ({
36
+ spawn: vi.fn(),
37
+ }));
38
+
39
+ import { existsSync } from "node:fs";
40
+ import { detect } from "package-manager-detector";
41
+ import { resolveCommand } from "package-manager-detector/commands";
42
+ import { ensurePackageManager, resolvePackageManagerSpec } from "#app/utils/packageManager";
43
+ import { spawn } from "#app/utils/subprocess";
44
+
45
+ const existsSyncMock = vi.mocked(existsSync);
46
+ const detectMock = vi.mocked(detect);
47
+ const resolveCommandMock = vi.mocked(resolveCommand);
48
+ const ensurePackageManagerMock = vi.mocked(ensurePackageManager);
49
+ const resolveSpecMock = vi.mocked(resolvePackageManagerSpec);
50
+ const spawnMock = vi.mocked(spawn);
51
+
52
+ const options: PrepOptions = { ignoreScripts: false, binDir: "/tmp/run/pm-bin" };
53
+
54
+ type SpawnArg = Parameters<typeof spawn>[0];
55
+
56
+ function spawnResult(init: { exitCode?: number; stdout?: string; stderr?: string } = {}) {
57
+ return {
58
+ exitCode: init.exitCode ?? 0,
59
+ stdout: init.stdout ?? "",
60
+ stderr: init.stderr ?? "",
61
+ durationMs: 1,
62
+ };
63
+ }
64
+
65
+ /** route spawn calls by command so test setup reads as a behavior table */
66
+ function routeSpawn(handlers: Record<string, (call: SpawnArg) => ReturnType<typeof spawnResult>>) {
67
+ spawnMock.mockImplementation(async (call) => {
68
+ const handler = handlers[call.cmd];
69
+ if (!handler) throw new Error(`unexpected spawn: ${call.cmd} ${call.args.join(" ")}`);
70
+ return handler(call);
71
+ });
72
+ }
73
+
74
+ function whichAvailable(available: boolean) {
75
+ return () => spawnResult({ exitCode: available ? 0 : 1 });
76
+ }
77
+
78
+ function declaredSpec(overrides: Partial<{ name: string; version: string }> = {}) {
79
+ return {
80
+ name: "pnpm",
81
+ version: "11.1.1",
82
+ concrete: true,
83
+ source: "packageManager",
84
+ ...overrides,
85
+ } as NonNullable<Awaited<ReturnType<typeof resolvePackageManagerSpec>>>;
86
+ }
87
+
88
+ beforeEach(() => {
89
+ vi.clearAllMocks();
90
+ detectMock.mockResolvedValue(null);
91
+ resolveSpecMock.mockResolvedValue(null);
92
+ ensurePackageManagerMock.mockResolvedValue(false);
93
+ });
94
+
95
+ describe("installNodeDependencies.shouldRun", () => {
96
+ it("runs only when package.json exists in cwd", () => {
97
+ existsSyncMock.mockReturnValueOnce(true);
98
+ expect(installNodeDependencies.shouldRun()).toBe(true);
99
+ existsSyncMock.mockReturnValueOnce(false);
100
+ expect(installNodeDependencies.shouldRun()).toBe(false);
101
+ });
102
+ });
103
+
104
+ describe("installNodeDependencies.run", () => {
105
+ it("skips install when no lockfile is detected (default npm path)", async () => {
106
+ routeSpawn({ which: whichAvailable(true) });
107
+
108
+ const result = await installNodeDependencies.run(options);
109
+
110
+ expect(result).toEqual({
111
+ language: "node",
112
+ packageManager: "npm",
113
+ dependenciesInstalled: false,
114
+ issues: [],
115
+ });
116
+ // only the availability probe ran — no installer
117
+ expect(spawnMock).toHaveBeenCalledTimes(1);
118
+ });
119
+
120
+ it("fails fast when the manager is missing and shell is disabled", async () => {
121
+ detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
122
+ routeSpawn({ which: whichAvailable(false) });
123
+
124
+ const result = await installNodeDependencies.run({ ...options, ignoreScripts: true });
125
+
126
+ expect(result.dependenciesInstalled).toBe(false);
127
+ expect(result.issues[0]).toContain("cannot be installed when shell is disabled");
128
+ });
129
+
130
+ it("provisions a declared manager via corepack and runs the frozen install", async () => {
131
+ resolveSpecMock.mockResolvedValue(declaredSpec());
132
+ detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
133
+ ensurePackageManagerMock.mockResolvedValue(true);
134
+ resolveCommandMock.mockReturnValue({ command: "pnpm", args: ["install", "--frozen-lockfile"] });
135
+ routeSpawn({
136
+ which: whichAvailable(false),
137
+ pnpm: () => spawnResult({ stdout: "done" }),
138
+ });
139
+
140
+ const result = await installNodeDependencies.run(options);
141
+
142
+ expect(ensurePackageManagerMock).toHaveBeenCalledWith({
143
+ spec: declaredSpec(),
144
+ binDir: options.binDir,
145
+ });
146
+ expect(result).toEqual({
147
+ language: "node",
148
+ packageManager: "pnpm",
149
+ dependenciesInstalled: true,
150
+ issues: [],
151
+ });
152
+ });
153
+
154
+ it("re-pins an already-available declared manager before installing", async () => {
155
+ resolveSpecMock.mockResolvedValue(declaredSpec());
156
+ detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
157
+ resolveCommandMock.mockReturnValue({ command: "pnpm", args: ["install", "--frozen-lockfile"] });
158
+ routeSpawn({
159
+ which: whichAvailable(true),
160
+ pnpm: () => spawnResult(),
161
+ });
162
+
163
+ const result = await installNodeDependencies.run(options);
164
+
165
+ expect(ensurePackageManagerMock).toHaveBeenCalledTimes(1);
166
+ expect(result.dependenciesInstalled).toBe(true);
167
+ });
168
+
169
+ it("falls back to npm install -g when corepack cannot provision", async () => {
170
+ resolveSpecMock.mockResolvedValue(declaredSpec({ name: "bun", version: "1.3.0" }));
171
+ detectMock.mockResolvedValue({ name: "bun", agent: "bun" });
172
+ ensurePackageManagerMock.mockResolvedValue(false);
173
+ resolveCommandMock.mockReturnValue({ command: "bun", args: ["install", "--frozen-lockfile"] });
174
+ const npmCalls: SpawnArg[] = [];
175
+ routeSpawn({
176
+ which: whichAvailable(false),
177
+ npm: (call) => {
178
+ npmCalls.push(call);
179
+ return spawnResult();
180
+ },
181
+ bun: () => spawnResult(),
182
+ });
183
+
184
+ const result = await installNodeDependencies.run(options);
185
+
186
+ expect(npmCalls[0]?.args).toEqual(["install", "-g", "bun@1.3.0"]);
187
+ expect(result.dependenciesInstalled).toBe(true);
188
+ });
189
+
190
+ it("surfaces fallback installer failures as issues", async () => {
191
+ detectMock.mockResolvedValue({ name: "yarn", agent: "yarn" });
192
+ routeSpawn({
193
+ which: whichAvailable(false),
194
+ npm: (call) => {
195
+ // exercise the stderr-forwarding callback
196
+ call.onStderr?.("");
197
+ return spawnResult({ exitCode: 1, stderr: "registry down" });
198
+ },
199
+ });
200
+
201
+ const result = await installNodeDependencies.run(options);
202
+
203
+ expect(result).toEqual({
204
+ language: "node",
205
+ packageManager: "yarn",
206
+ dependenciesInstalled: false,
207
+ issues: ["registry down"],
208
+ });
209
+ });
210
+
211
+ it("installs deno via curl and prepends its bin dir to PATH", async () => {
212
+ const originalPath = process.env.PATH;
213
+ const originalHome = process.env.HOME;
214
+ vi.stubEnv("HOME", "/home/runner");
215
+ vi.stubEnv("PATH", "/usr/bin");
216
+ try {
217
+ detectMock.mockResolvedValue({ name: "deno", agent: "deno" });
218
+ resolveCommandMock.mockReturnValue({ command: "deno", args: ["install", "--frozen"] });
219
+ const shCalls: SpawnArg[] = [];
220
+ routeSpawn({
221
+ which: whichAvailable(false),
222
+ sh: (call) => {
223
+ shCalls.push(call);
224
+ return spawnResult();
225
+ },
226
+ deno: () => spawnResult(),
227
+ });
228
+
229
+ const result = await installNodeDependencies.run(options);
230
+
231
+ expect(shCalls[0]?.args).toEqual(["-c", "curl -fsSL https://deno.land/install.sh | sh"]);
232
+ const denoBin = join("/home/runner", ".deno", "bin");
233
+ expect(process.env.PATH?.startsWith(`${denoBin}:`)).toBe(true);
234
+ expect(result.dependenciesInstalled).toBe(true);
235
+ } finally {
236
+ vi.unstubAllEnvs();
237
+ process.env.PATH = originalPath;
238
+ process.env.HOME = originalHome;
239
+ }
240
+ });
241
+
242
+ it("reports an issue when no frozen-install command exists for the agent", async () => {
243
+ detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
244
+ resolveCommandMock.mockReturnValue(null);
245
+ routeSpawn({ which: whichAvailable(true) });
246
+
247
+ const result = await installNodeDependencies.run(options);
248
+
249
+ expect(result.issues).toEqual(["no frozen-install command available for npm"]);
250
+ });
251
+
252
+ it("appends --ignore-scripts when shell is disabled", async () => {
253
+ detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
254
+ resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
255
+ const npmCalls: SpawnArg[] = [];
256
+ routeSpawn({
257
+ which: whichAvailable(true),
258
+ npm: (call) => {
259
+ npmCalls.push(call);
260
+ return spawnResult();
261
+ },
262
+ });
263
+
264
+ const result = await installNodeDependencies.run({ ...options, ignoreScripts: true });
265
+
266
+ expect(npmCalls[0]?.args).toEqual(["ci", "--ignore-scripts"]);
267
+ expect(result.dependenciesInstalled).toBe(true);
268
+ });
269
+
270
+ it("returns the command output as an issue when the install fails", async () => {
271
+ detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
272
+ resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
273
+ routeSpawn({
274
+ which: whichAvailable(true),
275
+ npm: () => spawnResult({ exitCode: 1, stdout: "npm ERR!", stderr: "lockfile out of sync" }),
276
+ });
277
+
278
+ const result = await installNodeDependencies.run(options);
279
+
280
+ expect(result.dependenciesInstalled).toBe(false);
281
+ expect(result.issues[0]).toContain("`npm ci` failed:");
282
+ expect(result.issues[0]).toContain("npm ERR!");
283
+ expect(result.issues[0]).toContain("lockfile out of sync");
284
+ });
285
+
286
+ it("reports the exit code when a failing install produces no output", async () => {
287
+ detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
288
+ resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
289
+ routeSpawn({
290
+ which: whichAvailable(true),
291
+ npm: () => spawnResult({ exitCode: 7 }),
292
+ });
293
+
294
+ const result = await installNodeDependencies.run(options);
295
+
296
+ expect(result.issues[0]).toContain("exited with code 7");
297
+ });
298
+ });
@@ -0,0 +1,196 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { detect } from "package-manager-detector";
4
+ import { resolveCommand } from "package-manager-detector/commands";
5
+ import type {
6
+ NodePackageManager,
7
+ NodePrepResult,
8
+ PrepDefinition,
9
+ PrepOptions,
10
+ } from "#app/prep/types";
11
+ import { log } from "#app/utils/cli";
12
+ import { ensurePackageManager, resolvePackageManagerSpec } from "#app/utils/packageManager";
13
+ import { spawn } from "#app/utils/subprocess";
14
+
15
+ async function isCommandAvailable(command: string): Promise<boolean> {
16
+ const result = await spawn({
17
+ cmd: "which",
18
+ args: [command],
19
+ env: { PATH: process.env.PATH || "" },
20
+ });
21
+ return result.exitCode === 0;
22
+ }
23
+
24
+ // fallback installers for managers corepack doesn't ship shims for.
25
+ // pnpm and yarn are handled by `ensurePackageManager` (corepack); bun and
26
+ // deno fall through to here because corepack ignores them.
27
+ async function installFallback(
28
+ name: NodePackageManager,
29
+ installSpec: string,
30
+ ): Promise<string | null> {
31
+ if (name === "npm") return null;
32
+ log.info(`» installing ${installSpec} via npm install -g (corepack does not manage ${name})`);
33
+ const args =
34
+ name === "deno"
35
+ ? ["-c", "curl -fsSL https://deno.land/install.sh | sh"]
36
+ : ["install", "-g", installSpec];
37
+ const cmd = name === "deno" ? "sh" : "npm";
38
+ const result = await spawn({
39
+ cmd,
40
+ args,
41
+ env: { PATH: process.env.PATH || "", HOME: process.env.HOME || "" },
42
+ onStderr: (chunk) => process.stderr.write(chunk),
43
+ });
44
+ if (result.exitCode !== 0) {
45
+ return result.stderr || `failed to install ${name}`;
46
+ }
47
+ if (name === "deno") {
48
+ const denoPath = join(process.env.HOME || "", ".deno", "bin");
49
+ process.env.PATH = `${denoPath}:${process.env.PATH}`;
50
+ }
51
+ log.info(`» installed ${name}`);
52
+ return null;
53
+ }
54
+
55
+ export const installNodeDependencies: PrepDefinition = {
56
+ name: "installNodeDependencies",
57
+
58
+ shouldRun: () => {
59
+ const packageJsonPath = join(process.cwd(), "package.json");
60
+ return existsSync(packageJsonPath);
61
+ },
62
+
63
+ run: async (options: PrepOptions): Promise<NodePrepResult> => {
64
+ // prefer the project's declared spec (devEngines.packageManager wins over
65
+ // packageManager). fall back to lockfile detection when nothing is declared.
66
+ // restrict detect() to the lockfile strategy: `detected` here doubles as
67
+ // the lockfile-presence gate below, and the default strategy set also
68
+ // returns positives off `packageManager`/`devEngines` fields (which would
69
+ // mask the very case we're trying to detect — declared manager but no
70
+ // lockfile committed).
71
+ const declared = await resolvePackageManagerSpec(process.cwd());
72
+ const detected = await detect({ cwd: process.cwd(), strategies: ["lockfile"] });
73
+
74
+ const packageManager: NodePackageManager =
75
+ declared?.name ?? (detected?.name as NodePackageManager) ?? "npm";
76
+ const agent = detected?.agent ?? packageManager;
77
+
78
+ if (declared) {
79
+ log.info(
80
+ `» using ${packageManager}@${declared.version} from package.json (${declared.source})`,
81
+ );
82
+ } else if (detected) {
83
+ log.info(`» detected package manager: ${packageManager} (${agent})`);
84
+ } else {
85
+ log.info(`» no package manager declared, defaulting to npm`);
86
+ }
87
+
88
+ // provisioning: corepack for pnpm/yarn, legacy npm-install-g for bun/deno.
89
+ // when shell is disabled we can't run installers (they execute code), so
90
+ // we require the binary to already be on PATH.
91
+ if (!(await isCommandAvailable(packageManager))) {
92
+ if (options.ignoreScripts) {
93
+ return {
94
+ language: "node",
95
+ packageManager,
96
+ dependenciesInstalled: false,
97
+ issues: [
98
+ `${packageManager} is not available and cannot be installed when shell is disabled (would execute code)`,
99
+ ],
100
+ };
101
+ }
102
+
103
+ let provisioned = false;
104
+ if (declared)
105
+ provisioned = await ensurePackageManager({ spec: declared, binDir: options.binDir });
106
+ if (!provisioned) {
107
+ const fallbackSpec = declared ? `${declared.name}@${declared.version}` : packageManager;
108
+ const installError = await installFallback(packageManager, fallbackSpec);
109
+ if (installError) {
110
+ return {
111
+ language: "node",
112
+ packageManager,
113
+ dependenciesInstalled: false,
114
+ issues: [installError],
115
+ };
116
+ }
117
+ }
118
+ } else if (declared) {
119
+ // PATH already has the binary — but it may be the wrong version.
120
+ // ensurePackageManager is idempotent (caches on `--version` match) so
121
+ // this is cheap when main.ts already activated it.
122
+ await ensurePackageManager({ spec: declared, binDir: options.binDir });
123
+ }
124
+
125
+ // frozen-lockfile install only. eager prep is non-mutating by contract:
126
+ // we run it before the agent starts and any artifact it leaves in the
127
+ // tree (e.g. a generated `package-lock.json`) trips the dirty-tree
128
+ // post-run gate and produces a spurious PR. `frozen` commands
129
+ // (`npm ci`, `pnpm install --frozen-lockfile`, etc.) were assumed to
130
+ // fail cleanly without a lockfile — that assumption is false for
131
+ // pnpm 11.1.1 against a no-deps `package.json` (it silently writes an
132
+ // empty `pnpm-lock.yaml` despite the flag). gate on `detect()` having
133
+ // found a lockfile; it walks up the tree (so monorepo subpackages
134
+ // resolve to the workspace-root lockfile) and recognizes every
135
+ // manager's accepted lockfile variants (`bun.lockb` + `bun.lock`,
136
+ // `npm-shrinkwrap.json` + `package-lock.json`, etc.). when none is
137
+ // present, the project either has no installable dependencies or
138
+ // opts into install via a `setup` lifecycle hook
139
+ // (`action/utils/lifecycle.ts`); either way, eager prep should skip.
140
+ if (!detected) {
141
+ log.info(
142
+ `» skipping ${packageManager} install: no lockfile found (would otherwise risk lockfile drift)`,
143
+ );
144
+ return { language: "node", packageManager, dependenciesInstalled: false, issues: [] };
145
+ }
146
+
147
+ const resolved = resolveCommand(agent, "frozen", []);
148
+ if (!resolved) {
149
+ return {
150
+ language: "node",
151
+ packageManager,
152
+ dependenciesInstalled: false,
153
+ issues: [`no frozen-install command available for ${agent}`],
154
+ };
155
+ }
156
+
157
+ // SECURITY: when shell is disabled, suppress lifecycle scripts to prevent
158
+ // agents from injecting arbitrary code execution via package.json scripts
159
+ if (options.ignoreScripts) {
160
+ resolved.args.push("--ignore-scripts");
161
+ log.info("» --ignore-scripts enabled (shell disabled)");
162
+ }
163
+
164
+ const fullCommand = `${resolved.command} ${resolved.args.join(" ")}`;
165
+ log.info(`» running: ${fullCommand}`);
166
+ const result = await spawn({
167
+ cmd: resolved.command,
168
+ args: resolved.args,
169
+ env: { PATH: process.env.PATH || "", HOME: process.env.HOME || "" },
170
+ });
171
+
172
+ const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
173
+ if (output) {
174
+ log.startGroup(`${fullCommand} output`);
175
+ log.info(output);
176
+ log.endGroup();
177
+ }
178
+
179
+ if (result.exitCode !== 0) {
180
+ const errorMessage = output || `exited with code ${result.exitCode}`;
181
+ return {
182
+ language: "node",
183
+ packageManager,
184
+ dependenciesInstalled: false,
185
+ issues: [`\`${fullCommand}\` failed:\n${errorMessage}`],
186
+ };
187
+ }
188
+
189
+ return {
190
+ language: "node",
191
+ packageManager,
192
+ dependenciesInstalled: true,
193
+ issues: [],
194
+ };
195
+ },
196
+ };