infraweaver 0.3.7 → 0.3.9

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 +31908 -18736
  6. package/dist/external.d.ts +2 -1
  7. package/dist/index.js +31992 -18749
  8. package/dist/internal.js +133 -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 +41 -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 +7 -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
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The `source` a module block in `fromDir` writes to call the module at
3
+ * `moduleDir`, both repo-relative POSIX paths. A local source is relative to the
4
+ * CALLER's directory, so `./modules/s3` is right only from the repo root; from
5
+ * `env/dev` the same module is `../../modules/s3`.
6
+ */
7
+ export declare function localSourceFrom(fromDir: string, moduleDir: string): string;
8
+ interface LocalModuleCall {
9
+ /** absolute path of the file declaring the call. */
10
+ file: string;
11
+ /** 1-based line of the `module` header; null in JSON configuration. */
12
+ line: number | null;
13
+ name: string;
14
+ source: string;
15
+ /** absolute directory the source resolves to. */
16
+ dir: string;
17
+ /** whether that directory holds any Terraform configuration. */
18
+ exists: boolean;
19
+ }
20
+ export interface LocalModuleClosure {
21
+ /** every local call reached from the root, missing ones included. */
22
+ calls: LocalModuleCall[];
23
+ /** the root and every existing local module directory it reaches. */
24
+ dirs: string[];
25
+ }
26
+ /** the configuration files directly in `dir` (not beneath it). */
27
+ export declare function configFilesIn(dir: string): string[];
28
+ export declare function localModuleClosure(rootDir: string): LocalModuleClosure;
29
+ export {};
@@ -0,0 +1,38 @@
1
+ import { type Concern } from "./types.js";
2
+ /** Where a scanner was told to put the modules it fetched, outside the tree. */
3
+ export interface FetchDir {
4
+ /** the directory the scanner's paths are relative to. */
5
+ cwd: string;
6
+ dir: string;
7
+ }
8
+ /**
9
+ * True when a scanner-reported path is a fetched module's copy, not repository
10
+ * code: under terraform's or checkov's module directory in the tree, or under
11
+ * the directory a scan had checkov download into. A path that merely leaves the
12
+ * scanned directory is not enough — a scan of one root reports the repository's
13
+ * own local modules as `../../modules/x`.
14
+ */
15
+ export declare function isFetchedModulePath(file: string, fetchedInto?: FetchDir): boolean;
16
+ /** 1-based line of `module "<name>"` in `file`, or null when it is not there. */
17
+ export declare function moduleCallLine(cwd: string, file: string, name: string): number | null;
18
+ /** `module.<name>…` → `<name>`, the call in the root that instantiates it. */
19
+ export declare function topModuleName(address: string): string | null;
20
+ export interface ModuleFinding {
21
+ /** the concern as the scanner reported it, against the fetched copy. */
22
+ concern: Concern;
23
+ /** the call that instantiates the module, in the repository. */
24
+ call: {
25
+ file: string;
26
+ line: number | null;
27
+ };
28
+ /** the module resource the finding is about, e.g. `module.data.aws_s3_bucket.this`. */
29
+ address: string;
30
+ }
31
+ /**
32
+ * Re-locate each finding to its call, saying in the evidence which module
33
+ * resource it is about and where in the module. Findings that land on the same
34
+ * call under the same rule are one concern — a concern's identity is its rule
35
+ * and location — so they are merged, every resource named: one concern per
36
+ * resource would be collapsed by the dedupe anyway, and silently.
37
+ */
38
+ export declare function relocateToCalls(findings: ModuleFinding[]): Concern[];
@@ -20,9 +20,9 @@
20
20
  * the walk rather than throwing — a truncated attribution makes the proof
21
21
  * refuse, which is the safe direction.
22
22
  */
23
- import type { FetchedModules } from "./workspace.js";
24
23
  import { type MovedBlock } from "./movedBlocks.js";
25
24
  import { type ResourceRef } from "./resources.js";
25
+ import type { FetchedModules } from "./workspace.js";
26
26
  import type { SourceFile } from "../tree.js";
