infraweaver 0.3.7 → 0.3.8
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/README.md +2 -2
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claudePretoolGate.d.ts +0 -36
- package/dist/agents/toolPlaneWatch.d.ts +31 -0
- package/dist/cli.mjs +31903 -18736
- package/dist/external.d.ts +2 -1
- package/dist/index.js +31987 -18749
- package/dist/internal.js +132 -52
- package/dist/mcp/baseBranch.d.ts +41 -0
- package/dist/mcp/changeSummary.d.ts +1 -1
- package/dist/mcp/findingAudit.d.ts +2 -0
- package/dist/mcp/guardrails.d.ts +38 -0
- package/dist/mcp/localContext.d.ts +1 -1
- package/dist/mcp/moduleExtraction.d.ts +5 -0
- package/dist/mcp/pr.d.ts +0 -31
- package/dist/mcp/reproveSuggestions.d.ts +27 -0
- package/dist/mcp/review.d.ts +126 -2
- package/dist/mcp/reviewCommentLimit.d.ts +17 -0
- package/dist/mcp/reviewComments.d.ts +9 -5
- package/dist/mcp/reviewDedup.d.ts +1 -0
- package/dist/mcp/reviewMarkers.d.ts +10 -0
- package/dist/mcp/reviewProvenance.d.ts +20 -6
- package/dist/mcp/shared.d.ts +15 -0
- package/dist/mcp/suggestionProver.d.ts +3 -2
- package/dist/mcp/terraform/checkovSeverities.d.ts +7 -0
- package/dist/mcp/terraform/consolidation.d.ts +26 -0
- package/dist/mcp/terraform/cost.d.ts +2 -2
- package/dist/mcp/terraform/currency.d.ts +11 -0
- package/dist/mcp/terraform/deltaSummary.d.ts +11 -1
- package/dist/mcp/terraform/emittedArtefacts.d.ts +11 -0
- package/dist/mcp/terraform/evidence.d.ts +10 -0
- package/dist/mcp/terraform/gateScan.d.ts +2 -16
- package/dist/mcp/terraform/hcl.d.ts +15 -0
- package/dist/mcp/terraform/initArtefacts.d.ts +21 -0
- package/dist/mcp/terraform/localModules.d.ts +29 -0
- package/dist/mcp/terraform/moduleFindings.d.ts +38 -0
- package/dist/mcp/terraform/refactor/attribution.d.ts +1 -1
- package/dist/mcp/terraform/refactor/equivalence.d.ts +7 -0
- package/dist/mcp/terraform/refactor/expressions.d.ts +62 -0
- package/dist/mcp/terraform/refactor/references.d.ts +15 -0
- package/dist/mcp/terraform/refactor/resources.d.ts +11 -0
- package/dist/mcp/terraform/refactor/substitution.d.ts +55 -37
- package/dist/mcp/terraform/scanDelta.d.ts +28 -0
- package/dist/mcp/terraform/scanSession.d.ts +13 -0
- package/dist/mcp/terraform/scanners.d.ts +67 -18
- package/dist/mcp/terraform/secretScan.d.ts +19 -0
- package/dist/mcp/terraform/standardsReport.d.ts +1 -1
- package/dist/mcp/terraform/tools/plan.d.ts +1 -1
- package/dist/mcp/terraform/tools/scan.d.ts +7 -7
- package/dist/mcp/terraform/tools/validate.d.ts +7 -2
- package/dist/mcp/terraform/tools/verifyRemediation.d.ts +1 -1
- package/dist/mcp/terraform/treeLayout.d.ts +57 -0
- package/dist/mcp/terraform/types.d.ts +1 -1
- package/dist/phases/applyPlan.d.ts +0 -2
- package/dist/toolState.d.ts +3 -0
- package/dist/utils/agent/codexOAuth.d.ts +1 -1
- package/dist/utils/cli.d.ts +1 -7
- package/dist/utils/cloud/cloudReport.d.ts +1 -1
- package/dist/utils/config/baseRefConfig.d.ts +1 -1
- package/dist/utils/config/infraweaverConfig.d.ts +1 -1
- package/dist/utils/config/payload.d.ts +5 -0
- package/dist/utils/markdownTable.d.ts +6 -0
- package/dist/utils/setup/toolLicensing.d.ts +1 -1
- package/dist/utils/unifiedDiff.d.ts +69 -0
- package/package.json +34 -28
- package/src/agents/claude.ts +48 -13
- package/src/agents/claudePretoolGate.ts +9 -17
- package/src/agents/sessionLabeler.ts +3 -3
- package/src/agents/toolPlaneWatch.ts +71 -0
- package/src/external.ts +3 -1
- package/src/mcp/assess.ts +8 -8
- package/src/mcp/baseBranch.ts +96 -0
- package/src/mcp/changeSummary.ts +10 -11
- package/src/mcp/checkSuite.ts +7 -10
- package/src/mcp/checkout.ts +13 -40
- package/src/mcp/crosswalk.ts +5 -0
- package/src/mcp/findingAudit.ts +1 -1
- package/src/mcp/git.ts +44 -2
- package/src/mcp/guardrails.ts +188 -36
- package/src/mcp/localContext.ts +2 -0
- package/src/mcp/localServer.ts +1 -0
- package/src/mcp/moduleExtraction.ts +17 -5
- package/src/mcp/moduleTests.ts +9 -2
- package/src/mcp/modules.ts +10 -7
- package/src/mcp/pr.ts +17 -65
- package/src/mcp/reproveSuggestions.ts +86 -0
- package/src/mcp/review.ts +249 -75
- package/src/mcp/reviewAnchor.ts +11 -23
- package/src/mcp/reviewCommentLimit.ts +79 -0
- package/src/mcp/reviewComments.ts +34 -81
- package/src/mcp/reviewDedup.ts +1 -1
- package/src/mcp/reviewMarkers.ts +25 -2
- package/src/mcp/reviewProvenance.ts +56 -21
- package/src/mcp/roots.ts +3 -2
- package/src/mcp/server.ts +2 -1
- package/src/mcp/shared.ts +40 -1
- package/src/mcp/shell.ts +27 -28
- package/src/mcp/suggestionProver.ts +8 -4
- package/src/mcp/terraform/checkovSeverities.ts +1156 -0
- package/src/mcp/terraform/concernResult.ts +2 -1
- package/src/mcp/terraform/consolidation.ts +232 -19
- package/src/mcp/terraform/cost.ts +16 -7
- package/src/mcp/terraform/cspm.ts +8 -4
- package/src/mcp/terraform/currency.ts +28 -12
- package/src/mcp/terraform/decisions.ts +1 -1
- package/src/mcp/terraform/deltaSummary.ts +50 -1
- package/src/mcp/terraform/emittedArtefacts.ts +63 -0
- package/src/mcp/terraform/evidence.ts +23 -3
- package/src/mcp/terraform/gateScan.ts +191 -128
- package/src/mcp/terraform/hcl.ts +27 -2
- package/src/mcp/terraform/idleFloor.ts +2 -1
- package/src/mcp/terraform/initArtefacts.ts +76 -0
- package/src/mcp/terraform/localModules.ts +112 -0
- package/src/mcp/terraform/moduleDocs.ts +2 -5
- package/src/mcp/terraform/moduleFindings.ts +102 -0
- package/src/mcp/terraform/moduleVersionConstraints.ts +1 -1
- package/src/mcp/terraform/planPairs.ts +2 -20
- package/src/mcp/terraform/policyGate.ts +0 -15
- package/src/mcp/terraform/refactor/attribution.ts +39 -5
- package/src/mcp/terraform/refactor/equivalence.ts +49 -10
- package/src/mcp/terraform/refactor/expressions.ts +323 -0
- package/src/mcp/terraform/refactor/references.ts +101 -0
- package/src/mcp/terraform/refactor/resources.ts +79 -7
- package/src/mcp/terraform/refactor/substitution.ts +852 -256
- package/src/mcp/terraform/scanDelta.ts +34 -0
- package/src/mcp/terraform/scanSession.ts +30 -0
- package/src/mcp/terraform/scannerCache.ts +4 -0
- package/src/mcp/terraform/scanners.ts +553 -233
- package/src/mcp/terraform/secretScan.ts +131 -0
- package/src/mcp/terraform/standardsReport.ts +14 -13
- package/src/mcp/terraform/tools/consolidationCandidates.ts +3 -0
- package/src/mcp/terraform/tools/emitEvidence.ts +13 -5
- package/src/mcp/terraform/tools/emitOscal.ts +3 -2
- package/src/mcp/terraform/tools/emitVex.ts +10 -4
- package/src/mcp/terraform/tools/equivalenceCheck.ts +139 -21
- package/src/mcp/terraform/tools/infracostDiff.ts +2 -2
- package/src/mcp/terraform/tools/plan.ts +30 -17
- package/src/mcp/terraform/tools/reviewScanDelta.ts +3 -0
- package/src/mcp/terraform/tools/scan.ts +5 -3
- package/src/mcp/terraform/tools/standardsReport.ts +1 -1
- package/src/mcp/terraform/tools/validate.ts +20 -5
- package/src/mcp/terraform/tools/verifyRemediation.ts +14 -7
- package/src/mcp/terraform/tools/versionCurrency.ts +3 -1
- package/src/mcp/terraform/treeLayout.ts +378 -0
- package/src/mcp/terraform/types.ts +4 -1
- package/src/mcp/terraform/versionRequirements.ts +11 -2
- package/src/modes/__snapshots__/assemblePrompt.test.ts.snap +2 -0
- package/src/modes/modernize-deprecated.ts +2 -2
- package/src/modes/refactor.ts +9 -9
- package/src/modes/remediate.ts +1 -1
- package/src/modes/terraform-code-review.ts +6 -3
- package/src/modes/update-dependencies.ts +6 -6
- package/src/phases/applyPlan.ts +33 -14
- package/src/phases/applySuggestions.ts +2 -2
- package/src/phases/runAgentWithWatchdogs.ts +2 -1
- package/src/toolState.ts +7 -0
- package/src/utils/agent/codexOAuth.ts +7 -7
- package/src/utils/agent/openCodeModels.ts +4 -6
- package/src/utils/cli.ts +1 -31
- package/src/utils/cloud/runContext.ts +1 -8
- package/src/utils/config/baseRefConfig.ts +6 -0
- package/src/utils/config/infraweaverConfig.ts +2 -0
- package/src/utils/config/payload.ts +41 -0
- package/src/utils/github/assets.ts +1 -6
- package/src/utils/github/github.ts +42 -150
- package/src/utils/github/token.ts +6 -11
- package/src/utils/log.ts +4 -5
- package/src/utils/markdownTable.ts +11 -0
- package/src/utils/prompt/changeImpact.ts +18 -26
- package/src/utils/prompt/instructions.ts +1 -1
- package/src/utils/prompt/promptDirectives.ts +1 -1
- package/src/utils/prompt/toolSelection.ts +11 -2
- package/src/utils/setup/toolLicensing.ts +7 -0
- package/src/utils/telemetry.ts +2 -1
- package/src/utils/unifiedDiff.ts +120 -0
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import type { LocalToolContext } from "../../localContext.js";
|
|
2
|
-
import { checkArgumentsAgainstSchema, collectProviderRequirements, scanFmt, scanTflint, scanValidate } from "../scanners.js";
|
|
2
|
+
import { checkArgumentsAgainstSchema, collectProviderRequirements, initFailures, scanFmt, scanTflint, scanValidate } from "../scanners.js";
|
|
3
3
|
export interface ValidateScanners {
|
|
4
4
|
scanFmt: typeof scanFmt;
|
|
5
5
|
scanValidate: typeof scanValidate;
|
|
6
6
|
scanTflint: typeof scanTflint;
|
|
7
7
|
collectProviderRequirements: typeof collectProviderRequirements;
|
|
8
8
|
checkArgumentsAgainstSchema: typeof checkArgumentsAgainstSchema;
|
|
9
|
+
initFailures: typeof initFailures;
|
|
9
10
|
}
|
|
10
11
|
export declare function runValidate(ctx: LocalToolContext, scanners?: ValidateScanners): Promise<import("../../shared.js").ToolOk<{
|
|
11
12
|
passed: boolean;
|
|
12
|
-
checks_ran: ("checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
|
|
13
|
+
checks_ran: ("betterleaks" | "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
|
|
13
14
|
validate_incomplete: boolean;
|
|
15
|
+
init_failed: {
|
|
16
|
+
root: string;
|
|
17
|
+
error: string;
|
|
18
|
+
}[];
|
|
14
19
|
remaining_issues: import("../types.js").Concern[];
|
|
15
20
|
preexisting_fmt_drift: string[];
|
|
16
21
|
providers: import("../scanners.js").ProviderRequirement[];
|
|
@@ -18,7 +18,7 @@ export declare function runVerifyRemediation(ctx: LocalToolContext, { concern_id
|
|
|
18
18
|
regressions_note?: string;
|
|
19
19
|
confidence: import("../decisions.js").Confidence;
|
|
20
20
|
confidence_reasons: string[];
|
|
21
|
-
scanners_ran: ("checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
|
|
21
|
+
scanners_ran: ("betterleaks" | "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
|
|
22
22
|
}>>;
|
|
23
23
|
export declare function TerraformVerifyRemediationTool(ctx: LocalToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
|
|
24
24
|
concern_ids: string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** A variable file assigned to a root by the `var_files` setting. */
|
|
2
|
+
export interface VarFileMapping {
|
|
3
|
+
/** the root module's directory, relative to the scan directory ("" = itself) */
|
|
4
|
+
root: string;
|
|
5
|
+
/** the variable file, relative to the scan directory */
|
|
6
|
+
file: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RootLayout {
|
|
9
|
+
/** the root module's directory, relative to the scan directory ("" = itself) */
|
|
10
|
+
dir: string;
|
|
11
|
+
/** the root's directory and every local module directory it calls, transitively */
|
|
12
|
+
closure: string[];
|
|
13
|
+
/** the variable files Terraform loads by itself, in its load order */
|
|
14
|
+
autoVarFiles: string[];
|
|
15
|
+
/** variable files passed with `-var-file`: from `var_files`, or matched to this root */
|
|
16
|
+
varFiles: string[];
|
|
17
|
+
/** the root's configuration and its modules', for finding files it reads by name */
|
|
18
|
+
source: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TreeLayout {
|
|
21
|
+
roots: RootLayout[];
|
|
22
|
+
/** every directory that holds Terraform configuration */
|
|
23
|
+
tfDirs: string[];
|
|
24
|
+
/** variable files no root could be matched to, and why */
|
|
25
|
+
unmatched: {
|
|
26
|
+
file: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
/** True when `dir` is `ancestor` or lies beneath it. */
|
|
31
|
+
export declare function isWithin(dir: string, ancestor: string): boolean;
|
|
32
|
+
/** The names a variable file assigns at its top level. */
|
|
33
|
+
export declare function varFileKeys(text: string, json: boolean): string[];
|
|
34
|
+
/** The tree's root modules, what each reaches, and the variable files each loads. */
|
|
35
|
+
export declare function treeLayout(cwd: string, explicit?: readonly VarFileMapping[]): TreeLayout;
|
|
36
|
+
/** `root=file` lines (the `var_files` setting) as mappings; malformed lines are reported. */
|
|
37
|
+
export declare function parseVarFileMappings(raw: string | undefined): {
|
|
38
|
+
mappings: VarFileMapping[];
|
|
39
|
+
invalid: string[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The directories a pull request's scan must cover, from the layouts of both
|
|
43
|
+
* sides and the files it changed (relative to the scan directory). A root is
|
|
44
|
+
* reached when a changed file lies in a directory of its closure, is one of
|
|
45
|
+
* its variable files, or is named in its configuration (a policy read with
|
|
46
|
+
* `file()`, wherever it lives); every directory of a reached root's closure is kept.
|
|
47
|
+
* A changed configuration file no root reaches keeps its own directory, as a
|
|
48
|
+
* whole-tree scan would have scanned it. Null — scan everything — when a
|
|
49
|
+
* scanner or Infraweaver settings file changed.
|
|
50
|
+
*/
|
|
51
|
+
export declare function scopeFor(layouts: readonly TreeLayout[], changed: Iterable<string>): string[] | null;
|
|
52
|
+
/**
|
|
53
|
+
* The directories to leave out of a scan that should cover only `keep`: every
|
|
54
|
+
* configuration directory that is neither kept nor above a kept one, and only
|
|
55
|
+
* the topmost of those, since skipping a directory skips what is beneath it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function skipDirsFor(tfDirs: readonly string[], keep: readonly string[]): string[];
|
|
@@ -33,7 +33,7 @@ export interface Concern {
|
|
|
33
33
|
* ✗→✓ verifiable. `reviewer` marks a concern loaded from a terraform-reviewer
|
|
34
34
|
* findings.json whose original tool isn't one Infraweaver re-runs (tfsec / infracost
|
|
35
35
|
* / llm) — its provenance lives in `rule_id`. */
|
|
36
|
-
source: "terraform-fmt" | "terraform-validate" | "tflint" | "trivy" | "checkov" | "infraweaver" | "reviewer";
|
|
36
|
+
source: "terraform-fmt" | "terraform-validate" | "tflint" | "trivy" | "checkov" | "infraweaver" | "betterleaks" | "reviewer";
|
|
37
37
|
/** original namespaced rule, e.g. "trivy:AVD-AWS-0088" */
|
|
38
38
|
rule_id: string;
|
|
39
39
|
severity: Severity;
|
|
@@ -25,8 +25,6 @@ export interface SkippedSuggestion {
|
|
|
25
25
|
path: string;
|
|
26
26
|
reason: string;
|
|
27
27
|
}
|
|
28
|
-
/** The text of a comment's `suggestion` block, without its closing newline. */
|
|
29
|
-
export declare function suggestionText(body: string): string | undefined;
|
|
30
28
|
export declare function planApply(input: {
|
|
31
29
|
comments: readonly ReviewCommentLike[];
|
|
32
30
|
/** root comment id → whether its thread is still open */
|
package/dist/toolState.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DiffCoverageState } from "./vendor/@infraweaver-io/ci/diffCoverage
|
|
|
2
2
|
import { type ProgressComment, type ProgressCommentType } from "./vendor/@infraweaver-io/ci/progressComment.js";
|
|
3
3
|
import type { AgentUsage } from "./vendor/@infraweaver-io/models/usage.js";
|
|
4
4
|
import { DispatchRecorder } from "./agents/dispatchMetrics.js";
|
|
5
|
+
import type { StagedReview } from "./mcp/review.js";
|
|
5
6
|
import { type ScanSession } from "./mcp/terraform/scanSession.js";
|
|
6
7
|
import type { PrepResult } from "./prep/types.js";
|
|
7
8
|
import type { AgentDiagnostic } from "./utils/agent/agentHangReport.js";
|
|
@@ -91,6 +92,7 @@ export interface ToolState {
|
|
|
91
92
|
beforeSha?: string;
|
|
92
93
|
selectedMode?: string;
|
|
93
94
|
remediationPrsOpened?: number;
|
|
95
|
+
pushedHeads?: Map<string, string>;
|
|
94
96
|
scanSession?: ScanSession;
|
|
95
97
|
orgModuleHcl?: Map<string, string>;
|
|
96
98
|
prepushFailureCount: number;
|
|
@@ -103,6 +105,7 @@ export interface ToolState {
|
|
|
103
105
|
nodeId: string;
|
|
104
106
|
reviewedSha: string | undefined;
|
|
105
107
|
};
|
|
108
|
+
stagedReview?: StagedReview | undefined;
|
|
106
109
|
reviewReplies?: Map<number, {
|
|
107
110
|
commentId: number;
|
|
108
111
|
url: string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Codex OAuth refresh (fetch) + JWT exp decoding (jose, decode-only).
|
|
3
3
|
*
|
|
4
4
|
* Deliberately free of node:child_process / spawn / mkdtemp: the Cloud's
|
|
5
5
|
* Next.js server imports `refreshCodexAuthBody` through the
|
package/dist/utils/cli.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI utilities
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
5
|
-
/**
|
|
6
|
-
* Finds a CLI executable path by checking if it's installed globally
|
|
7
|
-
* @param name The name of the CLI executable to find
|
|
8
|
-
* @returns The path to the CLI executable, or null if not found
|
|
9
|
-
*/
|
|
10
|
-
export declare function findCliPath(name: string): string | null;
|
|
4
|
+
export { formatJsonValue, formatUsageSummary, log, writeSummary } from "./log.js";
|
|
@@ -62,7 +62,7 @@ export declare function buildReportBody(toolState: ToolState): {
|
|
|
62
62
|
findings: {
|
|
63
63
|
concern_id: string;
|
|
64
64
|
concern_key: string;
|
|
65
|
-
source: "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy";
|
|
65
|
+
source: "betterleaks" | "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy";
|
|
66
66
|
rule_id: string;
|
|
67
67
|
severity: "critical" | "high" | "info" | "low" | "medium";
|
|
68
68
|
category: "correctness" | "cost" | "security" | "style";
|
|
@@ -55,7 +55,7 @@ import { type InfraweaverFileValues } from "./infraweaverConfig.js";
|
|
|
55
55
|
* asserts that partition, so a key added later cannot silently default to
|
|
56
56
|
* "PR-controllable".
|
|
57
57
|
*/
|
|
58
|
-
export declare const GATE_RELEVANT_CONFIG_KEYS: readonly ["allowed_paths", "protected_paths", "required_controls", "severity_threshold", "autonomy_threshold", "tools_enabled", "scan_scope", "iac_languages", "repeated_literal_threshold"];
|
|
58
|
+
export declare const GATE_RELEVANT_CONFIG_KEYS: readonly ["allowed_paths", "protected_paths", "required_controls", "severity_threshold", "autonomy_threshold", "tools_enabled", "scan_scope", "iac_languages", "repeated_literal_threshold", "var_files", "review_comment_limit"];
|
|
59
59
|
/**
|
|
60
60
|
* Keys a PR may set from its own head: none can hide a finding, relax a
|
|
61
61
|
* guardrail, or shrink what is scanned. `language` is prose only; `grouping` is
|
|
@@ -31,7 +31,7 @@ export declare const INFRAWEAVER_CONFIG_FILENAMES: readonly [".infraweaver.json"
|
|
|
31
31
|
/** the repo-level keys `.infraweaver.yml` may set. Each is the snake_case name of
|
|
32
32
|
* the matching action input, so the file value can be fed straight through the
|
|
33
33
|
* input's own parser in `resolvePayload`. */
|
|
34
|
-
export declare const INFRAWEAVER_CONFIG_KEYS: readonly ["tools_enabled", "protected_paths", "allowed_paths", "scan_scope", "iac_languages", "language", "severity_threshold", "grouping", "autonomy_threshold", "module_catalogue", "repeated_literal_threshold", "required_controls"];
|
|
34
|
+
export declare const INFRAWEAVER_CONFIG_KEYS: readonly ["tools_enabled", "protected_paths", "allowed_paths", "scan_scope", "iac_languages", "language", "severity_threshold", "grouping", "autonomy_threshold", "module_catalogue", "repeated_literal_threshold", "required_controls", "var_files", "review_comment_limit"];
|
|
35
35
|
export type InfraweaverConfigKey = (typeof INFRAWEAVER_CONFIG_KEYS)[number];
|
|
36
36
|
/** normalized string values (lists newline-joined), keyed by input name. */
|
|
37
37
|
export type InfraweaverFileValues = Partial<Record<InfraweaverConfigKey, string>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PayloadEvent } from "../../external.js";
|
|
2
|
+
import { type VarFileMapping } from "../../mcp/terraform/treeLayout.js";
|
|
2
3
|
import type { RepoSettings } from "../cloud/runContext.js";
|
|
3
4
|
export { parseMode, resolveEffectivePrompt, VETTED_MODE_PROMPTS, } from "../prompt/modePrompts.js";
|
|
4
5
|
export { parseConcernIds, parseLanguage, parseRefactorSource, type RefactorSource, type UserFacingLanguage, withConcernFocus, withLanguageDirective, withRefactorSource, } from "../prompt/promptDirectives.js";
|
|
@@ -45,6 +46,8 @@ export declare const Inputs: import("arktype/internal/variants/object.ts").Objec
|
|
|
45
46
|
protected_paths?: string | undefined;
|
|
46
47
|
autonomy_threshold?: string | undefined;
|
|
47
48
|
repeated_literal_threshold?: string | undefined;
|
|
49
|
+
var_files?: string | undefined;
|
|
50
|
+
review_comment_limit?: string | undefined;
|
|
48
51
|
required_controls?: string | undefined;
|
|
49
52
|
gitleaks?: string | undefined;
|
|
50
53
|
cost_increase_block_usd?: string | undefined;
|
|
@@ -129,6 +132,8 @@ managedCredentials?: {
|
|
|
129
132
|
protectedPaths: string[] | undefined;
|
|
130
133
|
autonomyThreshold: string | undefined;
|
|
131
134
|
repeatedLiteralThreshold: number | undefined;
|
|
135
|
+
varFiles: VarFileMapping[] | undefined;
|
|
136
|
+
reviewCommentLimit: number;
|
|
132
137
|
requiredControls: string[] | undefined;
|
|
133
138
|
gitleaks: boolean;
|
|
134
139
|
costIncreaseBlockUsd: number | undefined;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* [[toolSelection]].
|
|
13
13
|
*/
|
|
14
14
|
/** every external tool the engine can be configured to run. */
|
|
15
|
-
export type ToolId = "terraform" | "tflint" | "trivy" | "checkov" | "infracost" | "gitleaks" | "conftest" | "terratest" | "terratag" | "terraform_mcp" | "opentofu_mcp";
|
|
15
|
+
export type ToolId = "terraform" | "tflint" | "trivy" | "checkov" | "infracost" | "gitleaks" | "betterleaks" | "conftest" | "terratest" | "terratag" | "terraform_mcp" | "opentofu_mcp";
|
|
16
16
|
/**
|
|
17
17
|
* Coarse licence family the gate reasons about:
|
|
18
18
|
* - `permissive` — MIT / Apache-2.0 / BSD / ISC: default-enabled.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one unified-diff reader: hunk headers, line classification and per-side
|
|
3
|
+
* line numbering for every caller that walks a patch — a single GitHub `patch`
|
|
4
|
+
* fragment (hunks only, no file headers), a truncated review-comment `diff_hunk`,
|
|
5
|
+
* or a multi-file `git diff`.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately TOLERANT, and that is why this is not jsdiff's `parsePatch`: that
|
|
8
|
+
* one validates every hunk's line counts and throws on a mismatch, but GitHub's
|
|
9
|
+
* `diff_hunk` is cut off at the commented line by design, and the callers here are
|
|
10
|
+
* pure functions fed external text where a parse error must not cost the whole
|
|
11
|
+
* result. Lines are numbered from the hunk header and classified by their marker;
|
|
12
|
+
* the declared counts are used for exactly one thing (see below).
|
|
13
|
+
*/
|
|
14
|
+
/** a parsed `@@ -a,b +c,d @@` header; an omitted count is 1, as git writes it */
|
|
15
|
+
export type HunkHeader = {
|
|
16
|
+
oldStart: number;
|
|
17
|
+
oldCount: number;
|
|
18
|
+
newStart: number;
|
|
19
|
+
newCount: number;
|
|
20
|
+
};
|
|
21
|
+
export type DiffLine =
|
|
22
|
+
/** a `@@` hunk header */
|
|
23
|
+
{
|
|
24
|
+
kind: "hunk";
|
|
25
|
+
raw: string;
|
|
26
|
+
header: HunkHeader;
|
|
27
|
+
}
|
|
28
|
+
/** a `+++` new-side file header; `path` has `b/` stripped, `/dev/null` for a deletion */
|
|
29
|
+
| {
|
|
30
|
+
kind: "file";
|
|
31
|
+
raw: string;
|
|
32
|
+
path: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "add";
|
|
35
|
+
raw: string;
|
|
36
|
+
text: string;
|
|
37
|
+
newLine: number;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "remove";
|
|
40
|
+
raw: string;
|
|
41
|
+
text: string;
|
|
42
|
+
oldLine: number;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "context";
|
|
45
|
+
raw: string;
|
|
46
|
+
text: string;
|
|
47
|
+
oldLine: number;
|
|
48
|
+
newLine: number;
|
|
49
|
+
}
|
|
50
|
+
/** anything else: `diff --git`/`---`/`index` headers, ``,
|
|
51
|
+
* lines outside any hunk, or an unknown marker — never numbered */
|
|
52
|
+
| {
|
|
53
|
+
kind: "meta";
|
|
54
|
+
raw: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function parseHunkHeader(line: string): HunkHeader | null;
|
|
57
|
+
/**
|
|
58
|
+
* Classify and number every line of `diff`.
|
|
59
|
+
*
|
|
60
|
+
* `--- `/`+++ ` are file headers only OUTSIDE a hunk body: once a hunk's declared
|
|
61
|
+
* counts are used up (or before any hunk). Inside one they are a removed line
|
|
62
|
+
* starting `-- ` or an added line starting `++ `, which a marker-only reader would
|
|
63
|
+
* mistake for the next file. Everything else ignores the counts, so a truncated
|
|
64
|
+
* hunk or a hand-written one with wrong counts still numbers its lines.
|
|
65
|
+
*
|
|
66
|
+
* An empty line inside a hunk is a context line whose single space was stripped,
|
|
67
|
+
* except the input's LAST line — that is the artefact of a trailing newline.
|
|
68
|
+
*/
|
|
69
|
+
export declare function parseDiffLines(diff: string): DiffLine[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infraweaver",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "GitHub Action that remediates Terraform to best practices and opens one scoped pull request per concern.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-coding-agent",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"fixtures:state": "node scripts/synthesise-state.ts",
|
|
74
74
|
"gen:aws-prices": "node scripts/generate-aws-price-snapshot.ts",
|
|
75
75
|
"gen:azure-prices": "node scripts/generate-azure-price-snapshot.ts",
|
|
76
|
+
"gen:checkov-severities": "node scripts/generate-checkov-severities.ts",
|
|
76
77
|
"gen:crosswalk-digest": "node scripts/generate-crosswalk-digest.ts",
|
|
77
78
|
"knip": "knip",
|
|
78
79
|
"lint": "biome check .",
|
|
@@ -92,50 +93,55 @@
|
|
|
92
93
|
"upDeps": "pnpm up --latest"
|
|
93
94
|
},
|
|
94
95
|
"devDependencies": {
|
|
95
|
-
"@actions/cache": "^6.
|
|
96
|
+
"@actions/cache": "^6.3.0",
|
|
96
97
|
"@actions/core": "^3.0.1",
|
|
97
|
-
"@anthropic-ai/claude-code": "2.1.
|
|
98
|
-
"@biomejs/biome": "2.5.
|
|
98
|
+
"@anthropic-ai/claude-code": "2.1.285",
|
|
99
|
+
"@biomejs/biome": "2.5.15",
|
|
99
100
|
"@cdktf/hcl2json": "^0.21.0",
|
|
100
|
-
"@
|
|
101
|
-
"@infraweaver-io/
|
|
102
|
-
"@infraweaver-io/
|
|
103
|
-
"@infraweaver-io/
|
|
104
|
-
"@infraweaver-io/
|
|
105
|
-
"@infraweaver-io/
|
|
106
|
-
"@infraweaver-io/
|
|
107
|
-
"@infraweaver-io/
|
|
108
|
-
"@
|
|
101
|
+
"@fast-check/vitest": "^0.5.0",
|
|
102
|
+
"@infraweaver-io/attest": "^0.1.7",
|
|
103
|
+
"@infraweaver-io/ci": "^0.1.8",
|
|
104
|
+
"@infraweaver-io/core": "^0.1.5",
|
|
105
|
+
"@infraweaver-io/crosswalk": "^1.2.0",
|
|
106
|
+
"@infraweaver-io/models": "^0.1.6",
|
|
107
|
+
"@infraweaver-io/policy-spec": "^1.2.0",
|
|
108
|
+
"@infraweaver-io/report-contract": "^0.1.5",
|
|
109
|
+
"@infraweaver-io/sandbox": "^0.1.8",
|
|
110
|
+
"@modelcontextprotocol/client": "^2.2.0",
|
|
111
|
+
"@octokit/auth-app": "^8.3.1",
|
|
112
|
+
"@octokit/openapi-webhooks-types": "^12.1.0",
|
|
109
113
|
"@octokit/plugin-throttling": "^11.0.5",
|
|
110
114
|
"@octokit/rest": "^22.0.1",
|
|
111
|
-
"@
|
|
112
|
-
"@opencode-ai/sdk": "1.18.30",
|
|
115
|
+
"@opencode-ai/sdk": "1.18.33",
|
|
113
116
|
"@standard-schema/spec": "1.1.0",
|
|
114
117
|
"@stryker-mutator/core": "^10.0.0",
|
|
115
118
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
116
119
|
"@toon-format/toon": "^4.1.1",
|
|
117
|
-
"@types/node": "^26.
|
|
120
|
+
"@types/node": "^26.6.3",
|
|
118
121
|
"@types/turndown": "^5.0.6",
|
|
119
|
-
"@vitest/coverage-v8": "^5.0.
|
|
122
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
120
123
|
"ajv": "^8.20.0",
|
|
121
124
|
"arg": "^5.0.2",
|
|
122
|
-
"arkregex": "0.0.
|
|
123
|
-
"arktype": "2.2.
|
|
124
|
-
"dotenv": "^
|
|
125
|
+
"arkregex": "0.0.10",
|
|
126
|
+
"arktype": "2.2.5",
|
|
127
|
+
"dotenv": "^18.0.5",
|
|
125
128
|
"esbuild": "^0.28.2",
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
129
|
+
"fast-check": "^4.10.2",
|
|
130
|
+
"fastmcp": "^4.22.1",
|
|
131
|
+
"file-type": "^22.1.1",
|
|
132
|
+
"jose": "^6.2.12",
|
|
133
|
+
"knip": "^6.39.0",
|
|
134
|
+
"lefthook": "^2.1.16",
|
|
135
|
+
"opencode-ai": "1.18.33",
|
|
130
136
|
"package-manager-detector": "^1.8.0",
|
|
131
137
|
"picocolors": "^1.1.1",
|
|
132
138
|
"table": "^6.9.0",
|
|
133
139
|
"turndown": "^7.2.4",
|
|
134
140
|
"typescript": "7.0.2",
|
|
135
|
-
"undici": "^8.
|
|
136
|
-
"verkit": "0.
|
|
137
|
-
"vitest": "^5.0.
|
|
138
|
-
"yaml": "^2.9.
|
|
141
|
+
"undici": "^8.11.2",
|
|
142
|
+
"verkit": "0.5.0",
|
|
143
|
+
"vitest": "^5.0.3",
|
|
144
|
+
"yaml": "^2.9.1"
|
|
139
145
|
},
|
|
140
146
|
"engines": {
|
|
141
147
|
"node": ">=24",
|
package/src/agents/claude.ts
CHANGED
|
@@ -75,6 +75,11 @@ import {
|
|
|
75
75
|
MCP_SERVER_TOKEN_ENV,
|
|
76
76
|
mergeAgentUsage,
|
|
77
77
|
} from "#app/agents/shared";
|
|
78
|
+
import {
|
|
79
|
+
createToolPlaneWatch,
|
|
80
|
+
ToolPlaneLostError,
|
|
81
|
+
type ToolPlaneWatch,
|
|
82
|
+
} from "#app/agents/toolPlaneWatch";
|
|
78
83
|
import { writePolicyPath } from "#app/agents/writePolicy";
|
|
79
84
|
import { PRODUCT_NAME } from "#app/brand";
|
|
80
85
|
import { infraweaverMcpName } from "#app/external";
|
|
@@ -260,7 +265,7 @@ function writePretoolGateAssets(
|
|
|
260
265
|
* 'sonnet' | 'opus' | 'haiku') and takes precedence over what's set here —
|
|
261
266
|
* we don't pass it; the per-subagent `model` field is the right default.
|
|
262
267
|
*
|
|
263
|
-
* The `model` is route-aware: the bare Anthropic-API id `claude-sonnet-5`
|
|
268
|
+
* The `model` is route-aware: the bare Anthropic-API id `claude-sonnet-5-5`
|
|
264
269
|
* is only valid on the direct API. On Bedrock (`eu.anthropic.…`) and Vertex
|
|
265
270
|
* (`@`-versioned) routes that id is rejected, and claude-code does not
|
|
266
271
|
* translate per-agent model names — so on those routes we omit `model`
|
|
@@ -282,7 +287,7 @@ export function buildAgentsJson(routeAware?: { isBedrock: boolean; isVertex: boo
|
|
|
282
287
|
"Reads only — no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
|
|
283
288
|
prompt: REVIEWER_SYSTEM_PROMPT,
|
|
284
289
|
// omit on non-direct-API routes so the id can't be rejected by the provider.
|
|
285
|
-
...(inheritOrchestratorModel ? {} : { model: "claude-sonnet-5" }),
|
|
290
|
+
...(inheritOrchestratorModel ? {} : { model: "claude-sonnet-5-5" }),
|
|
286
291
|
},
|
|
287
292
|
};
|
|
288
293
|
return JSON.stringify(agents);
|
|
@@ -290,7 +295,7 @@ export function buildAgentsJson(routeAware?: { isBedrock: boolean; isVertex: boo
|
|
|
290
295
|
|
|
291
296
|
// ── model helpers ─────────────────────────────────────────────────────────────
|
|
292
297
|
|
|
293
|
-
// claude CLI expects bare model names (e.g. "claude-sonnet-5"), not provider-prefixed specifiers
|
|
298
|
+
// claude CLI expects bare model names (e.g. "claude-sonnet-5-5"), not provider-prefixed specifiers
|
|
294
299
|
export function stripProviderPrefix(specifier: string): string {
|
|
295
300
|
const slashIndex = specifier.indexOf("/");
|
|
296
301
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
@@ -558,8 +563,13 @@ interface ClaudeRunState {
|
|
|
558
563
|
// LLM10. The activity watchdog bounds silence; a remediation loop that keeps
|
|
559
564
|
// emitting tokens is never silent, so it needs its own bound. Aborting the
|
|
560
565
|
// spawn is what actually stops consumption — returning a flag would let the
|
|
561
|
-
// CLI keep burning tokens until it happened to finish.
|
|
562
|
-
|
|
566
|
+
// CLI keep burning tokens until it happened to finish. Aborted with the
|
|
567
|
+
// reason: a quota error, or the tool server being lost (see toolPlane).
|
|
568
|
+
stopRun: AbortController;
|
|
569
|
+
// tool_use id → tool name, so a tool result can be told apart by the tool
|
|
570
|
+
// that produced it.
|
|
571
|
+
toolNames: Map<string, string>;
|
|
572
|
+
toolPlane: ToolPlaneWatch;
|
|
563
573
|
// The source key must be unique PER INVOCATION, not per label: a `--resume`
|
|
564
574
|
// retry reuses the label but starts a fresh accumulator from zero, and the
|
|
565
575
|
// quota charges only increases — so a shared key would make every retry's
|
|
@@ -595,7 +605,9 @@ function newClaudeRunState(params: RunParams): ClaudeRunState {
|
|
|
595
605
|
accumulatedTokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
596
606
|
accumulatedCostUsd: 0,
|
|
597
607
|
tokensLogged: false,
|
|
598
|
-
|
|
608
|
+
stopRun: new AbortController(),
|
|
609
|
+
toolNames: new Map(),
|
|
610
|
+
toolPlane: createToolPlaneWatch(),
|
|
599
611
|
quotaSource: `${params.label}#${nextQuotaSourceId()}`,
|
|
600
612
|
recentStderr: [],
|
|
601
613
|
recentNonJsonStdout: [],
|
|
@@ -624,10 +636,10 @@ function timerFor(state: ClaudeRunState, label: string): ThinkingTimer {
|
|
|
624
636
|
|
|
625
637
|
function chargeTokenQuota(state: ClaudeRunState): void {
|
|
626
638
|
const quota = state.params.tokenQuota;
|
|
627
|
-
if (!quota || state.
|
|
639
|
+
if (!quota || state.stopRun.signal.aborted) return;
|
|
628
640
|
if (!quota.observe(state.quotaSource, state.accumulatedTokens)) return;
|
|
629
641
|
log.info(`» ${state.params.label}: ${quota.gap()}`);
|
|
630
|
-
state.
|
|
642
|
+
state.stopRun.abort(new TokenQuotaExceededError(quota.gap()));
|
|
631
643
|
}
|
|
632
644
|
|
|
633
645
|
// The money half of the same bound. Claude Code reports cost only on the
|
|
@@ -637,10 +649,10 @@ function chargeTokenQuota(state: ClaudeRunState): void {
|
|
|
637
649
|
// stops before spending anything.
|
|
638
650
|
function chargeCostQuota(state: ClaudeRunState): void {
|
|
639
651
|
const quota = state.params.costQuota;
|
|
640
|
-
if (!quota || state.
|
|
652
|
+
if (!quota || state.stopRun.signal.aborted) return;
|
|
641
653
|
if (!quota.observe(state.quotaSource, state.accumulatedCostUsd)) return;
|
|
642
654
|
log.info(`» ${state.params.label}: ${quota.gap()}`);
|
|
643
|
-
state.
|
|
655
|
+
state.stopRun.abort(new CostQuotaExceededError(quota.gap()));
|
|
644
656
|
}
|
|
645
657
|
|
|
646
658
|
function buildUsage(state: ClaudeRunState): AgentUsage | undefined {
|
|
@@ -674,6 +686,7 @@ function onSystemEvent(state: ClaudeRunState, event: ClaudeSystemEvent): void {
|
|
|
674
686
|
function onAssistantToolUse(state: ClaudeRunState, label: string, block: ContentBlock): void {
|
|
675
687
|
const { params } = state;
|
|
676
688
|
const toolName = block.name || "unknown";
|
|
689
|
+
if (block.id) state.toolNames.set(block.id, toolName);
|
|
677
690
|
// only the orchestrator's tool calls feed the diff-coverage tracker —
|
|
678
691
|
// a specialist subagent reading the diff cannot satisfy the primary
|
|
679
692
|
// reviewer's coverage obligation. Matches the opencode path's
|
|
@@ -814,6 +827,16 @@ function onUserEvent(state: ClaudeRunState, event: ClaudeUserEvent): void {
|
|
|
814
827
|
} else {
|
|
815
828
|
log.debug(withLabel(label, `» tool output: ${outputContent}`));
|
|
816
829
|
}
|
|
830
|
+
|
|
831
|
+
const lost = state.toolPlane.observe({
|
|
832
|
+
toolName: block.tool_use_id ? state.toolNames.get(block.tool_use_id) : undefined,
|
|
833
|
+
isError: block.is_error === true,
|
|
834
|
+
text: outputContent,
|
|
835
|
+
});
|
|
836
|
+
if (lost && !state.stopRun.signal.aborted) {
|
|
837
|
+
log.info(`» ${state.params.label}: ${lost.message}`);
|
|
838
|
+
state.stopRun.abort(lost);
|
|
839
|
+
}
|
|
817
840
|
}
|
|
818
841
|
}
|
|
819
842
|
}
|
|
@@ -1133,6 +1156,18 @@ function failRun(state: ClaudeRunState, error: unknown): ClaudeRunResult {
|
|
|
1133
1156
|
};
|
|
1134
1157
|
}
|
|
1135
1158
|
|
|
1159
|
+
// The same: the run was stopped on purpose, and why is already the message.
|
|
1160
|
+
if (error instanceof ToolPlaneLostError) {
|
|
1161
|
+
log.info(`» ${params.label} stopped after ${(duration / 1000).toFixed(1)}s: ${message}`);
|
|
1162
|
+
return {
|
|
1163
|
+
success: false,
|
|
1164
|
+
output: state.finalOutput || state.output.toString(),
|
|
1165
|
+
error: message,
|
|
1166
|
+
usage: buildUsage(state),
|
|
1167
|
+
sessionId: state.sessionId,
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1136
1171
|
const stderrContext = state.recentStderr.slice(-10).join("\n");
|
|
1137
1172
|
const diagnosis = state.lastProviderError
|
|
1138
1173
|
? `likely cause: ${state.lastProviderError}`
|
|
@@ -1171,9 +1206,9 @@ export async function runClaude(params: RunParams): Promise<ClaudeRunResult> {
|
|
|
1171
1206
|
// sit well under it, so no per-toolcall suspend bracketing is needed.
|
|
1172
1207
|
activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
1173
1208
|
onActivityTimeout: params.onActivityTimeout,
|
|
1174
|
-
//
|
|
1175
|
-
//
|
|
1176
|
-
abortSignal: state.
|
|
1209
|
+
// kills the CLI (and its group) the moment the run's token ceiling is
|
|
1210
|
+
// reached (chargeTokenQuota) or its tool server is lost (onUserEvent).
|
|
1211
|
+
abortSignal: state.stopRun.signal,
|
|
1177
1212
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1178
1213
|
// run claude in its own process group so SIGKILL on activity timeout /
|
|
1179
1214
|
// outer cancellation reaches any subprocesses it spawns (rg, file
|
|
@@ -38,16 +38,12 @@
|
|
|
38
38
|
import { WRITE_GATE_HELPERS_SOURCE } from "@infraweaver-io/sandbox/writeGateSource";
|
|
39
39
|
import { PRODUCT_NAME } from "#app/brand";
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
* The
|
|
43
|
-
* gate
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `claudePretoolGate.test.ts` asserts this equals the version pinned in
|
|
49
|
-
* `package.json` — that test fails on any bump, forcing the re-verification
|
|
50
|
-
* before the pin and this constant are updated together.
|
|
41
|
+
/*
|
|
42
|
+
* The subagent gate's `agent_id` discriminator (see the contract notes in the
|
|
43
|
+
* gate source below) is an assumption about claude-code's PreToolUse hook
|
|
44
|
+
* payload. The gate fails OPEN for subagents if claude-code ever stops
|
|
45
|
+
* populating `agent_id`, so it is worth re-checking `createBaseHookInput` when
|
|
46
|
+
* claude-code moves a long way.
|
|
51
47
|
*
|
|
52
48
|
* 2.1.266 verified 2026-09-26 against the schema embedded in the installed
|
|
53
49
|
* binary: the base hook input still declares `agent_id` as
|
|
@@ -70,10 +66,8 @@ import { PRODUCT_NAME } from "#app/brand";
|
|
|
70
66
|
* Scope of this verification, stated honestly: it is static inspection of the
|
|
71
67
|
* shipped binary. No CI job exercises the Claude subagent gate against a live
|
|
72
68
|
* agent — `security-tests.yml` is deliberately opencode-only (free keyless
|
|
73
|
-
* model; Claude Code cannot use an `opencode/*` gateway model).
|
|
74
|
-
* plus the tripwire test, IS the contract check. Keep both.
|
|
69
|
+
* model; Claude Code cannot use an `opencode/*` gateway model).
|
|
75
70
|
*/
|
|
76
|
-
export const CLAUDE_CODE_AGENT_ID_VERIFIED_VERSION = "2.1.266" as const;
|
|
77
71
|
|
|
78
72
|
/**
|
|
79
73
|
* Source written to `<ctx.tmpdir>/infraweaver-pretool-gate.mjs`. Plain ESM,
|
|
@@ -196,10 +190,8 @@ process.stdin.on("end", () => {
|
|
|
196
190
|
// subagent discriminator: on the orchestrator's main thread agent_id is
|
|
197
191
|
// undefined. agent_type can be set on the orchestrator itself via --agent,
|
|
198
192
|
// so it's not a reliable subagent discriminator on its own; agent_id is.
|
|
199
|
-
// contract verified against @anthropic-ai/claude-code 2.1.
|
|
200
|
-
//
|
|
201
|
-
// tripwire in claudePretoolGate.test.ts); re-verify createBaseHookInput if
|
|
202
|
-
// that bumps — if agent_id ever stops being populated the gate fails OPEN.
|
|
193
|
+
// contract last verified against @anthropic-ai/claude-code 2.1.266 — if
|
|
194
|
+
// agent_id ever stops being populated the gate fails OPEN.
|
|
203
195
|
if (!agentId) process.exit(0);
|
|
204
196
|
const bare = stripMcpPrefix(toolName);
|
|
205
197
|
if (!SUBAGENT_DENIED_TOOLS.has(bare)) process.exit(0);
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* the read-only contract — only for log readability.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import pc from "picocolors";
|
|
23
|
+
|
|
22
24
|
export interface TaskDispatchInput {
|
|
23
25
|
description?: string | undefined;
|
|
24
26
|
subagent_type?: string | undefined;
|
|
@@ -168,9 +170,7 @@ export class SessionLabeler {
|
|
|
168
170
|
* as the dormant withLogPrefix-based ones.
|
|
169
171
|
*/
|
|
170
172
|
export function formatWithLabel(label: string, message: string): string {
|
|
171
|
-
const
|
|
172
|
-
const RESET = "\x1b[0m";
|
|
173
|
-
const colored = `${MAGENTA}[${label}]${RESET} `;
|
|
173
|
+
const colored = `${pc.magenta(`[${label}]`)} `;
|
|
174
174
|
return message
|
|
175
175
|
.split("\n")
|
|
176
176
|
.map((line) => `${colored}${line}`)
|