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,305 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { discoverTerraformRoots } from "#app/mcp/roots";
|
|
5
|
+
import { toolSkip } from "#app/mcp/shared";
|
|
6
|
+
import { resolveEnv } from "#app/utils/secrets";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A degrade-green skip result carrying the structured §3.5 envelope
|
|
10
|
+
* (`ok: false` + machine `code` + human `detail`) PLUS the legacy alias the tool
|
|
11
|
+
* returned before it converged on the envelope — `ran`/`found` and
|
|
12
|
+
* `skipped_reason`/`reason` — so existing prompt + test contracts keep working.
|
|
13
|
+
* Additive, never breaking. `extra` folds in any tool-specific fields (e.g.
|
|
14
|
+
* read_findings' empty `concerns`/`groups`).
|
|
15
|
+
*/
|
|
16
|
+
export function skipResult(
|
|
17
|
+
code: string,
|
|
18
|
+
detail: string,
|
|
19
|
+
opts: {
|
|
20
|
+
key?: "ran" | "found";
|
|
21
|
+
reasonKey?: "skipped_reason" | "reason";
|
|
22
|
+
extra?: Record<string, any>;
|
|
23
|
+
} = {},
|
|
24
|
+
): Record<string, any> {
|
|
25
|
+
const key = opts.key ?? "ran";
|
|
26
|
+
const reasonKey = opts.reasonKey ?? "skipped_reason";
|
|
27
|
+
return { ...toolSkip(code, detail), [key]: false, [reasonKey]: detail, ...(opts.extra ?? {}) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Internal "concern" model — the Remediator's ground truth for "what is not
|
|
32
|
+
* best practice". Produced by `terraform_scan` from the fork's own deterministic
|
|
33
|
+
* Terraform check tools (fmt / validate / tflint / trivy / checkov).
|
|
34
|
+
*
|
|
35
|
+
* This is a deliberate SUBSET of the reviewer's `findings.schema.json` v1.0
|
|
36
|
+
* (../terraform-reviewer/schemas/findings.schema.json). The reviewer's findings
|
|
37
|
+
* add `lens` / `standard` / `control_id` / `state` on top. Keeping the shape a
|
|
38
|
+
* subset means a future reviewer integration (read_findings) can emit the same
|
|
39
|
+
* Concern[] with no change to the modes or the rest of the tools — only the
|
|
40
|
+
* SOURCE of concerns swaps.
|
|
41
|
+
*/
|
|
42
|
+
export interface Concern {
|
|
43
|
+
/** stable content id: sha1(source|rule_id|file|line). idempotency key for branch/PR naming. */
|
|
44
|
+
id: string;
|
|
45
|
+
/** producing tool. `reviewer` marks a concern loaded from a terraform-reviewer
|
|
46
|
+
* findings.json whose original tool isn't one Terramend re-runs (tfsec / infracost
|
|
47
|
+
* / llm) — its provenance lives in `rule_id`. */
|
|
48
|
+
source: "terraform-fmt" | "terraform-validate" | "tflint" | "trivy" | "checkov" | "reviewer";
|
|
49
|
+
/** original namespaced rule, e.g. "trivy:AVD-AWS-0088" */
|
|
50
|
+
rule_id: string;
|
|
51
|
+
severity: Severity;
|
|
52
|
+
category: "security" | "style" | "correctness" | "cost";
|
|
53
|
+
/** the scanner message — what is wrong */
|
|
54
|
+
evidence: string;
|
|
55
|
+
location: { file: string; line: number | null };
|
|
56
|
+
remediation_hint: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const SEVERITIES = ["critical", "high", "medium", "low", "info"] as const;
|
|
60
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
61
|
+
|
|
62
|
+
export const SEVERITY_RANK: Record<Severity, number> = {
|
|
63
|
+
critical: 4,
|
|
64
|
+
high: 3,
|
|
65
|
+
medium: 2,
|
|
66
|
+
low: 1,
|
|
67
|
+
info: 0,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function concernId(
|
|
71
|
+
source: string,
|
|
72
|
+
ruleId: string,
|
|
73
|
+
file: string,
|
|
74
|
+
line: number | null,
|
|
75
|
+
): string {
|
|
76
|
+
return createHash("sha1")
|
|
77
|
+
.update(`${source}|${ruleId}|${file}|${line ?? ""}`)
|
|
78
|
+
.digest("hex")
|
|
79
|
+
.slice(0, 12);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Normalize a scanner-reported path to a repo-relative POSIX path. Each scanner
|
|
84
|
+
* reports the file differently — tflint gives `main.tf` (relative), trivy a
|
|
85
|
+
* scan-dir-relative `Target`, terraform an absolute path (`/repo/main.tf` or
|
|
86
|
+
* `D:\repo\main.tf`), checkov a leading-slash path (`/main.tf`). Left
|
|
87
|
+
* unnormalized, these leak into `location.file` AND the
|
|
88
|
+
* content-derived `concernId`, making the id (and the `remediate/<id>` branch)
|
|
89
|
+
* machine-dependent and breaking the ✗→✓ re-scan id match across environments.
|
|
90
|
+
* So normalize BEFORE building any Concern.
|
|
91
|
+
*/
|
|
92
|
+
export function toRepoRelative(raw: string | undefined, cwd: string): string {
|
|
93
|
+
if (!raw) return "(unknown)";
|
|
94
|
+
const posix = raw.replace(/\\/g, "/");
|
|
95
|
+
const cwdPosix = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
96
|
+
let rel = posix;
|
|
97
|
+
if (cwdPosix && posix.toLowerCase().startsWith(`${cwdPosix.toLowerCase()}/`)) {
|
|
98
|
+
rel = posix.slice(cwdPosix.length + 1);
|
|
99
|
+
}
|
|
100
|
+
// strip any leading "./" or "/" (checkov reports paths relative to its -d root
|
|
101
|
+
// with a leading separator).
|
|
102
|
+
rel = rel.replace(/^(?:\.\/|\/)+/, "");
|
|
103
|
+
return rel || "(unknown)";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type RunResult = { status: number; stdout: string; stderr: string; missing: boolean };
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Hard wall-clock cap on any single scanner/terraform invocation. Bounds a hung
|
|
110
|
+
* subprocess (e.g. `terraform init`/`plan` stalling on a private registry, a
|
|
111
|
+
* tflint plugin fetch that never returns) so it can't block the run forever.
|
|
112
|
+
* Generous (5 min) so it only ever fires on a genuine hang, never a slow-but-
|
|
113
|
+
* progressing plan. A timeout surfaces as a non-zero/`-1` status the caller
|
|
114
|
+
* already treats as "this scanner did not produce results".
|
|
115
|
+
*/
|
|
116
|
+
export const SUBPROCESS_TIMEOUT_MS = 300_000;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Run a scanner without throwing. Terraform tools exit non-zero when they FIND
|
|
120
|
+
* issues, so a non-zero status is normal, not an error. `missing` is set when
|
|
121
|
+
* the binary isn't on PATH (ENOENT) — the scanner then degrades to "skipped"
|
|
122
|
+
* rather than failing the run (plan §9.2: a tool being absent must not block).
|
|
123
|
+
*
|
|
124
|
+
* `extraEnv` opts a specific command back into a needed credential: the scanners
|
|
125
|
+
* run with a restricted env that strips every `*_KEY`/secret, but infracost
|
|
126
|
+
* legitimately needs its `INFRACOST_API_KEY`. resolveEnv(object) merges the
|
|
127
|
+
* restricted base with the explicit vars, so only the named keys get through.
|
|
128
|
+
*/
|
|
129
|
+
export function run(
|
|
130
|
+
cmd: string,
|
|
131
|
+
args: string[],
|
|
132
|
+
cwd: string,
|
|
133
|
+
extraEnv?: Record<string, string>,
|
|
134
|
+
): RunResult {
|
|
135
|
+
const result = spawnSync(cmd, args, {
|
|
136
|
+
cwd,
|
|
137
|
+
encoding: "utf-8",
|
|
138
|
+
// restricted env: keeps PATH/HOME, strips secrets so a scanner (or a tflint
|
|
139
|
+
// plugin) can't exfiltrate credentials. `extraEnv` re-admits only the named vars.
|
|
140
|
+
env: resolveEnv(extraEnv ?? "restricted") as NodeJS.ProcessEnv,
|
|
141
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
142
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
143
|
+
timeout: SUBPROCESS_TIMEOUT_MS,
|
|
144
|
+
});
|
|
145
|
+
if (result.error) {
|
|
146
|
+
// ENOENT = binary absent (degrade to "skipped"); a timeout (ETIMEDOUT, or a
|
|
147
|
+
// SIGTERM kill on timeout) is a real failure surfaced as status -1, which the
|
|
148
|
+
// scanner callers already treat as "no results from this tool".
|
|
149
|
+
const missing = (result.error as NodeJS.ErrnoException).code === "ENOENT";
|
|
150
|
+
return { status: -1, stdout: "", stderr: result.error.message, missing };
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
status: result.status ?? -1,
|
|
154
|
+
stdout: result.stdout ?? "",
|
|
155
|
+
stderr: result.stderr ?? "",
|
|
156
|
+
missing: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type ScannerOutcome = {
|
|
161
|
+
source: Concern["source"];
|
|
162
|
+
ran: boolean;
|
|
163
|
+
skipped_reason?: string;
|
|
164
|
+
concerns: Concern[];
|
|
165
|
+
/**
|
|
166
|
+
* For `terraform validate` only: count of roots where terraform RAN but its
|
|
167
|
+
* `-json` output could not be parsed (a real CLI-level failure, not a missing
|
|
168
|
+
* binary). Lets the validate tool report `passed` as fail-closed instead of
|
|
169
|
+
* silently treating an un-validated root as clean. Undefined/0 elsewhere.
|
|
170
|
+
*/
|
|
171
|
+
unvalidated?: number;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export function skipped(source: Concern["source"], reason: string): ScannerOutcome {
|
|
175
|
+
return { source, ran: false, skipped_reason: reason, concerns: [] };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface ResolvedRoot {
|
|
179
|
+
/** absolute dir the per-root command (init/plan/validate) runs in. */
|
|
180
|
+
absDir: string;
|
|
181
|
+
/** that root's path relative to the scan `cwd` ("" when the root IS cwd) —
|
|
182
|
+
* prepended to per-root concern files so they stay cwd-relative. */
|
|
183
|
+
relDir: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The Terraform root modules to operate on under `cwd`. A repo can hold several
|
|
188
|
+
* roots (hepcare: `terraform/` + `terraform/core/`); `terraform validate` /
|
|
189
|
+
* `plan` are per-root, so they must run in EACH. Falls back to `cwd` itself as a
|
|
190
|
+
* single root when none is detected — so a normal single-root repo behaves
|
|
191
|
+
* exactly as before (no rebasing, one iteration).
|
|
192
|
+
*/
|
|
193
|
+
export function resolveRoots(cwd: string): ResolvedRoot[] {
|
|
194
|
+
const discovered = discoverTerraformRoots(cwd);
|
|
195
|
+
if (discovered.length === 0) return [{ absDir: cwd, relDir: "" }];
|
|
196
|
+
return discovered.map((r) => ({ absDir: r.dir ? join(cwd, r.dir) : cwd, relDir: r.dir }));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Re-base a concern produced by a per-root command (its file is relative to the
|
|
201
|
+
* root dir) onto the scan `cwd` by prefixing the root's `relDir`, and recompute
|
|
202
|
+
* the content id so it stays consistent with the cwd-relative scanners (✗→✓).
|
|
203
|
+
* A no-op when `relDir` is "" (the root IS cwd).
|
|
204
|
+
*/
|
|
205
|
+
export function rebaseConcern(c: Concern, relDir: string): Concern {
|
|
206
|
+
if (!relDir) return c;
|
|
207
|
+
const file = `${relDir}/${c.location.file}`.replace(/\/+/g, "/");
|
|
208
|
+
const prefix = `${c.source}:`;
|
|
209
|
+
const bareRule = c.rule_id.startsWith(prefix) ? c.rule_id.slice(prefix.length) : c.rule_id;
|
|
210
|
+
return {
|
|
211
|
+
...c,
|
|
212
|
+
location: { ...c.location, file },
|
|
213
|
+
id: concernId(c.source, bareRule, file, c.location.line),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** true when a path is a Terraform source file Terramend may remediate. */
|
|
218
|
+
export function isTerraformFile(file: string): boolean {
|
|
219
|
+
const f = file.toLowerCase();
|
|
220
|
+
return f.endsWith(".tf") || f.endsWith(".tfvars");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Terramend is Terraform-only. A concern in a non-`.tf`/`.tfvars` file can never
|
|
225
|
+
* be remediated (the `allowed_paths` push guardrail blocks it) and is pure noise,
|
|
226
|
+
* so any scanner that also inspects other IaC — checkov's github_actions, trivy's
|
|
227
|
+
* dockerfile/kubernetes — gets filtered down to Terraform here. This is the
|
|
228
|
+
* catch-all backstop; `scanCheckov` also scopes itself with `--framework terraform`.
|
|
229
|
+
*/
|
|
230
|
+
export function isTerraformConcern(c: Concern): boolean {
|
|
231
|
+
return isTerraformFile(c.location.file);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function dedupe(concerns: Concern[]): Concern[] {
|
|
235
|
+
const seen = new Set<string>();
|
|
236
|
+
const out: Concern[] = [];
|
|
237
|
+
for (const c of concerns) {
|
|
238
|
+
if (seen.has(c.id)) continue;
|
|
239
|
+
seen.add(c.id);
|
|
240
|
+
out.push(c);
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function sortConcerns(concerns: Concern[]): Concern[] {
|
|
246
|
+
return [...concerns].sort((a, b) => {
|
|
247
|
+
const sev = SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity];
|
|
248
|
+
if (sev !== 0) return sev;
|
|
249
|
+
return a.id.localeCompare(b.id);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function lowerSeverity(s: string | undefined): Severity {
|
|
254
|
+
const v = (s ?? "").toLowerCase();
|
|
255
|
+
return (SEVERITIES as readonly string[]).includes(v) ? (v as Severity) : "medium";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** the repo's base ref for diff-scope, or null when one can't be determined. */
|
|
259
|
+
export function resolveBaseRef(cwd: string): string | null {
|
|
260
|
+
const head = run("git", ["rev-parse", "--abbrev-ref", "origin/HEAD"], cwd);
|
|
261
|
+
if (head.status === 0 && head.stdout.trim()) return head.stdout.trim();
|
|
262
|
+
for (const ref of ["origin/main", "origin/master"]) {
|
|
263
|
+
const verify = run("git", ["rev-parse", "--verify", "--quiet", ref], cwd);
|
|
264
|
+
if (verify.status === 0 && verify.stdout.trim()) return ref;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* A scoped unit of work = all concerns in one file. Different scanners flag the
|
|
271
|
+
* same underlying defect under different rule ids (trivy ∩ checkov overlap
|
|
272
|
+
* heavily on e.g. S3 buckets), so per-concern PRs would spam many PRs for one
|
|
273
|
+
* bad file. Remediate acts on ONE group per PR (branch `remediate/<group.id>`),
|
|
274
|
+
* fixing every concern in the file together and proving them all cleared (✗→✓).
|
|
275
|
+
*/
|
|
276
|
+
export interface ConcernGroup {
|
|
277
|
+
/** stable id — the remediation branch/PR key (`remediate/<id>`). Derived from
|
|
278
|
+
* the file (by-file grouping) or the rule (by-rule grouping). */
|
|
279
|
+
id: string;
|
|
280
|
+
/** the group's primary file (by-file) or a human label like "3 files"
|
|
281
|
+
* (by-rule); `files` carries the full list for by-rule groups. */
|
|
282
|
+
file: string;
|
|
283
|
+
/** §3.11 — every file the group spans. one entry for a by-file group; the
|
|
284
|
+
* full set for a by-rule group (the agent must fix the rule in all of them). */
|
|
285
|
+
files?: string[];
|
|
286
|
+
/** how the group was formed — `file` (default) or `rule` (§3.11). */
|
|
287
|
+
grouping?: "file" | "rule";
|
|
288
|
+
/** highest severity among the group's concerns. */
|
|
289
|
+
severity: Severity;
|
|
290
|
+
concern_count: number;
|
|
291
|
+
/** distinct rule ids in the group, for the PR body. */
|
|
292
|
+
rule_ids: string[];
|
|
293
|
+
/** the concern ids the re-scan must confirm are gone to call this ✓. */
|
|
294
|
+
concern_ids: string[];
|
|
295
|
+
/** §3.9 — `auto` (open a normal PR) or `needs-human` (escalate). attached by
|
|
296
|
+
* the scan tool from the group's concerns, not by `groupConcerns` (which has
|
|
297
|
+
* no autonomy threshold). undefined until the scan tool annotates it. */
|
|
298
|
+
autonomy?: Autonomy;
|
|
299
|
+
/** §3.9 — why the group was escalated (empty/absent for `auto`). */
|
|
300
|
+
autonomy_reasons?: string[];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export type Autonomy = "auto" | "needs-human";
|
|
304
|
+
|
|
305
|
+
export type BlastTier = "low" | "medium" | "high";
|