27
27
  export interface Attribution {
28
28
  resources: ResourceRef[];
@@ -91,6 +91,13 @@ export interface EquivalenceResult {
91
91
  before: string;
92
92
  after: string;
93
93
  }[];
94
+ /** relocations that changed the provider configuration managing the resource
95
+ * (G16) — another region or account, with every argument unchanged. */
96
+ provider_diffs: {
97
+ resource: string;
98
+ before: string;
99
+ after: string;
100
+ }[];
94
101
  /** relocations where the repetition changed LEVEL — it sat on the resource and
95
102
  * now sits on a module call, or the reverse (G15). The instance key moves to a
96
103
  * different position in the address, so the same instances live at entirely
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A Terraform expression read into its SHAPE, for the refactor proof's value
3
+ * comparison ([[substitution]]).
4
+ *
5
+ * Not an evaluator and not a full HCL grammar. It recognises the forms whose
6
+ * value can be followed without running anything — literals, quoted templates,
7
+ * tuples, objects, traversals (`var.x`, `aws_s3_bucket.b.arn`, `data.t.n.json`)
8
+ * and a top-level conditional — and keeps everything else (function calls,
9
+ * operators, `for` expressions) as `raw`: the code text, with the traversals
10
+ * and strings inside it lifted out so they can still be resolved. A raw
11
+ * expression is only ever compared as "the same code over the same values",
12
+ * which can say equal, never different.
13
+ *
14
+ * Pure.
15
+ */
16
+ export type Step = {
17
+ attr: string;
18
+ } | {
19
+ index: Expr;
20
+ };
21
+ export type Expr = {
22
+ kind: "literal";
23
+ value: string | number | boolean | null;
24
+ }
25
+ /** a quoted string; `parts` alternate text and `${…}` interpolations. */
26
+ | {
27
+ kind: "template";
28
+ parts: (string | Expr)[];
29
+ } | {
30
+ kind: "tuple";
31
+ items: Expr[];
32
+ } | {
33
+ kind: "object";
34
+ entries: {
35
+ key: string;
36
+ value: Expr;
37
+ }[];
38
+ }
39
+ /** `root.step.step[index]…` — `root` is `var`, `local`, a resource type, … */
40
+ | {
41
+ kind: "traversal";
42
+ root: string;
43
+ steps: Step[];
44
+ text: string;
45
+ } | {
46
+ kind: "conditional";
47
+ cond: Expr;
48
+ whenTrue: Expr;
49
+ whenFalse: Expr;
50
+ }
51
+ /** anything else: code text interleaved with the expressions found inside it. */
52
+ | {
53
+ kind: "raw";
54
+ parts: (string | Expr)[];
55
+ }
56
+ /** text whose inside cannot be read safely — a heredoc, a template directive. */
57
+ | {
58
+ kind: "opaque";
59
+ text: string;
60
+ };
61
+ /** read one expression's text into its shape. */
62
+ export declare function parseExpression(text: string): Expr;
@@ -0,0 +1,15 @@
1
+ import type { MovedBlock } from "./movedBlocks.js";
2
+ import type { SourceFile } from "../tree.js";
3
+ export interface StaleReference {
4
+ file: string;
5
+ line: number;
6
+ /** the relocated address the reference still names, without instance key. */
7
+ address: string;
8
+ }
9
+ /**
10
+ * Every place in a relocation's root that still names the address it moved
11
+ * away from. Each reference found is a defect the proof must refuse.
12
+ */
13
+ export declare function staleReferences(after: readonly SourceFile[], relocations: readonly MovedBlock[]): StaleReference[];
14
+ /** The proof's reason for a set of stale references; null when there are none. */
15
+ export declare function staleReferenceReason(stale: readonly StaleReference[]): string | null;
@@ -39,7 +39,17 @@ export interface ResourceRef {
39
39
  * single instance. See [[Multiplicity]] — this is deliberately NOT folded into
40
40
  * `argNames`, because a correct extraction MOVES it onto the module call. */
41
41
  multiplicity: Multiplicity[];
42
+ /** the provider configuration that manages the resource, as the ROOT names it
43
+ * (`aws`, `aws.replica`): the resource's own `provider` meta-argument, or the
44
+ * default for its type, resolved through every module call's `providers`
45
+ * map. Absent on a hand-built ref, which is not compared. See
46
+ * [[moduleCallProviders]]. */
47
+ provider?: string;
42
48
  }
49
+ /** `module "<name>" { … }` call name → its `providers` map (the module's name
50
+ * for a configuration → the caller's), or null when the call passes none and
51
+ * the module inherits the caller's default configurations. */
52
+ export declare function moduleCallProviders(hcl: string): Map<string, Map<string, string> | null>;
43
53
  /**
44
54
  * How many instances of a resource exist, and keyed by what.
45
55
  *
@@ -142,4 +152,5 @@ export declare function parseResourcesWithArgs(hcl: string): {
142
152
  name: string;
143
153
  argNames: string[];
144
154
  multiplicity: Multiplicity | null;
155
+ provider: string | null;
145
156
  }[];
@@ -1,13 +1,12 @@
1
1
  /**
2
- * The VALUE half of a refactor proof: did each relocated argument keep the value
3
- * it had?
2
+ * The VALUE half of a refactor proof: does every resource the change touches
3
+ * still say what it said?
4
4
  *
5
5
  * [[equivalence]] compares argument NAMES and never values, for a good reason —
6
- * the names are what survives being moved behind a module boundary, and a static
7
- * read cannot evaluate an expression. That reading is exactly right for a b.3
8
- * extraction whose arguments move verbatim. It is a HOLE the moment the refactor
9
- * PARAMETERISES those arguments, which is what consolidating N environments onto
10
- * one shared module does to every value that differs between them:
6
+ * the names are what survives being moved behind a module boundary. On its own
7
+ * that is a hole wherever the change PARAMETERISES a value, which is what
8
+ * consolidating N environments onto one shared module does to every value that
9
+ * differs between them:
11
10
  *
12
11
  * before env/dev/main.tf bucket = "acme-dev-logs"
13
12
  * env/prod/main.tf bucket = "acme-prod-logs"
@@ -15,61 +14,80 @@
15
14
  * env/dev/main.tf module "logging" { bucket = "acme-prod-logs" } ← swapped
16
15
  * env/prod/main.tf module "logging" { bucket = "acme-dev-logs" } ← swapped
17
16
  *
18
- * Every existing gate passes that tree. The resource set is identical, the
19
- * argument names are identical, the `moved {}` blocks are complete, `terraform
20
- * validate` is clean, and — because both roots still declare exactly one bucket
21
- * each — so is a plan. The apply destroys prod.
17
+ * Every name-level gate passes that tree, and so does a plan. The apply
18
+ * destroys prod. The same hole has other doors: a shared module whose body was
19
+ * edited on the way (every caller changes, moved or not), a nested block whose
20
+ * value now comes from a call input, a `dynamic` block fed one rule fewer, a
21
+ * data source the moved resource reads.
22
22
  *
23
- * WHAT THIS DOES, AND ONLY THIS. For an argument whose after-value is a single
24
- * bare `var.<X>`, it follows `X` back UP the call chain to the value written at
25
- * the call site, and compares that against the before-value as TEXT. Both sides
26
- * are then expressions in the SAME root's scope, so `var.bucket_name` on one
27
- * side and `var.bucket_name` on the other really are the same value — which is
28
- * why the walk stops the moment it reaches the root and never tries to resolve a
29
- * root variable further. Nothing is evaluated, here or anywhere else in the
30
- * proof.
23
+ * WHAT THIS DOES. Every resource present on both sides — matched through the
24
+ * relocations — is read into VALUES ([[expressions]]) and compared. A value is
25
+ * followed, never run: `var.x` back up the call chain to what the call site
26
+ * wrote (or the module's default), `local.x` to its definition, `module.m.o` to
27
+ * the output's expression, a `data` source to its body, a reference to another
28
+ * resource to that resource's identity (through the relocations, so
29
+ * `aws_s3_bucket.data.id` in the root and `aws_s3_bucket.this.id` in the module
30
+ * are the same value). A `dynamic` block is expanded when its `for_each`
31
+ * resolves to a literal collection; a conditional is folded when its condition
32
+ * does; a template whose parts are all literals is joined. Nothing else is
33
+ * evaluated — a function call or an operator is compared only as "the same
34
+ * code over the same values".
31
35
  *
32
- * AND WHAT IT REFUSES TO SAY. `null` is NOT CHECKED and is used freely: an
33
- * after-value that is not a bare `var.<X>`, an input the call site does not set
34
- * and the module does not default, a module whose directory does not resolve. A
35
- * mismatch is only ever reported when both sides were actually read — the trade
36
- * is missed instances, never false ones.
36
+ * AND WHAT IT REFUSES TO SAY. A comparison has three outcomes: equal, CHANGED,
37
+ * or NOT CHECKED. CHANGED is reported only when the difference is established
38
+ * — two literals that differ, a collection of a different length, a field one
39
+ * side lacks, a block that has no counterpart at all. Anything that would need
40
+ * evaluating is NOT CHECKED. The trade is missed instances, never false ones.
41
+ *
42
+ * Root-scoped values are never resolved further: `var.x` in a root means
43
+ * whatever tfvars says on both sides, so it is equal to itself and to nothing
44
+ * else.
37
45
  *
38
46
  * Pure: two already-read trees and the relocations in, findings out.
39
47
  */
40
48
  import type { MovedBlock } from "./movedBlocks.js";
41
49
  import type { SourceFile } from "../tree.js";
42
50
  interface ValueSubstitution {
43
- /** the root the relocation happened in. */
51
+ /** the root module the resource belongs to. */
44
52
  root: string;
45
53
  /** the BEFORE address, as `expected_moves` spells it. */
46
54
  resource: string;
55
+ /** the argument, with a path into nested blocks: `versioning_configuration[0].status`. */
47
56
  argument: string;
48
- /** the value the argument had before the refactor, in the root's scope. */
57
+ /** the value as the before tree writes it. */
49
58
  before: string;
50
- /** the value written on the relocated resource — typically `var.<X>`. */
59
+ /** the value as the after tree writes it — often `var.<x>`. */
51
60
  after: string;
52
- /** what `after` resolves to AT THE CALL SITE, i.e. back in the root's scope.
53
- * `null` means NOT CHECKED — see `not_checked` for which case. */
61
+ /** what `after` resolves to, followed back to the root's scope. `null` when
62
+ * the comparison was NOT CHECKED. */
54
63
  after_resolved: string | null;
55
- /** `true`/`false` only when both sides were read; `null` = NOT CHECKED. */
64
+ /** `true`/`false` only when the comparison settled; `null` = NOT CHECKED. */
56
65
  match: boolean | null;
57
- /** why this argument was not checked, or null when it was. */
66
+ /** why this value was not checked, or null when it was. */
58
67
  not_checked: string | null;
59
68
  }
60
69
  export interface SubstitutionResult {
61
- /** every argument considered, checked or not. `null` means the question did
62
- * not arise: no relocation put a resource inside a module call, so there was
63
- * no parameter substitution to verify. Distinct from `[]`, which means
64
- * relocations were examined and none had a comparable argument. */
70
+ /** every value compared on a RELOCATED resource, plus every established
71
+ * change anywhere. `null` means the question did not arise: nothing moved
72
+ * and nothing changed. */
65
73
  value_substitutions: ValueSubstitution[] | null;
66
- /** the established mismatches — the subset with `match: false`. A non-empty
74
+ /** the established changes — the subset with `match: false`. A non-empty
67
75
  * list is a violation, not a warning. */
68
76
  value_mismatches: ValueSubstitution[];
77
+ /** calls of a module this tree does not contain — a registry module, a git
78
+ * module nothing fetched — whose `source` or `version` changed. The code the
79
+ * resources come from was swapped outside the tree, so no comparison here
80
+ * can say what changed: never a proof, in either direction. */
81
+ opaque_module_changes: {
82
+ root: string;
83
+ call: string;
84
+ before: string;
85
+ after: string;
86
+ }[];
69
87
  reasons: string[];
70
88
  }
71
89
  /**
72
- * Verify that every relocated argument's value survived being parameterised.
90
+ * Compare the values of every resource present on both sides.
73
91
  *
74
92
  * `moves` is `computeEquivalence`'s `expected_moves` — the authoritative set of
75
93
  * relocations, so this check and the proof it augments always speak about the
@@ -8,6 +8,11 @@ export interface DeltaFinding {
8
8
  block?: string;
9
9
  /** an introduced finding whose resource may be an undeclared rename. */
10
10
  possiblyPreexisting?: true;
11
+ /** with `possiblyPreexisting`: the vanished resource it pairs with on the base. */
12
+ renamedFrom?: {
13
+ address: string;
14
+ file: string;
15
+ };
11
16
  }
