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.
Files changed (175) hide show
  1. package/README.md +2 -2
  2. package/dist/agents/claude.d.ts +1 -1
  3. package/dist/agents/claudePretoolGate.d.ts +0 -36
  4. package/dist/agents/toolPlaneWatch.d.ts +31 -0
  5. package/dist/cli.mjs +31903 -18736
  6. package/dist/external.d.ts +2 -1
  7. package/dist/index.js +31987 -18749
  8. package/dist/internal.js +132 -52
  9. package/dist/mcp/baseBranch.d.ts +41 -0
  10. package/dist/mcp/changeSummary.d.ts +1 -1
  11. package/dist/mcp/findingAudit.d.ts +2 -0
  12. package/dist/mcp/guardrails.d.ts +38 -0
  13. package/dist/mcp/localContext.d.ts +1 -1
  14. package/dist/mcp/moduleExtraction.d.ts +5 -0
  15. package/dist/mcp/pr.d.ts +0 -31
  16. package/dist/mcp/reproveSuggestions.d.ts +27 -0
  17. package/dist/mcp/review.d.ts +126 -2
  18. package/dist/mcp/reviewCommentLimit.d.ts +17 -0
  19. package/dist/mcp/reviewComments.d.ts +9 -5
  20. package/dist/mcp/reviewDedup.d.ts +1 -0
  21. package/dist/mcp/reviewMarkers.d.ts +10 -0
  22. package/dist/mcp/reviewProvenance.d.ts +20 -6
  23. package/dist/mcp/shared.d.ts +15 -0
  24. package/dist/mcp/suggestionProver.d.ts +3 -2
  25. package/dist/mcp/terraform/checkovSeverities.d.ts +7 -0
  26. package/dist/mcp/terraform/consolidation.d.ts +26 -0
  27. package/dist/mcp/terraform/cost.d.ts +2 -2
  28. package/dist/mcp/terraform/currency.d.ts +11 -0
  29. package/dist/mcp/terraform/deltaSummary.d.ts +11 -1
  30. package/dist/mcp/terraform/emittedArtefacts.d.ts +11 -0
  31. package/dist/mcp/terraform/evidence.d.ts +10 -0
  32. package/dist/mcp/terraform/gateScan.d.ts +2 -16
  33. package/dist/mcp/terraform/hcl.d.ts +15 -0
  34. package/dist/mcp/terraform/initArtefacts.d.ts +21 -0
  35. package/dist/mcp/terraform/localModules.d.ts +29 -0
  36. package/dist/mcp/terraform/moduleFindings.d.ts +38 -0
  37. package/dist/mcp/terraform/refactor/attribution.d.ts +1 -1
  38. package/dist/mcp/terraform/refactor/equivalence.d.ts +7 -0
  39. package/dist/mcp/terraform/refactor/expressions.d.ts +62 -0
  40. package/dist/mcp/terraform/refactor/references.d.ts +15 -0
  41. package/dist/mcp/terraform/refactor/resources.d.ts +11 -0
  42. package/dist/mcp/terraform/refactor/substitution.d.ts +55 -37
  43. package/dist/mcp/terraform/scanDelta.d.ts +28 -0
  44. package/dist/mcp/terraform/scanSession.d.ts +13 -0
  45. package/dist/mcp/terraform/scanners.d.ts +67 -18
  46. package/dist/mcp/terraform/secretScan.d.ts +19 -0
  47. package/dist/mcp/terraform/standardsReport.d.ts +1 -1
  48. package/dist/mcp/terraform/tools/plan.d.ts +1 -1
  49. package/dist/mcp/terraform/tools/scan.d.ts +7 -7
  50. package/dist/mcp/terraform/tools/validate.d.ts +7 -2
  51. package/dist/mcp/terraform/tools/verifyRemediation.d.ts +1 -1
  52. package/dist/mcp/terraform/treeLayout.d.ts +57 -0
  53. package/dist/mcp/terraform/types.d.ts +1 -1
  54. package/dist/phases/applyPlan.d.ts +0 -2
  55. package/dist/toolState.d.ts +3 -0
  56. package/dist/utils/agent/codexOAuth.d.ts +1 -1
  57. package/dist/utils/cli.d.ts +1 -7
  58. package/dist/utils/cloud/cloudReport.d.ts +1 -1
  59. package/dist/utils/config/baseRefConfig.d.ts +1 -1
  60. package/dist/utils/config/infraweaverConfig.d.ts +1 -1
  61. package/dist/utils/config/payload.d.ts +5 -0
  62. package/dist/utils/markdownTable.d.ts +6 -0
  63. package/dist/utils/setup/toolLicensing.d.ts +1 -1
  64. package/dist/utils/unifiedDiff.d.ts +69 -0
  65. package/package.json +34 -28
  66. package/src/agents/claude.ts +48 -13
  67. package/src/agents/claudePretoolGate.ts +9 -17
  68. package/src/agents/sessionLabeler.ts +3 -3
  69. package/src/agents/toolPlaneWatch.ts +71 -0
  70. package/src/external.ts +3 -1
  71. package/src/mcp/assess.ts +8 -8
  72. package/src/mcp/baseBranch.ts +96 -0
  73. package/src/mcp/changeSummary.ts +10 -11
  74. package/src/mcp/checkSuite.ts +7 -10
  75. package/src/mcp/checkout.ts +13 -40
  76. package/src/mcp/crosswalk.ts +5 -0
  77. package/src/mcp/findingAudit.ts +1 -1
  78. package/src/mcp/git.ts +44 -2
  79. package/src/mcp/guardrails.ts +188 -36
  80. package/src/mcp/localContext.ts +2 -0
  81. package/src/mcp/localServer.ts +1 -0
  82. package/src/mcp/moduleExtraction.ts +17 -5
  83. package/src/mcp/moduleTests.ts +9 -2
  84. package/src/mcp/modules.ts +10 -7
  85. package/src/mcp/pr.ts +17 -65
  86. package/src/mcp/reproveSuggestions.ts +86 -0
  87. package/src/mcp/review.ts +249 -75
  88. package/src/mcp/reviewAnchor.ts +11 -23
  89. package/src/mcp/reviewCommentLimit.ts +79 -0
  90. package/src/mcp/reviewComments.ts +34 -81
  91. package/src/mcp/reviewDedup.ts +1 -1
  92. package/src/mcp/reviewMarkers.ts +25 -2
  93. package/src/mcp/reviewProvenance.ts +56 -21
  94. package/src/mcp/roots.ts +3 -2
  95. package/src/mcp/server.ts +2 -1
  96. package/src/mcp/shared.ts +40 -1
  97. package/src/mcp/shell.ts +27 -28
  98. package/src/mcp/suggestionProver.ts +8 -4
  99. package/src/mcp/terraform/checkovSeverities.ts +1156 -0
  100. package/src/mcp/terraform/concernResult.ts +2 -1
  101. package/src/mcp/terraform/consolidation.ts +232 -19
  102. package/src/mcp/terraform/cost.ts +16 -7
  103. package/src/mcp/terraform/cspm.ts +8 -4
  104. package/src/mcp/terraform/currency.ts +28 -12
  105. package/src/mcp/terraform/decisions.ts +1 -1
  106. package/src/mcp/terraform/deltaSummary.ts +50 -1
  107. package/src/mcp/terraform/emittedArtefacts.ts +63 -0
  108. package/src/mcp/terraform/evidence.ts +23 -3
  109. package/src/mcp/terraform/gateScan.ts +191 -128
  110. package/src/mcp/terraform/hcl.ts +27 -2
  111. package/src/mcp/terraform/idleFloor.ts +2 -1
  112. package/src/mcp/terraform/initArtefacts.ts +76 -0
  113. package/src/mcp/terraform/localModules.ts +112 -0
  114. package/src/mcp/terraform/moduleDocs.ts +2 -5
  115. package/src/mcp/terraform/moduleFindings.ts +102 -0
  116. package/src/mcp/terraform/moduleVersionConstraints.ts +1 -1
  117. package/src/mcp/terraform/planPairs.ts +2 -20
  118. package/src/mcp/terraform/policyGate.ts +0 -15
  119. package/src/mcp/terraform/refactor/attribution.ts +39 -5
  120. package/src/mcp/terraform/refactor/equivalence.ts +49 -10
  121. package/src/mcp/terraform/refactor/expressions.ts +323 -0
  122. package/src/mcp/terraform/refactor/references.ts +101 -0
  123. package/src/mcp/terraform/refactor/resources.ts +79 -7
  124. package/src/mcp/terraform/refactor/substitution.ts +852 -256
  125. package/src/mcp/terraform/scanDelta.ts +34 -0
  126. package/src/mcp/terraform/scanSession.ts +30 -0
  127. package/src/mcp/terraform/scannerCache.ts +4 -0
  128. package/src/mcp/terraform/scanners.ts +553 -233
  129. package/src/mcp/terraform/secretScan.ts +131 -0
  130. package/src/mcp/terraform/standardsReport.ts +14 -13
  131. package/src/mcp/terraform/tools/consolidationCandidates.ts +3 -0
  132. package/src/mcp/terraform/tools/emitEvidence.ts +13 -5
  133. package/src/mcp/terraform/tools/emitOscal.ts +3 -2
  134. package/src/mcp/terraform/tools/emitVex.ts +10 -4
  135. package/src/mcp/terraform/tools/equivalenceCheck.ts +139 -21
  136. package/src/mcp/terraform/tools/infracostDiff.ts +2 -2
  137. package/src/mcp/terraform/tools/plan.ts +30 -17
  138. package/src/mcp/terraform/tools/reviewScanDelta.ts +3 -0
  139. package/src/mcp/terraform/tools/scan.ts +5 -3
  140. package/src/mcp/terraform/tools/standardsReport.ts +1 -1
  141. package/src/mcp/terraform/tools/validate.ts +20 -5
  142. package/src/mcp/terraform/tools/verifyRemediation.ts +14 -7
  143. package/src/mcp/terraform/tools/versionCurrency.ts +3 -1
  144. package/src/mcp/terraform/treeLayout.ts +378 -0
  145. package/src/mcp/terraform/types.ts +4 -1
  146. package/src/mcp/terraform/versionRequirements.ts +11 -2
  147. package/src/modes/__snapshots__/assemblePrompt.test.ts.snap +2 -0
  148. package/src/modes/modernize-deprecated.ts +2 -2
  149. package/src/modes/refactor.ts +9 -9
  150. package/src/modes/remediate.ts +1 -1
  151. package/src/modes/terraform-code-review.ts +6 -3
  152. package/src/modes/update-dependencies.ts +6 -6
  153. package/src/phases/applyPlan.ts +33 -14
  154. package/src/phases/applySuggestions.ts +2 -2
  155. package/src/phases/runAgentWithWatchdogs.ts +2 -1
  156. package/src/toolState.ts +7 -0
  157. package/src/utils/agent/codexOAuth.ts +7 -7
  158. package/src/utils/agent/openCodeModels.ts +4 -6
  159. package/src/utils/cli.ts +1 -31
  160. package/src/utils/cloud/runContext.ts +1 -8
  161. package/src/utils/config/baseRefConfig.ts +6 -0
  162. package/src/utils/config/infraweaverConfig.ts +2 -0
  163. package/src/utils/config/payload.ts +41 -0
  164. package/src/utils/github/assets.ts +1 -6
  165. package/src/utils/github/github.ts +42 -150
  166. package/src/utils/github/token.ts +6 -11
  167. package/src/utils/log.ts +4 -5
  168. package/src/utils/markdownTable.ts +11 -0
  169. package/src/utils/prompt/changeImpact.ts +18 -26
  170. package/src/utils/prompt/instructions.ts +1 -1
  171. package/src/utils/prompt/promptDirectives.ts +1 -1
  172. package/src/utils/prompt/toolSelection.ts +11 -2
  173. package/src/utils/setup/toolLicensing.ts +7 -0
  174. package/src/utils/telemetry.ts +2 -1
  175. 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 */
