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
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: terraform-best-practices
|
|
3
|
+
description: Fix Terraform to best practice from a scanner concern — the minimal, correct change for a trivy/checkov/tflint/fmt/validate finding, plus the security, structure, and naming conventions a good fix must follow. Use when remediating Terraform, applying a `terraform_scan` concern, or generating new HCL that must start compliant.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Terraform best-practice remediation
|
|
7
|
+
|
|
8
|
+
You are fixing Terraform against a **concern** emitted by `terraform_scan` (or
|
|
9
|
+
`terraform_validate`). Each concern names the producing `source`, a `rule_id`,
|
|
10
|
+
the `location` (file + line), an `evidence` string (what's wrong), and often a
|
|
11
|
+
`remediation_hint`. Your job is the **smallest correct change** that clears that
|
|
12
|
+
concern — nothing more.
|
|
13
|
+
|
|
14
|
+
## The remediation loop
|
|
15
|
+
|
|
16
|
+
1. **Read the concern.** The `rule_id` tells you the class of problem; the
|
|
17
|
+
`evidence` tells you the specifics; `location.file:line` tells you where.
|
|
18
|
+
2. **Open the file and understand the surrounding resource** before editing.
|
|
19
|
+
Don't fix a line in isolation — know which `resource` / `module` / `variable`
|
|
20
|
+
block it belongs to.
|
|
21
|
+
3. **Apply the minimal fix** (see the catalogue below). Touch only `*.tf` /
|
|
22
|
+
`*.tfvars`. Do not reformat, reorder, or "improve" unrelated code — that
|
|
23
|
+
buries the real fix and breaks the one-concern-per-PR contract.
|
|
24
|
+
4. **Re-validate** with `terraform_validate`. If it doesn't pass, your fix is
|
|
25
|
+
incomplete or introduced a new problem — fix that before opening a PR.
|
|
26
|
+
5. **Confirm the concern cleared** by re-running `terraform_scan` on the branch.
|
|
27
|
+
The concern's `id` must be gone. If it isn't, say so honestly.
|
|
28
|
+
|
|
29
|
+
## What a good fix looks like, by source
|
|
30
|
+
|
|
31
|
+
- **`terraform-fmt:unformatted`** — run `terraform fmt` on the named file. This
|
|
32
|
+
is whitespace/alignment only; never combine it with a behavioural change in
|
|
33
|
+
the same PR.
|
|
34
|
+
- **`tflint:*`** — idiomatic-HCL and provider-rule issues. Common fixes: remove
|
|
35
|
+
unused `variable`/`local`/`data` declarations; pin deprecated syntax forward;
|
|
36
|
+
add missing required provider/version constraints. Follow the rule's link.
|
|
37
|
+
- **`trivy:*` / `checkov:*`** — security misconfiguration. These are the
|
|
38
|
+
high-value fixes. Apply the **secure default**, e.g.:
|
|
39
|
+
- **encryption at rest** — add the `server_side_encryption_configuration` /
|
|
40
|
+
`encryption` block (S3, RDS, EBS, etc.); prefer a CMK where the rule asks.
|
|
41
|
+
- **no public access** — set `block_public_acls`/`block_public_policy` etc. to
|
|
42
|
+
`true`; remove `acl = "public-read"`; tighten `0.0.0.0/0` ingress to the
|
|
43
|
+
real CIDR or a referenced security group.
|
|
44
|
+
- **least privilege** — replace `"*"` actions/resources in IAM policies with
|
|
45
|
+
the specific actions/ARNs actually needed.
|
|
46
|
+
- **logging / versioning** — add the access-logging, audit, or versioning
|
|
47
|
+
block the rule requires.
|
|
48
|
+
- **`terraform-validate:*`** — a correctness error (bad reference, type
|
|
49
|
+
mismatch, missing required argument). Fix the actual HCL so `terraform
|
|
50
|
+
validate` passes.
|
|
51
|
+
|
|
52
|
+
## Secure-default catalogue (by problem class)
|
|
53
|
+
|
|
54
|
+
Copy-pasteable shapes for the highest-frequency security concerns. These target
|
|
55
|
+
the **AWS provider v5+** layout (encryption / versioning / ACL / public-access
|
|
56
|
+
are standalone resources, not inline `aws_s3_bucket` blocks). Adapt resource and
|
|
57
|
+
attribute names to the block the concern points at — don't paste blindly.
|
|
58
|
+
|
|
59
|
+
### Encryption at rest
|
|
60
|
+
|
|
61
|
+
S3 — server-side encryption with a customer-managed key (preferred over `AES256`
|
|
62
|
+
when the rule asks for a CMK):
|
|
63
|
+
|
|
64
|
+
```hcl
|
|
65
|
+
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
|
|
66
|
+
bucket = aws_s3_bucket.this.id
|
|
67
|
+
rule {
|
|
68
|
+
apply_server_side_encryption_by_default {
|
|
69
|
+
sse_algorithm = "aws:kms"
|
|
70
|
+
kms_master_key_id = aws_kms_key.this.arn
|
|
71
|
+
}
|
|
72
|
+
bucket_key_enabled = true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
EBS volume / root device: `encrypted = true` (add `kms_key_id` when a CMK is
|
|
78
|
+
required). RDS: `storage_encrypted = true` (+ `kms_key_id`). When `aws:kms` needs
|
|
79
|
+
a key and none exists, reference an existing `aws_kms_key`/`var.*`; only add a new
|
|
80
|
+
`aws_kms_key` resource if the stack genuinely has none — keep `AES256` if the rule
|
|
81
|
+
is satisfied by SSE-S3 alone.
|
|
82
|
+
|
|
83
|
+
### Block public access
|
|
84
|
+
|
|
85
|
+
S3 — the four-flag public-access block is the canonical fix; wire it to the same
|
|
86
|
+
bucket:
|
|
87
|
+
|
|
88
|
+
```hcl
|
|
89
|
+
resource "aws_s3_bucket_public_access_block" "this" {
|
|
90
|
+
bucket = aws_s3_bucket.this.id
|
|
91
|
+
block_public_acls = true
|
|
92
|
+
block_public_policy = true
|
|
93
|
+
ignore_public_acls = true
|
|
94
|
+
restrict_public_buckets = true
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Also remove any `acl = "public-read"` (move to a private `aws_s3_bucket_acl` if an
|
|
99
|
+
ACL is needed at all).
|
|
100
|
+
|
|
101
|
+
### Network ingress
|
|
102
|
+
|
|
103
|
+
Replace world-open ingress with the real source. Never leave `0.0.0.0/0` on admin
|
|
104
|
+
ports (22/3389/database ports):
|
|
105
|
+
|
|
106
|
+
```hcl
|
|
107
|
+
ingress {
|
|
108
|
+
from_port = 443
|
|
109
|
+
to_port = 443
|
|
110
|
+
protocol = "tcp"
|
|
111
|
+
cidr_blocks = [var.allowed_cidr] # or: security_groups = [aws_security_group.lb.id]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### IAM least privilege
|
|
116
|
+
|
|
117
|
+
Replace wildcard `Action`/`Resource` with the specific actions and ARNs the
|
|
118
|
+
workload actually uses. If you can't determine the exact set from the surrounding
|
|
119
|
+
code, this is a human decision — report it rather than guessing a narrow policy
|
|
120
|
+
that breaks the stack.
|
|
121
|
+
|
|
122
|
+
### Versioning & logging
|
|
123
|
+
|
|
124
|
+
S3 versioning and access logging as standalone resources:
|
|
125
|
+
|
|
126
|
+
```hcl
|
|
127
|
+
resource "aws_s3_bucket_versioning" "this" {
|
|
128
|
+
bucket = aws_s3_bucket.this.id
|
|
129
|
+
versioning_configuration { status = "Enabled" }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### EC2 instance metadata (IMDSv2)
|
|
134
|
+
|
|
135
|
+
Require token-backed metadata to close the SSRF→credential path:
|
|
136
|
+
|
|
137
|
+
```hcl
|
|
138
|
+
metadata_options {
|
|
139
|
+
http_endpoint = "enabled"
|
|
140
|
+
http_tokens = "required"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Deprecations & style
|
|
145
|
+
|
|
146
|
+
- **Provider v4→v5 S3 split** — inline `server_side_encryption_configuration` /
|
|
147
|
+
`versioning` / `acl` / `logging` blocks on `aws_s3_bucket` are deprecated; move
|
|
148
|
+
each to its standalone resource (above). Don't bump the provider major version
|
|
149
|
+
as a side effect of a remediation — if a fix genuinely requires a newer
|
|
150
|
+
provider, report that as a blocker.
|
|
151
|
+
- **`terraform-fmt:unformatted`** — run `terraform fmt` on the named file only;
|
|
152
|
+
never fold a formatting pass into a behavioural fix.
|
|
153
|
+
|
|
154
|
+
## Don't over-reach
|
|
155
|
+
|
|
156
|
+
- **Smallest change that clears the concern.** Add the missing block; don't
|
|
157
|
+
refactor the resource, rename it, or restructure the file around it.
|
|
158
|
+
- **Don't add speculative hardening** the concern didn't ask for (extra KMS keys,
|
|
159
|
+
new modules, blanket tagging) — that's scope creep and buries the real fix.
|
|
160
|
+
- **Don't widen or bump version pins** to reach a resource or attribute.
|
|
161
|
+
- **One concern's blast radius only.** If the secure default would break the
|
|
162
|
+
stack or needs a human call (a real CIDR, an IAM action set, a CMK policy),
|
|
163
|
+
stop and report it instead of opening a broken or guessed PR.
|
|
164
|
+
|
|
165
|
+
## Conventions every fix must honour
|
|
166
|
+
|
|
167
|
+
- **Variables over hardcoded values.** Don't bake an account id, region, CIDR,
|
|
168
|
+
or ARN into a fix — reference an existing `var.*`/`local.*`, or add a typed
|
|
169
|
+
`variable` with a sensible `default` and `description` when one is genuinely
|
|
170
|
+
needed.
|
|
171
|
+
- **Pin versions.** When adding a provider or module, include a version
|
|
172
|
+
constraint. Never widen an existing pin as a side effect.
|
|
173
|
+
- **Approved modules first.** Call `list_modules` — if a catalogue is configured,
|
|
174
|
+
prefer the catalogue module (a registry module or a local/house module) + its
|
|
175
|
+
exact variable names over inlining a raw resource, and pin its `version`. See
|
|
176
|
+
*Using Terraform modules* below.
|
|
177
|
+
- **No secrets in HCL or state.** Never introduce a literal credential. If the
|
|
178
|
+
fix needs a secret, reference a variable or a secrets data source.
|
|
179
|
+
- **Idempotent, reviewable diffs.** The diff should read so a senior engineer
|
|
180
|
+
approves it without hesitation: one concern, one rationale, no churn.
|
|
181
|
+
|
|
182
|
+
## Gold standards (the bar every fix is measured against)
|
|
183
|
+
|
|
184
|
+
These are the non-negotiable defaults a "good" Terraform change embodies. A fix
|
|
185
|
+
should never *move away* from any of these, and should move *toward* them when
|
|
186
|
+
the concern is adjacent:
|
|
187
|
+
|
|
188
|
+
- **Encrypt everything, at rest and in transit.** Storage encrypted (CMK where
|
|
189
|
+
the data is sensitive); TLS-only endpoints; no plaintext in state.
|
|
190
|
+
- **Private by default.** No `0.0.0.0/0` to admin/database ports; public access
|
|
191
|
+
blocked unless the resource's whole purpose is to be public (and then scoped).
|
|
192
|
+
- **Least privilege.** No `"*"` IAM actions/resources; scope to what the workload
|
|
193
|
+
uses. Prefer a referenced role/policy over an inline wildcard.
|
|
194
|
+
- **Pinned + reproducible.** `required_version` and every `required_providers` /
|
|
195
|
+
module `version` constrained (`~>`); no floating `latest`.
|
|
196
|
+
- **Parameterised, not hardcoded** (see below).
|
|
197
|
+
- **Tagged + named consistently.** Match the repo's existing tag keys and naming
|
|
198
|
+
scheme; don't invent a new convention.
|
|
199
|
+
- **Observable.** Logging / versioning / audit trails enabled where the provider
|
|
200
|
+
supports them and the concern is about data durability or traceability.
|
|
201
|
+
- **Idempotent + deterministic.** No `timestamp()`/`uuid()`/unkeyed `random_*`
|
|
202
|
+
driving a value that lands in state — it produces a perpetual diff.
|
|
203
|
+
|
|
204
|
+
When in doubt, the secure/idiomatic default from the catalogue above IS the gold
|
|
205
|
+
standard. Don't gold-plate beyond the concern, but never regress one of these.
|
|
206
|
+
|
|
207
|
+
## Parameterize, don't hardcode (§4.13)
|
|
208
|
+
|
|
209
|
+
A value that varies by environment, account, or deployment must be a `variable`
|
|
210
|
+
or `local`, never a literal baked into a resource:
|
|
211
|
+
|
|
212
|
+
- **Reuse first.** If the repo already exposes `var.region` / `local.tags` /
|
|
213
|
+
`var.vpc_cidr`, reference it — don't introduce a parallel one.
|
|
214
|
+
- **Introduce a typed variable** when none fits: give it a `type`, a
|
|
215
|
+
`description`, and a safe `default` only when a default is genuinely sane
|
|
216
|
+
(secrets and account-specific ids get NO default). Match the repo's existing
|
|
217
|
+
file layout — add to `variables.tf` if the repo separates them, otherwise keep
|
|
218
|
+
it next to the resource as the repo does.
|
|
219
|
+
- **Derive with `locals`.** Computed/repeated values (a name prefix, a merged tag
|
|
220
|
+
map) belong in `locals`, referenced everywhere — not copy-pasted.
|
|
221
|
+
- **Never inline a secret, account id, ARN, or CIDR.** A "fix" that pastes a
|
|
222
|
+
literal credential is itself a finding (and the secret-scan guardrail will
|
|
223
|
+
block the push). Reference a variable or a secrets data source.
|
|
224
|
+
|
|
225
|
+
## Using Terraform modules (registry, private git libraries, your own)
|
|
226
|
+
|
|
227
|
+
Prefer a well-formed module over a pile of raw resources when one cleanly fits —
|
|
228
|
+
it carries the secure defaults for you. **Always call `list_modules` first**; it
|
|
229
|
+
returns three things: the operator's `module_catalogue`, the
|
|
230
|
+
`discovered_house_modules` (local modules already used in THIS repo), and a note.
|
|
231
|
+
|
|
232
|
+
Three source kinds you'll encounter, each pinned differently:
|
|
233
|
+
|
|
234
|
+
- **Public registry module** — `terraform-aws-modules/vpc/aws`. Pin with a
|
|
235
|
+
`version = "~> 5.0"` argument. The big public collections
|
|
236
|
+
([terraform-aws-modules](https://registry.terraform.io/namespaces/terraform-aws-modules):
|
|
237
|
+
`vpc`, `s3-bucket`, `rds`, `eks`, …) are the default when nothing is configured.
|
|
238
|
+
A registry submodule is `…/aws//modules/log-group`.
|
|
239
|
+
- **Private git module library** — e.g.
|
|
240
|
+
`git::https://github.com/acme/tf-modules.git//aws/s3?ref=s3-v0.1.2`. The
|
|
241
|
+
`//aws/s3` selects the module within the repo and **`?ref=s3-v0.1.2` IS the
|
|
242
|
+
version pin** (git modules have no `version` argument). Keep the exact `ref`;
|
|
243
|
+
never float it. Many orgs (e.g. UKHSA's `data-integration-terraform-modules`)
|
|
244
|
+
ship a whole library this way, tagged per-module.
|
|
245
|
+
- **House module** — one of the repo's own `modules/<name>` dirs. `list_modules`
|
|
246
|
+
surfaces these under `discovered_house_modules` with their caller files — reuse
|
|
247
|
+
the existing one with its **real variable names** (read its `variables.tf`)
|
|
248
|
+
rather than re-implementing it.
|
|
249
|
+
|
|
250
|
+
Rules: use the module's **exact variable names**, set the secure-relevant inputs
|
|
251
|
+
the concern is about, and keep the version **pinned**. Don't introduce a module
|
|
252
|
+
mid-remediation just to "improve" things — using a module is right when
|
|
253
|
+
*generating* new infra or when the fix is genuinely a module swap; for a one-line
|
|
254
|
+
security fix on an existing raw resource, fix the resource in place.
|
|
255
|
+
|
|
256
|
+
When the **`terraform` MCP server** is registered (the `terraform_mcp` input),
|
|
257
|
+
prefer querying it for the current module version and provider argument shapes
|
|
258
|
+
over recalling them from memory — registry knowledge moves faster than any
|
|
259
|
+
training data, and a fix written against a remembered-but-renamed argument
|
|
260
|
+
breaks `plan`. Without it, `terraform_version_currency` (versions) and
|
|
261
|
+
`terraform_provider_schema` (installed-provider arguments) are the fallbacks.
|
|
262
|
+
|
|
263
|
+
### Authoring a reusable module (standard layout)
|
|
264
|
+
|
|
265
|
+
When you GENERATE a reusable module, follow the conventional layout real module
|
|
266
|
+
libraries use so it's drop-in familiar:
|
|
267
|
+
|
|
268
|
+
- `main.tf` (resources), `variables.tf` (every input typed, with a `description`
|
|
269
|
+
and a `validation` block where it helps), `outputs.tf`, `versions.tf` /
|
|
270
|
+
`providers.tf` (`required_version` + `required_providers` pinned), `README.md`
|
|
271
|
+
(usage + inputs/outputs), and a `CHANGELOG.md` if the repo versions modules.
|
|
272
|
+
- Do **not** generate `examples/` fixtures. Document usage in the module's
|
|
273
|
+
`README.md` instead; test coverage comes from the opt-in terratest scaffold
|
|
274
|
+
(see below), which plans the module directly.
|
|
275
|
+
|
|
276
|
+
### Module-source-aware fixes (§4.14)
|
|
277
|
+
|
|
278
|
+
Before fixing a concern, call `terraform_module_graph` to see where the file
|
|
279
|
+
lives in the module call-graph:
|
|
280
|
+
|
|
281
|
+
- **Concern inside a LOCAL module dir** (listed in `local_module_dirs`): fix it
|
|
282
|
+
**once at the module source**. The fix propagates to every caller — do not
|
|
283
|
+
patch each call site. Note the callers in the PR body so a reviewer sees the
|
|
284
|
+
blast radius.
|
|
285
|
+
- **Concern that would require editing a REGISTRY / git / remote module**: that
|
|
286
|
+
source lives outside this repo — you **cannot** fix it here. Do **not** vendor
|
|
287
|
+
the module or fork it inline. Instead report it (open an issue / PR comment)
|
|
288
|
+
naming the upstream module + version and the concern, so a human routes it.
|
|
289
|
+
|
|
290
|
+
### Modularization as remediation (M2)
|
|
291
|
+
|
|
292
|
+
`module_extraction_candidates` finds clusters of raw resources that should be a
|
|
293
|
+
module call, each matched against the repo's house modules (real resource-type
|
|
294
|
+
signature + `required_variables`) and the operator's `module_catalogue`. Turn a
|
|
295
|
+
candidate into a refactor PR under this contract:
|
|
296
|
+
|
|
297
|
+
- **One PR per cluster**, branch `remediate/modularize-<cluster file/prefix>` —
|
|
298
|
+
never mix two clusters or a behavioural fix into a modularization PR.
|
|
299
|
+
- Replace the cluster's raw resources with ONE `module` block calling the
|
|
300
|
+
candidate (pin the version for registry sources; wire the module's REAL
|
|
301
|
+
variable names from `required_variables` / `terraform_module_interface`).
|
|
302
|
+
- **Preserve state with `moved {}` blocks** — one per resource, mapping the old
|
|
303
|
+
address to its new `module.<name>.` address:
|
|
304
|
+
|
|
305
|
+
```hcl
|
|
306
|
+
moved {
|
|
307
|
+
from = aws_s3_bucket.logs
|
|
308
|
+
to = module.logging.aws_s3_bucket.this
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
- **The gate:** the PR may proceed only when `terraform_validate` passes AND
|
|
313
|
+
`terraform_plan` reports `refactor_safe: true` (a pure-move plan — zero
|
|
314
|
+
add/change/destroy). Anything else means a moved block is wrong or the module
|
|
315
|
+
diverges from the raw resources — fix that or report it; never accept
|
|
316
|
+
resource churn as "the refactor".
|
|
317
|
+
- A required variable you can't derive from the existing attributes is a
|
|
318
|
+
**PR question for the reviewer**, never a guessed value.
|
|
319
|
+
|
|
320
|
+
### Version currency (provider + module upgrades, M3)
|
|
321
|
+
|
|
322
|
+
`terraform_version_currency` reports which pinned providers and registry modules
|
|
323
|
+
trail the registry's latest stable version, and which registry modules are
|
|
324
|
+
unpinned. Turn its rows into upgrade PRs under this contract:
|
|
325
|
+
|
|
326
|
+
- **One `chore(deps)` PR per upgrade group** — never mix a version bump with a
|
|
327
|
+
behavioural fix; a bump PR must read as "only the constraint changed".
|
|
328
|
+
- **Minor/patch bumps** (`outdated` with `majors_behind: 0`) may proceed
|
|
329
|
+
autonomously: update the `version` constraint to admit `newest_satisfying` →
|
|
330
|
+
`latest`, then prove it with `terraform_validate` (and `terraform_plan` when
|
|
331
|
+
credentials exist).
|
|
332
|
+
- **Major bumps** (`majors_behind > 0`) mean the provider/module interface may
|
|
333
|
+
have changed — apply only with the `needs-human` label, and consult
|
|
334
|
+
`terraform_module_interface` / `terraform_provider_schema` for what moved.
|
|
335
|
+
- **Unpinned registry modules**: pin to the reported `latest` (`version = "~> X.Y"`),
|
|
336
|
+
one PR for all unpinned modules in a file group — pinning is low-risk and sweeps well.
|
|
337
|
+
- An upgrade whose `terraform_plan` shows **destructive changes is never auto** —
|
|
338
|
+
escalate with the plan excerpt in the PR body.
|
|
339
|
+
|
|
340
|
+
## Tests for modules (§28)
|
|
341
|
+
|
|
342
|
+
Terramend does **not** create or edit `examples/` fixtures — leave any the repo
|
|
343
|
+
already ships untouched. Module test coverage is **opt-in** via the `terratest`
|
|
344
|
+
input:
|
|
345
|
+
|
|
346
|
+
- **Terratest (opt-in).** When the `terratest` input is enabled, call
|
|
347
|
+
`scaffold_terratest` (module name + dir) to generate a plan-only Go
|
|
348
|
+
[Terratest](https://terratest.gruntwork.io/) smoke test (`test/<name>_test.go`)
|
|
349
|
+
**and** a Terraform-native `*.tftest.hcl` in the module's own `tests/` dir.
|
|
350
|
+
Both plan the **module directly** (no example fixture). Write the returned files
|
|
351
|
+
(the input also widens `allowed_paths` to permit them). If the repo already has
|
|
352
|
+
a Terratest suite, update its options/assertions to match the new interface
|
|
353
|
+
instead.
|
|
354
|
+
|
|
355
|
+
In all cases: Terramend **never runs** the tests — it holds no cloud credentials,
|
|
356
|
+
and Terratest needs Go + a real `apply`. It keeps the test source consistent with
|
|
357
|
+
the module and flags in the PR that the suite should be run in the user's
|
|
358
|
+
pipeline. **Never weaken an assertion or delete a test to go green.**
|
|
359
|
+
|
|
360
|
+
## Hard rules
|
|
361
|
+
|
|
362
|
+
- Only modify `*.tf` / `*.tfvars` (and, when `allowed_paths` permits — i.e. the
|
|
363
|
+
`terratest` input is on — that module's test files). Never touch CI, application
|
|
364
|
+
code, or unrelated config in a remediation PR.
|
|
365
|
+
- One concern per PR. If you notice other issues, leave them for their own runs.
|
|
366
|
+
- Never auto-merge. The PR is for a human to review.
|
|
367
|
+
- If you cannot fix a concern cleanly (it needs a human decision, or the secure
|
|
368
|
+
default would break the stack), do **not** open a broken PR — report it
|
|
369
|
+
instead with what's blocking.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { initToolState } from "#app/toolState";
|
|
3
|
+
import { log } from "#app/utils/cli";
|
|
4
|
+
|
|
5
|
+
vi.mock("#app/utils/cli", () => ({
|
|
6
|
+
log: { info: vi.fn(), warning: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("initToolState", () => {
|
|
10
|
+
it("returns the literal base state without a progress comment", () => {
|
|
11
|
+
const state = initToolState({ progressComment: undefined });
|
|
12
|
+
|
|
13
|
+
expect(state.progressComment).toBeUndefined();
|
|
14
|
+
expect(state.hadProgressComment).toBe(false);
|
|
15
|
+
expect(state.prepushFailureCount).toBe(0);
|
|
16
|
+
expect(state.backgroundProcesses).toEqual(new Map());
|
|
17
|
+
expect(state.usageEntries).toEqual([]);
|
|
18
|
+
expect(log.info).not.toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("parses a pre-created progress comment and logs it", () => {
|
|
22
|
+
const state = initToolState({ progressComment: { id: "123", type: "issue" } });
|
|
23
|
+
|
|
24
|
+
expect(state.progressComment).toEqual({ id: 123, type: "issue" });
|
|
25
|
+
expect(state.hadProgressComment).toBe(true);
|
|
26
|
+
expect(log.info).toHaveBeenCalledWith("» using pre-created progress comment: 123 (issue)");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("treats an unparseable progress comment id as absent", () => {
|
|
30
|
+
expect(initToolState({ progressComment: { id: "abc", type: "issue" } })).toMatchObject({
|
|
31
|
+
progressComment: undefined,
|
|
32
|
+
hadProgressComment: false,
|
|
33
|
+
});
|
|
34
|
+
expect(initToolState({ progressComment: { id: "0", type: "review" } })).toMatchObject({
|
|
35
|
+
progressComment: undefined,
|
|
36
|
+
hadProgressComment: false,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("preserves the review comment type", () => {
|
|
41
|
+
const state = initToolState({ progressComment: { id: "77", type: "review" } });
|
|
42
|
+
|
|
43
|
+
expect(state.progressComment).toEqual({ id: 77, type: "review" });
|
|
44
|
+
});
|
|
45
|
+
});
|
package/src/toolState.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { AgentUsage } from "#app/agents/shared";
|
|
2
|
+
import type { Concern } from "#app/mcp/terraform/types";
|
|
3
|
+
import type { PrepResult } from "#app/prep/types";
|
|
4
|
+
import type { AgentDiagnostic } from "#app/utils/agentHangReport";
|
|
5
|
+
import { log } from "#app/utils/cli";
|
|
6
|
+
import type { DiffCoverageState } from "#app/utils/diffCoverage";
|
|
7
|
+
import {
|
|
8
|
+
type ProgressComment,
|
|
9
|
+
type ProgressCommentType,
|
|
10
|
+
parseProgressComment,
|
|
11
|
+
} from "#app/utils/progressComment";
|
|
12
|
+
import type { TodoTracker } from "#app/utils/todoTracking";
|
|
13
|
+
|
|
14
|
+
export type BackgroundProcess = {
|
|
15
|
+
pid: number;
|
|
16
|
+
outputPath: string;
|
|
17
|
+
pidPath: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type StoredPushDest = {
|
|
21
|
+
remoteName: string;
|
|
22
|
+
remoteBranch: string;
|
|
23
|
+
localBranch: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Valid inline-comment anchor lines per side at a particular checkout SHA.
|
|
28
|
+
* Lives here (not in `mcp/review.ts`) so `ToolState` — which caches
|
|
29
|
+
* `Map<path, CommentableLines>` per checkout — does not pull the MCP server
|
|
30
|
+
* graph into every consumer of run state (the action's main loop, agent
|
|
31
|
+
* harnesses, cf-worker indexing).
|
|
32
|
+
*/
|
|
33
|
+
export type CommentableLines = { RIGHT: Set<number>; LEFT: Set<number> };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* mutable per-run record of facts that occurred during execution. shared
|
|
37
|
+
* between the action process and the MCP server (one process — toolState is
|
|
38
|
+
* just a JS object passed by reference into both surfaces).
|
|
39
|
+
*
|
|
40
|
+
* design rule: ToolState is LITERAL. each field records a thing that
|
|
41
|
+
* happened — `review` is set when `create_pull_request_review` succeeded,
|
|
42
|
+
* `finalSummaryWritten` flips when `report_progress` wrote a non-plan body,
|
|
43
|
+
* `selectedMode` is set when `select_mode` was called. fields should never
|
|
44
|
+
* encode the absence of an event ("unsubmittedReview", "missingArtifact"),
|
|
45
|
+
* speculative state, or values derived from other fields.
|
|
46
|
+
*
|
|
47
|
+
* any predicate the rest of the code needs ("the agent picked review mode but
|
|
48
|
+
* never produced a review or progress write") is computed inline at the call
|
|
49
|
+
* site, not stored. derived state in this struct invariably drifts from the
|
|
50
|
+
* literal fields under refactors and is the wrong layer for the check.
|
|
51
|
+
*
|
|
52
|
+
* write narrowly: prefer adding state inside the tool that mutates it (e.g.
|
|
53
|
+
* `create_pull_request_review` populates `toolState.review`) and reading
|
|
54
|
+
* narrowly elsewhere. don't introduce flags from main.ts that mirror what an
|
|
55
|
+
* MCP tool already records.
|
|
56
|
+
*/
|
|
57
|
+
export interface ToolState {
|
|
58
|
+
// where we're allowed to push - base repo initially, fork URL for fork PRs
|
|
59
|
+
// set by setupGit, updated by checkout_pr. always set before push validation.
|
|
60
|
+
pushUrl?: string;
|
|
61
|
+
// push destination set by checkout_pr - used as primary source in push_branch
|
|
62
|
+
// because git config reads can fail in certain environments
|
|
63
|
+
pushDest?: StoredPushDest;
|
|
64
|
+
// HEAD identity captured by setupGit at run start. load-bearing for the
|
|
65
|
+
// checkout_pr initial-branch invariant: the only sanctioned HEAD positions
|
|
66
|
+
// when calling checkout_pr are the run-entry HEAD or the target `pr-N`.
|
|
67
|
+
// blocks the zed-style cross-PR clobber where a subagent left HEAD on
|
|
68
|
+
// someone else's `pr-X` and the orchestrator's next checkout_pr inherited
|
|
69
|
+
// that position.
|
|
70
|
+
//
|
|
71
|
+
// discriminated by `kind` because `git rev-parse --abbrev-ref HEAD` returns
|
|
72
|
+
// the literal sentinel string `"HEAD"` on detached entry, which is the
|
|
73
|
+
// default state from `actions/checkout` on `pull_request` events (it
|
|
74
|
+
// checks out the merge commit as a detached SHA). without the kind tag,
|
|
75
|
+
// detached-entry runs would trivially accept any future detached state.
|
|
76
|
+
initialHead?: { kind: "branch"; name: string } | { kind: "detached"; sha: string };
|
|
77
|
+
// issue or PR number (same number space in GitHub)
|
|
78
|
+
issueNumber?: number;
|
|
79
|
+
// PR/issue numbers this run CREATED (create_pull_request / create_issue).
|
|
80
|
+
// read by the REST-write scope guard (mcp/scope.ts) so the agent may edit the
|
|
81
|
+
// body of / comment on a PR or issue it opened this run, even though that
|
|
82
|
+
// number differs from the run's triggering issue_number. a merely checked-out
|
|
83
|
+
// PR is intentionally NOT recorded here — checkout_pr is agent-controlled, so
|
|
84
|
+
// letting it widen write scope would defeat the guard.
|
|
85
|
+
createdTargets?: Set<number>;
|
|
86
|
+
// PR HEAD sha at checkout time — used to detect new commits pushed during a review
|
|
87
|
+
checkoutSha?: string;
|
|
88
|
+
// commentable lines per file at checkoutSha — captured during checkout_pr so
|
|
89
|
+
// review-time inline-comment validation matches the diff GitHub will anchor
|
|
90
|
+
// to (commit_id=checkoutSha). without this, a PR update between checkout and
|
|
91
|
+
// review would make listFiles (latest HEAD) disagree with the anchor,
|
|
92
|
+
// silently dropping valid comments or letting invalid ones through.
|
|
93
|
+
//
|
|
94
|
+
// commentableLinesPullNumber records WHICH PR this snapshot belongs to. if
|
|
95
|
+
// the agent checks out PR B and then reviews PR A in the same session, the
|
|
96
|
+
// cached snapshot for B would silently mis-validate A's comments — keying
|
|
97
|
+
// by PR number forces a re-fetch when the target changes.
|
|
98
|
+
//
|
|
99
|
+
// commentableLinesCheckoutSha pins the snapshot to the SHA it was built
|
|
100
|
+
// against. if a second checkout_pr for the SAME PR bumps checkoutSha but
|
|
101
|
+
// fails before repopulating the cache (e.g., listFiles rate-limits), the
|
|
102
|
+
// stale snapshot would silently mis-validate comments against the new SHA.
|
|
103
|
+
// comparing both fields forces a re-fetch when either moves.
|
|
104
|
+
commentableLinesByFile?: Map<string, CommentableLines>;
|
|
105
|
+
commentableLinesPullNumber?: number;
|
|
106
|
+
commentableLinesCheckoutSha?: string | undefined;
|
|
107
|
+
// SHA to diff incrementally against — set from event payload on first checkout,
|
|
108
|
+
// then from checkoutSha when review.ts detects new commits mid-review
|
|
109
|
+
beforeSha?: string;
|
|
110
|
+
selectedMode?: string;
|
|
111
|
+
// number of remediation PRs opened this run. set only in Remediate mode by
|
|
112
|
+
// create_pull_request; read by the max_prs guardrail (mcp/guardrails.ts).
|
|
113
|
+
remediationPrsOpened?: number;
|
|
114
|
+
// destructive resources the most recent terraform_plan reported, partitioned
|
|
115
|
+
// into stateful (data-bearing, high-risk) and ephemeral. set by
|
|
116
|
+
// terraform_plan; read by the destroy-block guardrail (mcp/guardrails.ts) at
|
|
117
|
+
// push time so a fix that would delete/replace a datastore is blocked on the
|
|
118
|
+
// plan's evidence rather than the agent's self-report.
|
|
119
|
+
plannedDestroy?: {
|
|
120
|
+
stateful: { address: string; action: string; type: string }[];
|
|
121
|
+
ephemeral: { address: string; action: string; type: string }[];
|
|
122
|
+
};
|
|
123
|
+
// full pre-fix scan id set (the deduped union of every scanner's concern ids,
|
|
124
|
+
// unfiltered by severity), captured by terraform_scan. read by
|
|
125
|
+
// terraform_verify_remediation to compute §1.4 regressions = current −
|
|
126
|
+
// baseline (concern ids the fix INTRODUCED). undefined until the first scan.
|
|
127
|
+
baselineConcernIds?: string[];
|
|
128
|
+
// the most recent terraform_scan's reported concern set (post scope/severity
|
|
129
|
+
// filtering — what the run acted on). read at end-of-run by
|
|
130
|
+
// finalizeSuccessRun to emit the SARIF artifact + findings-count output
|
|
131
|
+
// (§5.4). undefined until a scan ran; [] means the scan came back clean.
|
|
132
|
+
lastScanConcerns?: Concern[];
|
|
133
|
+
// absolute path of a SARIF file the agent wrote via terraform_emit_sarif.
|
|
134
|
+
// set by that tool on a successful write. read by finalizeSuccessRun's
|
|
135
|
+
// findings-output step so the end-of-run safety-net emit does NOT clobber an
|
|
136
|
+
// agent-emitted report (which may use a lower threshold / custom path) — it
|
|
137
|
+
// points findings-sarif-path at this file instead of rewriting.
|
|
138
|
+
emittedSarifPath?: string;
|
|
139
|
+
// verification signals the confidence label (§5.19) aggregates, each recorded
|
|
140
|
+
// by the tool that produced it: terraform_plan sets blastTier + idempotent
|
|
141
|
+
// (undefined when no plan ran), infracost_diff sets costDirection. read by
|
|
142
|
+
// terraform_verify_remediation's computeConfidence so the PR's confidence is
|
|
143
|
+
// computed from real evidence, not the agent's word.
|
|
144
|
+
lastBlastTier?: "low" | "medium" | "high";
|
|
145
|
+
lastIdempotent?: boolean;
|
|
146
|
+
lastCostDirection?: "increase" | "decrease" | "no-change" | "unknown";
|
|
147
|
+
// number of prepush hook failures this run. push_branch runs the hook
|
|
148
|
+
// while this is 0 and skips it once non-zero; never decremented within
|
|
149
|
+
// a run.
|
|
150
|
+
prepushFailureCount: number;
|
|
151
|
+
backgroundProcesses: Map<string, BackgroundProcess>;
|
|
152
|
+
review?: {
|
|
153
|
+
id: number;
|
|
154
|
+
nodeId: string;
|
|
155
|
+
reviewedSha: string | undefined;
|
|
156
|
+
};
|
|
157
|
+
// dedupe key: parent review comment_id → most-recent reply written this
|
|
158
|
+
// session by reply_to_review_comment. used by duplicateReplyDecision to
|
|
159
|
+
// skip identical-body re-emissions of the same call (PR #610 root cause).
|
|
160
|
+
// body-keyed (not just id-keyed) so legitimate follow-up replies with
|
|
161
|
+
// different content still go through.
|
|
162
|
+
reviewReplies?: Map<
|
|
163
|
+
number,
|
|
164
|
+
{ commentId: number; url: string | undefined; bodyWithFooter: string }
|
|
165
|
+
>;
|
|
166
|
+
dependencyInstallation?: {
|
|
167
|
+
status: "not_started" | "in_progress" | "completed" | "failed";
|
|
168
|
+
promise: Promise<PrepResult[]> | undefined;
|
|
169
|
+
results: PrepResult[] | undefined;
|
|
170
|
+
};
|
|
171
|
+
// undefined = no comment yet, object = active comment, null = deliberately deleted
|
|
172
|
+
progressComment: ProgressComment | null | undefined;
|
|
173
|
+
// immutable snapshot: true if a progress comment was pre-created at init time.
|
|
174
|
+
// survives deleteProgressComment so handleAgentResult can still detect "expected but never reported".
|
|
175
|
+
hadProgressComment: boolean;
|
|
176
|
+
lastProgressBody?: string;
|
|
177
|
+
wasUpdated?: boolean;
|
|
178
|
+
// set after a non-plan report_progress successfully writes the final summary.
|
|
179
|
+
// decoupled from todoTracker.enabled so cleanup detection survives API failures.
|
|
180
|
+
finalSummaryWritten?: boolean;
|
|
181
|
+
// set by select_mode when Plan + issue_number and plan-comment API returns existing plan (for report_progress target_plan_comment)
|
|
182
|
+
existingPlanCommentId?: number;
|
|
183
|
+
previousPlanBody?: string;
|
|
184
|
+
// absolute path to the PR summary markdown file the agent edits in place.
|
|
185
|
+
// seeded by main.ts before the agent starts when payload.generateSummary is set;
|
|
186
|
+
// read back at end-of-run to persist to DB.
|
|
187
|
+
summaryFilePath?: string;
|
|
188
|
+
// exact bytes of the seeded snapshot file at run start. compared against
|
|
189
|
+
// the file content at end-of-run to detect "agent never touched it" — in
|
|
190
|
+
// that case persistSummary skips the DB write (saving the seed verbatim
|
|
191
|
+
// would either re-write what the DB already has, on incremental runs, or
|
|
192
|
+
// serialize the placeholder scaffold, on first runs).
|
|
193
|
+
summarySeed?: string;
|
|
194
|
+
// set to true after persistSummary completes once. prevents the error-path
|
|
195
|
+
// call (which exists so a successful agent edit before a crash still gets
|
|
196
|
+
// persisted) from redundantly re-running the DB PATCH on the
|
|
197
|
+
// success-then-late-throw path.
|
|
198
|
+
summaryPersistAttempted?: boolean;
|
|
199
|
+
// absolute path to the rolling repo-level learnings markdown file the
|
|
200
|
+
// agent reads at startup and may edit at end-of-run. seeded by main.ts
|
|
201
|
+
// for every run from `Repo.learnings` (empty file when no learnings
|
|
202
|
+
// exist yet); read back at end-of-run to persist any edits.
|
|
203
|
+
learningsFilePath?: string;
|
|
204
|
+
// exact bytes of the seeded learnings file at run start. compared
|
|
205
|
+
// against the file content at end-of-run to detect "agent never touched
|
|
206
|
+
// it" — in that case persistLearnings skips the DB PATCH (saving the
|
|
207
|
+
// identical content would be a no-op write that wastes a LearningsRevision
|
|
208
|
+
// row and the API round-trip).
|
|
209
|
+
learningsSeed?: string;
|
|
210
|
+
// mirror of `summaryPersistAttempted` for the learnings tmpfile — guards
|
|
211
|
+
// the error-path / exit-signal callers from a redundant second PATCH
|
|
212
|
+
// after the success path already persisted.
|
|
213
|
+
learningsPersistAttempted?: boolean;
|
|
214
|
+
output?: string | undefined;
|
|
215
|
+
usageEntries: AgentUsage[];
|
|
216
|
+
model?: string | undefined;
|
|
217
|
+
// set by main.ts when the BYOK fallback engaged (configured model needed
|
|
218
|
+
// a provider key the runner didn't have). carried into PR-comment footers
|
|
219
|
+
// so users can see "Using <free model> (credentials for <configured> not
|
|
220
|
+
// configured)" rather than just being silently downgraded. literal record
|
|
221
|
+
// of an event that happened — matches the ToolState design rule.
|
|
222
|
+
modelFallback?: { from: string } | undefined;
|
|
223
|
+
todoTracker?: TodoTracker | undefined;
|
|
224
|
+
diffCoverage?: DiffCoverageState | undefined;
|
|
225
|
+
// mutable handle the agent harness writes to as a run progresses (recent
|
|
226
|
+
// stderr ring buffer reference, last provider-error label, event count).
|
|
227
|
+
// read by main.ts's outer catch so a watchdog-fired activity timeout still
|
|
228
|
+
// surfaces the same agent-side context the harness's own catch path returns
|
|
229
|
+
// via `result.error`. see `utils/agentHangReport.ts`.
|
|
230
|
+
agentDiagnostic?: AgentDiagnostic | undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface InitToolStateParams {
|
|
234
|
+
progressComment: { id: string; type: ProgressCommentType } | undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function initToolState(params: InitToolStateParams): ToolState {
|
|
238
|
+
const resolved = parseProgressComment(params.progressComment);
|
|
239
|
+
|
|
240
|
+
if (resolved) {
|
|
241
|
+
log.info(`» using pre-created progress comment: ${resolved.id} (${resolved.type})`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
progressComment: resolved,
|
|
246
|
+
hadProgressComment: !!resolved,
|
|
247
|
+
prepushFailureCount: 0,
|
|
248
|
+
backgroundProcesses: new Map(),
|
|
249
|
+
createdTargets: new Set(),
|
|
250
|
+
usageEntries: [],
|
|
251
|
+
};
|
|
252
|
+
}
|