12
17
  export interface ScanDelta {
13
18
  introduced: DeltaFinding[];
@@ -60,6 +65,29 @@ export interface ScanDeltaInput {
60
65
  */
61
66
  export declare function findingKey(f: DeltaFinding): string;
62
67
  export declare function computeScanDelta(input: ScanDeltaInput): ScanDelta;
68
+ /** One resource that looks renamed (or moved to another directory) without a
69
+ * `moved` block, and how many introduced findings ride on it. */
70
+ export interface UndeclaredRename {
71
+ from: {
72
+ address: string;
73
+ dir: string;
74
+ };
75
+ to: {
76
+ address: string;
77
+ dir: string;
78
+ };
79
+ findings: number;
80
+ }
81
+ /**
82
+ * The possibly-pre-existing findings grouped by the rename behind them.
83
+ *
84
+ * Fifteen findings on one renamed bucket are one thing for the author to do —
85
+ * add a `moved` block — not fifteen, and without that block `terraform apply`
86
+ * destroys the old resource and creates the new one. So a verdict reads better
87
+ * naming the resource once than listing every rule it carries. Order follows
88
+ * the findings (which are sorted by location), so the output is stable.
89
+ */
90
+ export declare function undeclaredRenames(introduced: readonly DeltaFinding[]): UndeclaredRename[];
63
91
  /** Every `resource` / `data` address declared in one file. */
64
92
  export declare function declaredAddresses(hcl: string): string[];
65
93
  /** A scan delta pinned to the commits it compared. */
@@ -43,6 +43,7 @@ export interface ScanSession {
43
43
  baselineConcernKeys?: string[];
44
44
  baselineFmtDriftFiles?: string[];
45
45
  lastScanConcerns?: Concern[];
46
+ concernsById?: Map<string, Concern>;
46
47
  gateConcerns?: Concern[];
47
48
  policyRules?: unknown[] | null;
48
49
  policyGate?: PolicyGateResult;
@@ -73,6 +74,7 @@ export interface ScanSession {
73
74
  lastCostDirection?: "increase" | "decrease" | "no-change" | "unknown";
74
75
  lastIdleFloor?: IdleFloorReport | undefined;
75
76
  lastDrift?: DriftEvidenceStatement | undefined;
77
+ emittedArtefacts?: Record<string, string[]>;
76
78
  emittedSarifPath?: string;
77
79
  refactorEquivalence?: {
78
80
  equivalent: boolean;
@@ -129,6 +131,10 @@ export interface ScanSession {
129
131
  fingerprint: string;
130
132
  pairs: PairAnalysis;
131
133
  };
134
+ baselineValidateErrors?: {
135
+ base: string;
136
+ errors: string[];
137
+ };
132
138
  planFollowedUpgradeDetection?: boolean;
133
139
  }
134
140
  /**
@@ -153,6 +159,13 @@ export declare function createScanSession(): ScanSession;
153
159
  export declare function scanSessionOf(toolState: {
154
160
  scanSession?: ScanSession;
155
161
  }): ScanSession;
162
+ /** Publish a scan's reported concern set, and remember each concern for the
163
+ * rest of the run (see `concernsById`). */
164
+ export declare function recordScanConcerns(session: ScanSession, concerns: Concern[]): void;
165
+ /** Remember each concern for the rest of the run, so
166
+ * terraform_verify_remediation can key an id from ANY concern source — a
167
+ * findings file or an external report as much as a scan. */
168
+ export declare function recordConcernKeys(session: ScanSession, concerns: Concern[]): void;
156
169
  /**
157
170
  * true once terraform_scan / read_findings captured a pre-fix baseline this
158
171
  * session. terraform_verify_remediation key-maps requested ids against the
@@ -1,20 +1,40 @@
1
1
  import { type ToolsNetworkDirective } from "./scannerEgress.js";
2
+ import { type TreeLayout, type VarFileMapping } from "./treeLayout.js";
2
3
  import { type Concern, type ScannerOutcome } from "./types.js";
3
4
  import { type ResolvedToolSelection } from "../../utils/prompt/toolSelection.js";
4
- /**
5
- * Run `terraform init -backend=false` once per dir so `terraform validate` has
6
- * provider schemas to check against (Bug 3 / gap B). Without init, validate only
7
- * emits "missing required provider" — which VALIDATE_NOISE drops — so it was
8
- * effectively inert. `-backend=false` avoids needing real backend credentials;
9
- * `-input=false` keeps it non-interactive. Network-dependent and best-effort: if
10
- * it fails (offline, private module, etc.) validate still runs, just shallow.
11
- */
12
5
  /** Forget every init, so the next validate re-runs it — after its artefacts were removed. */
13
6
  export declare function resetTerraformInitCache(): void;
14
- export declare function scanFmt(cwd: string): ScannerOutcome;
7
+ /**
8
+ * The roots under `cwd` whose last `terraform init` failed, with init's error.
9
+ * Their validate is shallow — "Module not installed" is all it can say, and that
10
+ * is dropped as noise — so nothing that leans on validate may count them as
11
+ * checked.
12
+ */
13
+ export declare function initFailures(cwd: string): {
14
+ root: string;
15
+ error: string;
16
+ }[];
17
+ /** {@link initFailures}, the roots only. */
18
+ export declare function initFailedRoots(cwd: string): string[];
19
+ /**
20
+ * `terraform` when installed, else `tofu` — OpenTofu-only runners keep fmt
21
+ * coverage (identical CLI surface; the source label stays "terraform-fmt" so
22
+ * rule ids and content hashes never fork on the binary name).
23
+ *
24
+ * Safe to run alongside `terraform init`: `fmt -recursive` skips dot-directories,
25
+ * so it never reads the `.terraform/` tree init is writing (verified against the
26
+ * installed CLI — an unformatted file planted under `.terraform/` is not reported).
27
+ */
28
+ export declare function scanFmt(cwd: string): Promise<ScannerOutcome>;
15
29
  /** `terraform fmt -check -list=true` prints one unformatted file path per line. */
16
30
  export declare function parseFmtOutput(stdout: string, cwd?: string): Concern[];
17
- export declare function scanValidate(cwd: string, extraEnv?: Record<string, string>): ScannerOutcome;
31
+ /**
32
+ * Run `terraform validate` across EVERY root and aggregate. `validate` is the
33
+ * one scanner that's per-root (fmt/tflint/trivy/checkov are recursive over the
34
+ * whole tree), so a multi-root repo only catches subdir-root validate errors
35
+ * when we visit each root. Inits first, through the same queue a scan uses.
36
+ */
37
+ export declare function scanValidate(cwd: string, extraEnv?: Record<string, string>): Promise<ScannerOutcome>;
18
38
  /** parse `terraform validate -json`; keeps real errors, drops uninitialized-dir noise. */
19
39
  export declare function parseValidateOutput(stdout: string, cwd?: string): Concern[];
20
40
  export interface ProviderRequirement {
@@ -84,17 +104,30 @@ export interface LocatedResource extends ResourceArguments {
84
104
  * way as `collectProviderRequirements` — an unreadable file is skipped rather
85
105
  * than failing the walk. */
86
106
  export declare function collectResourceArguments(cwd: string): LocatedResource[];
87
- export declare function scanTflint(cwd: string, network?: ToolsNetworkDirective): ScannerOutcome;
107
+ export declare function scanTflint(cwd: string, network?: ToolsNetworkDirective): Promise<ScannerOutcome>;
88
108
  /** parse `tflint --format json` output into concerns. */
89
109
  export declare function parseTflintOutput(stdout: string, cwd?: string): Concern[];
110
+ /** One `trivy config` run: the variable files it loads, the directories it skips. */
111
+ interface TrivyRun {
112
+ tfVars: string[];
113
+ skipDirs: string[];
114
+ /** a root's run keeps only findings in that root's closure */
115
+ keep?: string[];
116
+ }
90
117
  /**
91
- * `trivy config` arguments for `cwd`. trivy evaluates variables from their
92
- * defaults unless told otherwise, so a value set in `terraform.tfvars` — an open
93
- * CIDR, a public flag — is invisible to it. With ONE root module its auto-loaded
94
- * variable files are passed, exactly as `terraform plan` would load them. With
95
- * several, trivy would apply one root's values to every root, so none are.
118
+ * The trivy runs that cover `layout`. trivy evaluates variables from their
119
+ * defaults unless told otherwise, so a value set in a `.tfvars` file is
120
+ * invisible to a plain run, and a file passed to a whole-tree run applies to
121
+ * every root in it. So the whole tree is scanned once without variable files,
122
+ * then each root that has some once per file set, with every other root
123
+ * skipped (a root's own directory alone would lose its `../modules`): its
124
+ * auto-loaded files, then those plus each `-var-file` file in turn, as each
125
+ * `terraform plan` would load them. A tree with a single root loads its
126
+ * auto-loaded files in the whole-tree run.
96
127
  */
97
- export declare function trivyArgs(cwd: string): string[];
128
+ export declare function trivyRuns(layout: TreeLayout, scope: readonly string[] | undefined): TrivyRun[];
129
+ /** `trivy config` arguments for one run over the scan directory. */
130
+ export declare function trivyArgs(run: TrivyRun): string[];
98
131
  /**
99
132
  * Parse `trivy config --format json` output into concerns. Trivy nests
100
133
  * misconfigurations under `Results[].Misconfigurations[]`, keyed to the result's
@@ -114,10 +147,19 @@ export declare function parseTrivyOutput(stdout: string, cwd?: string): Concern[
114
147
  * unreadable file just leaves the snippet null. Pure-ish (read-only I/O).
115
148
  */
116
149
  export declare function attachSourceSnippets(concerns: Concern[], cwd: string): Concern[];
150
+ /**
151
+ * checkov `--skip-path` values for the directories a scoped scan leaves out.
152
+ * checkov matches each as a regular expression against a path whose form
153
+ * depends on how it walked there, so a pattern is anchored at a path
154
+ * separator and the end; one that would also match a directory the scan
155
+ * keeps (`envs/prod` and `nested/envs/prod`) is not passed — the scope filter
156
+ * still drops its findings, the scan just reads it.
157
+ */
158
+ export declare function checkovSkipPaths(tfDirs: readonly string[], skip: readonly string[]): string[];
117
159
  /** parse `checkov -o json` output (object for one framework, array for several).
118
160
  * Each block's `check_type` names the framework that produced it (terraform /
119
161
  * cloudformation / kubernetes / …) — carried onto every concern as its language. */
120
- export declare function parseCheckovOutput(stdout: string, cwd?: string): Concern[];
162
+ export declare function parseCheckovOutput(stdout: string, cwd?: string, downloadDir?: string): Concern[];
121
163
  /**
122
164
  * Terraform files changed on the current branch vs the base. Returns null when
123
165
  * the base can't be determined (caller then falls back to a full scan).
@@ -150,6 +192,12 @@ export interface RunScannersOptions {
150
192
  * `"partial"` accordingly. Absent ⇒ open egress (today's behaviour) — deny is
151
193
  * opt-in. */
152
194
  toolsNetwork?: ToolsNetworkDirective | undefined;
195
+ /** the directories a scoped scan covers (`scopeFor`), relative to the scan
196
+ * directory; a finding elsewhere is dropped and roots outside are not
197
+ * initialised. Undefined scans the whole tree. */
198
+ scope?: string[] | undefined;
199
+ /** `-var-file` files whose root the `var_files` setting names. */
200
+ varFiles?: VarFileMapping[] | undefined;
153
201
  }
154
202
  /** run every scanner once over `cwd`. shared by `terraform_scan` and the
155
203
  * deterministic remediation verifier so both see the identical toolchain. A
@@ -210,3 +258,4 @@ export declare function regressionIdsByKey(current: {
210
258
  id: string;
211
259
  key: string;
212
260
  }[], baselineKeys: Set<string>): string[];
261
+ export {};
@@ -0,0 +1,19 @@
1
+ import { type Concern } from "./types.js";
2
+ export interface SecretScan {
3
+ ran: boolean;
4
+ /** why it did not run */
5
+ reason?: string;
6
+ concerns: Concern[];
7
+ }
8
+ /**
9
+ * Parse a betterleaks v2 JSON report into concerns, paths made relative to the
10
+ * scan directory (`prefix` is its path in the repository), and whether the
11
+ * scan completed: an incomplete one keeps the findings it made before it
12
+ * stopped. Throws on a report that is not v2's.
13
+ */
14
+ export declare function parseBetterleaksReport(json: string, prefix?: string): {
15
+ concerns: Concern[];
16
+ complete: boolean;
17
+ };
18
+ /** Scan the lines the commits after `mergeBase` add, from the scan directory `cwd`. */
19
+ export declare function scanAddedSecrets(cwd: string, mergeBase: string, tmp: string): Promise<SecretScan>;
@@ -212,7 +212,7 @@ export declare function buildStandardsReport(files: SourceFile[], concerns: Conc
212
212
  * offered — `buildStandardsReport` is the pure seam for a caller that already
213
213
  * holds both.
214
214
  */
215
- export declare function standardsReport(cwd: string): StandardsReport;
215
+ export declare function standardsReport(cwd: string): Promise<StandardsReport>;
216
216
  /**
217
217
  * The report as Markdown — the handover artefact.
218
218
  *
@@ -17,7 +17,7 @@ export declare function collectCloudCredentials(): Record<string, string>;
17
17
  export interface PlanRunners {
18
18
  /** `tmpdir` is where the saved plan file goes — outside the repository,
19
19
  * because it holds attribute values. */
20
- planOneRoot: (absDir: string, creds: Record<string, string>, tmpdir?: string) => RootPlanOutcome;
20
+ planOneRoot: (absDir: string, creds: Record<string, string>, tmpdir?: string) => Promise<RootPlanOutcome>;
21
21
  hasCloudCredentials: () => boolean;
22
22
  }
23
23
  export declare function runPlan(ctx: LocalToolContext, runners?: PlanRunners): Promise<Record<string, any> | import("../../shared.js").ToolOk<{
@@ -21,9 +21,9 @@ export declare function runScan(ctx: LocalToolContext, { scan_scope, severity_th
21
21
  scope: string;
22
22
  scope_note?: string;
23
23
  grouping: "file" | "rule";
24
- scanners_ran: ("checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
24
+ scanners_ran: ("betterleaks" | "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy")[];
25
25
  scanners_skipped: {
26
- source: "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy";
26
+ source: "betterleaks" | "checkov" | "infraweaver" | "reviewer" | "terraform-fmt" | "terraform-validate" | "tflint" | "trivy";
27
27
  reason: string | undefined;
28
28
  }[];
29
29
  scanners_partial?: import("../types.js").CoverageGap[];
@@ -41,7 +41,6 @@ export declare function runScan(ctx: LocalToolContext, { scan_scope, severity_th
41
41
  by_severity: Record<string, number>;
42
42
  };
43
43
  groups: {
44
- doc_urls: Record<string, string>;
45
44
  id: string;
46
45
  file: string;
47
46
  files?: string[];
@@ -53,6 +52,7 @@ export declare function runScan(ctx: LocalToolContext, { scan_scope, severity_th
53
52
  autonomy?: import("../types.js").Autonomy;
54
53
  autonomy_reasons?: string[];
55
54
  priority?: import("../types.js").PriorityBand;
55
+ doc_urls: Record<string, string>;
56
56
  }[];
57
57
  batch_plan: import("../decisions.js").BatchPlan;
58
58
  co_located: import("../decisions.js").LocationCluster[];
@@ -64,11 +64,8 @@ export declare function runScan(ctx: LocalToolContext, { scan_scope, severity_th
64
64
  top_risks: import("../risk.js").RankedConcern[];
65
65
  reintroduced?: import("../lineage.js").ReintroducedFinding[];
66
66
  concerns: {
67
- doc_url: string | null;
68
- risk_score: number;
69
- noise_reason?: string;
70
67
  id: string;
71
- source: "terraform-fmt" | "terraform-validate" | "tflint" | "trivy" | "checkov" | "infraweaver" | "reviewer";
68
+ source: "terraform-fmt" | "terraform-validate" | "tflint" | "trivy" | "checkov" | "infraweaver" | "betterleaks" | "reviewer";
72
69
  rule_id: string;
73
70
  severity: Severity;
74
71
  category: "security" | "style" | "correctness" | "cost";
@@ -83,6 +80,9 @@ export declare function runScan(ctx: LocalToolContext, { scan_scope, severity_th
83
80
  suppression?: import("../types.js").ConcernSuppression;
84
81
  canonical_class?: string;
85
82
  flagged_by?: string[];
83
+ doc_url: string | null;
84
+ risk_score: number;
85
+ noise_reason?: string;
86
86
  }[];
87
87
  orphan_native_skips?: {
88
88
  file: string;