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.
- package/LICENSE +661 -0
- package/README.md +145 -0
- package/dist/agents/claude.d.ts +73 -0
- package/dist/agents/claudePretoolGate.d.ts +99 -0
- package/dist/agents/gateServer.d.ts +7 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/nativeFsDenies.d.ts +28 -0
- package/dist/agents/opencode.d.ts +231 -0
- package/dist/agents/opencodePlugin.d.ts +85 -0
- package/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +132 -0
- package/dist/agents/reviewer.d.ts +38 -0
- package/dist/agents/sessionLabeler.d.ts +97 -0
- package/dist/agents/shared.d.ts +189 -0
- package/dist/agents/subagentModels.d.ts +19 -0
- package/dist/agents/subagentToolGates.d.ts +55 -0
- package/dist/cli.mjs +197426 -0
- package/dist/external.d.ts +227 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +196783 -0
- package/dist/internal/index.d.ts +18 -0
- package/dist/internal.js +1714 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/main.d.ts +8 -0
- package/dist/mcp/arkConfig.d.ts +1 -0
- package/dist/mcp/checkSuite.d.ts +25 -0
- package/dist/mcp/checkout.d.ts +77 -0
- package/dist/mcp/comment.d.ts +119 -0
- package/dist/mcp/commitInfo.d.ts +9 -0
- package/dist/mcp/crosswalk.d.ts +105 -0
- package/dist/mcp/dependencies.d.ts +8 -0
- package/dist/mcp/geminiSanitizer.d.ts +28 -0
- package/dist/mcp/git.d.ts +46 -0
- package/dist/mcp/guardrails.d.ts +104 -0
- package/dist/mcp/issue.d.ts +18 -0
- package/dist/mcp/issueComments.d.ts +9 -0
- package/dist/mcp/issueEvents.d.ts +9 -0
- package/dist/mcp/issueInfo.d.ts +9 -0
- package/dist/mcp/labels.d.ts +12 -0
- package/dist/mcp/localContext.d.ts +19 -0
- package/dist/mcp/moduleExtraction.d.ts +71 -0
- package/dist/mcp/moduleTests.d.ts +104 -0
- package/dist/mcp/modules.d.ts +179 -0
- package/dist/mcp/output.d.ts +12 -0
- package/dist/mcp/pathSafety.d.ts +14 -0
- package/dist/mcp/policy.d.ts +48 -0
- package/dist/mcp/pr.d.ts +49 -0
- package/dist/mcp/prInfo.d.ts +9 -0
- package/dist/mcp/providerSchema.d.ts +50 -0
- package/dist/mcp/review.d.ts +199 -0
- package/dist/mcp/reviewComments.d.ts +178 -0
- package/dist/mcp/roots.d.ts +58 -0
- package/dist/mcp/scope.d.ts +15 -0
- package/dist/mcp/selectMode.d.ts +18 -0
- package/dist/mcp/server.d.ts +48 -0
- package/dist/mcp/shared.d.ts +47 -0
- package/dist/mcp/shell.d.ts +37 -0
- package/dist/mcp/staleFix.d.ts +51 -0
- package/dist/mcp/terraform/cost.d.ts +55 -0
- package/dist/mcp/terraform/currency.d.ts +94 -0
- package/dist/mcp/terraform/decisions.d.ts +178 -0
- package/dist/mcp/terraform/findings.d.ts +75 -0
- package/dist/mcp/terraform/plan.d.ts +157 -0
- package/dist/mcp/terraform/scanners.d.ts +131 -0
- package/dist/mcp/terraform/tools.d.ts +63 -0
- package/dist/mcp/terraform/types.d.ts +172 -0
- package/dist/mcp/terraform.d.ts +22 -0
- package/dist/mcp/terratest.d.ts +83 -0
- package/dist/mcp/upload.d.ts +6 -0
- package/dist/models.d.ts +171 -0
- package/dist/modes.d.ts +26 -0
- package/dist/prep/index.d.ts +7 -0
- package/dist/prep/installNodeDependencies.d.ts +2 -0
- package/dist/prep/installPythonDependencies.d.ts +2 -0
- package/dist/prep/types.d.ts +31 -0
- package/dist/reviewQuality.d.ts +64 -0
- package/dist/skills/terraform-best-practices/SKILL.md +369 -0
- package/dist/toolState.d.ts +135 -0
- package/dist/utils/activity.d.ts +40 -0
- package/dist/utils/agent.d.ts +20 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/apiFetch.d.ts +19 -0
- package/dist/utils/apiKeys.d.ts +41 -0
- package/dist/utils/apiUrl.d.ts +20 -0
- package/dist/utils/assets.d.ts +8 -0
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/body.d.ts +34 -0
- package/dist/utils/buildTerramendFooter.d.ts +25 -0
- package/dist/utils/byokFallback.d.ts +85 -0
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/cli.d.ts +10 -0
- package/dist/utils/codexHome.d.ts +29 -0
- package/dist/utils/codexOAuth.d.ts +60 -0
- package/dist/utils/diffCoverage.d.ts +63 -0
- package/dist/utils/errorReport.d.ts +17 -0
- package/dist/utils/exitHandler.d.ts +8 -0
- package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
- package/dist/utils/gitAuth.d.ts +84 -0
- package/dist/utils/gitAuthServer.d.ts +24 -0
- package/dist/utils/github.d.ts +78 -0
- package/dist/utils/globals.d.ts +3 -0
- package/dist/utils/install.d.ts +60 -0
- package/dist/utils/instructions.d.ts +48 -0
- package/dist/utils/leapingComment.d.ts +11 -0
- package/dist/utils/learnings.d.ts +62 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +57 -0
- package/dist/utils/log.d.ts +111 -0
- package/dist/utils/normalizeEnv.d.ts +30 -0
- package/dist/utils/openCodeModels.d.ts +11 -0
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/packageManager.d.ts +49 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
- package/dist/utils/payload.d.ts +105 -0
- package/dist/utils/prSummary.d.ts +61 -0
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/providerErrors.d.ts +31 -0
- package/dist/utils/rangeDiff.d.ts +51 -0
- package/dist/utils/remediationCommand.d.ts +55 -0
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/reviewCleanup.d.ts +14 -0
- package/dist/utils/run.d.ts +9 -0
- package/dist/utils/runContext.d.ts +60 -0
- package/dist/utils/runContextData.d.ts +23 -0
- package/dist/utils/runErrorRenderer.d.ts +64 -0
- package/dist/utils/runLifecycle.d.ts +86 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/secrets.d.ts +22 -0
- package/dist/utils/setup.d.ts +90 -0
- package/dist/utils/shell.d.ts +32 -0
- package/dist/utils/skills.d.ts +10 -0
- package/dist/utils/subprocess.d.ts +80 -0
- package/dist/utils/terraformMcp.d.ts +42 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/timer.d.ts +23 -0
- package/dist/utils/todoTracking.d.ts +16 -0
- package/dist/utils/token.d.ts +39 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/versioning.d.ts +7 -0
- package/dist/utils/vertex.d.ts +16 -0
- package/dist/utils/workflow.d.ts +13 -0
- package/package.json +119 -0
- package/src/agents/claude.test.ts +1016 -0
- package/src/agents/claude.ts +1246 -0
- package/src/agents/claudePretoolGate.test.ts +28 -0
- package/src/agents/claudePretoolGate.ts +173 -0
- package/src/agents/gateServer.test.ts +204 -0
- package/src/agents/gateServer.ts +124 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/nativeFsDenies.ts +82 -0
- package/src/agents/opencode.test.ts +1440 -0
- package/src/agents/opencode.ts +1312 -0
- package/src/agents/opencodePlugin.ts +222 -0
- package/src/agents/opencodeShared.test.ts +34 -0
- package/src/agents/opencodeShared.ts +121 -0
- package/src/agents/postRun.test.ts +549 -0
- package/src/agents/postRun.ts +535 -0
- package/src/agents/reviewer.ts +104 -0
- package/src/agents/sessionLabeler.test.ts +247 -0
- package/src/agents/sessionLabeler.ts +178 -0
- package/src/agents/shared.test.ts +76 -0
- package/src/agents/shared.ts +292 -0
- package/src/agents/subagentModels.test.ts +113 -0
- package/src/agents/subagentModels.ts +40 -0
- package/src/agents/subagentRegistration.test.ts +41 -0
- package/src/agents/subagentToolGates.ts +114 -0
- package/src/cli.test.ts +129 -0
- package/src/cli.ts +105 -0
- package/src/commands/gha.test.ts +192 -0
- package/src/commands/gha.ts +188 -0
- package/src/commands/mcp.ts +122 -0
- package/src/config.ts +1 -0
- package/src/entry.ts +7 -0
- package/src/entryPost.stdlibOnly.test.ts +109 -0
- package/src/entryPost.ts +99 -0
- package/src/external.test.ts +16 -0
- package/src/external.ts +302 -0
- package/src/index.ts +11 -0
- package/src/internal/index.ts +71 -0
- package/src/lifecycle.ts +2 -0
- package/src/main.test.ts +873 -0
- package/src/main.ts +712 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
- package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
- package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
- package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
- package/src/mcp/arkConfig.ts +7 -0
- package/src/mcp/checkSuite.test.ts +245 -0
- package/src/mcp/checkSuite.ts +255 -0
- package/src/mcp/checkout.test.ts +752 -0
- package/src/mcp/checkout.ts +886 -0
- package/src/mcp/comment.test.ts +772 -0
- package/src/mcp/comment.ts +582 -0
- package/src/mcp/commitInfo.test.ts +127 -0
- package/src/mcp/commitInfo.ts +61 -0
- package/src/mcp/crosswalk.test.ts +106 -0
- package/src/mcp/crosswalk.ts +339 -0
- package/src/mcp/dependencies.test.ts +309 -0
- package/src/mcp/dependencies.ts +189 -0
- package/src/mcp/geminiSanitizer.test.ts +287 -0
- package/src/mcp/geminiSanitizer.ts +207 -0
- package/src/mcp/git.test.ts +1083 -0
- package/src/mcp/git.ts +890 -0
- package/src/mcp/guardrails.test.ts +705 -0
- package/src/mcp/guardrails.ts +465 -0
- package/src/mcp/issue.test.ts +113 -0
- package/src/mcp/issue.ts +73 -0
- package/src/mcp/issueComments.test.ts +69 -0
- package/src/mcp/issueComments.ts +48 -0
- package/src/mcp/issueEvents.test.ts +134 -0
- package/src/mcp/issueEvents.ts +100 -0
- package/src/mcp/issueInfo.test.ts +104 -0
- package/src/mcp/issueInfo.ts +72 -0
- package/src/mcp/labels.test.ts +52 -0
- package/src/mcp/labels.ts +34 -0
- package/src/mcp/localContext.ts +28 -0
- package/src/mcp/localServer.test.ts +75 -0
- package/src/mcp/localServer.ts +131 -0
- package/src/mcp/moduleExtraction.test.ts +261 -0
- package/src/mcp/moduleExtraction.ts +313 -0
- package/src/mcp/moduleTests.test.ts +269 -0
- package/src/mcp/moduleTests.ts +421 -0
- package/src/mcp/modules.test.ts +640 -0
- package/src/mcp/modules.ts +696 -0
- package/src/mcp/output.test.ts +96 -0
- package/src/mcp/output.ts +70 -0
- package/src/mcp/pathSafety.test.ts +44 -0
- package/src/mcp/pathSafety.ts +28 -0
- package/src/mcp/policy.test.ts +282 -0
- package/src/mcp/policy.ts +199 -0
- package/src/mcp/pr.test.ts +387 -0
- package/src/mcp/pr.ts +194 -0
- package/src/mcp/prInfo.test.ts +96 -0
- package/src/mcp/prInfo.ts +91 -0
- package/src/mcp/providerSchema.test.ts +85 -0
- package/src/mcp/providerSchema.ts +175 -0
- package/src/mcp/review.test.ts +936 -0
- package/src/mcp/review.ts +923 -0
- package/src/mcp/reviewComments.test.ts +549 -0
- package/src/mcp/reviewComments.ts +896 -0
- package/src/mcp/roots.test.ts +175 -0
- package/src/mcp/roots.ts +217 -0
- package/src/mcp/scope.test.ts +59 -0
- package/src/mcp/scope.ts +65 -0
- package/src/mcp/security.test.ts +720 -0
- package/src/mcp/selectMode.test.ts +210 -0
- package/src/mcp/selectMode.ts +181 -0
- package/src/mcp/server.test.ts +292 -0
- package/src/mcp/server.ts +403 -0
- package/src/mcp/shared.ts +100 -0
- package/src/mcp/shell.test.ts +520 -0
- package/src/mcp/shell.ts +505 -0
- package/src/mcp/staleFix.test.ts +237 -0
- package/src/mcp/staleFix.ts +277 -0
- package/src/mcp/terraform/cost.ts +163 -0
- package/src/mcp/terraform/currency.test.ts +338 -0
- package/src/mcp/terraform/currency.ts +336 -0
- package/src/mcp/terraform/decisions.ts +527 -0
- package/src/mcp/terraform/findings.ts +333 -0
- package/src/mcp/terraform/plan.ts +348 -0
- package/src/mcp/terraform/scanners.ts +809 -0
- package/src/mcp/terraform/tools.test.ts +1071 -0
- package/src/mcp/terraform/tools.ts +908 -0
- package/src/mcp/terraform/types.ts +305 -0
- package/src/mcp/terraform.test.ts +1957 -0
- package/src/mcp/terraform.ts +23 -0
- package/src/mcp/terratest.test.ts +105 -0
- package/src/mcp/terratest.ts +196 -0
- package/src/mcp/toolFiltering.test.ts +85 -0
- package/src/mcp/upload.test.ts +180 -0
- package/src/mcp/upload.ts +112 -0
- package/src/models.test.ts +300 -0
- package/src/models.ts +708 -0
- package/src/modes.test.ts +107 -0
- package/src/modes.ts +880 -0
- package/src/prep/index.ts +43 -0
- package/src/prep/installNodeDependencies.test.ts +298 -0
- package/src/prep/installNodeDependencies.ts +196 -0
- package/src/prep/installPythonDependencies.test.ts +268 -0
- package/src/prep/installPythonDependencies.ts +199 -0
- package/src/prep/types.ts +38 -0
- package/src/reviewQuality.test.ts +63 -0
- package/src/reviewQuality.ts +134 -0
- package/src/runCli.test.ts +214 -0
- package/src/runCli.ts +282 -0
- package/src/skills/terraform-best-practices/SKILL.md +369 -0
- package/src/toolState.test.ts +45 -0
- package/src/toolState.ts +252 -0
- package/src/utils/activity.test.ts +188 -0
- package/src/utils/activity.ts +210 -0
- package/src/utils/agent.test.ts +251 -0
- package/src/utils/agent.ts +139 -0
- package/src/utils/agentHangReport.test.ts +203 -0
- package/src/utils/agentHangReport.ts +170 -0
- package/src/utils/apiFetch.test.ts +115 -0
- package/src/utils/apiFetch.ts +62 -0
- package/src/utils/apiKeys.test.ts +344 -0
- package/src/utils/apiKeys.ts +206 -0
- package/src/utils/apiUrl.test.ts +30 -0
- package/src/utils/apiUrl.ts +59 -0
- package/src/utils/assets.test.ts +153 -0
- package/src/utils/assets.ts +107 -0
- package/src/utils/billingErrors.test.ts +121 -0
- package/src/utils/billingErrors.ts +189 -0
- package/src/utils/body.test.ts +217 -0
- package/src/utils/body.ts +168 -0
- package/src/utils/buildTerramendFooter.test.ts +38 -0
- package/src/utils/buildTerramendFooter.ts +82 -0
- package/src/utils/byokFallback.test.ts +205 -0
- package/src/utils/byokFallback.ts +128 -0
- package/src/utils/claudeSubscription.test.ts +179 -0
- package/src/utils/claudeSubscription.ts +93 -0
- package/src/utils/cli.ts +31 -0
- package/src/utils/codexHome.test.ts +190 -0
- package/src/utils/codexHome.ts +191 -0
- package/src/utils/codexOAuth.ts +147 -0
- package/src/utils/codexRefreshDetect.test.ts +85 -0
- package/src/utils/codexRefreshDetect.ts +35 -0
- package/src/utils/diffCoverage.test.ts +468 -0
- package/src/utils/diffCoverage.ts +404 -0
- package/src/utils/errorReport.test.ts +135 -0
- package/src/utils/errorReport.ts +83 -0
- package/src/utils/exitHandler.ts +35 -0
- package/src/utils/fixDoubleEscapedString.ts +9 -0
- package/src/utils/ghaCore.ts +13 -0
- package/src/utils/gitAuth.test.ts +322 -0
- package/src/utils/gitAuth.ts +263 -0
- package/src/utils/gitAuthServer.test.ts +260 -0
- package/src/utils/gitAuthServer.ts +182 -0
- package/src/utils/github.test.ts +615 -0
- package/src/utils/github.ts +538 -0
- package/src/utils/globals.ts +9 -0
- package/src/utils/humanEditCapture.test.ts +100 -0
- package/src/utils/humanEditCapture.ts +193 -0
- package/src/utils/install.test.ts +768 -0
- package/src/utils/install.ts +492 -0
- package/src/utils/instructions.test.ts +240 -0
- package/src/utils/instructions.ts +543 -0
- package/src/utils/leapingComment.test.ts +51 -0
- package/src/utils/leapingComment.ts +18 -0
- package/src/utils/learnings.test.ts +87 -0
- package/src/utils/learnings.ts +138 -0
- package/src/utils/learningsTocRender.test.ts +116 -0
- package/src/utils/learningsTruncate.test.ts +39 -0
- package/src/utils/learningsTruncate.ts +42 -0
- package/src/utils/lifecycle.test.ts +195 -0
- package/src/utils/lifecycle.ts +198 -0
- package/src/utils/log.test.ts +402 -0
- package/src/utils/log.ts +432 -0
- package/src/utils/normalizeEnv.test.ts +91 -0
- package/src/utils/normalizeEnv.ts +106 -0
- package/src/utils/openCodeModels.ts +82 -0
- package/src/utils/overrides.test.ts +89 -0
- package/src/utils/overrides.ts +98 -0
- package/src/utils/packageManager.test.ts +321 -0
- package/src/utils/packageManager.ts +257 -0
- package/src/utils/patchWorkflowRunFields.test.ts +92 -0
- package/src/utils/patchWorkflowRunFields.ts +150 -0
- package/src/utils/payload.test.ts +497 -0
- package/src/utils/payload.ts +371 -0
- package/src/utils/postApiFetch.ts +51 -0
- package/src/utils/prSummary.test.ts +224 -0
- package/src/utils/prSummary.ts +147 -0
- package/src/utils/progressComment.ts +261 -0
- package/src/utils/providerErrors.test.ts +315 -0
- package/src/utils/providerErrors.ts +172 -0
- package/src/utils/rangeDiff.test.ts +236 -0
- package/src/utils/rangeDiff.ts +182 -0
- package/src/utils/remediationCommand.test.ts +163 -0
- package/src/utils/remediationCommand.ts +119 -0
- package/src/utils/retry.test.ts +153 -0
- package/src/utils/retry.ts +58 -0
- package/src/utils/reviewCleanup.ts +106 -0
- package/src/utils/run.ts +99 -0
- package/src/utils/runContext.ts +145 -0
- package/src/utils/runContextData.ts +58 -0
- package/src/utils/runErrorRenderer.test.ts +95 -0
- package/src/utils/runErrorRenderer.ts +259 -0
- package/src/utils/runFixture.ts +76 -0
- package/src/utils/runLifecycle.ts +237 -0
- package/src/utils/runStartupLog.ts +60 -0
- package/src/utils/secrets.test.ts +103 -0
- package/src/utils/secrets.ts +177 -0
- package/src/utils/setup.test.ts +509 -0
- package/src/utils/setup.ts +352 -0
- package/src/utils/shell.ts +103 -0
- package/src/utils/skills.test.ts +46 -0
- package/src/utils/skills.ts +67 -0
- package/src/utils/subprocess.test.ts +170 -0
- package/src/utils/subprocess.ts +438 -0
- package/src/utils/terraformMcp.test.ts +63 -0
- package/src/utils/terraformMcp.ts +83 -0
- package/src/utils/time.test.ts +105 -0
- package/src/utils/time.ts +59 -0
- package/src/utils/timer.test.ts +91 -0
- package/src/utils/timer.ts +72 -0
- package/src/utils/todoTracking.test.ts +223 -0
- package/src/utils/todoTracking.ts +167 -0
- package/src/utils/token.test.ts +239 -0
- package/src/utils/token.ts +186 -0
- package/src/utils/version.ts +10 -0
- package/src/utils/versioning.test.ts +34 -0
- package/src/utils/versioning.ts +44 -0
- package/src/utils/vertex.ts +85 -0
- package/src/utils/workflow.ts +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Terramend
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://github.com/terramend/terramend/actions/workflows/test.yml)
|
|
5
|
+
[](https://github.com/terramend/terramend/releases)
|
|
6
|
+
[](https://github.com/marketplace/actions/terramend)
|
|
7
|
+
|
|
8
|
+
**Terramend brings your Terraform up to best practice — automatically, as reviewable pull requests.**
|
|
9
|
+
|
|
10
|
+
Terramend is an open-source ([AGPL-3.0](#licence)) GitHub Action and agent runtime. Point it at a
|
|
11
|
+
repository and it scans the Terraform with standard deterministic tools, then opens **one scoped,
|
|
12
|
+
reviewable pull request per concern** that fixes the issue and **proves it fixed** by re-scanning the
|
|
13
|
+
branch (✗ → ✓). It never auto-merges — a human always reviews.
|
|
14
|
+
|
|
15
|
+
- **It proves its own fixes.** The PR body records `✗ → ✓ <rule> resolved`, produced by re-running the
|
|
16
|
+
same deterministic scanners on the branch. Anyone can reproduce it — evidence, not a claim.
|
|
17
|
+
- **Tools decide, the LLM assists.** Findings come from `terraform fmt`/`validate`, tflint, Trivy and
|
|
18
|
+
Checkov — not the model's opinion. The agent only applies the minimal, constrained fix.
|
|
19
|
+
- **One scoped PR per concern.** Small, reviewable diffs on stable `remediate/<id>` branches. Re-runs
|
|
20
|
+
update the existing PR rather than opening duplicates.
|
|
21
|
+
- **Guardrails enforced in code, not prompts.** Terraform-only edits, no inlined secrets, no destroying
|
|
22
|
+
stateful data, never auto-merges — all fail-closed at push time.
|
|
23
|
+
- **Module-aware.** Fixes land at the module source, version upgrades arrive as scoped `chore(deps)`
|
|
24
|
+
PRs, and resource piles become module calls only when a pure-`moved` plan proves the refactor is a
|
|
25
|
+
no-op.
|
|
26
|
+
- **Bring your own key, no hosted backend.** Supply your own LLM key, pointed at an approved endpoint
|
|
27
|
+
where data residency matters. Nothing leaves your runner that you didn't configure.
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
name: Terramend — Terraform remediation
|
|
33
|
+
on:
|
|
34
|
+
workflow_dispatch:
|
|
35
|
+
schedule:
|
|
36
|
+
- cron: "0 6 * * 1" # weekly drift sweep
|
|
37
|
+
|
|
38
|
+
permissions:
|
|
39
|
+
contents: write # push the remediation branch
|
|
40
|
+
pull-requests: write # open the PR
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
remediate:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
# Pin third-party actions to a commit SHA — a tag can be force-repointed.
|
|
47
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
48
|
+
|
|
49
|
+
# install the Terraform best-practice toolchain (absent tools are skipped, never fatal)
|
|
50
|
+
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
|
|
51
|
+
- uses: terraform-linters/setup-tflint@90f302c255ef959cbfb4bd10581afecdb7ece3e6 # v4
|
|
52
|
+
- uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
|
|
53
|
+
- run: pipx install checkov
|
|
54
|
+
|
|
55
|
+
- name: Run Terramend
|
|
56
|
+
uses: terramend/terramend@v0
|
|
57
|
+
with:
|
|
58
|
+
mode: remediate
|
|
59
|
+
severity_threshold: medium # only act on medium+ concerns
|
|
60
|
+
max_prs: 1 # one scoped PR per run
|
|
61
|
+
env:
|
|
62
|
+
# bring your own LLM key (BYOK)
|
|
63
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
64
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **Ready-to-use workflows:** [`examples/`](examples/) has copy-pasteable workflows — scheduled
|
|
68
|
+
> [remediation](examples/remediate.yml), [generation](examples/generate-terraform.yml),
|
|
69
|
+
> [comment-triggered fixes](examples/comment-fix.yml), and the full
|
|
70
|
+
> [SARIF + plan-gate + policy setup](examples/remediate-advanced.yml).
|
|
71
|
+
|
|
72
|
+
## How it works
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
A[Scan<br/>fmt · validate · tflint<br/>Trivy · Checkov] --> B[Concerns<br/>severity-ranked,<br/>grouped]
|
|
77
|
+
B --> C[Fix<br/>minimal change,<br/>Terraform-only]
|
|
78
|
+
C --> D[Validate<br/>fmt · validate · tflint]
|
|
79
|
+
D --> E[Plan gate<br/>optional, with creds]
|
|
80
|
+
E --> F[Open one<br/>scoped PR]
|
|
81
|
+
F --> G[Re-scan branch<br/>✗ → ✓ proof]
|
|
82
|
+
G -.->|regression / needs-human| H[Label for a human]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Scanners find the problem, the agent applies the minimal fix, and the scanners verify it** before a
|
|
86
|
+
single PR is opened. Coverage is inherited, not reinvented: findings come from the scanners Terramend
|
|
87
|
+
runs (Checkov's 1,000+ policies, Trivy's AVD checks, tflint's provider rulesets, `fmt`/`validate`), so
|
|
88
|
+
new upstream checks show up the day you update the scanner. The PR's **Validation (✗ → ✓)** section is
|
|
89
|
+
the part you can trust without trusting Terramend — re-run the same scanners on the branch and
|
|
90
|
+
reproduce it. Higher-risk fixes (a regression, a stateful destroy/replace, a large blast radius, a
|
|
91
|
+
non-deterministic plan) get a `> [!CAUTION]` banner and a `needs-human` label.
|
|
92
|
+
|
|
93
|
+
## How Terramend compares
|
|
94
|
+
|
|
95
|
+
| | Reports findings | Fixes the code | Proves the fix | Opens a PR | Auto-merges |
|
|
96
|
+
| --- | :---: | :---: | :---: | :---: | :---: |
|
|
97
|
+
| **Scanners** (Checkov, Trivy, tfsec, tflint) | ✅ | ❌ | ❌ | ❌ | — |
|
|
98
|
+
| **Plan orchestrators** (Atlantis, Digger) | ❌ | ❌ | ❌ | comments on yours | ❌ |
|
|
99
|
+
| **Dependency bots** (Dependabot, Renovate) | ✅ (deps) | ✅ (version bumps) | ❌ | ✅ | optional |
|
|
100
|
+
| **Auto-fix AI bots** | partial | ✅ | rarely | ✅ | often |
|
|
101
|
+
| **Terramend** | ✅ | ✅ | ✅ (✗ → ✓ re-scan) | ✅ (one per concern) | **never** |
|
|
102
|
+
|
|
103
|
+
## Documentation
|
|
104
|
+
|
|
105
|
+
| Doc | What's in it |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| [Action inputs & outputs](docs/action-inputs.md) | The complete `action.yml` reference (generated — never drifts) |
|
|
108
|
+
| [Configuration](docs/configuration.md) | Modes, comment-scoped runs, scoping out findings, the plan gate & OIDC roles, BYOK, SARIF, modules |
|
|
109
|
+
| [Security model](docs/security-model.md) | The code-level guardrails and the trust/data-privacy story |
|
|
110
|
+
| [MCP server](docs/mcp.md) | `terramend mcp` in your IDE + pairing with HashiCorp's terraform-mcp-server |
|
|
111
|
+
| [Tools](docs/tools.md) | Every MCP tool the agent uses, and the CLI binaries they shell out to |
|
|
112
|
+
| [Supported models](docs/models.md) | The model catalog and how selection works (generated) |
|
|
113
|
+
|
|
114
|
+
## Support
|
|
115
|
+
|
|
116
|
+
- **Getting started / usage** — this README, the [docs](docs/), and the [`examples/`](examples/) workflows.
|
|
117
|
+
- **Bug reports & feature requests** — open a [GitHub issue](https://github.com/terramend/terramend/issues).
|
|
118
|
+
- **Security vulnerabilities** — **don't** use a public issue; see [Security](#security) below.
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Contributions are welcome. Terramend standardises on **Node 24** and **pnpm 11**:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
corepack enable
|
|
126
|
+
pnpm install --frozen-lockfile
|
|
127
|
+
pnpm typecheck
|
|
128
|
+
pnpm test
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
All contributions are accepted under the [Contributor License Agreement](CLA.md) (enforced by the CLA
|
|
132
|
+
Assistant on your first PR), and releases are automated from [Conventional Commits](https://www.conventionalcommits.org)
|
|
133
|
+
via release-please. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full development, commit, and
|
|
134
|
+
action-pinning conventions.
|
|
135
|
+
|
|
136
|
+
## Security
|
|
137
|
+
|
|
138
|
+
Terramend runs AI coding agents with write access to repositories and CI secrets, and is positioned for
|
|
139
|
+
security- and compliance-sensitive use. **Please don't open public issues for vulnerabilities** — report
|
|
140
|
+
them privately via [GitHub Security Advisories](https://github.com/terramend/terramend/security/advisories/new).
|
|
141
|
+
See [`SECURITY.md`](SECURITY.md) for scope, supported versions, and response targets.
|
|
142
|
+
|
|
143
|
+
## Licence
|
|
144
|
+
|
|
145
|
+
Terramend is licensed under the **GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type AgentResult, type AgentRunContext } from "#app/agents/shared";
|
|
2
|
+
import type { TodoTracker } from "#app/utils/todoTracking";
|
|
3
|
+
export declare const CLAUDE_EXEC_TOOL_DENY_RULES: string[];
|
|
4
|
+
export declare function writeMcpConfig(ctx: AgentRunContext): string;
|
|
5
|
+
/**
|
|
6
|
+
* Build the `--agents` JSON definition for the `reviewfrog` subagent.
|
|
7
|
+
*
|
|
8
|
+
* The Claude Code path always runs against an Anthropic model (see
|
|
9
|
+
* resolveAgent), so we hardcode the cheaper-sibling downshift: lenses run
|
|
10
|
+
* on Sonnet, the orchestrator stays on whatever model `--model` was passed.
|
|
11
|
+
*
|
|
12
|
+
* Per-call model override is also possible (Task tool's `model` arg accepts
|
|
13
|
+
* 'sonnet' | 'opus' | 'haiku') and takes precedence over what's set here —
|
|
14
|
+
* we don't pass it; the per-subagent `model` field is the right default.
|
|
15
|
+
*
|
|
16
|
+
* The non-mutative + non-recursive contract is enforced by the prose system
|
|
17
|
+
* prompt baked into the agent — see action/agents/reviewer.ts for why we
|
|
18
|
+
* no longer wire per-agent `disallowedTools` here.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildAgentsJson(): string;
|
|
21
|
+
export declare function stripProviderPrefix(specifier: string): string;
|
|
22
|
+
type RunParams = {
|
|
23
|
+
label: string;
|
|
24
|
+
cmd: string;
|
|
25
|
+
args: string[];
|
|
26
|
+
cwd: string;
|
|
27
|
+
env: Record<string, string | undefined>;
|
|
28
|
+
todoTracker?: TodoTracker | undefined;
|
|
29
|
+
onActivityTimeout?: (() => void) | undefined;
|
|
30
|
+
onToolUse?: ((event: {
|
|
31
|
+
toolName: string;
|
|
32
|
+
input: unknown;
|
|
33
|
+
}) => void) | undefined;
|
|
34
|
+
};
|
|
35
|
+
type ClaudeRunResult = AgentResult & {
|
|
36
|
+
sessionId?: string | undefined;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Return the tail of `text` capped at `maxCodeUnits` UTF-16 code units,
|
|
40
|
+
* dropping any partial first line. used in the exit-non-zero stdout fallback
|
|
41
|
+
* so we never surface a truncated NDJSON event to operators —
|
|
42
|
+
* `result.stdout.slice(-2048)` would otherwise cut mid-line and produce a
|
|
43
|
+
* syntactically broken JSON fragment. code units rather than bytes because
|
|
44
|
+
* `String.prototype.slice` operates on UTF-16 units; for multi-byte UTF-8
|
|
45
|
+
* content the effective byte budget can be up to 4× the nominal limit.
|
|
46
|
+
*/
|
|
47
|
+
export declare function tailLines(text: string, maxCodeUnits: number): string;
|
|
48
|
+
export declare function runClaude(params: RunParams): Promise<ClaudeRunResult>;
|
|
49
|
+
/**
|
|
50
|
+
* managed Stop hook. swaps the old `--resume <sessionId>` follow-up
|
|
51
|
+
* subprocesses (reflection + every gate retry — cost audit on PR #792
|
|
52
|
+
* showed reflection alone burned ~$0.85 / 111K cache_write per Opus run,
|
|
53
|
+
* almost all of it wasted re-running `getAttachmentMessages` in the fresh
|
|
54
|
+
* process) for a `{decision: "block", reason: ...}` injection inside the
|
|
55
|
+
* live `queryLoop`. existing session context is already in the prompt
|
|
56
|
+
* cache so only the new reason text is fresh cache_write.
|
|
57
|
+
*
|
|
58
|
+
* the script is intentionally minimal — all decision logic lives in the
|
|
59
|
+
* sidecar gate server (`gateServer.ts`), which reads live `ctx.toolState`
|
|
60
|
+
* mutations from the same process the MCP server runs in. budget +
|
|
61
|
+
* one-shot tracking lives there too, so re-fires across multiple stops in
|
|
62
|
+
* one session are safe. claude-code's 8-consecutive-block override is the
|
|
63
|
+
* last-line backstop.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildStopHookScript(): string;
|
|
66
|
+
export interface ManagedSettingsParams {
|
|
67
|
+
ctx: AgentRunContext;
|
|
68
|
+
stopHookPath: string | null;
|
|
69
|
+
pretoolGateScriptPath: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function buildManagedSettings(params: ManagedSettingsParams): Record<string, unknown>;
|
|
72
|
+
export declare const claude: import("#app/agents/shared").Agent;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code `PreToolUse` hook source — written into `ctx.tmpdir` at runtime
|
|
3
|
+
* and registered via a tmpdir-scoped `settings.json` referenced by
|
|
4
|
+
* `--settings <path>` (see action/agents/claude.ts).
|
|
5
|
+
*
|
|
6
|
+
* Closes the subagent → state-mutating MCP tool path that motivated the
|
|
7
|
+
* 2026-05-18 zed-industries/cloud incident (`reviewfrog` lens called
|
|
8
|
+
* `checkout_pr` mid-review and the orchestrator's next push clobbered an
|
|
9
|
+
* unrelated branch). Pairs with the `tool.execute.before` hook in
|
|
10
|
+
* action/agents/opencodePlugin.ts; both runtimes share the deny list at
|
|
11
|
+
* action/agents/subagentToolGates.ts.
|
|
12
|
+
*
|
|
13
|
+
* PreToolUse hook contract (verified against yasasbanukaofficial/claude-code
|
|
14
|
+
* `src/utils/hooks/hooksConfigManager.ts` and `src/utils/hooks.ts`):
|
|
15
|
+
* - stdin: JSON with `hook_event_name: "PreToolUse"`, `tool_name`,
|
|
16
|
+
* `tool_input`, `tool_use_id`, `session_id`, `cwd`, `transcript_path`,
|
|
17
|
+
* and crucially `agent_id` / `agent_type` populated when the call
|
|
18
|
+
* originates from a subagent (set by the SDK when a Task/Agent
|
|
19
|
+
* dispatches a tool — see `createBaseHookInput` in claude-code source).
|
|
20
|
+
* - exit 0 → allow, no output shown
|
|
21
|
+
* - exit 2 → block tool call AND show stderr to model (this is the path
|
|
22
|
+
* we want for the deny case — the subagent gets a clear refusal it can
|
|
23
|
+
* reason about and pick a different action)
|
|
24
|
+
* - other → show stderr to user only, continue with tool call
|
|
25
|
+
*
|
|
26
|
+
* The hook itself is intentionally tiny: stdin → JSON → check `agent_id`
|
|
27
|
+
* presence + `tool_name` against the deny list → exit 0 or 2. No deps.
|
|
28
|
+
*
|
|
29
|
+
* Why the script source is a string template, not a separate `.ts` file
|
|
30
|
+
* shipped with the action: the action runs as a published npm package; at
|
|
31
|
+
* install time we don't have the source on disk in a stable place. Embedding
|
|
32
|
+
* the source into `dist/main.mjs` and writing it out per-run keeps the path
|
|
33
|
+
* inside `ctx.tmpdir` (where `--settings` can find it) and survives bundle
|
|
34
|
+
* minification.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The pinned `@anthropic-ai/claude-code` version against which the subagent
|
|
38
|
+
* gate's `agent_id` discriminator was last verified (see the contract notes in
|
|
39
|
+
* the gate source below). The gate fails OPEN for subagents if claude-code ever
|
|
40
|
+
* stops populating `agent_id` in the PreToolUse hook payload, so a version bump
|
|
41
|
+
* must be paired with a re-verification of `createBaseHookInput`.
|
|
42
|
+
*
|
|
43
|
+
* `claudePretoolGate.test.ts` asserts this equals the version pinned in
|
|
44
|
+
* `package.json` — that test fails on any bump, forcing the re-verification
|
|
45
|
+
* before the pin and this constant are updated together.
|
|
46
|
+
*
|
|
47
|
+
* 2.1.170 verified 2026-06-10 against the schema embedded in the shipped
|
|
48
|
+
* binary: the base hook input declares `agent_id` as optional with the
|
|
49
|
+
* describe-text "Present only when the hook fires from within a subagent
|
|
50
|
+
* (e.g., a tool called by an AgentTool worker). Absent for the main thread,
|
|
51
|
+
* even in --agent sessions." — exactly the discriminator the gate relies on.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CLAUDE_CODE_AGENT_ID_VERIFIED_VERSION: "2.1.170";
|
|
54
|
+
/**
|
|
55
|
+
* Source written to `<ctx.tmpdir>/terramend-pretool-gate.mjs`. Plain ESM,
|
|
56
|
+
* no TypeScript, no dependencies — node executes it directly via the
|
|
57
|
+
* `#!/usr/bin/env node` shebang and the executable bit set by the harness.
|
|
58
|
+
*/
|
|
59
|
+
export declare const CLAUDE_PRETOOL_GATE_FILENAME: "terramend-pretool-gate.mjs";
|
|
60
|
+
export declare const CLAUDE_PRETOOL_GATE_SOURCE: string;
|
|
61
|
+
/**
|
|
62
|
+
* Settings JSON shape registered via `claude --settings <path>`. The
|
|
63
|
+
* matcher `^mcp__terramend__` is treated as a regex by claude-code's
|
|
64
|
+
* `matchesPattern` helper (anything outside `[a-zA-Z0-9_|]` triggers the
|
|
65
|
+
* regex branch — verified in src/utils/hooks.ts), so this anchors at the
|
|
66
|
+
* start of the tool name and fires for every Terramend MCP tool. We narrow
|
|
67
|
+
* inside the script itself rather than declaring per-tool matchers because
|
|
68
|
+
* the deny list is the source of truth.
|
|
69
|
+
*
|
|
70
|
+
* The hook process inherits the parent's PATH, so `node` resolves to the
|
|
71
|
+
* runner's node binary; the `--settings` flag accepts either a path or a
|
|
72
|
+
* literal JSON string per claude-code source `src/main.tsx` (`Path to a
|
|
73
|
+
* settings JSON file or a JSON string`), but we use a path so the script
|
|
74
|
+
* and its config sit side-by-side under `ctx.tmpdir`.
|
|
75
|
+
*
|
|
76
|
+
* `execToolDenyRules` are the native exec tools (Bash/Monitor/REPL/Workflow +
|
|
77
|
+
* their `Agent(...)` forms) to deny at a settings-source rule — the
|
|
78
|
+
* authoritative, bypass-immune layer. `--disallowedTools` alone (a `cliArg`
|
|
79
|
+
* deny) was observed to leak under `--dangerously-skip-permissions`, so the
|
|
80
|
+
* deny is carried here too. Both consumers use both returned fields: the flag
|
|
81
|
+
* `--settings` JSON (covers non-CI runs) writes the whole object, and
|
|
82
|
+
* `buildManagedSettings` (CI, /etc managed settings) spreads `hooks` and folds
|
|
83
|
+
* `permissions.deny` into its richer deny list.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildClaudePretoolGateSettings(scriptAbsolutePath: string, execToolDenyRules: string[]): {
|
|
86
|
+
hooks: {
|
|
87
|
+
PreToolUse: Array<{
|
|
88
|
+
matcher: string;
|
|
89
|
+
hooks: Array<{
|
|
90
|
+
type: "command";
|
|
91
|
+
command: string;
|
|
92
|
+
timeout?: number;
|
|
93
|
+
}>;
|
|
94
|
+
}>;
|
|
95
|
+
};
|
|
96
|
+
permissions: {
|
|
97
|
+
deny: string[];
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AgentRunContext } from "#app/agents/shared";
|
|
2
|
+
export interface GateServerHandle {
|
|
3
|
+
url: string;
|
|
4
|
+
token: string;
|
|
5
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function startGateServer(ctx: AgentRunContext): Promise<GateServerHandle>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** worktree-relative blanket WRITE deny for the entire `.git` tree, in
|
|
2
|
+
* OpenCode Wildcard dialect (`*` compiles to regex `.*`, matching `/`
|
|
3
|
+
* recursively — see packages/core/src/util/wildcard.ts). spread into the
|
|
4
|
+
* `edit` ruleset after a `"*": "allow"` baseline — `evaluate` is
|
|
5
|
+
* last-match-wins by key order, so the deny keys must follow the wildcard
|
|
6
|
+
* allow.
|
|
7
|
+
*
|
|
8
|
+
* four patterns, because the root-anchored descendants glob only matches
|
|
9
|
+
* paths under a root `.git` *directory* — it misses `.git` when it's a gitfile
|
|
10
|
+
* (worktree / submodule layouts: a regular file whose `gitdir:` line redirects
|
|
11
|
+
* git metadata) and misses nested gitfiles (a `.git` inside a subdirectory).
|
|
12
|
+
* rewriting either pointer is the same code-exec surface (`core.hooksPath`,
|
|
13
|
+
* clean/smudge filters, credential.helper) the blanket deny exists to seal, so
|
|
14
|
+
* we cover the gitfile itself and any nested `.git` too. */
|
|
15
|
+
export declare const GIT_NATIVE_WRITE_DENY_OPENCODE: Record<string, "deny">;
|
|
16
|
+
/** worktree-relative narrow READ deny (`.git/config` only), in OpenCode
|
|
17
|
+
* Wildcard dialect. spread into the `read` ruleset after the `"*": "allow"`
|
|
18
|
+
* baseline. */
|
|
19
|
+
export declare const GIT_NATIVE_READ_DENY_OPENCODE: Record<string, "deny">;
|
|
20
|
+
/** Claude `permissions.deny` entries for the blanket `.git` WRITE deny —
|
|
21
|
+
* mirrors {@link GIT_NATIVE_WRITE_DENY_OPENCODE}. `**` is recursive. the exact
|
|
22
|
+
* `.git` entry plus the recursive-prefix gitfile entry cover the gitfile
|
|
23
|
+
* pointer (root + nested) that the root-anchored descendants glob alone misses;
|
|
24
|
+
* the recursive-prefix descendants entry covers nested gitdirs. */
|
|
25
|
+
export declare const GIT_NATIVE_WRITE_DENY_CLAUDE: string[];
|
|
26
|
+
/** Claude `permissions.deny` entries for the narrow `.git/config` READ deny,
|
|
27
|
+
* one per read/enumerate tool — mirrors {@link GIT_NATIVE_READ_DENY_OPENCODE}. */
|
|
28
|
+
export declare const GIT_NATIVE_READ_DENY_CLAUDE: string[];
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode agent — in-process harness (opencode-ai >=1.14.x SDK-v2 / Effect-ts
|
|
3
|
+
* CLI rewrite).
|
|
4
|
+
*
|
|
5
|
+
* Architecture, post v2-in-process migration:
|
|
6
|
+
*
|
|
7
|
+
* 1. Spawn ONE `opencode serve --port <p>` subprocess per Terramend run via
|
|
8
|
+
* `node:child_process.spawn` directly (NOT our `spawn()` wrapper — see
|
|
9
|
+
* `bootOpencodeServer` for why: long-lived stdio streaming, manual
|
|
10
|
+
* activity gating against the SDK event loop, killGroup teardown).
|
|
11
|
+
* 2. Talk to it over loopback HTTP via the typed `@opencode-ai/sdk/v2`
|
|
12
|
+
* `createOpencodeClient({ baseUrl })` — no `Server.Default()` embed,
|
|
13
|
+
* no `createOpencode()` SDK lifecycle (would re-wrap our subprocess).
|
|
14
|
+
* 3. Create ONE session up front (`client.session.create`).
|
|
15
|
+
* 4. Subscribe to events once (`client.event.subscribe`) and pump them
|
|
16
|
+
* through a single per-run handler set for live logging + activity
|
|
17
|
+
* tracking + subagent labeling.
|
|
18
|
+
* 5. Run the initial prompt via `client.session.prompt({ sessionID, parts })`.
|
|
19
|
+
* Every post-run gate retry AND the reflection turn re-enter the same
|
|
20
|
+
* session via another `client.session.prompt()` call. Warm MCP, warm
|
|
21
|
+
* plugins, warm provider connections, same context window — no
|
|
22
|
+
* `--continue` subprocess respawn.
|
|
23
|
+
* 6. Close the server in a finally.
|
|
24
|
+
*
|
|
25
|
+
* What that replaces (vs the pre-migration v2 harness):
|
|
26
|
+
* - The per-run `opencode run --format json --print-logs --thinking` CLI
|
|
27
|
+
* subprocess that emitted NDJSON envelopes.
|
|
28
|
+
* - The `runOpenCode(... args: [...baseArgs, "--continue", c.prompt] ...)`
|
|
29
|
+
* resume callback that booted a SECOND opencode process (fresh MCP,
|
|
30
|
+
* fresh plugins, cold cache) for each gate retry / reflection turn.
|
|
31
|
+
* - The `opencodePlugin.ts` bus-event re-emitter — we subscribe to the
|
|
32
|
+
* global event stream now, so subagent events arrive naturally without
|
|
33
|
+
* a stdout sentinel envelope.
|
|
34
|
+
*
|
|
35
|
+
* What stays identical:
|
|
36
|
+
* - bash: "deny" via OPENCODE_CONFIG_CONTENT
|
|
37
|
+
* - OPENCODE_PERMISSION filesystem sandbox — deny-all + allow /tmp
|
|
38
|
+
* - MCP Terramend server injected via `mcp.<name> = { type: "remote", url }`
|
|
39
|
+
* - ASKPASS for git auth
|
|
40
|
+
* - codex auth materialization + post-hook writeback
|
|
41
|
+
* - reviewfrog subagent config / model derivation
|
|
42
|
+
* - bedrock model prefix routing
|
|
43
|
+
* - skills install
|
|
44
|
+
* - todo tracker / onToolUse forwarding
|
|
45
|
+
*/
|
|
46
|
+
import { type ChildProcess } from "node:child_process";
|
|
47
|
+
import { type AssistantMessage, type EventSubscribeResponse, type OpencodeClient, type Part } from "@opencode-ai/sdk/v2";
|
|
48
|
+
import { SessionLabeler } from "#app/agents/sessionLabeler";
|
|
49
|
+
import { type AgentResult, type AgentRunContext, type AgentUsage } from "#app/agents/shared";
|
|
50
|
+
import type { ToolState } from "#app/toolState";
|
|
51
|
+
import type { AgentDiagnostic } from "#app/utils/agentHangReport";
|
|
52
|
+
import type { TodoTracker } from "#app/utils/todoTracking";
|
|
53
|
+
export declare function buildSecurityConfig(ctx: AgentRunContext, model: string | undefined): string;
|
|
54
|
+
/** split `<providerID>/<modelID>` into the SDK's prompt model shape. */
|
|
55
|
+
export declare function parseModel(value: string | undefined): {
|
|
56
|
+
providerID: string;
|
|
57
|
+
modelID: string;
|
|
58
|
+
} | undefined;
|
|
59
|
+
interface ServerHandle {
|
|
60
|
+
baseUrl: string;
|
|
61
|
+
proc: ChildProcess;
|
|
62
|
+
/** kill the server; idempotent. */
|
|
63
|
+
close: () => Promise<void>;
|
|
64
|
+
/** rolling tail of server stderr for diagnostics. */
|
|
65
|
+
recentStderr: string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Spawn `<cliPath> serve --port 0 --hostname 127.0.0.1` and wait for the
|
|
69
|
+
* "opencode server listening on http://..." stdout line.
|
|
70
|
+
*
|
|
71
|
+
* Direct node:child_process.spawn instead of our `spawn()` wrapper because
|
|
72
|
+
* the wrapper's contract is "Promise<SpawnResult> that resolves on exit" —
|
|
73
|
+
* we need a handle that stays alive across many session.prompt() calls.
|
|
74
|
+
* We still register with `trackChild()` so Ctrl-C kills the server alongside
|
|
75
|
+
* everything else.
|
|
76
|
+
*/
|
|
77
|
+
export declare function bootOpencodeServer(params: {
|
|
78
|
+
cliPath: string;
|
|
79
|
+
env: NodeJS.ProcessEnv;
|
|
80
|
+
cwd: string;
|
|
81
|
+
}): Promise<ServerHandle>;
|
|
82
|
+
/**
|
|
83
|
+
* What we collect during a single session.prompt() turn so we can render a
|
|
84
|
+
* unified AgentResult at the end. Per-turn snapshot is reset between turns
|
|
85
|
+
* inside the event loop via `beginTurn()` / `endTurn()`.
|
|
86
|
+
*/
|
|
87
|
+
export interface TurnAccumulator {
|
|
88
|
+
finalText: string;
|
|
89
|
+
/**
|
|
90
|
+
* Aggregate token totals from step-finish parts across the orchestrator AND
|
|
91
|
+
* any subagent sessions dispatched during the turn (e.g. reviewfrog).
|
|
92
|
+
* Mirrors v1's `accumulatedTokens` semantics so production billing/audit
|
|
93
|
+
* numbers stay apples-to-apples across the migration.
|
|
94
|
+
*/
|
|
95
|
+
tokens: {
|
|
96
|
+
input: number;
|
|
97
|
+
output: number;
|
|
98
|
+
cacheRead: number;
|
|
99
|
+
cacheWrite: number;
|
|
100
|
+
};
|
|
101
|
+
costUsd: number;
|
|
102
|
+
sessionError: string | null;
|
|
103
|
+
/** populated when a tool_use part on the orchestrator session reports error. */
|
|
104
|
+
lastToolError: string | null;
|
|
105
|
+
}
|
|
106
|
+
export declare function newTurn(): TurnAccumulator;
|
|
107
|
+
export interface RunnerContext {
|
|
108
|
+
client: OpencodeClient;
|
|
109
|
+
sessionID: string;
|
|
110
|
+
label: string;
|
|
111
|
+
orchestratorSessionID: string;
|
|
112
|
+
labeler: SessionLabeler;
|
|
113
|
+
toolState: ToolState;
|
|
114
|
+
todoTracker?: TodoTracker | undefined;
|
|
115
|
+
onActivityTimeout?: (() => void) | undefined;
|
|
116
|
+
onToolUse?: ((event: {
|
|
117
|
+
toolName: string;
|
|
118
|
+
input: unknown;
|
|
119
|
+
}) => void) | undefined;
|
|
120
|
+
/** current per-turn aggregator; nullable between turns. */
|
|
121
|
+
currentTurn: TurnAccumulator | null;
|
|
122
|
+
/** monotonic event count for diagnostics. */
|
|
123
|
+
eventCount: number;
|
|
124
|
+
/** last activity timestamp (event-stream silence detector). */
|
|
125
|
+
lastEventAt: number;
|
|
126
|
+
/** active task dispatch metadata keyed by callID (for subagent timing). */
|
|
127
|
+
taskDispatchByCallID: Map<string, {
|
|
128
|
+
label: string;
|
|
129
|
+
startedAt: number;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* orchestrator tool callIDs already surfaced via `log.info(» ${tool}(...))`,
|
|
133
|
+
* tracked so the end-of-turn fallback can re-emit only the calls the live
|
|
134
|
+
* event stream missed. closes the SSE-connect race against the first
|
|
135
|
+
* `session.prompt()` (the SDK opens the SSE lazily on first iteration; by
|
|
136
|
+
* then the server may already have emitted the turn's tool part-updated
|
|
137
|
+
* events). without the fallback those calls never appear in stdout, which
|
|
138
|
+
* breaks every validator that greps for tool-call shape.
|
|
139
|
+
*/
|
|
140
|
+
loggedToolCallIDs: Set<string>;
|
|
141
|
+
/** rolling stderr tail from the server process (for diagnostics). */
|
|
142
|
+
recentStderr: string[];
|
|
143
|
+
diagnostic: AgentDiagnostic;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* orchestrate the event stream consumer for the entire server lifetime.
|
|
147
|
+
*
|
|
148
|
+
* NB: the SDK subscribe is lazy — the SSE fetch only opens on the first
|
|
149
|
+
* iteration. so the first turn's tool part-updated events can race the
|
|
150
|
+
* connect and be missed. live-stream logging is best-effort; see the
|
|
151
|
+
* end-of-turn `logUnseenToolCalls` fallback for the guarantee.
|
|
152
|
+
*/
|
|
153
|
+
export declare function consumeEvents(ctx: RunnerContext, signal: AbortSignal): Promise<void>;
|
|
154
|
+
export declare function dispatchEvent(ctx: RunnerContext, event: EventSubscribeResponse): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* shared terminal bookkeeping for a tool part: log line, dedup callID, run
|
|
157
|
+
* orchestrator-side hooks (`onToolUse` → diff-coverage tracker; `todowrite` /
|
|
158
|
+
* `report_progress` → todo tracker; tool-error → `lastToolError`), and emit
|
|
159
|
+
* subagent-finish summary on `task` returns.
|
|
160
|
+
*
|
|
161
|
+
* called from both the live SSE path (`onToolPart`) and the end-of-turn
|
|
162
|
+
* fallback (`logUnseenToolCalls`) — `loggedToolCallIDs` is the dedup guard
|
|
163
|
+
* so each call's side effects fire exactly once across both paths. critical
|
|
164
|
+
* for diff-coverage: a first-turn `Read` that races SSE attach would
|
|
165
|
+
* otherwise be missed by `recordDiffReadFromToolUse`, and the subsequent
|
|
166
|
+
* `create_pull_request_review` pre-flight would reject the review.
|
|
167
|
+
*/
|
|
168
|
+
export declare function processTerminalToolPart(ctx: RunnerContext, part: Extract<Part, {
|
|
169
|
+
type: "tool";
|
|
170
|
+
}>, label: string, isOrchestrator: boolean): void;
|
|
171
|
+
export declare function formatPartDuration(time: {
|
|
172
|
+
start?: number;
|
|
173
|
+
end?: number;
|
|
174
|
+
} | undefined): string;
|
|
175
|
+
/**
|
|
176
|
+
* Run a single prompt turn against the persistent server. Resets the per-turn
|
|
177
|
+
* accumulator, calls `client.session.prompt()`, then assembles an AgentResult
|
|
178
|
+
* from the returned AssistantMessage + accumulated event state.
|
|
179
|
+
*
|
|
180
|
+
* Token / cost: `AssistantMessage.tokens` and `.cost` are authoritative for
|
|
181
|
+
* the turn. The event-stream accumulator is a fallback / sanity-check path
|
|
182
|
+
* used when the response is missing (e.g. abort, transport error) — and as
|
|
183
|
+
* the only source of per-step subagent attribution if we ever surface it.
|
|
184
|
+
*/
|
|
185
|
+
export declare function runPromptTurn(ctx: RunnerContext, params: {
|
|
186
|
+
text: string;
|
|
187
|
+
model: {
|
|
188
|
+
providerID: string;
|
|
189
|
+
modelID: string;
|
|
190
|
+
} | undefined;
|
|
191
|
+
signal: AbortSignal;
|
|
192
|
+
}): Promise<AgentResult>;
|
|
193
|
+
export declare function buildUsage(turn: TurnAccumulator, assistant: AssistantMessage | undefined): AgentUsage | undefined;
|
|
194
|
+
export declare function extractTextFromParts(parts: Part[] | undefined): string | undefined;
|
|
195
|
+
export declare function formatPromptError(error: unknown): string;
|
|
196
|
+
/**
|
|
197
|
+
* Start an event-silence watchdog. The outer process-level activity timer
|
|
198
|
+
* (main.ts `createProcessOutputActivityTimeout`) watches `process.stdout.write`
|
|
199
|
+
* which our harness log lines drive — but it doesn't see SSE event silence
|
|
200
|
+
* when the harness is itself quiet. This inner timer specifically watches
|
|
201
|
+
* `ctx.lastEventAt` and fires `onActivityTimeout` so main.ts can tear down
|
|
202
|
+
* the MCP server early, mirroring the per-spawn watchdog in `subprocess.ts`.
|
|
203
|
+
*
|
|
204
|
+
* `ctx.lastEventAt` is refreshed only on meaningful progress (token/tool
|
|
205
|
+
* part.updated), so any prolonged gap with no progress advances the clock —
|
|
206
|
+
* including a long in-flight tool call. the budget is the same flat idle
|
|
207
|
+
* timeout as the outer watchdog, sized to exceed the worst-case legitimate
|
|
208
|
+
* silent tool window (#760), so a real tool can't trip it; a genuinely stalled
|
|
209
|
+
* provider or a hung tool does, at the flat budget.
|
|
210
|
+
*/
|
|
211
|
+
export declare function startInnerActivityWatchdog(params: {
|
|
212
|
+
ctx: RunnerContext;
|
|
213
|
+
timeoutMs: number;
|
|
214
|
+
abortController: AbortController;
|
|
215
|
+
}): {
|
|
216
|
+
stop: () => void;
|
|
217
|
+
};
|
|
218
|
+
export declare const opencode: import("#app/agents/shared").Agent;
|
|
219
|
+
/**
|
|
220
|
+
* Safety net around a single turn: convert any unexpected throw that escapes
|
|
221
|
+
* `runPromptTurn` into a `success: false` result so the post-run gate loop
|
|
222
|
+
* (which expects a result, not a rejection) can surface it through the generic
|
|
223
|
+
* renderer.
|
|
224
|
+
*
|
|
225
|
+
* Watchdog-fired aborts do NOT reach here — `runPromptTurn` owns the abort
|
|
226
|
+
* signal, catches the aborted `session.prompt` rejection internally, and
|
|
227
|
+
* classifies it as an `activity timeout` error itself. This wrapper must not
|
|
228
|
+
* re-classify, since a stray post-prompt throw is not a hang.
|
|
229
|
+
*/
|
|
230
|
+
export declare function runTurnGuarded(ctx: RunnerContext, fn: () => Promise<AgentResult>): Promise<AgentResult>;
|
|
231
|
+
export {};
|