@@ -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
- * Pure-stdlib (fetch + Buffer) Codex OAuth refresh + JWT exp decoding.
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
@@ -1,10 +1,4 @@
1
1
  /**
2
2
  * CLI utilities
3
3
  */
4
- export { formatIndentedField, formatJsonValue, formatUsageSummary, log, writeSummary, } from "./log.js";
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;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Flatten text into one markdown table cell: escape pipes (an unescaped `|`
3
+ * starts a new column and shears the row) and fold newlines, which would end the
4
+ * row, into single spaces.
5
+ */
6
+ export declare function tableCell(text: string): string;
@@ -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.7",
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.2.0",
96
+ "@actions/cache": "^6.3.0",
96
97
  "@actions/core": "^3.0.1",
97
- "@anthropic-ai/claude-code": "2.1.266",
98
- "@biomejs/biome": "2.5.12",
98
+ "@anthropic-ai/claude-code": "2.1.285",
99
+ "@biomejs/biome": "2.5.15",
99
100
  "@cdktf/hcl2json": "^0.21.0",
100
- "@infraweaver-io/attest": "^0.1.6",
101
- "@infraweaver-io/ci": "^0.1.7",
102
- "@infraweaver-io/core": "^0.1.4",
103
- "@infraweaver-io/crosswalk": "^1.1.0",
104
- "@infraweaver-io/models": "^0.1.5",
105
- "@infraweaver-io/policy-spec": "^1.1.0",
106
- "@infraweaver-io/report-contract": "^0.1.4",
107
- "@infraweaver-io/sandbox": "^0.1.7",
108
- "@modelcontextprotocol/client": "^2.0.0",
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
- "@octokit/webhooks-types": "^7.6.1",
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.4.0",
120
+ "@types/node": "^26.6.3",
118
121
  "@types/turndown": "^5.0.6",
119
- "@vitest/coverage-v8": "^5.0.0",
122
+ "@vitest/coverage-v8": "^5.0.3",
120
123
  "ajv": "^8.20.0",
121
124
  "arg": "^5.0.2",
122
- "arkregex": "0.0.8",
123
- "arktype": "2.2.3",
124
- "dotenv": "^17.4.2",
125
+ "arkregex": "0.0.10",
126
+ "arktype": "2.2.5",
127
+ "dotenv": "^18.0.5",
125
128
  "esbuild": "^0.28.2",
126
- "fastmcp": "^4.20.6",
127
- "file-type": "^22.0.2",
128
- "knip": "^6.35.0",
129
- "opencode-ai": "1.18.30",
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.10.2",
136
- "verkit": "0.4.0",
137
- "vitest": "^5.0.0",
138
- "yaml": "^2.9.0"
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",
@@ -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
- quotaAbort: AbortController;
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
- quotaAbort: new AbortController(),
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.quotaAbort.signal.aborted) return;
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.quotaAbort.abort(new TokenQuotaExceededError(quota.gap()));
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.quotaAbort.signal.aborted) return;
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.quotaAbort.abort(new CostQuotaExceededError(quota.gap()));
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
- // LLM10: kills the CLI (and its group) the moment the run's token
1175
- // ceiling is reached. See chargeTokenQuota above.
1176
- abortSignal: state.quotaAbort.signal,
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 pinned `@anthropic-ai/claude-code` version against which the subagent
43
- * gate's `agent_id` discriminator was last verified (see the contract notes in
44
- * the gate source below). The gate fails OPEN for subagents if claude-code ever
45
- * stops populating `agent_id` in the PreToolUse hook payload, so a version bump
46
- * must be paired with a re-verification of `createBaseHookInput`.
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). So this pin,
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.220 (pinned in
200
- // package.json; see CLAUDE_CODE_AGENT_ID_VERIFIED_VERSION + the version
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 MAGENTA = "\x1b[35m";
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}`)