infraweaver 0.3.11 → 0.3.13

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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/opencode.d.ts +6 -0
  3. package/dist/agents/quotaTokens.d.ts +6 -0
  4. package/dist/cli.mjs +846 -412
  5. package/dist/index.js +806 -372
  6. package/dist/internal.js +4 -4
  7. package/dist/mcp/git.d.ts +6 -4
  8. package/dist/mcp/localContext.d.ts +7 -0
  9. package/dist/mcp/reviewCommentLimit.d.ts +55 -5
  10. package/dist/mcp/reviewMarkers.d.ts +5 -0
  11. package/dist/mcp/staleFix.d.ts +36 -1
  12. package/dist/mcp/terraform/awsPrices.d.ts +1 -1
  13. package/dist/mcp/terraform/azurePrices.d.ts +1 -1
  14. package/dist/mcp/terraform/concernResult.d.ts +9 -0
  15. package/dist/mcp/terraform/deltaSummary.d.ts +3 -0
  16. package/dist/mcp/terraform/hardcodedSecrets.d.ts +24 -10
  17. package/dist/mcp/terraform/refactorScope.d.ts +14 -2
  18. package/dist/mcp/terraform/ruleContext.d.ts +3 -0
  19. package/dist/mcp/terraform/scanSession.d.ts +2 -0
  20. package/dist/mcp/terraform/tree.d.ts +5 -0
  21. package/dist/mcp/terraform/types.d.ts +2 -1
  22. package/dist/mcp/terraform/verifySites.d.ts +56 -0
  23. package/dist/vendor/@infraweaver-io/models/tokenQuota.d.ts +7 -6
  24. package/package.json +2 -2
  25. package/src/agents/opencode.ts +8 -1
  26. package/src/agents/quotaTokens.ts +7 -1
  27. package/src/mcp/git.ts +64 -12
  28. package/src/mcp/localContext.ts +6 -1
  29. package/src/mcp/moduleExtraction.ts +5 -4
  30. package/src/mcp/review.ts +52 -3
  31. package/src/mcp/reviewCommentLimit.ts +169 -26
  32. package/src/mcp/reviewMarkers.ts +11 -0
  33. package/src/mcp/reviewProvenance.ts +1 -1
  34. package/src/mcp/staleFix.ts +73 -2
  35. package/src/mcp/terraform/awsPrices.ts +1 -1
  36. package/src/mcp/terraform/azurePrices.ts +23 -23
  37. package/src/mcp/terraform/concernResult.ts +18 -9
  38. package/src/mcp/terraform/deltaSummary.ts +4 -0
  39. package/src/mcp/terraform/hardcodedSecrets.ts +148 -27
  40. package/src/mcp/terraform/nativeRuleScanners.ts +2 -1
  41. package/src/mcp/terraform/nativeScan.ts +4 -0
  42. package/src/mcp/terraform/refactor/equivalence.ts +42 -9
  43. package/src/mcp/terraform/refactorScope.ts +90 -21
  44. package/src/mcp/terraform/ruleContext.ts +4 -0
  45. package/src/mcp/terraform/scanDelta.ts +67 -26
  46. package/src/mcp/terraform/scanSession.ts +6 -0
  47. package/src/mcp/terraform/tools/detectPlanDrift.ts +2 -0
  48. package/src/mcp/terraform/tools/ingestExternalFindings.ts +2 -0
  49. package/src/mcp/terraform/tools/normalizationCandidates.ts +5 -5
  50. package/src/mcp/terraform/tools/readFindings.ts +2 -0
  51. package/src/mcp/terraform/tools/scan.ts +3 -0
  52. package/src/mcp/terraform/tools/verifyRemediation.ts +23 -1
  53. package/src/mcp/terraform/tree.ts +8 -0
  54. package/src/mcp/terraform/types.ts +2 -1
  55. package/src/mcp/terraform/verifySites.ts +124 -0
  56. package/src/modes/refactor.ts +1 -1
  57. package/src/modes/remediate-and-refactor.ts +1 -1
  58. package/src/modes/remediate.ts +1 -1
  59. package/src/phases/runAgentWithWatchdogs.ts +3 -2
package/dist/index.js CHANGED
@@ -84161,14 +84161,14 @@ var require_turndown_cjs = __commonJS({
84161
84161
  } else if (node3.nodeType === 1) {
84162
84162
  replacement = replacementForNode.call(self2, node3);
84163
84163
  }
84164
- return join74(output2, replacement);
84164
+ return join75(output2, replacement);
84165
84165
  }, "");
84166
84166
  }
84167
84167
  function postProcess(output2) {
84168
84168
  var self2 = this;
84169
84169
  this.rules.forEach(function(rule) {
84170
84170
  if (typeof rule.append === "function") {
84171
- output2 = join74(output2, rule.append(self2.options));
84171
+ output2 = join75(output2, rule.append(self2.options));
84172
84172
  }
84173
84173
  });
84174
84174
  return output2.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -84180,7 +84180,7 @@ var require_turndown_cjs = __commonJS({
84180
84180
  if (whitespace.leading || whitespace.trailing) content = content.trim();
84181
84181
  return whitespace.leading + rule.replacement(content, node3, this.options) + whitespace.trailing;
84182
84182
  }
84183
- function join74(output2, replacement) {
84183
+ function join75(output2, replacement) {
84184
84184
  var s1 = trimTrailingNewlines(output2);
84185
84185
  var s2 = trimLeadingNewlines(replacement);
84186
84186
  var nls = Math.max(output2.length - s1.length, replacement.length - s2.length);
@@ -97300,7 +97300,7 @@ var require_polyfill = __commonJS({
97300
97300
  var {
97301
97301
  dirname: dirname15,
97302
97302
  isAbsolute: isAbsolute7,
97303
- join: join74,
97303
+ join: join75,
97304
97304
  parse: parse8,
97305
97305
  resolve: resolve11,
97306
97306
  sep: sep11,
@@ -97557,8 +97557,8 @@ var require_polyfill = __commonJS({
97557
97557
  const dir = await readdir2(src);
97558
97558
  for (let i = 0; i < dir.length; i++) {
97559
97559
  const item = dir[i];
97560
- const srcItem = join74(src, item);
97561
- const destItem = join74(dest, item);
97560
+ const srcItem = join75(src, item);
97561
+ const destItem = join75(dest, item);
97562
97562
  const { destStat } = await checkPaths2(srcItem, destItem, opts);
97563
97563
  await startCopy(destStat, srcItem, destItem, opts);
97564
97564
  }
@@ -97631,7 +97631,7 @@ var require_cp = __commonJS({
97631
97631
  // node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/with-temp-dir.js
97632
97632
  var require_with_temp_dir = __commonJS({
97633
97633
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/with-temp-dir.js"(exports2, module) {
97634
- var { join: join74, sep: sep11 } = __require("path");
97634
+ var { join: join75, sep: sep11 } = __require("path");
97635
97635
  var getOptions2 = require_get_options();
97636
97636
  var { mkdir: mkdir5, mkdtemp, rm: rm2 } = __require("fs/promises");
97637
97637
  var withTempDir = async (root, fn2, opts) => {
@@ -97639,7 +97639,7 @@ var require_with_temp_dir = __commonJS({
97639
97639
  copy: ["tmpPrefix"]
97640
97640
  });
97641
97641
  await mkdir5(root, { recursive: true });
97642
- const target = await mkdtemp(join74(`${root}${sep11}`, options.tmpPrefix || ""));
97642
+ const target = await mkdtemp(join75(`${root}${sep11}`, options.tmpPrefix || ""));
97643
97643
  let err;
97644
97644
  let result;
97645
97645
  try {
@@ -97664,13 +97664,13 @@ var require_with_temp_dir = __commonJS({
97664
97664
  var require_readdir_scoped = __commonJS({
97665
97665
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/readdir-scoped.js"(exports2, module) {
97666
97666
  var { readdir: readdir2 } = __require("fs/promises");
97667
- var { join: join74 } = __require("path");
97667
+ var { join: join75 } = __require("path");
97668
97668
  var readdirScoped = async (dir) => {
97669
97669
  const results = [];
97670
97670
  for (const item of await readdir2(dir)) {
97671
97671
  if (item.startsWith("@")) {
97672
- for (const scopedItem of await readdir2(join74(dir, item))) {
97673
- results.push(join74(item, scopedItem));
97672
+ for (const scopedItem of await readdir2(join75(dir, item))) {
97673
+ results.push(join75(item, scopedItem));
97674
97674
  }
97675
97675
  } else {
97676
97676
  results.push(item);
@@ -97685,7 +97685,7 @@ var require_readdir_scoped = __commonJS({
97685
97685
  // node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/move-file.js
97686
97686
  var require_move_file = __commonJS({
97687
97687
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/move-file.js"(exports2, module) {
97688
- var { dirname: dirname15, join: join74, resolve: resolve11, relative: relative8, isAbsolute: isAbsolute7 } = __require("path");
97688
+ var { dirname: dirname15, join: join75, resolve: resolve11, relative: relative8, isAbsolute: isAbsolute7 } = __require("path");
97689
97689
  var fs12 = __require("fs/promises");
97690
97690
  var pathExists2 = async (path15) => {
97691
97691
  try {
@@ -97715,7 +97715,7 @@ var require_move_file = __commonJS({
97715
97715
  if (sourceStat.isDirectory()) {
97716
97716
  const files = await fs12.readdir(source);
97717
97717
  await Promise.all(files.map(
97718
- (file2) => moveFile(join74(source, file2), join74(destination, file2), options, false, symlinks)
97718
+ (file2) => moveFile(join75(source, file2), join75(destination, file2), options, false, symlinks)
97719
97719
  ));
97720
97720
  } else if (sourceStat.isSymbolicLink()) {
97721
97721
  symlinks.push({ source, destination });
@@ -107552,23 +107552,23 @@ var require_helpers = __commonJS({
107552
107552
  "node_modules/.pnpm/ip-address@10.7.3/node_modules/ip-address/dist/v6/helpers.js"(exports2) {
107553
107553
  "use strict";
107554
107554
  Object.defineProperty(exports2, "__esModule", { value: true });
107555
- exports2.escapeHtml = escapeHtml;
107555
+ exports2.escapeHtml = escapeHtml2;
107556
107556
  exports2.spanAllZeroes = spanAllZeroes;
107557
107557
  exports2.spanAll = spanAll;
107558
107558
  exports2.spanLeadingZeroes = spanLeadingZeroes;
107559
107559
  exports2.simpleGroup = simpleGroup;
107560
- function escapeHtml(s) {
107560
+ function escapeHtml2(s) {
107561
107561
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
107562
107562
  }
107563
107563
  function spanAllZeroes(s) {
107564
- return escapeHtml(s).replace(/(0+)/g, '<span class="zero">$1</span>');
107564
+ return escapeHtml2(s).replace(/(0+)/g, '<span class="zero">$1</span>');
107565
107565
  }
107566
107566
  function spanAll(s, offset = 0) {
107567
107567
  const letters = s.split("");
107568
- return letters.map((n, i) => `<span class="digit value-${escapeHtml(n)} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
107568
+ return letters.map((n, i) => `<span class="digit value-${escapeHtml2(n)} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
107569
107569
  }
107570
107570
  function spanLeadingZeroesSimple(group2) {
107571
- return escapeHtml(group2).replace(/^(0+)/, '<span class="zero">$1</span>');
107571
+ return escapeHtml2(group2).replace(/^(0+)/, '<span class="zero">$1</span>');
107572
107572
  }
107573
107573
  function spanLeadingZeroes(address) {
107574
107574
  const groups2 = address.split(":");
@@ -160384,7 +160384,7 @@ var init_cacheTwirpClient = __esm({
160384
160384
  });
160385
160385
 
160386
160386
  // node_modules/.pnpm/@actions+cache@6.3.0_supports-color@7.2.0/node_modules/@actions/cache/lib/internal/tar.js
160387
- import { existsSync as existsSync17, writeFileSync as writeFileSync21 } from "fs";
160387
+ import { existsSync as existsSync16, writeFileSync as writeFileSync21 } from "fs";
160388
160388
  import * as path13 from "path";
160389
160389
  function getTarPath() {
160390
160390
  return __awaiter18(this, void 0, void 0, function* () {
@@ -160394,7 +160394,7 @@ function getTarPath() {
160394
160394
  const systemTar = SystemTarPathOnWindows;
160395
160395
  if (gnuTar) {
160396
160396
  return { path: gnuTar, type: ArchiveToolType.GNU };
160397
- } else if (existsSync17(systemTar)) {
160397
+ } else if (existsSync16(systemTar)) {
160398
160398
  return { path: systemTar, type: ArchiveToolType.BSD };
160399
160399
  }
160400
160400
  break;
@@ -250325,7 +250325,7 @@ var FastMCP = class extends FastMCPEventEmitter {
250325
250325
  }
250326
250326
  };
250327
250327
 
250328
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/catalog.js
250328
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/catalog.js
250329
250329
  function provider(config3) {
250330
250330
  return config3;
250331
250331
  }
@@ -258848,7 +258848,7 @@ function isGeminiRouted(ctx) {
258848
258848
  init_core3();
258849
258849
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
258850
258850
 
258851
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/usage.js
258851
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/usage.js
258852
258852
  function formatCostUsd(costUsd) {
258853
258853
  return costUsd.toFixed(4);
258854
258854
  }
@@ -259203,7 +259203,7 @@ import { randomBytes as randomBytes2 } from "node:crypto";
259203
259203
  // package.json
259204
259204
  var package_default = {
259205
259205
  name: "infraweaver",
259206
- version: "0.3.11",
259206
+ version: "0.3.13",
259207
259207
  description: "GitHub Action that remediates Terraform to best practices and opens one scoped pull request per concern.",
259208
259208
  keywords: [
259209
259209
  "ai-coding-agent",
@@ -259306,7 +259306,7 @@ var package_default = {
259306
259306
  "@infraweaver-io/ci": "^0.1.8",
259307
259307
  "@infraweaver-io/core": "^0.1.5",
259308
259308
  "@infraweaver-io/crosswalk": "^1.2.0",
259309
- "@infraweaver-io/models": "^0.1.6",
259309
+ "@infraweaver-io/models": "^0.1.7",
259310
259310
  "@infraweaver-io/policy-spec": "^1.2.0",
259311
259311
  "@infraweaver-io/report-contract": "^0.1.5",
259312
259312
  "@infraweaver-io/sandbox": "^0.1.8",
@@ -266110,7 +266110,18 @@ var SECRET_WORDS = /* @__PURE__ */ new Set([
266110
266110
  "credential",
266111
266111
  "credentials"
266112
266112
  ]);
266113
- var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set(["api", "secret", "private", "signing"]);
266113
+ var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set([
266114
+ "api",
266115
+ "secret",
266116
+ "private",
266117
+ "signing",
266118
+ "access",
266119
+ "account",
266120
+ "client",
266121
+ "shared",
266122
+ "preshared",
266123
+ "master"
266124
+ ]);
266114
266125
  var NOT_SECRET_TOKENS = /* @__PURE__ */ new Set(["creation", "idempotency", "client", "request", "continuation"]);
266115
266126
  function words(name) {
266116
266127
  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_-]+/).filter(Boolean);
@@ -266122,24 +266133,70 @@ function isSecretName(name) {
266122
266133
  if (last === void 0) return false;
266123
266134
  if (last === "key") return prev !== void 0 && SECRET_KEY_PREFIXES.has(prev);
266124
266135
  if (last === "token" && prev !== void 0 && NOT_SECRET_TOKENS.has(prev)) return false;
266136
+ if (last === "string") return prev === "connection";
266137
+ if (last === "secret" && w.length === 1) return false;
266125
266138
  return SECRET_WORDS.has(last);
266126
266139
  }
266127
266140
  function isPlaceholder(inner) {
266128
- return inner.trim() === "" || /^[*xX.]+$/.test(inner) || /^<[^>]*>$/.test(inner) || // the dummy credentials offline plans and LocalStack are given (`secret_key = "test"`)
266129
- /^(?:test|mock|fake|dummy|fixture|example|placeholder|sample)(?:[_-].*)?$/i.test(inner) || // a token's lifetime unit: `token_validity_units { access_token = "hours" }`
266141
+ return inner.trim() === "" || /^[*xX.]+$/.test(inner) || /^<[^>]*>$/.test(inner) || /^(?:true|false|null)$/.test(inner) || /^-?\d+(?:\.\d+)?$/.test(inner) || // a template directive is computed, like an interpolation: `"%{ if … }…%{ endif }"`
266142
+ inner.includes("%{") || // the dummy credentials offline plans and LocalStack are given (`secret_key = "test"`,
266143
+ // `"example-secret"`) — but only when that is the whole value: a lowercase tail at
266144
+ // most, so `"Test-Pr0dDbPassw0rd!"` is a password that happens to start with a word
266145
+ isDummy(inner) || // a token's lifetime unit: `token_validity_units { access_token = "hours" }`
266130
266146
  /^(?:seconds|minutes|hours|days)$/.test(inner);
266131
266147
  }
266132
- var HEADER = /^(resource|data|provider|module|locals|variable|output|terraform)\b([^{]*)\{\s*$/;
266148
+ function isDummy(inner) {
266149
+ const m = /^(?:test|mock|fake|dummy|fixture|example|placeholder|sample)([_-].*)?$/i.exec(inner);
266150
+ return m !== null && (m[1] === void 0 || /^[_-][a-z0-9_-]*$/.test(m[1]));
266151
+ }
266152
+ function isSecretReference(inner) {
266153
+ return inner.startsWith("arn:") || /^projects\/[^/]+\/secrets\//.test(inner) || /^https:\/\/[\w-]+\.vault\.azure\.net\/secrets\//i.test(inner) || /^\/[\w./-]+$/.test(inner);
266154
+ }
266155
+ var HEADER = /^(resource|data|provider|module|locals|variable|output|terraform)\b([^{]*)\{(.*)$/;
266133
266156
  var ASSIGNMENT = /^\s*(?:"([A-Za-z_][\w-]*)"|([A-Za-z_][\w-]*))\s*=(?!=)\s*(.*)$/;
266134
266157
  var HEREDOC = /<<-?([A-Za-z_]\w*)\s*$/;
266135
266158
  var INLINE_PAIR = /(?:"([A-Za-z_][\w-]*)"|\b([A-Za-z_][\w-]*))\s*=(?!=)\s*("(?:[^"\\]|\\.)*")/g;
266136
- function literalValue(raw2) {
266159
+ function literalValue(name, raw2) {
266137
266160
  const value2 = stripTrailingComment(raw2).replace(/,$/, "").trim();
266138
- return isStringLiteral(value2) && !isPlaceholder(value2.slice(1, -1));
266161
+ if (!isStringLiteral(value2)) return false;
266162
+ const inner = value2.slice(1, -1);
266163
+ if (isPlaceholder(inner) || isSecretReference(inner)) return false;
266164
+ return !(words(name).at(-1) === "auth" && /^[A-Z]+(?:_[A-Z]+)*$/.test(inner));
266165
+ }
266166
+ function variableName(holder) {
266167
+ return /^variable\s+"?([\w-]+)"?/.exec(holder)?.[1] ?? "";
266168
+ }
266169
+ function opensBlockComment(line) {
266170
+ for (let i = 0; i < line.length; i++) {
266171
+ const span = skipNonCode(line, i);
266172
+ if (!span) continue;
266173
+ if (line.startsWith("/*", i) && !line.includes("*/", i + 2)) return true;
266174
+ i = span.end;
266175
+ }
266176
+ return false;
266177
+ }
266178
+ function closesOnLine(rest) {
266179
+ let depth = 1;
266180
+ for (let i = 0; i < rest.length; i++) {
266181
+ const span = skipNonCode(rest, i);
266182
+ if (span) {
266183
+ i = span.end;
266184
+ continue;
266185
+ }
266186
+ if (rest[i] === "{") depth++;
266187
+ else if (rest[i] === "}" && --depth === 0) return true;
266188
+ }
266189
+ return false;
266139
266190
  }
266140
266191
  function findHardcodedSecrets(hcl2) {
266192
+ return findInLines(hcl2, null);
266193
+ }
266194
+ function findTfvarsSecrets(hcl2, path15) {
266195
+ return findInLines(hcl2, `tfvars input ${path15}`);
266196
+ }
266197
+ function findInLines(hcl2, fileHolder) {
266141
266198
  const out = [];
266142
- let holder = null;
266199
+ let holder = fileHolder;
266143
266200
  let heredoc = null;
266144
266201
  let inComment = false;
266145
266202
  const lines = hcl2.split("\n");
@@ -266159,12 +266216,28 @@ function findHardcodedSecrets(hcl2) {
266159
266216
  continue;
266160
266217
  }
266161
266218
  if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
266162
- const header = HEADER.exec(line);
266219
+ if (opensBlockComment(line)) inComment = true;
266220
+ const header = fileHolder === null ? HEADER.exec(line) : null;
266163
266221
  if (header) {
266164
266222
  holder = `${header[1]}${header[2].trimEnd() ? ` ${header[2].trim()}` : ""}`;
266223
+ const rest = header[3];
266224
+ const opens2 = HEREDOC.exec(rest);
266225
+ if (opens2) heredoc = opens2[1];
266226
+ if (!holder.startsWith("output ") && !holder.startsWith("terraform")) {
266227
+ for (const pair of rest.matchAll(INLINE_PAIR)) {
266228
+ const key = pair[1] ?? pair[2];
266229
+ const isVariable = holder.startsWith("variable ");
266230
+ if (isVariable && key !== "default") continue;
266231
+ const name2 = isVariable ? variableName(holder) : key;
266232
+ if (isSecretName(name2) && literalValue(name2, pair[3])) {
266233
+ out.push({ name: name2, holder, line: i + 1 });
266234
+ }
266235
+ }
266236
+ }
266237
+ if (closesOnLine(rest)) holder = null;
266165
266238
  continue;
266166
266239
  }
266167
- if (line.startsWith("}")) {
266240
+ if (fileHolder === null && line.startsWith("}")) {
266168
266241
  holder = null;
266169
266242
  continue;
266170
266243
  }
@@ -266176,30 +266249,34 @@ function findHardcodedSecrets(hcl2) {
266176
266249
  const name = m[1] ?? m[2];
266177
266250
  const value2 = m[3];
266178
266251
  if (holder.startsWith("variable ")) {
266179
- const variable = /^variable\s+"?([\w-]+)"?/.exec(holder)?.[1] ?? "";
266180
- if (name === "default" && isSecretName(variable) && literalValue(value2)) {
266252
+ const variable = variableName(holder);
266253
+ if (name === "default" && isSecretName(variable) && literalValue(variable, value2)) {
266181
266254
  out.push({ name: variable, holder, line: i + 1 });
266182
266255
  }
266183
266256
  continue;
266184
266257
  }
266185
266258
  if (holder.startsWith("output ") || holder.startsWith("terraform")) continue;
266186
- if (isSecretName(name) && literalValue(value2)) {
266259
+ if (isSecretName(name) && literalValue(name, value2)) {
266187
266260
  out.push({ name, holder, line: i + 1 });
266188
266261
  continue;
266189
266262
  }
266190
266263
  for (const pair of value2.matchAll(INLINE_PAIR)) {
266191
266264
  const key = pair[1] ?? pair[2];
266192
- if (isSecretName(key) && literalValue(pair[3])) {
266265
+ if (isSecretName(key) && literalValue(key, pair[3])) {
266193
266266
  out.push({ name: key, holder, line: i + 1 });
266194
266267
  }
266195
266268
  }
266196
266269
  }
266197
266270
  return out;
266198
266271
  }
266199
- function scanHardcodedSecrets(files) {
266272
+ function scanHardcodedSecrets(files, tfvarsFiles = []) {
266200
266273
  const concerns = [];
266201
- for (const { path: path15, hcl: hcl2 } of files) {
266202
- for (const hit of findHardcodedSecrets(hcl2)) {
266274
+ const hits = [
266275
+ ...files.map((f) => ({ path: f.path, found: findHardcodedSecrets(f.hcl) })),
266276
+ ...tfvarsFiles.map((f) => ({ path: f.path, found: findTfvarsSecrets(f.hcl, f.path) }))
266277
+ ];
266278
+ for (const { path: path15, found } of hits) {
266279
+ for (const hit of found) {
266203
266280
  concerns.push(
266204
266281
  buildConcern({
266205
266282
  source: NATIVE_SOURCE,
@@ -268202,7 +268279,8 @@ var NATIVE_RULE_SCANNERS = [
268202
268279
  name: "hardcoded-secrets",
268203
268280
  rules: [HARDCODED_SECRET_ARGUMENT],
268204
268281
  // Per file: the argument's name and its literal are both on the line itself.
268205
- scan: (ctx) => ({ concerns: scanHardcodedSecrets(ctx.files) })
268282
+ // The only rule that reads `.tfvars`: an input file holds values, not config.
268283
+ scan: (ctx) => ({ concerns: scanHardcodedSecrets(ctx.files, ctx.tfvarsFiles) })
268206
268284
  },
268207
268285
  {
268208
268286
  name: "write-only",
@@ -268287,6 +268365,7 @@ function createRuleContext(input2) {
268287
268365
  cwd: input2.cwd,
268288
268366
  files: input2.files,
268289
268367
  tofuFiles: input2.tofuFiles ?? [],
268368
+ tfvarsFiles: input2.tfvarsFiles ?? [],
268290
268369
  truncated: input2.truncated ?? null,
268291
268370
  repeatedLiteralThreshold: input2.repeatedLiteralThreshold
268292
268371
  };
@@ -268338,6 +268417,9 @@ function readTfTree(cwd, limits) {
268338
268417
  function readTofuTree(cwd, limits) {
268339
268418
  return readTree(cwd, ".tofu", limits);
268340
268419
  }
268420
+ function readTfvarsTree(cwd, limits) {
268421
+ return readTree(cwd, ".tfvars", limits);
268422
+ }
268341
268423
  var OVERSIZE_NAMED = 5;
268342
268424
  function describeOversize(paths) {
268343
268425
  const named = paths.slice(0, OVERSIZE_NAMED).join(", ");
@@ -268366,6 +268448,9 @@ function scanNative(cwd, opts = {}) {
268366
268448
  // walk over a suffix most repos do not use at all, and threading it through
268367
268449
  // the context keeps the scanners uniform.
268368
268450
  tofuFiles: readTofuTree(cwd).files,
268451
+ // likewise only for the hardcoded-secret rule: a value kept out of the `.tf`
268452
+ // source is most often committed here instead
268453
+ tfvarsFiles: readTfvarsTree(cwd).files,
268369
268454
  truncated: tree.truncated,
268370
268455
  repeatedLiteralThreshold: opts.repeatedLiteralThreshold ?? DEFAULT_REPEAT_THRESHOLD
268371
268456
  });
@@ -272923,9 +273008,9 @@ function TerraformChangeSummaryTool(ctx) {
272923
273008
  }
272924
273009
 
272925
273010
  // src/mcp/checkout.ts
272926
- import { createHash as createHash9 } from "node:crypto";
273011
+ import { createHash as createHash10 } from "node:crypto";
272927
273012
  import { statSync as statSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
272928
- import { join as join35 } from "node:path";
273013
+ import { join as join36 } from "node:path";
272929
273014
 
272930
273015
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/diffCoverage.js
272931
273016
  import { isAbsolute as isAbsolute3, normalize as normalize3, resolve as resolve5 } from "node:path";
@@ -273331,6 +273416,9 @@ import { spawnSync as spawnSync5 } from "node:child_process";
273331
273416
  import { readFileSync as readFileSync12, rmSync as rmSync2 } from "node:fs";
273332
273417
  import { join as join22 } from "node:path";
273333
273418
 
273419
+ // src/mcp/staleFix.ts
273420
+ import { createHash as createHash6 } from "node:crypto";
273421
+
273334
273422
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/progressComment.js
273335
273423
  function parseProgressComment(raw2) {
273336
273424
  if (!raw2?.id)
@@ -274283,7 +274371,31 @@ function groupIdFromBranch(branch) {
274283
274371
  }
274284
274372
  function remediationBranchSuffix(base, defaultBranch) {
274285
274373
  if (!base || !defaultBranch || base === defaultBranch) return "";
274286
- return `--${base.replace(/[^A-Za-z0-9._-]+/g, "-")}`;
274374
+ const slug2 = base.replace(/[^A-Za-z0-9._-]+/g, "-");
274375
+ if (slug2 === base) return `--${slug2}`;
274376
+ return `--${slug2}-${createHash6("sha1").update(base).digest("hex").slice(0, 8)}`;
274377
+ }
274378
+ function remediationBranchFor(id, base, defaultBranch, open3) {
274379
+ const computed = `remediate/${id}${remediationBranchSuffix(base, defaultBranch)}`;
274380
+ const heads = (open3 ?? []).filter((pr) => pr.base === base && groupIdFromBranch(pr.head) === id).map((pr) => pr.head).filter((head) => !(open3 ?? []).some((pr) => pr.head === head && pr.base !== base));
274381
+ return heads.includes(computed) ? computed : heads[0] ?? computed;
274382
+ }
274383
+ async function listOpenRemediationPrs(ctx) {
274384
+ const { octokit, repo } = ctx;
274385
+ if (ctx.toolState?.selectedMode !== "Remediate") return [];
274386
+ if (!octokit || !repo?.owner || !repo.name) return [];
274387
+ try {
274388
+ const open3 = await octokit.paginate(octokit.rest.pulls.list, {
274389
+ owner: repo.owner,
274390
+ repo: repo.name,
274391
+ state: "open",
274392
+ per_page: 100
274393
+ });
274394
+ return open3.filter((pr) => pr.head.repo?.id === pr.base.repo.id && groupIdFromBranch(pr.head.ref)).map((pr) => ({ head: pr.head.ref, base: pr.base.ref }));
274395
+ } catch (e) {
274396
+ log2.warning(`\xBB could not list open remediation PRs: ${errorMessage(e)}`);
274397
+ return [];
274398
+ }
274287
274399
  }
274288
274400
  function matchesBotIdentity(identity) {
274289
274401
  const normalized = identity.replace(/\[bot\]$/i, "").toLowerCase();
@@ -274539,13 +274651,13 @@ function ClosePullRequestTool(ctx) {
274539
274651
  }
274540
274652
 
274541
274653
  // src/mcp/terraform/emittedArtefacts.ts
274542
- import { createHash as createHash6 } from "node:crypto";
274654
+ import { createHash as createHash7 } from "node:crypto";
274543
274655
  import { writeFileSync as writeFileSync4 } from "node:fs";
274544
274656
  import { resolve as resolve6 } from "node:path";
274545
274657
  var ARTEFACT_DIR = "compliance/";
274546
274658
  function gitBlobId(bytes) {
274547
274659
  const content = Buffer.from(bytes, "utf8");
274548
- return createHash6("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
274660
+ return createHash7("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
274549
274661
  }
274550
274662
  function writeEmittedArtefact(toolState, path15, bytes) {
274551
274663
  writeFileSync4(path15, bytes);
@@ -274895,7 +275007,7 @@ function parseResourcesWithArgs(hcl2) {
274895
275007
  }
274896
275008
 
274897
275009
  // src/mcp/terraform/refactor/workspace.ts
274898
- import { createHash as createHash7 } from "node:crypto";
275010
+ import { createHash as createHash8 } from "node:crypto";
274899
275011
  import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
274900
275012
  import { join as join21, posix as posix6 } from "node:path";
274901
275013
  function readTreeAtRef(cwd, ref) {
@@ -274925,7 +275037,7 @@ function readWorkingTree(cwd) {
274925
275037
  return files;
274926
275038
  }
274927
275039
  function workingTreeFingerprint(files) {
274928
- const h = createHash7("sha256");
275040
+ const h = createHash8("sha256");
274929
275041
  for (const f of [...files].sort((a, b) => a.path.localeCompare(b.path, "en"))) {
274930
275042
  h.update(f.path);
274931
275043
  h.update("\0");
@@ -276636,7 +276748,7 @@ This mode standardises STRUCTURE while preserving BEHAVIOUR \u2014 the equivalen
276636
276748
  - \`${t("terraform_normalization_candidates")}\` \u2192 in-place idiomatic cleanups that change SYNTAX, not behaviour (redundant whole-string \`"\${expr}"\` interpolation; legacy HCL0.11 map-argument block syntax like \`vars {\` on a \`template_file\`). These relocate NOTHING \u2014 zero \`moved {}\` blocks \u2014 and are proven by the same equivalence check.
276637
276749
  - \`${t("terraform_consolidation_candidates")}\` \u2192 the SAME resource shape declared in several root directories (\`env/dev\` + \`env/staging\` + \`env/prod\`), with the single parameterised module they could all call already derived: which attributes are identical (module content) and which differ (per-environment inputs). A member root of a group will ALSO show up as a zero-candidate cluster in \`${t("module_extraction_candidates")}\` \u2014 prefer the consolidation, which does that work once for every environment instead of once per environment. Its \`near_groups\` are the opposite case: roots recognisably the same stack that DIFFER (one carries a resource the other does not), where folding them together is a design decision this mode has no basis for. Do NOT extract a near-group root's shared resources into a module on its own either \u2014 a module one twin calls and the other does not widens exactly the drift the near-group reports. Leave those roots alone and name the near-group and its \`differences\` in your report.
276638
276750
 
276639
- **A result marked \`report_only\` is an observation, not work.** The repository is a module other configurations call (\`report_only_reason\` says why), and restructuring it is churn for every caller: name those candidates in your \`${t("report_progress")}\` summary and open no PR for them. \`examples/\` roots are left out of every candidate list unless the operator set \`refactor_examples\`.
276751
+ **A candidate marked \`report_only\` is an observation, not work.** It sits in the code of a module other configurations call (\`report_only_reason\` says why), and restructuring that is churn for every caller: name those candidates in your \`${t("report_progress")}\` summary and open no PR for them. \`examples/\` roots are left out of every candidate list unless the operator set \`refactor_examples\`.
276640
276752
 
276641
276753
  **Before picking, check the interface.** \`${t("module_extraction_candidates")}\` reports \`unmapped_attributes\` per candidate and a top-level \`partial_interfaces\` list: arguments the raw resources SET that the target module does not. Adopting such a module DROPS them silently \u2014 and the equivalence check cannot catch it, because once the argument is gone it is absent from both sides. Two of these are unrecoverable rather than merely wrong (\`object_lock_enabled\` cannot be set after creation; \`force_destroy\` changes deletion semantics). So: **decline the candidate, or extend the module to expose the argument and say so in the PR body.** Deleting the argument to make the shapes match is never the fix. A candidate whose \`unmapped_attributes\` is \`null\` was NOT checked \u2014 the module is external and its code is not in this repo \u2014 which is not the same as clean.
276642
276754
 
@@ -276769,7 +276881,7 @@ function remediateMode(t) {
276769
276881
 
276770
276882
  4. **for the chosen group**:
276771
276883
  - **base branch**: this run's base branch is resolved deterministically \u2014 \`${t("create_pull_request")}\` targets the \`base_branch\` input if set, else the branch the run started on, else the repository's default branch (\`main\`, or \`master\`). You do not choose it; just **omit** the \`base\` argument when opening the PR (below) and it is filled in.
276772
- - **idempotency**: the remediation branch is the group's \`branch\` (\`remediate/<group-id>\`, with a \`--<base>\` suffix when the PR targets a non-default branch) \u2014 use it exactly, never a name of your own. Before doing anything, check whether that branch or an open PR for it already exists (\`${t("git")}\` / \`${t("get_pull_request")}\`). If one exists, update it rather than opening a duplicate.
276884
+ - **idempotency**: the remediation branch is the group's \`branch\` (\`remediate/<group-id>\`, with a \`--<base>\` suffix when the PR targets a non-default branch, or the branch of the PR already open for this group) \u2014 use it exactly, never a name of your own. Before doing anything, check whether that branch or an open PR for it already exists (\`${t("git")}\` / \`${t("get_pull_request")}\`). If one exists, update it rather than opening a duplicate.
276773
276885
  - **branch**: create the group's \`branch\` from the **current HEAD** (the checkout that was just scanned) via \`${t("git")}\` (\`git({ command: "checkout", args: ["-b", <branch>] })\`). Do NOT switch to a different base first \u2014 branching from the scanned checkout keeps the PR diff to exactly your fix.
276774
276886
  - **honest refusal (decide BEFORE fixing)**: if the group's concerns appear in the scan's \`refusal_candidates\` (the fix needs a human decision \u2014 narrowing an IAM wildcard, a KMS key policy, a real ingress CIDR), do **not** guess a fix that could break the stack. Instead open a structured issue (\`${t("create_issue")}\`) describing the concern, why it isn't auto-fixed, and what a human should do, and skip the PR for that group. A proven fix or an honest refusal \u2014 never a guessed, unverifiable PR.
276775
276887
  - **propose, then let me steer (when there's no single right fix)**: distinct from honest refusal (which refuses a fix a human must *decide*), this is for a finding with **2\u20133 genuinely distinct, defensible fixes** that differ in trade-offs, not correctness (e.g. encrypt with an AWS-managed key **vs** a customer-managed KMS key; a narrow security-group rule **vs** a prefix list **vs** a VPC endpoint). When such a fork exists **and the triggering comment did not already select a strategy**, do **not** silently pick for the reviewer: via \`${t("create_issue_comment")}\` post one short comment listing the options as **A / B / C** \u2014 each a single line (what it does + its trade-off) \u2014 and ask the reviewer to reply \`${COMMENT_COMMAND} fix #<concern-id> with strategy <A|B|C>\`. Then **skip the PR for this group** this run and note it in your final report (it resumes when the reviewer replies). When the comment **did** select one (\`fix #<id> with strategy B\`, or a bare \`strategy B\` reply on the proposal thread), apply **exactly** that strategy \u2014 don't second-guess it. Reserve this for real forks in the road; a fix with one obvious correct answer just gets made.
@@ -276830,7 +276942,7 @@ This mode composes the **Remediate** and **Refactor** verbs into a single run. I
276830
276942
  - **commit the fix** (\`git add\` only the changed \`*.tf\`/\`*.tfvars\`, a \`fix(tf): \u2026\` message). Do NOT push yet. This committed state is the refactor's equivalence BASELINE.
276831
276943
 
276832
276944
  3. **REFACTOR (phase 2) \u2014 modularise on top of the committed fix**: follow the **Refactor** mode's flow:
276833
- - \`${t("module_extraction_candidates")}\` / \`${t("terraform_normalization_candidates")}\` \u2192 pick one behaviour-preserving refactor; a result marked \`report_only\` (the repository is a module others call) is named in the summary, never proposed. Respect a pinned \`refactor_source\` if one was supplied (it constrains which module source you may use; the behaviour-altering b.4 path is only available when the operator pinned \`third-party\`).
276945
+ - \`${t("module_extraction_candidates")}\` / \`${t("terraform_normalization_candidates")}\` \u2192 pick one behaviour-preserving refactor; a candidate marked \`report_only\` (it sits in the code of a module others call) is named in the summary, never proposed. Respect a pinned \`refactor_source\` if one was supplied (it constrains which module source you may use; the behaviour-altering b.4 path is only available when the operator pinned \`third-party\`).
276834
276946
  - resolve the module source, wire the \`module\` call against its real interface (\`${t("terraform_module_interface")}\` for a local module dir, \`${t("terraform_module_lookup")}\` for a registry-sourced one), and emit a \`moved {}\` block for EVERY relocated address (\`${t("terraform_generate_moved")}\`).
276835
276947
  - \`terraform fmt\` + \`${t("terraform_validate")}\`, then \u2014 **with the refactor edits still UNCOMMITTED** \u2014 call \`${t("terraform_equivalence_check")}\`. In this mode it diffs the working tree against the COMMITTED fix (not the run-start commit), so it measures only the refactor. It must return \`equivalent: true\` (zero uncovered moves, resource set + arguments preserved, validate + fmt clean) before you proceed; \`${t("push_branch")}\` hard-blocks an unproven one \u2014 and, once this run has added a \`moved {}\` block, it also hard-blocks a push that never ran the check at all, so abandoning the refactor means REVERTING its edits rather than leaving them in with the fix. If it can't be proven equivalent, abandon the refactor and ship the fix alone.
276836
276948
  - **document the module (only when the \`docs\` input is enabled)**: after the equivalence check passes, call \`${t("terraform_module_docs")}\` for the module you created/adopted (pass the relocated addresses as \`moves\`). Do not hand-write docs \u2014 the tool builds from the interface, moved blocks, and equivalence verdict.
@@ -278383,15 +278495,52 @@ var Git = type({
278383
278495
  args: type.string.array().describe("Additional arguments for the git command").optional()
278384
278496
  });
278385
278497
  function splitWords(line) {
278386
- return [...line.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)].map((m) => m[1] ?? m[2] ?? m[3] ?? "");
278498
+ const words2 = [];
278499
+ let word = "";
278500
+ let inWord = false;
278501
+ let quote;
278502
+ for (let i = 0; i < line.length; i++) {
278503
+ const ch = line.charAt(i);
278504
+ if (quote === "'") {
278505
+ if (ch === "'") quote = void 0;
278506
+ else word += ch;
278507
+ } else if (quote === '"') {
278508
+ if (ch === '"') quote = void 0;
278509
+ else if (ch === "\\" && i + 1 < line.length && '"\\$`\n'.includes(line.charAt(i + 1))) {
278510
+ i += 1;
278511
+ if (line.charAt(i) !== "\n") word += line.charAt(i);
278512
+ } else word += ch;
278513
+ } else if (ch === " " || ch === " ") {
278514
+ if (inWord) words2.push(word);
278515
+ word = "";
278516
+ inWord = false;
278517
+ } else if (ch === "\n") {
278518
+ throw new Error(
278519
+ "command holds an unquoted newline \u2014 pass one git command per call, and quote a multi-line message"
278520
+ );
278521
+ } else if (ch === "\\") {
278522
+ if (i + 1 >= line.length) throw new Error("command ends in a backslash that escapes nothing");
278523
+ i += 1;
278524
+ if (line.charAt(i) === "\n") continue;
278525
+ word += line.charAt(i);
278526
+ inWord = true;
278527
+ } else if (ch === "'" || ch === '"') {
278528
+ quote = ch;
278529
+ inWord = true;
278530
+ } else {
278531
+ word += ch;
278532
+ inWord = true;
278533
+ }
278534
+ }
278535
+ if (quote) throw new Error(`command has an unterminated ${quote} quote`);
278536
+ if (inWord) words2.push(word);
278537
+ return words2;
278387
278538
  }
278388
278539
  function normalizeGitCall(params) {
278389
- let words2;
278390
- if (params.command === void 0) words2 = [...params.args ?? []];
278391
- else if (/^git\s/.test(params.command))
278392
- words2 = [...splitWords(params.command).slice(1), ...params.args ?? []];
278393
- else return { command: params.command, args: params.args ?? [] };
278394
- const [command, ...args2] = words2[0] === "git" ? words2.slice(1) : words2;
278540
+ const words2 = params.command === void 0 ? [] : /^git\s/.test(params.command) ? splitWords(params.command) : [params.command];
278541
+ words2.push(...params.args ?? []);
278542
+ while (words2[0] === "git") words2.shift();
278543
+ const [command, ...args2] = words2;
278395
278544
  if (command === void 0 || !/^[a-z][a-z0-9-]*$/.test(command)) {
278396
278545
  throw new Error(
278397
278546
  `command must be a Git subcommand such as 'status' or 'checkout' (was "${command ?? ""}")`
@@ -278577,7 +278726,7 @@ function PushTagsTool(ctx) {
278577
278726
  }
278578
278727
 
278579
278728
  // src/mcp/review.ts
278580
- import { readFileSync as readFileSync19 } from "node:fs";
278729
+ import { readFileSync as readFileSync20 } from "node:fs";
278581
278730
 
278582
278731
  // src/mcp/findingAudit.ts
278583
278732
  var COHERENCE_RULES = {
@@ -278827,7 +278976,7 @@ function claimsBlockingSeverity(body2) {
278827
278976
  init_core3();
278828
278977
  import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
278829
278978
 
278830
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/monotonicQuota.js
278979
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/monotonicQuota.js
278831
278980
  function createMonotonicQuota(params) {
278832
278981
  const { limit, unlimited, normalize: normalize5 } = params;
278833
278982
  let used = 0;
@@ -278852,7 +279001,7 @@ function createMonotonicQuota(params) {
278852
279001
  };
278853
279002
  }
278854
279003
 
278855
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/costQuota.js
279004
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/costQuota.js
278856
279005
  var CostQuotaExceededError = class extends Error {
278857
279006
  code = "COST_QUOTA_EXCEEDED";
278858
279007
  constructor(message2) {
@@ -278899,7 +279048,7 @@ function resolveRunCostQuota(parsed2) {
278899
279048
  return parsed2 === void 0 ? DEFAULT_RUN_COST_QUOTA_USD : parsed2;
278900
279049
  }
278901
279050
 
278902
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/tokenQuota.js
279051
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/tokenQuota.js
278903
279052
  var TokenQuotaExceededError = class extends Error {
278904
279053
  code = "TOKEN_QUOTA_EXCEEDED";
278905
279054
  constructor(message2) {
@@ -278909,19 +279058,16 @@ var TokenQuotaExceededError = class extends Error {
278909
279058
  };
278910
279059
  var DEFAULT_RUN_TOKEN_QUOTA = 1e7;
278911
279060
  var RUN_TOKEN_QUOTA_INPUT = "max_run_tokens";
278912
- function billableTokens(t) {
278913
- let total = 0;
278914
- for (const v of [t.input, t.output, t.cacheRead, t.cacheWrite]) {
278915
- if (typeof v === "number" && Number.isFinite(v) && v > 0)
278916
- total += v;
278917
- }
278918
- return Math.floor(total);
279061
+ var CACHE_READ_WEIGHT = 0.1;
279062
+ function quotaTokens(t) {
279063
+ const part = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
279064
+ return Math.floor(part(t.input) + part(t.output) + part(t.cacheWrite) + part(t.cacheRead) * CACHE_READ_WEIGHT);
278919
279065
  }
278920
279066
  function normalizeTotal(total) {
278921
- return typeof total === "number" ? Math.max(0, Math.floor(total)) : billableTokens(total);
279067
+ return typeof total === "number" ? Math.max(0, Math.floor(total)) : quotaTokens(total);
278922
279068
  }
278923
279069
  function tokenGap(used, ceiling) {
278924
- return `run token quota exhausted: ${used.toLocaleString("en-GB")} of ${ceiling.toLocaleString("en-GB")} billable tokens consumed, so the run was stopped before it finished. Its results are INCOMPLETE and must not be read as a clean assessment \u2014 a finding that would have been raised after this point was never looked for. Raise \`${RUN_TOKEN_QUOTA_INPUT}\` if the run legitimately needs more budget, or set \`${RUN_TOKEN_QUOTA_INPUT}: 0\` to remove the bound entirely. A run that reaches this limit without finishing is more often a remediation loop that cannot converge (a fix that re-breaks its own check) than a genuinely large repository.`;
279070
+ return `run token quota exhausted: ${used.toLocaleString("en-GB")} of ${ceiling.toLocaleString("en-GB")} tokens counted against the quota (a token read from the prompt cache counts as a tenth), so the run was stopped before it finished. Its results are INCOMPLETE and must not be read as a clean assessment \u2014 a finding that would have been raised after this point was never looked for. Raise \`${RUN_TOKEN_QUOTA_INPUT}\` if the run legitimately needs more budget, or set \`${RUN_TOKEN_QUOTA_INPUT}: 0\` to remove the bound entirely. A run that reaches this limit without finishing is more often a remediation loop that cannot converge (a fix that re-breaks its own check) than a genuinely large repository.`;
278925
279071
  }
278926
279072
  function unlimitedTokenQuota() {
278927
279073
  return createMonotonicQuota({
@@ -280064,6 +280210,12 @@ function parseCommentMarker(body2) {
280064
280210
  return null;
280065
280211
  }
280066
280212
  }
280213
+ function findingKeysIn(body2) {
280214
+ return [...body2.matchAll(new RegExp(MARKER.source, "g"))].flatMap((m) => {
280215
+ const marker2 = parseCommentMarker(m[0]);
280216
+ return marker2?.kind === "finding" ? [marker2.key] : [];
280217
+ });
280218
+ }
280067
280219
  var SUGGESTION = /```suggestion\r?\n([\s\S]*?)```/;
280068
280220
  function suggestionText(body2) {
280069
280221
  const m = SUGGESTION.exec(body2);
@@ -280220,23 +280372,54 @@ function computeScanDelta(input2) {
280220
280372
  };
280221
280373
  }
280222
280374
  function markPossibleRenames(introduced, resolved, headAddresses) {
280223
- const vanished = resolved.filter((r) => {
280224
- if (r.address === null) return false;
280225
- return !headAddresses(dirOf3(r.concern.location.file)).has(r.address);
280226
- });
280375
+ const byResource = (findings) => {
280376
+ const out = /* @__PURE__ */ new Map();
280377
+ for (const f of [...findings].sort((a, b) => byLocation(a.concern, b.concern))) {
280378
+ if (f.address === null) continue;
280379
+ const dir = dirOf3(f.concern.location.file);
280380
+ const key = `${dir}|${f.address}`;
280381
+ const entry4 = out.get(key);
280382
+ if (entry4) entry4.findings.push(f);
280383
+ else out.set(key, { dir, address: f.address, findings: [f] });
280384
+ }
280385
+ return [...out.values()];
280386
+ };
280387
+ const vanished = byResource(resolved).filter((r) => !headAddresses(r.dir).has(r.address));
280388
+ const appeared = byResource(introduced);
280389
+ const shared = (from, to) => {
280390
+ const left = /* @__PURE__ */ new Map();
280391
+ for (const f of from.findings)
280392
+ left.set(ruleKey(f.concern), (left.get(ruleKey(f.concern)) ?? 0) + 1);
280393
+ return to.findings.filter((f) => {
280394
+ const n = left.get(ruleKey(f.concern)) ?? 0;
280395
+ if (n === 0) return false;
280396
+ left.set(ruleKey(f.concern), n - 1);
280397
+ return true;
280398
+ });
280399
+ };
280400
+ const order2 = (a, b) => a.dir.localeCompare(b.dir) || a.address.localeCompare(b.address);
280401
+ const candidates = vanished.flatMap(
280402
+ (from) => appeared.filter(
280403
+ (to) => (
280404
+ // a `moved` block cannot change a resource's type
280405
+ typeOf(from.address) === typeOf(to.address) && // renamed in place, or moved elsewhere under its own name
280406
+ from.dir === to.dir !== (from.address === to.address)
280407
+ )
280408
+ ).map((to) => ({ from, to, findings: shared(from, to) })).filter((p) => p.findings.length > 0)
280409
+ );
280410
+ candidates.sort(
280411
+ (a, b) => b.findings.length - a.findings.length || order2(a.from, b.from) || order2(a.to, b.to)
280412
+ );
280227
280413
  const used = /* @__PURE__ */ new Set();
280228
- for (const finding of introduced) {
280229
- if (finding.address === null) continue;
280230
- const dir = dirOf3(finding.concern.location.file);
280231
- const match5 = vanished.find(
280232
- (r) => !used.has(r) && ruleKey(r.concern) === ruleKey(finding.concern) && // a `moved` block cannot change a resource's type
280233
- typeOf(r.address) === typeOf(finding.address) && // renamed in place, or moved elsewhere under its own name
280234
- dirOf3(r.concern.location.file) === dir !== (r.address === finding.address)
280235
- );
280236
- if (!match5) continue;
280237
- used.add(match5);
280238
- finding.possiblyPreexisting = true;
280239
- finding.renamedFrom = { address: match5.address, file: match5.concern.location.file };
280414
+ for (const { from, to, findings } of candidates) {
280415
+ if (used.has(from) || used.has(to)) continue;
280416
+ used.add(from);
280417
+ used.add(to);
280418
+ const file2 = from.findings[0]?.concern.location.file;
280419
+ for (const finding of findings) {
280420
+ finding.possiblyPreexisting = true;
280421
+ finding.renamedFrom = { address: from.address, file: file2 };
280422
+ }
280240
280423
  }
280241
280424
  }
280242
280425
  function typeOf(address) {
@@ -280505,39 +280688,115 @@ function formatDuplicateCommentsNote(duplicates) {
280505
280688
  var MAX_DUPLICATE_COMMENT_LINES = 50;
280506
280689
 
280507
280690
  // src/mcp/reviewCommentLimit.ts
280508
- var MAX_OVERFLOW_LINES = 50;
280691
+ var MAX_NOTE_CHARS = 3e4;
280692
+ var TRAILER_RESERVE = 400;
280509
280693
  var PROVEN_MARK = "\u2705 **Proven by re-scan**";
280510
- function rank(c) {
280694
+ var SEVERITY_LEVEL = {
280695
+ critical: 0,
280696
+ high: 1,
280697
+ medium: 2,
280698
+ low: 3,
280699
+ info: 4
280700
+ };
280701
+ var MARKER_LEVEL = { "\u{1F6A8}": 1, "\u26A0\uFE0F": 2, "\u26A0": 2, "\u2139\uFE0F": 3, \u2139: 3 };
280702
+ var UNMARKED_LEVEL = 2;
280703
+ function rank(c, severity) {
280511
280704
  const body2 = c.body ?? "";
280512
- const marker2 = leadingMarker(body2);
280513
- const severity = marker2 === "\u{1F6A8}" ? 0 : marker2 === "\u26A0\uFE0F" || marker2 === "\u26A0" ? 2 : 4;
280514
- return severity + (body2.includes(PROVEN_MARK) ? 0 : 1);
280705
+ const level = severity ? SEVERITY_LEVEL[severity] : MARKER_LEVEL[leadingMarker(body2) ?? ""] ?? UNMARKED_LEVEL;
280706
+ return level * 2 + (body2.includes(PROVEN_MARK) ? 0 : 1);
280515
280707
  }
280516
- function limitInlineComments(comments, limit) {
280708
+ function limitInlineComments(comments, limit, severityOf) {
280517
280709
  if (!limit || comments.length <= limit) return { kept: [...comments], overflow: [] };
280518
280710
  const keep = new Set(
280519
- comments.map((c, i) => ({ i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).slice(0, limit).map(({ i }) => i)
280711
+ comments.map((c, i) => ({ i, r: rank(c, severityOf?.(c)) })).sort((a, b) => a.r - b.r || a.i - b.i).slice(0, limit).map(({ i }) => i)
280520
280712
  );
280521
280713
  return {
280522
280714
  kept: comments.filter((_, i) => keep.has(i)),
280523
280715
  overflow: comments.filter((_, i) => !keep.has(i))
280524
280716
  };
280525
280717
  }
280526
- function formatOverflowNote(overflow, limit) {
280718
+ var escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
280719
+ var whereOf = (c) => c.line ? `${c.path}:${c.line}` : c.path;
280720
+ function closesItself(body2) {
280721
+ const occurrences = (re) => body2.match(re)?.length ?? 0;
280722
+ const fences = body2.split("\n").filter((l) => /^\s*(```|~~~)/.test(l)).length;
280723
+ return fences % 2 === 0 && occurrences(/<details\b/gi) === occurrences(/<\/details\s*>/gi) && occurrences(/<!--/g) === occurrences(/-->/g);
280724
+ }
280725
+ function asText(body2) {
280726
+ const longest = Math.max(0, ...(body2.match(/`+/g) ?? []).map((run2) => run2.length));
280727
+ const fence = "`".repeat(Math.max(3, longest + 1));
280728
+ return `${fence}text
280729
+ ${body2}
280730
+ ${fence}`;
280731
+ }
280732
+ function renderWhole(c, linkOf) {
280733
+ const where4 = escapeHtml(whereOf(c));
280734
+ const gist = gistOf(c.body);
280735
+ const href = linkOf?.(c);
280736
+ const body2 = c.body ?? "";
280737
+ return [
280738
+ `<details><summary><code>${where4}</code>${gist ? ` \u2014 ${gist}` : ""}</summary>`,
280739
+ ...href ? [`<a href="${escapeHtml(href)}">${where4}</a>`] : [],
280740
+ ...body2 ? [closesItself(body2) ? body2 : asText(body2)] : [],
280741
+ "</details>"
280742
+ ].join("\n\n");
280743
+ }
280744
+ function renderBrief(c) {
280745
+ const gist = gistOf(c.body);
280746
+ const markers = findingKeysIn(c.body ?? "").map(
280747
+ (key) => renderCommentMarker({ kind: "finding", key, proven: false })
280748
+ );
280749
+ return `- \`${whereOf(c).replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}${markers.map((m) => ` ${m}`).join("")}`;
280750
+ }
280751
+ function formatOverflowNote(overflow, limit, linkOf) {
280527
280752
  if (overflow.length === 0) return "";
280528
- const shown = overflow.slice(0, MAX_OVERFLOW_LINES).map((c) => {
280529
- const where4 = c.line ? `${c.path}:${c.line}` : c.path;
280530
- const gist = gistOf(c.body);
280531
- return `- \`${where4.replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}`;
280532
- });
280533
- const rest = overflow.length - shown.length;
280534
- if (rest > 0) shown.push(`- \u2026and ${rest} more not shown`);
280535
- return `
280753
+ const header = `
280536
280754
 
280537
280755
  ---
280538
280756
 
280539
- **Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead:
280540
- ` + shown.join("\n");
280757
+ **Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead:`;
280758
+ const SEP = 2;
280759
+ let room = MAX_NOTE_CHARS - TRAILER_RESERVE - header.length;
280760
+ const items = [];
280761
+ for (const c of overflow) {
280762
+ const brief2 = renderBrief(c);
280763
+ if (brief2.length + SEP > room) break;
280764
+ items.push(brief2);
280765
+ room -= brief2.length + SEP;
280766
+ }
280767
+ let brief = items.length;
280768
+ for (const [i, c] of overflow.slice(0, items.length).entries()) {
280769
+ const whole = renderWhole(c, linkOf);
280770
+ const extra = whole.length - items[i].length;
280771
+ if (extra > room) continue;
280772
+ items[i] = whole;
280773
+ room -= extra;
280774
+ brief--;
280775
+ }
280776
+ const omitted = overflow.length - items.length;
280777
+ if (omitted > 0) items.push(`- \u2026and ${omitted} more not shown`);
280778
+ const cut = [
280779
+ brief > 0 ? `${brief} of them ${brief === 1 ? "is" : "are"} shown by place and first line only` : "",
280780
+ omitted > 0 ? `${omitted} ${omitted === 1 ? "is" : "are"} not shown at all` : ""
280781
+ ].filter(Boolean);
280782
+ if (cut.length > 0) {
280783
+ items.push(`${cut.join(" and ")}, to keep this review within GitHub's size limit.`);
280784
+ }
280785
+ return `${header}
280786
+
280787
+ ${items.join("\n\n")}`;
280788
+ }
280789
+ function foldedFindingsStillIntroduced(input2) {
280790
+ const folded = new Set(
280791
+ input2.reviews.flatMap(
280792
+ (r) => r.body && isOwnReviewComment(r.user, r.body) ? findingKeysIn(r.body) : []
280793
+ )
280794
+ );
280795
+ if (folded.size === 0) return [];
280796
+ const dismissed = new Set(input2.dismissed.map((d) => d.finding_id));
280797
+ return input2.delta.introduced.filter(
280798
+ (f) => folded.has(findingKey(f)) && !dismissed.has(f.concern.id)
280799
+ );
280541
280800
  }
280542
280801
 
280543
280802
  // src/mcp/reviewComments.ts
@@ -281368,7 +281627,8 @@ function deltaVerdict(delta, requiredControls) {
281368
281627
  return {
281369
281628
  pass: gate.pass && required3.length === 0,
281370
281629
  gating: gate.gating.length,
281371
- required: required3.length
281630
+ required: required3.length,
281631
+ blocking: (/* @__PURE__ */ new Set([...gate.gating, ...required3])).size
281372
281632
  };
281373
281633
  }
281374
281634
  function failsGate(finding, requiredControls) {
@@ -281495,7 +281755,7 @@ function where2(f) {
281495
281755
  function renderReviewVerdict(input2) {
281496
281756
  const { delta, verdict: verdict2 } = input2;
281497
281757
  const introduced = delta.introduced.length;
281498
- const blocking = verdict2.gating + verdict2.required;
281758
+ const { blocking } = verdict2;
281499
281759
  const renamedBlocking = delta.introduced.filter(
281500
281760
  (f) => f.possiblyPreexisting && failsGate(f, [...input2.requiredControls ?? []])
281501
281761
  ).length;
@@ -281604,7 +281864,7 @@ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, rmSync as
281604
281864
  import { join as join28, posix as posix8 } from "node:path";
281605
281865
 
281606
281866
  // src/mcp/terraform/lineage.ts
281607
- import { createHash as createHash8 } from "node:crypto";
281867
+ import { createHash as createHash9 } from "node:crypto";
281608
281868
  import { readFileSync as readFileSync15 } from "node:fs";
281609
281869
  import { join as join27 } from "node:path";
281610
281870
  function repoFileReader(cwd) {
@@ -281617,7 +281877,7 @@ function repoFileReader(cwd) {
281617
281877
  };
281618
281878
  }
281619
281879
  function lineageKeyOf(c, address) {
281620
- return createHash8("sha1").update(`${c.source}|${bareRuleOf(c.source, c.rule_id)}|${c.location.file}|${address ?? ""}`).digest("hex").slice(0, 16);
281880
+ return createHash9("sha1").update(`${c.source}|${bareRuleOf(c.source, c.rule_id)}|${c.location.file}|${address ?? ""}`).digest("hex").slice(0, 16);
281621
281881
  }
281622
281882
  function lineageResolver(read3) {
281623
281883
  const files = /* @__PURE__ */ new Map();
@@ -281789,7 +282049,7 @@ function apply(worktree, path15, cs) {
281789
282049
 
281790
282050
  // src/mcp/terraform/gateScan.ts
281791
282051
  import { existsSync as existsSync7, mkdtempSync as mkdtempSync3, rmSync as rmSync6 } from "node:fs";
281792
- import { join as join32, posix as posix10 } from "node:path";
282052
+ import { join as join33, posix as posix10 } from "node:path";
281793
282053
 
281794
282054
  // src/mcp/terraform/coverage.ts
281795
282055
  function bandOf(group2, concerns, signals) {
@@ -282227,6 +282487,68 @@ function dedupeCrossScanner(concerns) {
282227
282487
  return [...merged, ...passthrough];
282228
282488
  }
282229
282489
 
282490
+ // src/mcp/terraform/verifySites.ts
282491
+ import { readFileSync as readFileSync17 } from "node:fs";
282492
+ import { join as join29 } from "node:path";
282493
+ function fileReader(cwd) {
282494
+ const cache3 = /* @__PURE__ */ new Map();
282495
+ return (file2) => {
282496
+ if (!cache3.has(file2)) {
282497
+ let text;
282498
+ try {
282499
+ text = readFileSync17(join29(cwd, file2), "utf8");
282500
+ } catch {
282501
+ text = null;
282502
+ }
282503
+ cache3.set(file2, text);
282504
+ }
282505
+ return cache3.get(file2) ?? null;
282506
+ };
282507
+ }
282508
+ function siteOf(c, read3) {
282509
+ const text = read3(c.location.file);
282510
+ return text === null ? null : resourceAddressAt(text, c.location.line);
282511
+ }
282512
+ function recordConcernSites(session, concerns, cwd, linesMatchTree) {
282513
+ const read3 = fileReader(cwd);
282514
+ session.siteById ??= /* @__PURE__ */ new Map();
282515
+ session.firstSitesByKey ??= /* @__PURE__ */ new Map();
282516
+ const first = session.firstSitesByKey;
282517
+ const fresh = /* @__PURE__ */ new Map();
282518
+ for (const c of concerns) {
282519
+ const site = linesMatchTree ? siteOf(c, read3) : null;
282520
+ const prior = session.siteById.get(c.id);
282521
+ session.siteById.set(c.id, prior === void 0 || prior === site ? site : null);
282522
+ if (!linesMatchTree) continue;
282523
+ const key = concernKeyOf(c);
282524
+ if (first.has(key)) continue;
282525
+ const sites = fresh.get(key) ?? /* @__PURE__ */ new Set();
282526
+ sites.add(site);
282527
+ fresh.set(key, sites);
282528
+ }
282529
+ for (const [key, sites] of fresh) first.set(key, sites);
282530
+ }
282531
+ function resolvedAtSite(params) {
282532
+ const firingByKey = /* @__PURE__ */ new Map();
282533
+ for (const c of params.current) {
282534
+ const list = firingByKey.get(c.key) ?? [];
282535
+ list.push(c.site);
282536
+ firingByKey.set(c.key, list);
282537
+ }
282538
+ const out = [];
282539
+ for (const r of params.requested) {
282540
+ if (r.site === null || r.file === void 0) continue;
282541
+ const firing = firingByKey.get(r.key) ?? [];
282542
+ const known = params.firstSites.get(r.key);
282543
+ if (known === void 0) continue;
282544
+ if (firing.some((s) => s === null || !known.has(s))) continue;
282545
+ if (firing.includes(r.site)) continue;
282546
+ if (!params.declared(r.file).has(r.site)) continue;
282547
+ out.push(r.id);
282548
+ }
282549
+ return out;
282550
+ }
282551
+
282230
282552
  // src/mcp/terraform/concernResult.ts
282231
282553
  function annotateConcerns(rawConcerns, opts) {
282232
282554
  const inScope = opts.inScope ?? (() => true);
@@ -282253,6 +282575,7 @@ function buildConcernResult(ctx, rawConcerns, opts) {
282253
282575
  fullBaseline.map((c) => concernKeyOf(c))
282254
282576
  );
282255
282577
  recordConcernKeys(session, fullBaseline);
282578
+ recordConcernSites(session, fullBaseline, opts.cwd, opts.linesMatchTree ?? false);
282256
282579
  session.baselineFmtDriftFiles = union3(
282257
282580
  session.baselineFmtDriftFiles,
282258
282581
  fullBaseline.filter((c) => c.source === "terraform-fmt").map((c) => c.location.file)
@@ -282267,13 +282590,13 @@ function buildConcernResult(ctx, rawConcerns, opts) {
282267
282590
  active,
282268
282591
  opts.autonomyThreshold
282269
282592
  );
282270
- const suffix2 = remediationBranchSuffix(
282271
- remediationBaseOf(ctx),
282272
- ctx.repo?.data.default_branch?.trim() || void 0
282273
- );
282274
- const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: `remediate/${g.id}${suffix2}` }));
282593
+ const base = remediationBaseOf(ctx);
282594
+ const defaultBranch = ctx.repo?.data.default_branch?.trim() || void 0;
282595
+ const branchFor = (id) => remediationBranchFor(id, base, defaultBranch, opts.openRemediationPrs);
282596
+ const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: branchFor(g.id) }));
282275
282597
  const planned = planBatches(groups2);
282276
- const batchPlan = planned.batch_branch ? { ...planned, batch_branch: `${planned.batch_branch}${suffix2}` } : planned;
282598
+ const batchId = planned.batch_branch ? groupIdFromBranch(planned.batch_branch) : null;
282599
+ const batchPlan = batchId ? { ...planned, batch_branch: branchFor(batchId) } : planned;
282277
282600
  const bySeverity = {};
282278
282601
  for (const c of active) bySeverity[c.severity] = (bySeverity[c.severity] ?? 0) + 1;
282279
282602
  return {
@@ -282289,7 +282612,7 @@ function buildConcernResult(ctx, rawConcerns, opts) {
282289
282612
 
282290
282613
  // src/mcp/terraform/initArtefacts.ts
282291
282614
  import { rmSync as rmSync4 } from "node:fs";
282292
- import { join as join29, posix as posix9 } from "node:path";
282615
+ import { join as join30, posix as posix9 } from "node:path";
282293
282616
  var INIT_ARTEFACTS = /* @__PURE__ */ new Set([".terraform", ".terraform.lock.hcl"]);
282294
282617
  function initArtefacts(cwd) {
282295
282618
  const r = run("git", ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], cwd);
@@ -282312,7 +282635,7 @@ function removeNewInitArtefacts(cwd, before, by) {
282312
282635
  const created = [...after?.untracked ?? []].filter((p) => !before.untracked.has(p));
282313
282636
  const changed = [...after?.modifiedLocks ?? []].filter((p) => !before.modifiedLocks.has(p));
282314
282637
  if (!top || created.length + changed.length === 0) return;
282315
- for (const p of created) rmSync4(join29(top, p), { recursive: true, force: true });
282638
+ for (const p of created) rmSync4(join30(top, p), { recursive: true, force: true });
282316
282639
  if (changed.length > 0) run("git", ["checkout", "--", ...changed], top);
282317
282640
  resetTerraformInitCache();
282318
282641
  log2.info(
@@ -282321,8 +282644,8 @@ function removeNewInitArtefacts(cwd, before, by) {
282321
282644
  }
282322
282645
 
282323
282646
  // src/mcp/terraform/secretScan.ts
282324
- import { readFileSync as readFileSync17, rmSync as rmSync5 } from "node:fs";
282325
- import { join as join30 } from "node:path";
282647
+ import { readFileSync as readFileSync18, rmSync as rmSync5 } from "node:fs";
282648
+ import { join as join31 } from "node:path";
282326
282649
  function parseBetterleaksReport(json5, prefix2 = "") {
282327
282650
  const doc = JSON.parse(json5);
282328
282651
  if (Array.isArray(doc) || !doc || typeof doc !== "object" || !("findings" in doc)) {
@@ -282356,7 +282679,7 @@ async function scanAddedSecrets(cwd, mergeBase, tmp) {
282356
282679
  const top = run("git", ["rev-parse", "--show-toplevel"], cwd).stdout.trim();
282357
282680
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd).stdout.trim();
282358
282681
  if (!top) return { ran: false, reason: "not a git checkout", concerns: [] };
282359
- const report2 = join30(tmp, `betterleaks-${process.pid}.json`);
282682
+ const report2 = join31(tmp, `betterleaks-${process.pid}.json`);
282360
282683
  try {
282361
282684
  const r = await runAsync(
282362
282685
  "betterleaks",
@@ -282379,7 +282702,7 @@ async function scanAddedSecrets(cwd, mergeBase, tmp) {
282379
282702
  const why = (r.stderr || r.stdout).trim().split("\n").pop() ?? "";
282380
282703
  let parsed2;
282381
282704
  try {
282382
- parsed2 = parseBetterleaksReport(readFileSync17(report2, "utf-8"), prefix2);
282705
+ parsed2 = parseBetterleaksReport(readFileSync18(report2, "utf-8"), prefix2);
282383
282706
  } catch (error63) {
282384
282707
  const reason = r.status !== 0 ? `betterleaks exited ${r.status}: ${why}` : `${error63}`;
282385
282708
  return { ran: false, reason, concerns: [] };
@@ -282394,8 +282717,8 @@ async function scanAddedSecrets(cwd, mergeBase, tmp) {
282394
282717
  }
282395
282718
 
282396
282719
  // src/mcp/terraform/tools/scan.ts
282397
- import { readFileSync as readFileSync18 } from "node:fs";
282398
- import { join as join31 } from "node:path";
282720
+ import { readFileSync as readFileSync19 } from "node:fs";
282721
+ import { join as join32 } from "node:path";
282399
282722
 
282400
282723
  // src/mcp/terraform/noiseProfile.ts
282401
282724
  var MIN_DISMISSAL_REPOS = 3;
@@ -282569,7 +282892,9 @@ async function runScan(ctx, {
282569
282892
  grouping,
282570
282893
  autonomyThreshold,
282571
282894
  inScope,
282572
- priority
282895
+ priority,
282896
+ openRemediationPrs: await listOpenRemediationPrs(ctx),
282897
+ linesMatchTree: true
282573
282898
  }
282574
282899
  );
282575
282900
  const { active, suppressed, groups: groups2, batchPlan, bySeverity: by_severity } = result;
@@ -282581,7 +282906,7 @@ async function runScan(ctx, {
282581
282906
  outcomes.flatMap((o) => o.concerns),
282582
282907
  (rel) => {
282583
282908
  try {
282584
- return readFileSync18(join31(cwd, rel), "utf-8");
282909
+ return readFileSync19(join32(cwd, rel), "utf-8");
282585
282910
  } catch {
282586
282911
  return null;
282587
282912
  }
@@ -282733,8 +283058,8 @@ function checkOutBase(cwd, pr, tmp) {
282733
283058
  }
282734
283059
  const mergeBase = run("git", ["merge-base", pr.baseSha, "HEAD"], cwd).stdout.trim() || pr.baseSha;
282735
283060
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd).stdout.trim();
282736
- const parent = mkdtempSync3(join32(tmp, "scan-delta-base-"));
282737
- const worktree = join32(parent, "wt");
283061
+ const parent = mkdtempSync3(join33(tmp, "scan-delta-base-"));
283062
+ const worktree = join33(parent, "wt");
282738
283063
  if (run("git", ["worktree", "add", "--detach", worktree, mergeBase], cwd).status !== 0) {
282739
283064
  rmSync6(parent, { recursive: true, force: true });
282740
283065
  log2.warning("\xBB scan delta: could not check out the base \u2014 every finding is treated as new.");
@@ -282742,7 +283067,7 @@ function checkOutBase(cwd, pr, tmp) {
282742
283067
  }
282743
283068
  return {
282744
283069
  mergeBase,
282745
- cwd: prefix2 ? join32(worktree, prefix2) : worktree,
283070
+ cwd: prefix2 ? join33(worktree, prefix2) : worktree,
282746
283071
  close: () => {
282747
283072
  run("git", ["worktree", "remove", "--force", worktree], cwd);
282748
283073
  rmSync6(parent, { recursive: true, force: true });
@@ -283210,6 +283535,22 @@ function CreatePullRequestReviewTool(ctx) {
283210
283535
  `cannot approve: ${outstanding} unresolved ${PRODUCT_NAME} review thread(s) still open on this PR. approval requires every prior ${PRODUCT_NAME} finding to be resolved, not just the latest commits to be clean. inspect them with \`${listRef}\`; for each thread the current code genuinely addresses, reply then call \`${resolveRef2}\`, and retry this approval. if any thread is a real outstanding issue, do NOT approve \u2014 submit a non-approving review (omit \`approved\`, or set \`request_changes\` for a blocking one) that covers it instead.`
283211
283536
  );
283212
283537
  }
283538
+ if (delta) {
283539
+ const reviews = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listReviews, {
283540
+ owner: ctx.repo.owner,
283541
+ repo: ctx.repo.name,
283542
+ pull_number,
283543
+ per_page: 100
283544
+ });
283545
+ const folded = foldedFindingsStillIntroduced({ reviews, delta, dismissed });
283546
+ if (folded.length > 0) {
283547
+ throw new Error(
283548
+ `cannot approve: ${folded.length} introduced finding(s) an earlier ${PRODUCT_NAME} review listed in its body (past \`review_comment_limit\`, so with no thread to resolve) are still introduced by this head: ` + folded.slice(0, 20).map(
283549
+ (f) => `${f.concern.id} (${f.concern.rule_id} at ${f.concern.location.file}:${f.concern.location.line ?? "?"})`
283550
+ ).join(", ") + `. approval requires every prior ${PRODUCT_NAME} finding to be addressed. if one genuinely needs no change, list it in \`dismissed\` with the reason and retry; otherwise do NOT approve \u2014 submit a non-approving review that covers it.`
283551
+ );
283552
+ }
283553
+ }
283213
283554
  }
283214
283555
  const skip = reviewSkipDecision({
283215
283556
  approved: approved ?? false,
@@ -283316,6 +283657,7 @@ ${formatReanchoredNote(reanchored.moved)}`
283316
283657
  params.event = event;
283317
283658
  }
283318
283659
  const proofs = delta ? await proveReviewSuggestions(ctx, auditedComments) : /* @__PURE__ */ new Map();
283660
+ const findingSeverity = /* @__PURE__ */ new Map();
283319
283661
  const reviewComments = auditedComments.map((comment, index) => {
283320
283662
  let commentBody = fixDoubleEscapedString(comment.body || "");
283321
283663
  if (comment.suggestion !== void 0) {
@@ -283357,6 +283699,9 @@ ${marker2}`;
283357
283699
  reviewComment.start_line = comment.start_line;
283358
283700
  reviewComment.start_side = side;
283359
283701
  }
283702
+ if (comment.provenance?.kind === "scanner") {
283703
+ findingSeverity.set(reviewComment, comment.provenance.finding.concern.severity);
283704
+ }
283360
283705
  return reviewComment;
283361
283706
  });
283362
283707
  let droppedComments = [];
@@ -283392,7 +283737,11 @@ ${marker2}`;
283392
283737
  `withholding ${duplicateComments.length}/${reviewComments.length} inline comment(s) that duplicate open review threads`
283393
283738
  );
283394
283739
  }
283395
- const limited = limitInlineComments(deduped.kept, ctx.payload.reviewCommentLimit);
283740
+ const limited = limitInlineComments(
283741
+ deduped.kept,
283742
+ ctx.payload.reviewCommentLimit,
283743
+ (c) => findingSeverity.get(c)
283744
+ );
283396
283745
  overflowComments = limited.overflow;
283397
283746
  if (overflowComments.length > 0) {
283398
283747
  log2.info(
@@ -283424,7 +283773,13 @@ ${marker2}`;
283424
283773
  body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
283425
283774
  }
283426
283775
  if (overflowComments.length > 0) {
283427
- const note = formatOverflowNote(overflowComments, ctx.payload.reviewCommentLimit);
283776
+ const server = process.env.GITHUB_SERVER_URL || "https://github.com";
283777
+ const commit = params.commit_id;
283778
+ const note = formatOverflowNote(
283779
+ overflowComments,
283780
+ ctx.payload.reviewCommentLimit,
283781
+ commit ? (c) => `${server}/${ctx.repo.owner}/${ctx.repo.name}/blob/${commit}/${c.path.split("/").map(encodeURIComponent).join("/")}${c.line && c.side !== "LEFT" ? `#L${c.line}` : ""}` : void 0
283782
+ );
283428
283783
  body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
283429
283784
  }
283430
283785
  let reproved = 0;
@@ -283677,7 +284032,7 @@ ${coverageState.lastBreakdown}`
283677
284032
  }
283678
284033
  function readReviewedFile(path15) {
283679
284034
  try {
283680
- return readFileSync19(resolveWithinCwd(resolveRepoRoot(), path15), "utf-8");
284035
+ return readFileSync20(resolveWithinCwd(resolveRepoRoot(), path15), "utf-8");
283681
284036
  } catch {
283682
284037
  return null;
283683
284038
  }
@@ -283992,7 +284347,7 @@ function fastForwardDiff(params, exec2) {
283992
284347
 
283993
284348
  // src/utils/prompt/changeImpact.ts
283994
284349
  import { writeFileSync as writeFileSync9 } from "node:fs";
283995
- import { join as join33 } from "node:path";
284350
+ import { join as join34 } from "node:path";
283996
284351
  var MAX_ATOMS = 20;
283997
284352
  var MAX_REFS_PER_ATOM = 6;
283998
284353
  var MAX_EXCERPT = 160;
@@ -284172,7 +284527,7 @@ function createChangeImpactArtifact(params) {
284172
284527
  fileCount: params.files.length,
284173
284528
  lookupError
284174
284529
  });
284175
- const path15 = join33(
284530
+ const path15 = join34(
284176
284531
  params.tmpdir,
284177
284532
  `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`
284178
284533
  );
@@ -284192,7 +284547,7 @@ function createChangeImpactArtifact(params) {
284192
284547
  import { execFileSync, execSync as execSync2 } from "node:child_process";
284193
284548
  import { mkdtempSync as mkdtempSync4, readdirSync as readdirSync5, realpathSync as realpathSync4, unlinkSync as unlinkSync2 } from "node:fs";
284194
284549
  import { tmpdir as tmpdir2 } from "node:os";
284195
- import { join as join34 } from "node:path";
284550
+ import { join as join35 } from "node:path";
284196
284551
 
284197
284552
  // src/utils/github/ghaCore.ts
284198
284553
  import { randomUUID as randomUUID6 } from "node:crypto";
@@ -284215,7 +284570,7 @@ var TEMP_DIR_STATE_KEY = "infraweaver_temp_dir";
284215
284570
 
284216
284571
  // src/utils/setup/setup.ts
284217
284572
  function createTempDirectory() {
284218
- const sharedTempDir = mkdtempSync4(join34(tmpdir2(), `${PRODUCT_SLUG}-`));
284573
+ const sharedTempDir = mkdtempSync4(join35(tmpdir2(), `${PRODUCT_SLUG}-`));
284219
284574
  process.env.INFRAWEAVER_TEMP_DIR = sharedTempDir;
284220
284575
  saveState2(TEMP_DIR_STATE_KEY, sharedTempDir);
284221
284576
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
@@ -284261,13 +284616,13 @@ function wipeRunnerLeakSurface() {
284261
284616
  return [];
284262
284617
  }
284263
284618
  };
284264
- const fileCommandsDir = join34(runnerTemp, "_runner_file_commands");
284619
+ const fileCommandsDir = join35(runnerTemp, "_runner_file_commands");
284265
284620
  for (const entry4 of listDir(fileCommandsDir)) {
284266
- tryUnlink(join34(fileCommandsDir, entry4));
284621
+ tryUnlink(join35(fileCommandsDir, entry4));
284267
284622
  }
284268
284623
  for (const entry4 of listDir(runnerTemp)) {
284269
284624
  if (entry4.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry4)) {
284270
- tryUnlink(join34(runnerTemp, entry4));
284625
+ tryUnlink(join35(runnerTemp, entry4));
284271
284626
  }
284272
284627
  }
284273
284628
  if (wiped.length > 0) {
@@ -284436,7 +284791,7 @@ function formatFilesWithLineNumbers(files) {
284436
284791
  }
284437
284792
  const tocLines = [`## Files (${files.length})`];
284438
284793
  for (const entry4 of tocEntries) {
284439
- const anchor2 = createHash9("sha256").update(entry4.filename).digest("hex");
284794
+ const anchor2 = createHash10("sha256").update(entry4.filename).digest("hex");
284440
284795
  tocLines.push(
284441
284796
  `- ${entry4.filename} \u2192 lines ${entry4.startLine}-${entry4.endLine} \xB7 diff-${anchor2}`
284442
284797
  );
@@ -284766,7 +285121,7 @@ function CheckoutPrTool(ctx) {
284766
285121
  headSha: ctx.toolState.checkoutSha
284767
285122
  });
284768
285123
  if (incremental) {
284769
- incrementalDiffPath = join35(
285124
+ incrementalDiffPath = join36(
284770
285125
  tempDir,
284771
285126
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
284772
285127
  );
@@ -284780,7 +285135,7 @@ function CheckoutPrTool(ctx) {
284780
285135
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
284781
285136
  log2.debug(`formatted diff preview (first 100 lines):
284782
285137
  ${diffPreview}`);
284783
- const diffPath = join35(tempDir, `pr-${pull_number}-${headShort}.diff`);
285138
+ const diffPath = join36(tempDir, `pr-${pull_number}-${headShort}.diff`);
284784
285139
  writeFileSync10(diffPath, formatResult.content);
284785
285140
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
284786
285141
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -284921,7 +285276,7 @@ ${dirty}`
284921
285276
 
284922
285277
  // src/mcp/checkSuite.ts
284923
285278
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync11 } from "node:fs";
284924
- import { join as join36 } from "node:path";
285279
+ import { join as join37 } from "node:path";
284925
285280
  import { stripVTControlCharacters } from "node:util";
284926
285281
  var GetCheckSuiteLogs = type({
284927
285282
  check_suite_id: type.number.describe("the id from check_suite.id")
@@ -285016,7 +285371,7 @@ function GetCheckSuiteLogsTool(ctx) {
285016
285371
  };
285017
285372
  }
285018
285373
  const tempDir = getTempDir();
285019
- const logsDir = join36(tempDir, "ci-logs");
285374
+ const logsDir = join37(tempDir, "ci-logs");
285020
285375
  mkdirSync3(logsDir, { recursive: true });
285021
285376
  const jobLists = await mapWithConcurrency(
285022
285377
  failedRuns,
@@ -285040,7 +285395,7 @@ function GetCheckSuiteLogsTool(ctx) {
285040
285395
  request: { signal: AbortSignal.timeout(1e4) }
285041
285396
  });
285042
285397
  const logsText = typeof data === "string" ? data : Buffer.from(data).toString("utf8");
285043
- const logPath = join36(logsDir, `job-${job.id}.log`);
285398
+ const logPath = join37(logsDir, `job-${job.id}.log`);
285044
285399
  writeFileSync11(logPath, logsText);
285045
285400
  const analysis = analyzeLog(logsText, 80);
285046
285401
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -285102,7 +285457,7 @@ function GetCheckSuiteLogsTool(ctx) {
285102
285457
 
285103
285458
  // src/mcp/commitInfo.ts
285104
285459
  import { writeFileSync as writeFileSync12 } from "node:fs";
285105
- import { join as join37 } from "node:path";
285460
+ import { join as join38 } from "node:path";
285106
285461
  var CommitInfo = type({
285107
285462
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
285108
285463
  });
@@ -285121,7 +285476,7 @@ function CommitInfoTool(ctx) {
285121
285476
  const files = data.files ?? [];
285122
285477
  const formatResult = formatFilesWithLineNumbers(files);
285123
285478
  const tempDir = getTempDir("get_commit_info");
285124
- const diffFile = join37(tempDir, `commit-${sha.slice(0, 7)}.diff`);
285479
+ const diffFile = join38(tempDir, `commit-${sha.slice(0, 7)}.diff`);
285125
285480
  writeFileSync12(diffFile, formatResult.content);
285126
285481
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
285127
285482
  return {
@@ -285283,7 +285638,7 @@ import { performance as performance3 } from "node:perf_hooks";
285283
285638
 
285284
285639
  // src/prep/installNodeDependencies.ts
285285
285640
  import { existsSync as existsSync8 } from "node:fs";
285286
- import { join as join38 } from "node:path";
285641
+ import { join as join39 } from "node:path";
285287
285642
 
285288
285643
  // node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
285289
285644
  function splitRunArgs(args2, valueFlags = []) {
@@ -285669,7 +286024,7 @@ async function installFallback(name, installSpec) {
285669
286024
  return result.stderr || `failed to install ${name}`;
285670
286025
  }
285671
286026
  if (name === "deno") {
285672
- const denoPath = join38(process.env.HOME || "", ".deno", "bin");
286027
+ const denoPath = join39(process.env.HOME || "", ".deno", "bin");
285673
286028
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
285674
286029
  }
285675
286030
  log2.info(`\xBB installed ${name}`);
@@ -285678,7 +286033,7 @@ async function installFallback(name, installSpec) {
285678
286033
  var installNodeDependencies = {
285679
286034
  name: "installNodeDependencies",
285680
286035
  shouldRun: () => {
285681
- const packageJsonPath = join38(process.cwd(), "package.json");
286036
+ const packageJsonPath = join39(process.cwd(), "package.json");
285682
286037
  return existsSync8(packageJsonPath);
285683
286038
  },
285684
286039
  run: async (options) => {
@@ -285787,8 +286142,8 @@ ${errorMessage2}`]
285787
286142
  };
285788
286143
 
285789
286144
  // src/prep/installPythonDependencies.ts
285790
- import { existsSync as existsSync9, readFileSync as readFileSync20 } from "node:fs";
285791
- import { join as join39 } from "node:path";
286145
+ import { existsSync as existsSync9, readFileSync as readFileSync21 } from "node:fs";
286146
+ import { join as join40 } from "node:path";
285792
286147
  var PYTHON_CONFIGS = [
285793
286148
  {
285794
286149
  file: "requirements.txt",
@@ -285823,7 +286178,7 @@ var PYTHON_CONFIGS = [
285823
286178
  ];
285824
286179
  function pyprojectDeclaresBuildSystem(cwd) {
285825
286180
  try {
285826
- const content = readFileSync20(join39(cwd, "pyproject.toml"), "utf8");
286181
+ const content = readFileSync21(join40(cwd, "pyproject.toml"), "utf8");
285827
286182
  return /^\s*\[build-system\]/m.test(content);
285828
286183
  } catch {
285829
286184
  return false;
@@ -285873,13 +286228,13 @@ var installPythonDependencies = {
285873
286228
  }
285874
286229
  const cwd = process.cwd();
285875
286230
  return PYTHON_CONFIGS.some(
285876
- (config3) => existsSync9(join39(cwd, config3.file)) && configApplies(config3, cwd)
286231
+ (config3) => existsSync9(join40(cwd, config3.file)) && configApplies(config3, cwd)
285877
286232
  );
285878
286233
  },
285879
286234
  run: async (options) => {
285880
286235
  const cwd = process.cwd();
285881
286236
  const config3 = PYTHON_CONFIGS.find(
285882
- (c) => existsSync9(join39(cwd, c.file)) && configApplies(c, cwd)
286237
+ (c) => existsSync9(join40(cwd, c.file)) && configApplies(c, cwd)
285883
286238
  );
285884
286239
  if (!config3) {
285885
286240
  return {
@@ -286400,12 +286755,12 @@ function AddLabelsTool(ctx) {
286400
286755
  }
286401
286756
 
286402
286757
  // src/mcp/moduleExtraction.ts
286403
- import { readFileSync as readFileSync23 } from "node:fs";
286404
- import { join as join42, posix as posix11 } from "node:path";
286758
+ import { readFileSync as readFileSync24 } from "node:fs";
286759
+ import { join as join43, posix as posix11 } from "node:path";
286405
286760
 
286406
286761
  // src/mcp/terraform/partialInterface.ts
286407
- import { readdirSync as readdirSync6, readFileSync as readFileSync21 } from "node:fs";
286408
- import { join as join40 } from "node:path";
286762
+ import { readdirSync as readdirSync6, readFileSync as readFileSync22 } from "node:fs";
286763
+ import { join as join41 } from "node:path";
286409
286764
  function unmappedArguments(resource, moduleResources) {
286410
286765
  const matching = moduleResources.filter((m) => m.type === resource.type);
286411
286766
  if (matching.length === 0) return [];
@@ -286415,10 +286770,10 @@ function unmappedArguments(resource, moduleResources) {
286415
286770
  function readDirHcl(cwd, dir) {
286416
286771
  let text = "";
286417
286772
  try {
286418
- for (const f of readdirSync6(join40(cwd, dir)).sort((a, b) => a.localeCompare(b, "en"))) {
286773
+ for (const f of readdirSync6(join41(cwd, dir)).sort((a, b) => a.localeCompare(b, "en"))) {
286419
286774
  if (!f.endsWith(".tf")) continue;
286420
286775
  try {
286421
- text += `${readFileSync21(join40(cwd, dir, f), "utf8")}
286776
+ text += `${readFileSync22(join41(cwd, dir, f), "utf8")}
286422
286777
  `;
286423
286778
  } catch {
286424
286779
  }
@@ -286448,7 +286803,7 @@ function findPartialInterfaces(cwd) {
286448
286803
  if (isInHouseModule(file2, graph)) continue;
286449
286804
  let text;
286450
286805
  try {
286451
- text = readFileSync21(join40(cwd, file2), "utf8");
286806
+ text = readFileSync22(join41(cwd, file2), "utf8");
286452
286807
  } catch {
286453
286808
  continue;
286454
286809
  }
@@ -286465,12 +286820,47 @@ function findPartialInterfaces(cwd) {
286465
286820
  }
286466
286821
 
286467
286822
  // src/mcp/terraform/refactorScope.ts
286468
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync22, statSync as statSync6 } from "node:fs";
286469
- import { join as join41 } from "node:path";
286823
+ import { readdirSync as readdirSync7, readFileSync as readFileSync23, statSync as statSync6 } from "node:fs";
286824
+ import { join as join42 } from "node:path";
286470
286825
  function isExamplePath(path15) {
286471
- return /(?:^|\/)examples?\//.test(path15.replace(/\\/g, "/"));
286826
+ return /(?:^|\/)examples\/|^example\//i.test(path15.replace(/\\/g, "/"));
286827
+ }
286828
+ var PROVIDER_OR_BACKEND = new RegExp(
286829
+ String.raw`(?:^|\n)\s*(?:provider|backend)\s+${BLOCK_LABEL}\s*\{`
286830
+ );
286831
+ var CLOUD_BLOCK = /(?:^|\n)\s*cloud\s*\{/;
286832
+ function configuresDeployment(name, text) {
286833
+ if (name.endsWith(".tf.json")) {
286834
+ let doc;
286835
+ try {
286836
+ doc = JSON.parse(text);
286837
+ } catch {
286838
+ return false;
286839
+ }
286840
+ if (typeof doc !== "object" || doc === null) return false;
286841
+ const { provider: provider2, terraform } = doc;
286842
+ if (provider2 !== void 0) return true;
286843
+ return [terraform].flat().some((t) => typeof t === "object" && t !== null && ("backend" in t || "cloud" in t));
286844
+ }
286845
+ if (PROVIDER_OR_BACKEND.test(text)) return true;
286846
+ return parseBlocks(text, ["terraform"]).some((b) => CLOUD_BLOCK.test(b.body));
286847
+ }
286848
+ var isTerraformFile2 = (name) => name.endsWith(".tf") || name.endsWith(".tf.json");
286849
+ function deploysFrom(dir) {
286850
+ let names;
286851
+ try {
286852
+ names = readdirSync7(dir).filter(isTerraformFile2);
286853
+ } catch {
286854
+ return false;
286855
+ }
286856
+ return names.some((name) => {
286857
+ try {
286858
+ return configuresDeployment(name, readFileSync23(join42(dir, name), "utf8"));
286859
+ } catch {
286860
+ return false;
286861
+ }
286862
+ });
286472
286863
  }
286473
- var PROVIDER_OR_BACKEND = /^\s*(?:provider\s+"|backend\s+")/m;
286474
286864
  function holdsTerraform(dir, depth) {
286475
286865
  let entries;
286476
286866
  try {
@@ -286479,7 +286869,7 @@ function holdsTerraform(dir, depth) {
286479
286869
  return false;
286480
286870
  }
286481
286871
  for (const name of entries) {
286482
- const path15 = join41(dir, name);
286872
+ const path15 = join42(dir, name);
286483
286873
  if (name.endsWith(".tf")) return true;
286484
286874
  if (depth > 0 && !name.startsWith(".") && statSync6(path15, { throwIfNoEntry: false })?.isDirectory()) {
286485
286875
  if (holdsTerraform(path15, depth - 1)) return true;
@@ -286488,26 +286878,35 @@ function holdsTerraform(dir, depth) {
286488
286878
  return false;
286489
286879
  }
286490
286880
  function libraryRepoReason(cwd) {
286491
- let rootFiles;
286881
+ let rootEntries;
286492
286882
  try {
286493
- rootFiles = readdirSync7(cwd).filter((f) => f.endsWith(".tf"));
286883
+ rootEntries = readdirSync7(cwd);
286494
286884
  } catch {
286495
286885
  return null;
286496
286886
  }
286497
- if (rootFiles.length === 0) return null;
286498
- for (const f of rootFiles) {
286499
- let text;
286500
- try {
286501
- text = readFileSync22(join41(cwd, f), "utf8");
286502
- } catch {
286503
- continue;
286504
- }
286505
- if (PROVIDER_OR_BACKEND.test(text)) return null;
286506
- }
286507
- const examples = ["examples", "example"].find((d) => existsSync10(join41(cwd, d)));
286508
- if (examples === void 0 || !holdsTerraform(join41(cwd, examples), 2)) return null;
286887
+ if (!rootEntries.some(isTerraformFile2) || deploysFrom(cwd)) return null;
286888
+ const examples = rootEntries.find((d) => /^examples?$/i.test(d));
286889
+ if (examples === void 0 || !holdsTerraform(join42(cwd, examples), 2)) return null;
286509
286890
  return `the repository root configures no provider and no backend, and ships \`${examples}/\`: it is a module other configurations call, so restructuring it is churn for every caller`;
286510
286891
  }
286892
+ function libraryScope(cwd) {
286893
+ const reason = libraryRepoReason(cwd);
286894
+ if (reason === null) return () => null;
286895
+ const deploys = /* @__PURE__ */ new Map();
286896
+ return (file2) => {
286897
+ const dirs = file2.replace(/\\/g, "/").split("/").slice(0, -1);
286898
+ for (let i = dirs.length; i > 0; i--) {
286899
+ const dir = dirs.slice(0, i).join("/");
286900
+ let d = deploys.get(dir);
286901
+ if (d === void 0) {
286902
+ d = deploysFrom(join42(cwd, dir));
286903
+ deploys.set(dir, d);
286904
+ }
286905
+ if (d) return null;
286906
+ }
286907
+ return reason;
286908
+ };
286909
+ }
286511
286910
  function reportOnly(reason) {
286512
286911
  return reason === null ? {} : { report_only: true, report_only_reason: reason };
286513
286912
  }
@@ -286800,7 +287199,7 @@ function findExtractionCandidates(cwd, rawCatalogue, orgModuleHcl) {
286800
287199
  if (isInHouseModule(file2, graph) !== null) continue;
286801
287200
  let text;
286802
287201
  try {
286803
- text = readFileSync23(join42(cwd, file2), "utf8");
287202
+ text = readFileSync24(join43(cwd, file2), "utf8");
286804
287203
  } catch {
286805
287204
  continue;
286806
287205
  }
@@ -286865,12 +287264,12 @@ function ModuleExtractionCandidatesTool(ctx) {
286865
287264
  parameters: ModuleExtractionCandidatesParams,
286866
287265
  execute: execute(async () => {
286867
287266
  const cwd = ctx.payload.cwd ?? process.cwd();
287267
+ const library = libraryScope(cwd);
286868
287268
  const candidates = findExtractionCandidates(
286869
287269
  cwd,
286870
287270
  ctx.payload.moduleCatalogue,
286871
287271
  ctx.toolState.orgModuleHcl
286872
- ).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file));
286873
- const library = libraryRepoReason(cwd);
287272
+ ).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file)).map((c) => ({ ...reportOnly(library(c.cluster.file)), ...c }));
286874
287273
  const matched = candidates.filter((c) => c.candidates.length > 0);
286875
287274
  const reasons = {};
286876
287275
  for (const c of candidates)
@@ -286884,7 +287283,6 @@ function ModuleExtractionCandidatesTool(ctx) {
286884
287283
  `\xBB module_extraction_candidates: ${candidates.length} cluster(s), ${matched.length} with a module match` + (partialInterfaces.length > 0 ? `, ${partialInterfaces.length} partial-interface block(s)` : "")
286885
287284
  );
286886
287285
  return toolOk({
286887
- ...reportOnly(library),
286888
287286
  cluster_count: candidates.length,
286889
287287
  matched_count: matched.length,
286890
287288
  candidates,
@@ -286908,8 +287306,8 @@ function ModuleExtractionCandidatesTool(ctx) {
286908
287306
  }
286909
287307
 
286910
287308
  // src/mcp/moduleTests.ts
286911
- import { existsSync as existsSync11, readdirSync as readdirSync8, readFileSync as readFileSync24, statSync as statSync7 } from "node:fs";
286912
- import { join as join43 } from "node:path";
287309
+ import { existsSync as existsSync10, readdirSync as readdirSync8, readFileSync as readFileSync25, statSync as statSync7 } from "node:fs";
287310
+ import { join as join44 } from "node:path";
286913
287311
  var MODULE_META_ARGS = /* @__PURE__ */ new Set([
286914
287312
  "source",
286915
287313
  "version",
@@ -287029,7 +287427,7 @@ function resolveSourceDir(fromDir, source) {
287029
287427
  }
287030
287428
  function isDir(p) {
287031
287429
  try {
287032
- return existsSync11(p) && statSync7(p).isDirectory();
287430
+ return existsSync10(p) && statSync7(p).isDirectory();
287033
287431
  } catch {
287034
287432
  return false;
287035
287433
  }
@@ -287050,9 +287448,9 @@ function listSubdirs(absDir) {
287050
287448
  }
287051
287449
  function readAllTf(cwd, relDir) {
287052
287450
  let text = "";
287053
- for (const f of listFiles(join43(cwd, relDir), ".tf")) {
287451
+ for (const f of listFiles(join44(cwd, relDir), ".tf")) {
287054
287452
  try {
287055
- text += `${readFileSync24(join43(cwd, relDir, f), "utf8")}
287453
+ text += `${readFileSync25(join44(cwd, relDir, f), "utf8")}
287056
287454
  `;
287057
287455
  } catch {
287058
287456
  }
@@ -287064,8 +287462,8 @@ function discoverModuleTestAssets(cwd, moduleDir) {
287064
287462
  const assets = [];
287065
287463
  const matchesModule = (source, fromDir) => resolveSourceDir(fromDir, source) === md;
287066
287464
  for (const examplesRoot of [`${md}/examples`, "examples"]) {
287067
- if (!isDir(join43(cwd, examplesRoot))) continue;
287068
- for (const sub of listSubdirs(join43(cwd, examplesRoot))) {
287465
+ if (!isDir(join44(cwd, examplesRoot))) continue;
287466
+ for (const sub of listSubdirs(join44(cwd, examplesRoot))) {
287069
287467
  const exDir = `${examplesRoot}/${sub}`;
287070
287468
  const hcl2 = readAllTf(cwd, exDir);
287071
287469
  if (!hcl2) continue;
@@ -287081,12 +287479,12 @@ function discoverModuleTestAssets(cwd, moduleDir) {
287081
287479
  }
287082
287480
  }
287083
287481
  for (const testDir of [`${md}/tests`, md]) {
287084
- if (!isDir(join43(cwd, testDir))) continue;
287085
- for (const f of listFiles(join43(cwd, testDir), ".tftest.hcl")) {
287482
+ if (!isDir(join44(cwd, testDir))) continue;
287483
+ for (const f of listFiles(join44(cwd, testDir), ".tftest.hcl")) {
287086
287484
  const rel = `${testDir}/${f}`;
287087
287485
  let hcl2;
287088
287486
  try {
287089
- hcl2 = readFileSync24(join43(cwd, rel), "utf8");
287487
+ hcl2 = readFileSync25(join44(cwd, rel), "utf8");
287090
287488
  } catch {
287091
287489
  continue;
287092
287490
  }
@@ -287094,12 +287492,12 @@ function discoverModuleTestAssets(cwd, moduleDir) {
287094
287492
  }
287095
287493
  }
287096
287494
  for (const goDir of [`${md}/test`, `${md}/tests`]) {
287097
- if (!isDir(join43(cwd, goDir))) continue;
287098
- for (const f of listFiles(join43(cwd, goDir), "_test.go")) {
287495
+ if (!isDir(join44(cwd, goDir))) continue;
287496
+ for (const f of listFiles(join44(cwd, goDir), "_test.go")) {
287099
287497
  const rel = `${goDir}/${f}`;
287100
287498
  let go;
287101
287499
  try {
287102
- go = readFileSync24(join43(cwd, rel), "utf8");
287500
+ go = readFileSync25(join44(cwd, rel), "utf8");
287103
287501
  } catch {
287104
287502
  continue;
287105
287503
  }
@@ -287223,8 +287621,8 @@ function SetOutputTool(ctx, outputSchema) {
287223
287621
 
287224
287622
  // src/mcp/policy.ts
287225
287623
  import { spawnSync as spawnSync8 } from "node:child_process";
287226
- import { existsSync as existsSync12, readFileSync as readFileSync25 } from "node:fs";
287227
- import { join as join44 } from "node:path";
287624
+ import { existsSync as existsSync11, readFileSync as readFileSync26 } from "node:fs";
287625
+ import { join as join45 } from "node:path";
287228
287626
  function parseConftestOutput(stdout) {
287229
287627
  let parsed2;
287230
287628
  try {
@@ -287258,7 +287656,7 @@ var CONFTEST_CONFIG = "conftest.toml";
287258
287656
  function conftestSelectsNamespaces(conftestCwd) {
287259
287657
  let raw2;
287260
287658
  try {
287261
- raw2 = readFileSync25(join44(conftestCwd, CONFTEST_CONFIG), "utf-8");
287659
+ raw2 = readFileSync26(join45(conftestCwd, CONFTEST_CONFIG), "utf-8");
287262
287660
  } catch {
287263
287661
  return false;
287264
287662
  }
@@ -287274,8 +287672,8 @@ function namespaceArgs(conftestCwd) {
287274
287672
  }
287275
287673
  function findPlanJson(cwd) {
287276
287674
  for (const candidate of PLAN_JSON_NAMES) {
287277
- const abs = join44(cwd, candidate);
287278
- if (existsSync12(abs)) return abs;
287675
+ const abs = join45(cwd, candidate);
287676
+ if (existsSync11(abs)) return abs;
287279
287677
  }
287280
287678
  return null;
287281
287679
  }
@@ -287316,11 +287714,11 @@ function resolvePolicyDir(cwd, explicit) {
287316
287714
  } catch {
287317
287715
  return null;
287318
287716
  }
287319
- return existsSync12(abs) ? abs : null;
287717
+ return existsSync11(abs) ? abs : null;
287320
287718
  }
287321
287719
  for (const d of DEFAULT_POLICY_DIRS) {
287322
- const abs = join44(cwd, d);
287323
- if (existsSync12(abs)) return abs;
287720
+ const abs = join45(cwd, d);
287721
+ if (existsSync11(abs)) return abs;
287324
287722
  }
287325
287723
  return null;
287326
287724
  }
@@ -287354,7 +287752,7 @@ function PolicyCheckTool(ctx) {
287354
287752
  } catch {
287355
287753
  abs = null;
287356
287754
  }
287357
- targetFile = abs && existsSync12(abs) ? abs : null;
287755
+ targetFile = abs && existsSync11(abs) ? abs : null;
287358
287756
  if (!targetFile) {
287359
287757
  return toolSkip(
287360
287758
  "target_not_found",
@@ -287627,9 +288025,9 @@ function PullRequestInfoTool(ctx) {
287627
288025
 
287628
288026
  // node_modules/.pnpm/@infraweaver-io+sandbox@0.1.8/node_modules/@infraweaver-io/sandbox/dist/writePolicy.js
287629
288027
  import { renameSync, writeFileSync as writeFileSync13 } from "node:fs";
287630
- import { join as join45 } from "node:path";
288028
+ import { join as join46 } from "node:path";
287631
288029
  function writePolicyPath(tmpdir5, filename) {
287632
- return join45(tmpdir5, filename);
288030
+ return join46(tmpdir5, filename);
287633
288031
  }
287634
288032
  function publishWritePolicy(params) {
287635
288033
  try {
@@ -287800,12 +288198,12 @@ function SelectModeTool(ctx) {
287800
288198
  }
287801
288199
 
287802
288200
  // src/mcp/terraform/tools/advise.ts
287803
- import { existsSync as existsSync13, readFileSync as readFileSync27 } from "node:fs";
287804
- import { join as join47 } from "node:path";
288201
+ import { existsSync as existsSync12, readFileSync as readFileSync28 } from "node:fs";
288202
+ import { join as join48 } from "node:path";
287805
288203
 
287806
288204
  // src/mcp/terraform/fixMemory.ts
287807
- import { readFileSync as readFileSync26 } from "node:fs";
287808
- import { join as join46 } from "node:path";
288205
+ import { readFileSync as readFileSync27 } from "node:fs";
288206
+ import { join as join47 } from "node:path";
287809
288207
  var FIX_MEMORY_FILENAME = FIX_MEMORY_FILE;
287810
288208
  var MAX_DESCRIPTION = 400;
287811
288209
  var MAX_BEFORE_EXAMPLE = 1200;
@@ -287892,7 +288290,7 @@ function loadFixMemory(cwd) {
287892
288290
  const root = cwd ?? process.cwd();
287893
288291
  for (const filename of [FIX_MEMORY_FILENAME, LEGACY_FIX_MEMORY_FILENAME]) {
287894
288292
  try {
287895
- return parseFixMemory(readFileSync26(join46(root, filename), "utf-8")) ?? [];
288293
+ return parseFixMemory(readFileSync27(join47(root, filename), "utf-8")) ?? [];
287896
288294
  } catch {
287897
288295
  }
287898
288296
  }
@@ -287958,10 +288356,10 @@ function inlineWaivers(content, file2) {
287958
288356
  ];
287959
288357
  }
287960
288358
  function readLearnings(tmpdir5) {
287961
- const path15 = join47(tmpdir5, LEARNINGS_FILE);
287962
- if (!existsSync13(path15)) return [];
288359
+ const path15 = join48(tmpdir5, LEARNINGS_FILE);
288360
+ if (!existsSync12(path15)) return [];
287963
288361
  try {
287964
- return readFileSync27(path15, "utf-8").split("\n");
288362
+ return readFileSync28(path15, "utf-8").split("\n");
287965
288363
  } catch {
287966
288364
  return [];
287967
288365
  }
@@ -287980,7 +288378,7 @@ function runAdvise(ctx, input2) {
287980
288378
  const learnings = readLearnings(ctx.tmpdir);
287981
288379
  const openConcerns = session.lastScanConcerns;
287982
288380
  const dossiers = files.map((file2) => {
287983
- const content = readTextCapped(join47(cwd, file2));
288381
+ const content = readTextCapped(join48(cwd, file2));
287984
288382
  const inline = content === null ? [] : inlineWaivers(content, file2);
287985
288383
  const reviewedInline = reviewedCopy(
287986
288384
  cwd,
@@ -289318,8 +289716,8 @@ function ConsolidationCandidatesTool(ctx) {
289318
289716
  }
289319
289717
 
289320
289718
  // src/mcp/terraform/tools/detectPlanDrift.ts
289321
- import { readFileSync as readFileSync28 } from "node:fs";
289322
- import { join as join48 } from "node:path";
289719
+ import { readFileSync as readFileSync29 } from "node:fs";
289720
+ import { join as join49 } from "node:path";
289323
289721
 
289324
289722
  // src/mcp/terraform/cspm.ts
289325
289723
  var ExternalResource = type({
@@ -289737,7 +290135,7 @@ function readTfFiles(cwd) {
289737
290135
  const out = [];
289738
290136
  for (const file2 of walkTfFiles(cwd)) {
289739
290137
  try {
289740
- out.push({ file: file2, hcl: readFileSync28(join48(cwd, file2), "utf8") });
290138
+ out.push({ file: file2, hcl: readFileSync29(join49(cwd, file2), "utf8") });
289741
290139
  } catch {
289742
290140
  }
289743
290141
  }
@@ -289754,7 +290152,7 @@ function DetectPlanDriftTool(ctx) {
289754
290152
  const planPath = path15 || !envPlanPath ? resolveWithinCwd(cwd, path15 ?? "plan.json") : envPlanPath;
289755
290153
  let raw2;
289756
290154
  try {
289757
- raw2 = readFileSync28(planPath, "utf8");
290155
+ raw2 = readFileSync29(planPath, "utf8");
289758
290156
  } catch {
289759
290157
  return skipResult(
289760
290158
  "plan_artifact_not_found",
@@ -289824,7 +290222,8 @@ function DetectPlanDriftTool(ctx) {
289824
290222
  autonomyThreshold,
289825
290223
  // drift is a narrow concern source — join any prior scan baseline so a
289826
290224
  // later ✗→✓ verify doesn't read the whole workspace as regressions.
289827
- baselineMode: "merge"
290225
+ baselineMode: "merge",
290226
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
289828
290227
  });
289829
290228
  log2.info(
289830
290229
  `\xBB detect_plan_drift: ${active.length} drifted resource(s) \u2265 ${threshold} from ${planPath}` + (noiseFiltered.length ? `, ${noiseFiltered.length} noise-filtered` : "") + (unlocated.length ? `, ${unlocated.length} unlocated` : "") + (statefulForgotten.length ? `, ${statefulForgotten.length} STATEFUL forget(s)` : "") + (suppressed.length ? ` (${suppressed.length} suppressed)` : "")
@@ -289886,13 +290285,13 @@ function DetectPlanDriftTool(ctx) {
289886
290285
  }
289887
290286
 
289888
290287
  // src/mcp/terraform/tools/emitCost.ts
289889
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "node:fs";
289890
- import { join as join49 } from "node:path";
290288
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync14 } from "node:fs";
290289
+ import { join as join50 } from "node:path";
289891
290290
 
289892
290291
  // src/mcp/terraform/awsPrices.ts
289893
290292
  var AWS_PRICE_REGION = "eu-west-2";
289894
290293
  var AWS_PRICE_CURRENCY = "USD";
289895
- var AWS_PRICE_FETCHED_AT = "2026-09-23";
290294
+ var AWS_PRICE_FETCHED_AT = "2026-10-03";
289896
290295
  var AWS_HOURLY_PRICES = {
289897
290296
  "eks-cluster-standard": 0.1,
289898
290297
  "elb-application": 0.02646,
@@ -289910,30 +290309,30 @@ var AWS_PER_GB_PRICES = {
289910
290309
  // src/mcp/terraform/azurePrices.ts
289911
290310
  var AZURE_PRICE_REGION = "westeurope";
289912
290311
  var AZURE_PRICE_CURRENCY = "EUR";
289913
- var AZURE_PRICE_FETCHED_AT = "2026-09-03";
290312
+ var AZURE_PRICE_FETCHED_AT = "2026-10-03";
289914
290313
  var AZURE_HOURLY_PRICES = {
289915
- "app-service-p0v3": 0.0764,
289916
- "app-service-p1v3": 0.1528,
289917
- "app-service-p2v3": 0.3057,
289918
- "app-service-p3v3": 0.6114,
289919
- "bastion-basic": 0.1631,
289920
- "bastion-premium": 0.3864,
289921
- "bastion-standard": 0.249,
289922
- "firewall-basic": 0.3392,
289923
- "firewall-premium": 1.5027,
289924
- "firewall-standard": 1.0733,
289925
- "redis-c0": 0.0236,
289926
- "redis-c1": 0.0592,
289927
- "redis-c2": 0.0962,
289928
- "redis-c3": 0.1932,
289929
- "redis-p1": 0.2378,
289930
- "redis-p2": 0.4766,
289931
- "redis-p3": 0.9523,
289932
- "vpn-gateway-vpngw1": 0.1631,
289933
- "vpn-gateway-vpngw2": 0.4207,
289934
- "vpn-gateway-vpngw3": 1.0733,
289935
- "vpn-gateway-vpngw4": 1.8032,
289936
- "vpn-gateway-vpngw5": 3.1341
290314
+ "app-service-p0v3": 0.0783,
290315
+ "app-service-p1v3": 0.1566,
290316
+ "app-service-p2v3": 0.3133,
290317
+ "app-service-p3v3": 0.6265,
290318
+ "bastion-basic": 0.1672,
290319
+ "bastion-premium": 0.396,
290320
+ "bastion-standard": 0.2552,
290321
+ "firewall-basic": 0.3476,
290322
+ "firewall-premium": 1.54,
290323
+ "firewall-standard": 1.1,
290324
+ "redis-c0": 0.0242,
290325
+ "redis-c1": 0.0607,
290326
+ "redis-c2": 0.0986,
290327
+ "redis-c3": 0.198,
290328
+ "redis-p1": 0.2438,
290329
+ "redis-p2": 0.4884,
290330
+ "redis-p3": 0.9759,
290331
+ "vpn-gateway-vpngw1": 0.1672,
290332
+ "vpn-gateway-vpngw2": 0.4312,
290333
+ "vpn-gateway-vpngw3": 1.1,
290334
+ "vpn-gateway-vpngw4": 1.8479,
290335
+ "vpn-gateway-vpngw5": 3.2119
289937
290336
  };
289938
290337
 
289939
290338
  // src/mcp/terraform/idleFloor.ts
@@ -290211,7 +290610,7 @@ function readTfFiles2(cwd) {
290211
290610
  const out = [];
290212
290611
  for (const file2 of walkTfFiles(cwd)) {
290213
290612
  try {
290214
- out.push({ file: file2, hcl: readFileSync29(join49(cwd, file2), "utf8") });
290613
+ out.push({ file: file2, hcl: readFileSync30(join50(cwd, file2), "utf8") });
290215
290614
  } catch {
290216
290615
  }
290217
290616
  }
@@ -290316,7 +290715,7 @@ function buildSuppressionSummary(waivers) {
290316
290715
  }
290317
290716
 
290318
290717
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/signing.js
290319
- import { createHash as createHash10, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
290718
+ import { createHash as createHash11, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
290320
290719
  var SIGNATURE_ALGORITHM = "Ed25519";
290321
290720
  var DEFAULT_KEY_LABEL = "the signing key";
290322
290721
  function loadSigningKey(raw2, options) {
@@ -290343,7 +290742,7 @@ function loadSigningKey(raw2, options) {
290343
290742
  return {
290344
290743
  privateKey,
290345
290744
  publicKeyPem,
290346
- fingerprint: createHash10("sha256").update(spkiDer).digest("hex")
290745
+ fingerprint: createHash11("sha256").update(spkiDer).digest("hex")
290347
290746
  };
290348
290747
  }
290349
290748
  function signBytes(bytes, key) {
@@ -290578,7 +290977,7 @@ function buildEvidenceBundle(args2) {
290578
290977
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keyless.js
290579
290978
  var import_bundle = __toESM(require_dist6(), 1);
290580
290979
  var import_sign = __toESM(require_dist9(), 1);
290581
- import { createHash as createHash11 } from "node:crypto";
290980
+ import { createHash as createHash12 } from "node:crypto";
290582
290981
 
290583
290982
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keylessOidc.js
290584
290983
  var SIGSTORE_OIDC_AUDIENCE = "sigstore";
@@ -290607,7 +291006,7 @@ function keylessSigningPossible(env2, envName) {
290607
291006
  }
290608
291007
  function buildInTotoStatement(args2) {
290609
291008
  const data = typeof args2.artifactBytes === "string" ? Buffer.from(args2.artifactBytes, "utf8") : args2.artifactBytes;
290610
- const sha2562 = createHash11("sha256").update(data).digest("hex");
291009
+ const sha2562 = createHash12("sha256").update(data).digest("hex");
290611
291010
  return {
290612
291011
  _type: "https://in-toto.io/Statement/v1",
290613
291012
  subject: [{ name: args2.subjectName, digest: { sha256: sha2562 } }],
@@ -290817,12 +291216,12 @@ import { mkdirSync as mkdirSync5 } from "node:fs";
290817
291216
  import { dirname as dirname6 } from "node:path";
290818
291217
 
290819
291218
  // src/mcp/terraform/oscal.ts
290820
- import { createHash as createHash12 } from "node:crypto";
291219
+ import { createHash as createHash13 } from "node:crypto";
290821
291220
  var OSCAL_VERSION = "1.1.2";
290822
291221
  var INDICATIVE_REMARK = "Indicative, crosswalk-derived assessment from a deterministic starter rule-pack \u2014 NOT a formal OSCAL assessment against an authored assessment plan. Statuses are code-verified only; absence of a finding is not proof of compliance.";
290823
291222
  var NS = "https://infraweaver.io/ns/oscal";
290824
291223
  function deterministicUuid(seed) {
290825
- const hex4 = createHash12("sha256").update(seed).digest("hex").slice(0, 32).split("");
291224
+ const hex4 = createHash13("sha256").update(seed).digest("hex").slice(0, 32).split("");
290826
291225
  hex4[12] = "5";
290827
291226
  hex4[16] = (Number.parseInt(hex4[16] ?? "0", 16) & 3 | 8).toString(16);
290828
291227
  const u = hex4.join("");
@@ -291475,7 +291874,7 @@ function TerraformEmitVexTool(ctx) {
291475
291874
  // src/mcp/terraform/tools/equivalenceCheck.ts
291476
291875
  import { mkdtempSync as mkdtempSync5, rmSync as rmSync7 } from "node:fs";
291477
291876
  import { tmpdir as tmpdir3 } from "node:os";
291478
- import { join as join50 } from "node:path";
291877
+ import { join as join51 } from "node:path";
291479
291878
 
291480
291879
  // src/mcp/terraform/refactor/attribution.ts
291481
291880
  import { posix as posix13 } from "node:path";
@@ -291938,16 +292337,32 @@ function uncoveredInstanceKeys(p) {
291938
292337
  if (expected === null) return null;
291939
292338
  const kind = level[0].kind;
291940
292339
  const written = (k) => kind === "count" ? k : `"${k}"`;
292340
+ const fromAt = level[0].at;
292341
+ const toAt = p.after.multiplicity[0]?.at;
292342
+ if (fromAt === void 0 || toAt === void 0) return null;
291941
292343
  const declared2 = /* @__PURE__ */ new Set();
291942
292344
  for (const blk of p.blocks) {
292345
+ if (stripIndices(normaliseAddress(blk.from)) !== p.before.address || stripIndices(normaliseAddress(blk.to)) !== p.after.address)
292346
+ return null;
291943
292347
  const from = instanceKeysOf(blk.from);
291944
292348
  const to = instanceKeysOf(blk.to);
291945
292349
  if (from.length > 1 || to.length > 1) return null;
291946
292350
  if (from.length === 0 && to.length === 0) return null;
291947
- if (from.length === 1 && to.length === 1 && from[0] === to[0]) declared2.add(from[0]);
292351
+ if (from.length !== 1 || to.length !== 1 || from[0] !== to[0]) continue;
292352
+ const fromPlaced = keyAtLevel(blk.from, fromAt);
292353
+ const toPlaced = keyAtLevel(blk.to, toAt);
292354
+ if (fromPlaced === false || toPlaced === false) continue;
292355
+ if (fromPlaced === null || toPlaced === null) return null;
292356
+ declared2.add(from[0]);
291948
292357
  }
291949
292358
  return expected.filter((k) => !declared2.has(written(k)));
291950
292359
  }
292360
+ function keyAtLevel(address, at) {
292361
+ const onResource = instanceKeysOf(stripModuleHops(normaliseAddress(address))).length > 0;
292362
+ if (at === "resource") return onResource;
292363
+ if (onResource) return false;
292364
+ return moduleHops(address).length === 1 ? true : null;
292365
+ }
291951
292366
  function computeEquivalence(before, after, movedBlocks, ambiguous = [], planPairs, opts = {}) {
291952
292367
  const reasons = [];
291953
292368
  const corr = correspond(before, after, movedBlocks);
@@ -293267,12 +293682,12 @@ async function baselineValidateErrors(cwd, base, env2, session) {
293267
293682
  }
293268
293683
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd);
293269
293684
  if (prefix2.status !== 0) return null;
293270
- const tree = mkdtempSync5(join50(tmpdir3(), "iw-baseline-"));
293685
+ const tree = mkdtempSync5(join51(tmpdir3(), "iw-baseline-"));
293271
293686
  try {
293272
293687
  if (run("git", ["worktree", "add", "--detach", "--quiet", tree, base], cwd).status !== 0) {
293273
293688
  return null;
293274
293689
  }
293275
- const outcome2 = await scanValidate(join50(tree, prefix2.stdout.trim()), env2);
293690
+ const outcome2 = await scanValidate(join51(tree, prefix2.stdout.trim()), env2);
293276
293691
  if (!outcome2.ran || outcome2.unvalidated) return null;
293277
293692
  const errors = outcome2.concerns.map(validateErrorKey);
293278
293693
  session.baselineValidateErrors = { base, errors };
@@ -293373,7 +293788,7 @@ function TerraformGenerateMovedTool(ctx) {
293373
293788
 
293374
293789
  // src/mcp/terraform/cost.ts
293375
293790
  import { mkdtempSync as mkdtempSync6, rmSync as rmSync8 } from "node:fs";
293376
- import { join as join51 } from "node:path";
293791
+ import { join as join52 } from "node:path";
293377
293792
  function parseInfracostBreakdown(stdout) {
293378
293793
  const parsed2 = parseScannerJson(InfracostBreakdownOutput, stdout, "infracost");
293379
293794
  const raw2 = parsed2.totalMonthlyCost;
@@ -293455,15 +293870,15 @@ async function infracostBaseline(cwd, key, tmpdir5) {
293455
293870
  if (!baseRef) return null;
293456
293871
  const prefixResult = run("git", ["rev-parse", "--show-prefix"], cwd);
293457
293872
  const prefix2 = prefixResult.status === 0 ? prefixResult.stdout.trim() : "";
293458
- const baseDir = mkdtempSync6(join51(tmpdir5, "infracost-base-"));
293459
- const worktree = join51(baseDir, "wt");
293873
+ const baseDir = mkdtempSync6(join52(tmpdir5, "infracost-base-"));
293874
+ const worktree = join52(baseDir, "wt");
293460
293875
  const add = run("git", ["worktree", "add", "--detach", worktree, baseRef], cwd);
293461
293876
  if (add.status !== 0) {
293462
293877
  rmSync8(baseDir, { recursive: true, force: true });
293463
293878
  return null;
293464
293879
  }
293465
293880
  try {
293466
- const scanCwd = prefix2 ? join51(worktree, prefix2) : worktree;
293881
+ const scanCwd = prefix2 ? join52(worktree, prefix2) : worktree;
293467
293882
  const r = await runInfracostBreakdown(scanCwd, key);
293468
293883
  if (r.missing || r.status !== 0) return null;
293469
293884
  return parseInfracostBreakdown(r.stdout);
@@ -293534,7 +293949,7 @@ function InfracostDiffTool(ctx) {
293534
293949
 
293535
293950
  // src/mcp/terraform/tools/ingestExternalFindings.ts
293536
293951
  import { statSync as statSync8 } from "node:fs";
293537
- import { join as join52 } from "node:path";
293952
+ import { join as join53 } from "node:path";
293538
293953
 
293539
293954
  // src/mcp/terraform/prowlerOcsf.ts
293540
293955
  var DETECTION_FINDING_CREATE = 200401;
@@ -293760,7 +294175,7 @@ var MAX_FEED_BYTES = 100 * 1024 * 1024;
293760
294175
  function readTfFiles3(cwd) {
293761
294176
  const out = [];
293762
294177
  for (const file2 of walkTfFiles(cwd)) {
293763
- const hcl2 = readTextCapped(join52(cwd, file2));
294178
+ const hcl2 = readTextCapped(join53(cwd, file2));
293764
294179
  if (hcl2 !== null) out.push({ file: file2, hcl: hcl2 });
293765
294180
  }
293766
294181
  return out;
@@ -293874,7 +294289,8 @@ function IngestExternalFindingsTool(ctx) {
293874
294289
  // an external feed is a narrow concern source — join any prior scan
293875
294290
  // baseline so a later ✗→✓ verify doesn't read the workspace as regressions.
293876
294291
  baselineMode: "merge",
293877
- seedCaps: seedCapsFrom(input2)
294292
+ seedCaps: seedCapsFrom(input2),
294293
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
293878
294294
  });
293879
294295
  const cappedBlock = cappedSummary(capped);
293880
294296
  log2.info(
@@ -293917,8 +294333,8 @@ function IngestExternalFindingsTool(ctx) {
293917
294333
  }
293918
294334
 
293919
294335
  // src/mcp/terraform/tools/moduleDocs.ts
293920
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync16 } from "node:fs";
293921
- import { join as join53 } from "node:path";
294336
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync16 } from "node:fs";
294337
+ import { join as join54 } from "node:path";
293922
294338
 
293923
294339
  // src/mcp/terraform/moduleDocs.ts
293924
294340
  var VERDICT_CLAIM = {
@@ -294032,7 +294448,7 @@ function TerraformModuleDocsTool(ctx) {
294032
294448
  );
294033
294449
  }
294034
294450
  const absDir = resolveWithinCwd(cwd, module_dir);
294035
- const target = join53(absDir, "README.md");
294451
+ const target = join54(absDir, "README.md");
294036
294452
  let gitHead = null;
294037
294453
  try {
294038
294454
  gitHead = $("git", ["rev-parse", "HEAD"], { cwd, log: false });
@@ -294040,7 +294456,7 @@ function TerraformModuleDocsTool(ctx) {
294040
294456
  }
294041
294457
  let existing = null;
294042
294458
  try {
294043
- existing = readFileSync30(target, "utf8");
294459
+ existing = readFileSync31(target, "utf8");
294044
294460
  } catch {
294045
294461
  }
294046
294462
  const priorHead = readFooterGitHead(existing);
@@ -294344,15 +294760,13 @@ function NormalizationCandidatesTool(ctx) {
294344
294760
  parameters: NormalizationCandidatesParams,
294345
294761
  execute: execute(async () => {
294346
294762
  const cwd = ctx.payload.cwd ?? process.cwd();
294347
- const files = findNormalizationCandidates(cwd).filter(
294348
- (f) => ctx.payload.refactorExamples || !isExamplePath(f.file)
294349
- );
294763
+ const library = libraryScope(cwd);
294764
+ const files = findNormalizationCandidates(cwd).filter((f) => ctx.payload.refactorExamples || !isExamplePath(f.file)).map((f) => ({ ...reportOnly(library(f.file)), ...f }));
294350
294765
  const total = files.reduce((n, f) => n + f.count, 0);
294351
294766
  log2.info(
294352
294767
  `\xBB terraform_normalization_candidates: ${total} site(s) across ${files.length} file(s)`
294353
294768
  );
294354
294769
  return toolOk({
294355
- ...reportOnly(libraryRepoReason(cwd)),
294356
294770
  file_count: files.length,
294357
294771
  total,
294358
294772
  files,
@@ -294364,7 +294778,7 @@ function NormalizationCandidatesTool(ctx) {
294364
294778
 
294365
294779
  // src/mcp/terraform/tools/plan.ts
294366
294780
  import { rmSync as rmSync9 } from "node:fs";
294367
- import { join as join54 } from "node:path";
294781
+ import { join as join55 } from "node:path";
294368
294782
  var PLAN_JSON_ARGS = ["plan", "-input=false", "-no-color", "-lock=false", "-json"];
294369
294783
  var planFileSeq = 0;
294370
294784
  async function capturePlanPairs(absDir, planFile, creds) {
@@ -294387,7 +294801,7 @@ async function planOneRoot(absDir, creds, tmpdir5) {
294387
294801
  skipReason: `terraform init failed: ${init.stderr.trim().slice(0, 200) || "unknown error"}`
294388
294802
  };
294389
294803
  }
294390
- const planFile = tmpdir5 ? join54(tmpdir5, `plan-${process.pid}-${planFileSeq++}.tfplan`) : void 0;
294804
+ const planFile = tmpdir5 ? join55(tmpdir5, `plan-${process.pid}-${planFileSeq++}.tfplan`) : void 0;
294391
294805
  const plan = await runTfAsync(
294392
294806
  planFile ? [...PLAN_JSON_ARGS, `-out=${planFile}`] : PLAN_JSON_ARGS,
294393
294807
  absDir,
@@ -294757,8 +295171,8 @@ function TerraformProviderUpgradeTool(ctx) {
294757
295171
  }
294758
295172
 
294759
295173
  // src/mcp/terraform/tfquery.ts
294760
- import { readFileSync as readFileSync31 } from "node:fs";
294761
- import { join as join55 } from "node:path";
295174
+ import { readFileSync as readFileSync32 } from "node:fs";
295175
+ import { join as join56 } from "node:path";
294762
295176
  var TFQUERY_SUFFIX = ".tfquery.hcl";
294763
295177
  function parseQueryFile(hcl2) {
294764
295178
  return parseBlocks(hcl2, ["list"]).map((block) => {
@@ -294784,7 +295198,7 @@ function collectQueryInventory(cwd) {
294784
295198
  for (const rel of walkFilesBySuffix(cwd, TFQUERY_SUFFIX)) {
294785
295199
  let text;
294786
295200
  try {
294787
- text = readFileSync31(join55(cwd, rel), "utf8");
295201
+ text = readFileSync32(join56(cwd, rel), "utf8");
294788
295202
  } catch {
294789
295203
  continue;
294790
295204
  }
@@ -294819,7 +295233,7 @@ function TerraformQueryInventoryTool(ctx) {
294819
295233
  }
294820
295234
 
294821
295235
  // src/mcp/terraform/tools/readFindings.ts
294822
- import { readFileSync as readFileSync32 } from "node:fs";
295236
+ import { readFileSync as readFileSync33 } from "node:fs";
294823
295237
  var ReadFindingsParams = type({
294824
295238
  "path?": type.string.describe(
294825
295239
  "path to the Assessor's findings.json. Defaults to $INFRAWEAVER_FINDINGS_PATH, then ./findings.json in the workspace."
@@ -294844,7 +295258,7 @@ function ReadFindingsTool(ctx) {
294844
295258
  const findingsPath = path15 || !envFindingsPath ? resolveWithinCwd(cwd, path15 ?? "findings.json") : envFindingsPath;
294845
295259
  let raw2;
294846
295260
  try {
294847
- raw2 = readFileSync32(findingsPath, "utf8");
295261
+ raw2 = readFileSync33(findingsPath, "utf8");
294848
295262
  } catch {
294849
295263
  return skipResult(
294850
295264
  "findings_not_found",
@@ -294887,7 +295301,8 @@ function ReadFindingsTool(ctx) {
294887
295301
  minRank,
294888
295302
  grouping,
294889
295303
  autonomyThreshold,
294890
- seedCaps: seedCapsFrom(input2)
295304
+ seedCaps: seedCapsFrom(input2),
295305
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
294891
295306
  });
294892
295307
  const cappedBlock = cappedSummary(capped);
294893
295308
  log2.info(
@@ -295522,7 +295937,25 @@ async function runVerifyRemediation(ctx, { concern_ids, download_external_module
295522
295937
  if (key !== void 0) keyed.push({ id, key });
295523
295938
  else unkeyed.push(id);
295524
295939
  }
295525
- const keyedVerdict = partitionByKey(keyed, currentKeys);
295940
+ const byKey = partitionByKey(keyed, currentKeys);
295941
+ const read3 = fileReader(cwd);
295942
+ const remainingKeyed = keyed.filter((k) => byKey.remaining.includes(k.id));
295943
+ const atSite = new Set(
295944
+ resolvedAtSite({
295945
+ requested: remainingKeyed.map((k) => ({
295946
+ ...k,
295947
+ site: session.siteById?.get(k.id) ?? null,
295948
+ file: reported.get(k.id)?.location.file
295949
+ })),
295950
+ current: currentConcerns.map((c) => ({ key: concernKeyOf(c), site: siteOf(c, read3) })),
295951
+ firstSites: session.firstSitesByKey ?? /* @__PURE__ */ new Map(),
295952
+ declared: (file2) => new Set(declaredAddresses2(read3(file2) ?? ""))
295953
+ })
295954
+ );
295955
+ const keyedVerdict = {
295956
+ resolved: [...byKey.resolved, ...byKey.remaining.filter((id) => atSite.has(id))],
295957
+ remaining: byKey.remaining.filter((id) => !atSite.has(id))
295958
+ };
295526
295959
  const fallbackVerdict = computeRemediationVerdict(unkeyed, new Set(currentIds));
295527
295960
  const resolved = [...keyedVerdict.resolved, ...fallbackVerdict.resolved];
295528
295961
  const remaining = [...keyedVerdict.remaining, ...fallbackVerdict.remaining];
@@ -298776,15 +299209,15 @@ async function startMcpHttpServer(ctx, options) {
298776
299209
 
298777
299210
  // src/mcp/terraform/policyGate.ts
298778
299211
  import {
298779
- existsSync as existsSync14,
299212
+ existsSync as existsSync13,
298780
299213
  mkdirSync as mkdirSync7,
298781
299214
  mkdtempSync as mkdtempSync7,
298782
299215
  readdirSync as readdirSync9,
298783
- readFileSync as readFileSync34,
299216
+ readFileSync as readFileSync35,
298784
299217
  writeFileSync as writeFileSync17
298785
299218
  } from "node:fs";
298786
299219
  import { tmpdir as osTmpdir } from "node:os";
298787
- import { dirname as dirname8, isAbsolute as isAbsolute6, join as join57, relative as relative6, resolve as resolve8 } from "node:path";
299220
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join58, relative as relative6, resolve as resolve8 } from "node:path";
298788
299221
  function coercePolicyRules(raw2) {
298789
299222
  const rules = [];
298790
299223
  const rejected = [];
@@ -299036,7 +299469,7 @@ function evaluateRulesStatically(cwd, rules, scope2 = { kind: "all", reason: ""
299036
299469
  const files = [];
299037
299470
  for (const rel of walk.files) {
299038
299471
  try {
299039
- files.push({ rel, text: readFileSync34(join57(cwd, rel), "utf-8") });
299472
+ files.push({ rel, text: readFileSync35(join58(cwd, rel), "utf-8") });
299040
299473
  } catch {
299041
299474
  }
299042
299475
  }
@@ -299151,16 +299584,16 @@ function cloudEmittedRego(rules) {
299151
299584
  }
299152
299585
  function readOrEmpty(path15) {
299153
299586
  try {
299154
- return readFileSync34(path15, "utf-8");
299587
+ return readFileSync35(path15, "utf-8");
299155
299588
  } catch {
299156
299589
  return "";
299157
299590
  }
299158
299591
  }
299159
299592
  function regoDirIn(root, skip) {
299160
299593
  for (const dir of DEFAULT_POLICY_DIRS) {
299161
- const abs = join57(root, dir);
299162
- if (existsSync14(abs) && walkFilesBySuffix(abs, ".rego").some(
299163
- (rel) => !skip(join57(dir, rel), readOrEmpty(join57(abs, rel)))
299594
+ const abs = join58(root, dir);
299595
+ if (existsSync13(abs) && walkFilesBySuffix(abs, ".rego").some(
299596
+ (rel) => !skip(join58(dir, rel), readOrEmpty(join58(abs, rel)))
299164
299597
  )) {
299165
299598
  return dir;
299166
299599
  }
@@ -299168,14 +299601,14 @@ function regoDirIn(root, skip) {
299168
299601
  return null;
299169
299602
  }
299170
299603
  function withoutSkipped(root, dir, skip, tmpdir5) {
299171
- const abs = join57(root, dir);
299172
- const files = readdirSync9(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile()).map((e) => relative6(abs, join57(e.parentPath, e.name)));
299173
- const kept = files.filter((rel) => !skip(join57(dir, rel), readOrEmpty(join57(abs, rel))));
299604
+ const abs = join58(root, dir);
299605
+ const files = readdirSync9(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile()).map((e) => relative6(abs, join58(e.parentPath, e.name)));
299606
+ const kept = files.filter((rel) => !skip(join58(dir, rel), readOrEmpty(join58(abs, rel))));
299174
299607
  if (kept.length === files.length) return abs;
299175
- const copy = join57(mkdtempSync7(join57(tmpdir5 ?? osTmpdir(), "policy-own-")), dir);
299608
+ const copy = join58(mkdtempSync7(join58(tmpdir5 ?? osTmpdir(), "policy-own-")), dir);
299176
299609
  for (const rel of kept) {
299177
- mkdirSync7(dirname8(join57(copy, rel)), { recursive: true });
299178
- writeFileSync17(join57(copy, rel), readFileSync34(join57(abs, rel)));
299610
+ mkdirSync7(dirname8(join58(copy, rel)), { recursive: true });
299611
+ writeFileSync17(join58(copy, rel), readFileSync35(join58(abs, rel)));
299179
299612
  }
299180
299613
  return copy;
299181
299614
  }
@@ -299197,18 +299630,18 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir5, notes, skip) {
299197
299630
  }
299198
299631
  const own2 = tree.files.filter((f) => !skip(f.path, f.raw));
299199
299632
  if (!own2.some((f) => f.path.endsWith(".rego"))) continue;
299200
- const root = mkdtempSync7(join57(tmpdir5 ?? osTmpdir(), "policy-base-"));
299633
+ const root = mkdtempSync7(join58(tmpdir5 ?? osTmpdir(), "policy-base-"));
299201
299634
  for (const f of own2) {
299202
- mkdirSync7(dirname8(join57(root, f.path)), { recursive: true });
299203
- writeFileSync17(join57(root, f.path), f.raw);
299635
+ mkdirSync7(dirname8(join58(root, f.path)), { recursive: true });
299636
+ writeFileSync17(join58(root, f.path), f.raw);
299204
299637
  }
299205
299638
  const toml = readFileAtBaseRef(cwd, baseRef, CONFTEST_CONFIG);
299206
- if (toml.kind === "file") writeFileSync17(join57(root, CONFTEST_CONFIG), toml.raw);
299207
- const same = head === dir && readdirSync9(join57(cwd, dir), { recursive: true, withFileTypes: true }).filter(
299639
+ if (toml.kind === "file") writeFileSync17(join58(root, CONFTEST_CONFIG), toml.raw);
299640
+ const same = head === dir && readdirSync9(join58(cwd, dir), { recursive: true, withFileTypes: true }).filter(
299208
299641
  (e) => e.isFile()
299209
299642
  ).length === tree.files.length && tree.files.every((f) => {
299210
299643
  try {
299211
- return readFileSync34(join57(cwd, f.path), "utf-8") === f.raw;
299644
+ return readFileSync35(join58(cwd, f.path), "utf-8") === f.raw;
299212
299645
  } catch {
299213
299646
  return false;
299214
299647
  }
@@ -299218,7 +299651,7 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir5, notes, skip) {
299218
299651
  `the Rego policy differs from the base ref (${tree.ref}) \u2014 applying the BASE ${dir}/ so this change cannot relax the policy reviewing it. It takes effect once merged.`
299219
299652
  );
299220
299653
  }
299221
- return { policyDir: join57(root, dir), conftestCwd: root };
299654
+ return { policyDir: join58(root, dir), conftestCwd: root };
299222
299655
  }
299223
299656
  if (head) {
299224
299657
  notes.push(
@@ -299228,7 +299661,7 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir5, notes, skip) {
299228
299661
  return null;
299229
299662
  }
299230
299663
  function findWorkflowPlanJson(cwd, notes) {
299231
- const present = PLAN_JSON_NAMES.filter((name) => existsSync14(join57(cwd, name)));
299664
+ const present = PLAN_JSON_NAMES.filter((name) => existsSync13(join58(cwd, name)));
299232
299665
  if (present.length === 0) return null;
299233
299666
  const listing = run("git", ["ls-files", "-z"], cwd);
299234
299667
  if (listing.status !== 0) {
@@ -299245,7 +299678,7 @@ function findWorkflowPlanJson(cwd, notes) {
299245
299678
  );
299246
299679
  continue;
299247
299680
  }
299248
- return join57(cwd, name);
299681
+ return join58(cwd, name);
299249
299682
  }
299250
299683
  return null;
299251
299684
  }
@@ -299275,7 +299708,7 @@ function evaluatePolicyGate(cwd, rawRules, opts = {}) {
299275
299708
  let plan;
299276
299709
  if (planPath) {
299277
299710
  try {
299278
- plan = asPlan(JSON.parse(readFileSync34(planPath, "utf-8")));
299711
+ plan = asPlan(JSON.parse(readFileSync35(planPath, "utf-8")));
299279
299712
  } catch {
299280
299713
  }
299281
299714
  if (!plan) notes.push(`${planPath} is not a Terraform plan JSON \u2014 ignored`);
@@ -299378,7 +299811,7 @@ function capturePolicyGate(ctx) {
299378
299811
  if (!result) return;
299379
299812
  const workspace = process.env.GITHUB_WORKSPACE ?? cwd;
299380
299813
  for (const o of [...result.violations, ...result.inconclusive]) {
299381
- if (o.file) o.file = posix16(relative6(workspace, join57(cwd, o.file)));
299814
+ if (o.file) o.file = posix16(relative6(workspace, join58(cwd, o.file)));
299382
299815
  }
299383
299816
  session.policyGate = result;
299384
299817
  for (const n of result.notes) log2.warning(`\xBB policy gate: ${n}`);
@@ -299713,7 +300146,7 @@ var ThinkingTimer = class {
299713
300146
  // src/agents/claude.ts
299714
300147
  import { execFileSync as execFileSync3 } from "node:child_process";
299715
300148
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync10, writeFileSync as writeFileSync22 } from "node:fs";
299716
- import { join as join66 } from "node:path";
300149
+ import { join as join67 } from "node:path";
299717
300150
  import { performance as performance6 } from "node:perf_hooks";
299718
300151
  import { setTimeout as sleep6 } from "node:timers/promises";
299719
300152
 
@@ -300073,7 +300506,7 @@ import { readFile as readFile2 } from "node:fs/promises";
300073
300506
 
300074
300507
  // src/agents/shared.ts
300075
300508
  import { execFileSync as execFileSync2 } from "node:child_process";
300076
- import { join as join58 } from "node:path";
300509
+ import { join as join59 } from "node:path";
300077
300510
  var MAX_STDERR_LINES = 20;
300078
300511
  var MAX_POST_RUN_RETRIES = 3;
300079
300512
  function getGitStatus() {
@@ -300110,7 +300543,7 @@ var agent = (input2) => {
300110
300543
  };
300111
300544
  };
300112
300545
  function agentHomeEnv(tmpdir5) {
300113
- return { HOME: tmpdir5, XDG_CONFIG_HOME: join58(tmpdir5, ".config") };
300546
+ return { HOME: tmpdir5, XDG_CONFIG_HOME: join59(tmpdir5, ".config") };
300114
300547
  }
300115
300548
  function baseAgentEnv(homeEnv, repoDir) {
300116
300549
  return { ...process.env, ...homeEnv, PWD: repoDir };
@@ -300587,9 +301020,9 @@ function shouldRunGraceTurn(params) {
300587
301020
  }
300588
301021
 
300589
301022
  // src/agents/quotaTokens.ts
300590
- var CACHE_READ_WEIGHT = 0.1;
300591
- function quotaTokens(t) {
300592
- return t.input + t.output + t.cacheWrite + t.cacheRead * CACHE_READ_WEIGHT;
301023
+ var CACHE_READ_WEIGHT2 = 0.1;
301024
+ function quotaTokens2(t) {
301025
+ return t.input + t.output + (t.reasoning ?? 0) + t.cacheWrite + t.cacheRead * CACHE_READ_WEIGHT2;
300593
301026
  }
300594
301027
 
300595
301028
  // src/agents/sessionLabeler.ts
@@ -300913,7 +301346,7 @@ function matchesRouteSentinel(envVar, model) {
300913
301346
  import { randomUUID as randomUUID9 } from "node:crypto";
300914
301347
  import { mkdirSync as mkdirSync8, rmSync as rmSync10, writeFileSync as writeFileSync18 } from "node:fs";
300915
301348
  import { homedir } from "node:os";
300916
- import { join as join59 } from "node:path";
301349
+ import { join as join60 } from "node:path";
300917
301350
  var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
300918
301351
  var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
300919
301352
  var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
@@ -300937,7 +301370,7 @@ function readProjectIdFromVertexServiceAccountJson() {
300937
301370
  }
300938
301371
  function createSecretDir() {
300939
301372
  const base = process.env.INFRAWEAVER_SECRET_HOME || process.env.HOME || homedir();
300940
- const secretDir = join59(base, `.${PRODUCT_SLUG}`, "secrets", randomUUID9());
301373
+ const secretDir = join60(base, `.${PRODUCT_SLUG}`, "secrets", randomUUID9());
300941
301374
  mkdirSync8(secretDir, { recursive: true, mode: 448 });
300942
301375
  return secretDir;
300943
301376
  }
@@ -300946,7 +301379,7 @@ function materializeVertexCredentials(params) {
300946
301379
  const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
300947
301380
  if (!blob) return void 0;
300948
301381
  const secretDir = createSecretDir();
300949
- const credentialsPath = join59(secretDir, "vertex-sa.json");
301382
+ const credentialsPath = join60(secretDir, "vertex-sa.json");
300950
301383
  writeFileSync18(credentialsPath, blob, { mode: 384 });
300951
301384
  process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
300952
301385
  const projectId = readProjectIdFromVertexServiceAccountJson();
@@ -301082,29 +301515,29 @@ function createProcessOutputActivityTimeout(ctx) {
301082
301515
  }
301083
301516
 
301084
301517
  // src/utils/prompt/skills.ts
301085
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync35, writeFileSync as writeFileSync19 } from "node:fs";
301086
- import { dirname as dirname9, join as join60 } from "node:path";
301518
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync36, writeFileSync as writeFileSync19 } from "node:fs";
301519
+ import { dirname as dirname9, join as join61 } from "node:path";
301087
301520
  import { fileURLToPath } from "node:url";
301088
301521
  var BUNDLED_SKILL_NAMES = ["terraform-best-practices"];
301089
301522
  function resolveSkillPath(name) {
301090
301523
  const here = dirname9(fileURLToPath(import.meta.url));
301091
301524
  const candidates = [
301092
- join60(here, "..", "..", "skills", name, "SKILL.md"),
301093
- join60(here, "skills", name, "SKILL.md")
301525
+ join61(here, "..", "..", "skills", name, "SKILL.md"),
301526
+ join61(here, "skills", name, "SKILL.md")
301094
301527
  ];
301095
301528
  for (const candidate of candidates) {
301096
- if (existsSync15(candidate)) return candidate;
301529
+ if (existsSync14(candidate)) return candidate;
301097
301530
  }
301098
301531
  throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
301099
301532
  }
301100
301533
  var SKILL_TARGET_DIRS = [".opencode/skills", ".claude/skills", ".agents/skills"];
301101
301534
  function installBundledSkills(params) {
301102
301535
  for (const name of BUNDLED_SKILL_NAMES) {
301103
- const content = readFileSync35(resolveSkillPath(name), "utf8");
301536
+ const content = readFileSync36(resolveSkillPath(name), "utf8");
301104
301537
  for (const targetDir of SKILL_TARGET_DIRS) {
301105
- const skillDir = join60(params.home, targetDir, name);
301538
+ const skillDir = join61(params.home, targetDir, name);
301106
301539
  mkdirSync9(skillDir, { recursive: true });
301107
- writeFileSync19(join60(skillDir, "SKILL.md"), content);
301540
+ writeFileSync19(join61(skillDir, "SKILL.md"), content);
301108
301541
  }
301109
301542
  }
301110
301543
  log2.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -301112,9 +301545,9 @@ function installBundledSkills(params) {
301112
301545
 
301113
301546
  // src/utils/setup/install.ts
301114
301547
  import { spawnSync as spawnSync9 } from "node:child_process";
301115
- import { createHash as createHash14 } from "node:crypto";
301116
- import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as existsSync18, readFileSync as readFileSync36 } from "node:fs";
301117
- import { join as join65 } from "node:path";
301548
+ import { createHash as createHash15 } from "node:crypto";
301549
+ import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as existsSync17, readFileSync as readFileSync37 } from "node:fs";
301550
+ import { join as join66 } from "node:path";
301118
301551
  import { pipeline as pipeline2 } from "node:stream/promises";
301119
301552
 
301120
301553
  // src/utils/setup/tarballCache.ts
@@ -301169,7 +301602,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
301169
301602
  `Refusing to install ${descriptor}: the registry published no integrity digest (neither dist.integrity nor dist.shasum), so the download cannot be verified.`
301170
301603
  );
301171
301604
  }
301172
- const bytes = readFileSync36(tarballPath);
301605
+ const bytes = readFileSync37(tarballPath);
301173
301606
  if (integrity2) {
301174
301607
  const candidates = integrity2.trim().split(/\s+/).filter(Boolean);
301175
301608
  for (const candidate of candidates) {
@@ -301179,7 +301612,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
301179
301612
  const expected = candidate.slice(separator2 + 1);
301180
301613
  let actual;
301181
301614
  try {
301182
- actual = createHash14(algorithm).update(bytes).digest("base64");
301615
+ actual = createHash15(algorithm).update(bytes).digest("base64");
301183
301616
  } catch {
301184
301617
  continue;
301185
301618
  }
@@ -301189,7 +301622,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
301189
301622
  `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published digest (${integrity2}). Refusing to execute it.`
301190
301623
  );
301191
301624
  }
301192
- const actualShasum = createHash14("sha1").update(bytes).digest("hex");
301625
+ const actualShasum = createHash15("sha1").update(bytes).digest("hex");
301193
301626
  if (actualShasum !== shasum) {
301194
301627
  throw new Error(
301195
301628
  `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published shasum (expected ${shasum}, got ${actualShasum}). Refusing to execute it.`
@@ -301198,9 +301631,9 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
301198
301631
  }
301199
301632
  async function installFromNpmTarball(params) {
301200
301633
  const tempDir = getTempDir();
301201
- const extractedDir = join65(tempDir, "package");
301202
- const cliPath = join65(extractedDir, params.executablePath);
301203
- if (existsSync18(cliPath)) {
301634
+ const extractedDir = join66(tempDir, "package");
301635
+ const cliPath = join66(extractedDir, params.executablePath);
301636
+ if (existsSync17(cliPath)) {
301204
301637
  log2.debug(`\xBB using cached binary at ${cliPath}`);
301205
301638
  return cliPath;
301206
301639
  }
@@ -301251,7 +301684,7 @@ async function installFromNpmTarball(params) {
301251
301684
  dist = (await manifestResponse.json()).dist;
301252
301685
  }
301253
301686
  log2.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
301254
- const tarballPath = join65(tempDir, "package.tgz");
301687
+ const tarballPath = join66(tempDir, "package.tgz");
301255
301688
  const tarballFileName = params.packageName.startsWith("@") ? params.packageName.split("/")[1] : params.packageName;
301256
301689
  const tarballUrl = `${npmRegistry}/${encodedName}/-/${tarballFileName}-${resolvedVersion}.tgz`;
301257
301690
  const restored = await restoreTarball({
@@ -301291,7 +301724,7 @@ async function installFromNpmTarball(params) {
301291
301724
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
301292
301725
  );
301293
301726
  }
301294
- if (!existsSync18(cliPath)) {
301727
+ if (!existsSync17(cliPath)) {
301295
301728
  throw new Error(`Executable not found in extracted package at ${cliPath}`);
301296
301729
  }
301297
301730
  if (params.installDependencies) {
@@ -301402,9 +301835,9 @@ var CLAUDE_DISALLOWED_TOOLS = CLAUDE_EXEC_TOOL_DENY_RULES.join(",");
301402
301835
  var INFRAWEAVER_MCP_AUTH_HEADER = `Bearer \${${MCP_SERVER_TOKEN_ENV}}`;
301403
301836
  var CLAUDE_MAX_CONCURRENT_SUBAGENTS_DEFAULT = "4";
301404
301837
  function writeMcpConfig(ctx) {
301405
- const configDir = join66(ctx.tmpdir, ".claude");
301838
+ const configDir = join67(ctx.tmpdir, ".claude");
301406
301839
  mkdirSync10(configDir, { recursive: true });
301407
- const configPath = join66(configDir, "mcp.json");
301840
+ const configPath = join67(configDir, "mcp.json");
301408
301841
  const terraformMcp = resolveTerraformMcp(ctx.payload);
301409
301842
  if (terraformMcp.kind === "docker_missing") log2.info(`\xBB ${terraformMcp.note}`);
301410
301843
  const opentofuMcp = resolveOpenTofuMcp(ctx.payload);
@@ -301436,13 +301869,13 @@ function writeMcpConfig(ctx) {
301436
301869
  return configPath;
301437
301870
  }
301438
301871
  function writePretoolGateAssets(ctx, stopHookPath) {
301439
- const scriptPath = join66(ctx.tmpdir, CLAUDE_PRETOOL_GATE_FILENAME);
301872
+ const scriptPath = join67(ctx.tmpdir, CLAUDE_PRETOOL_GATE_FILENAME);
301440
301873
  writeFileSync22(
301441
301874
  scriptPath,
301442
301875
  buildClaudePretoolGateSource(ctx.subagentDeniedTools, writePolicyPath2(ctx.tmpdir))
301443
301876
  );
301444
301877
  chmodSync3(scriptPath, 493);
301445
- const settingsPath = join66(ctx.tmpdir, "infraweaver-claude-settings.json");
301878
+ const settingsPath = join67(ctx.tmpdir, "infraweaver-claude-settings.json");
301446
301879
  const settings = buildClaudePretoolGateSettings(scriptPath, CLAUDE_EXEC_TOOL_DENY_RULES);
301447
301880
  settings.permissions.deny.push(...harnessTmpdirWriteDenyClaude(ctx.tmpdir));
301448
301881
  if (stopHookPath) {
@@ -301531,7 +301964,7 @@ function timerFor(state5, label) {
301531
301964
  function chargeTokenQuota(state5) {
301532
301965
  const quota = state5.params.tokenQuota;
301533
301966
  if (!quota || state5.stopRun.signal.aborted) return;
301534
- if (!quota.observe(state5.quotaSource, quotaTokens(state5.accumulatedTokens))) return;
301967
+ if (!quota.observe(state5.quotaSource, quotaTokens2(state5.accumulatedTokens))) return;
301535
301968
  log2.info(`\xBB ${state5.params.label}: ${quota.gap()}`);
301536
301969
  state5.stopRun.abort(new TokenQuotaExceededError(quota.gap()));
301537
301970
  }
@@ -302067,11 +302500,11 @@ var claude = agent({
302067
302500
  const onVertex = matchesRouteSentinel(VERTEX_MODEL_ID_ENV, specifier) && specifier !== void 0 && isVertexAnthropicId(specifier);
302068
302501
  const model = !specifier ? void 0 : onBedrock ? specifier : onVertex ? void 0 : stripProviderPrefix(specifier);
302069
302502
  const homeEnv = agentHomeEnv(ctx.tmpdir);
302070
- mkdirSync10(join66(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
302503
+ mkdirSync10(join67(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
302071
302504
  installBundledSkills({ home: homeEnv.HOME });
302072
302505
  const mcpConfigPath = writeMcpConfig(ctx);
302073
302506
  const effort = resolveEffort(model);
302074
- const stopHookPath = join66(ctx.tmpdir, "infraweaver-stop-hook.sh");
302507
+ const stopHookPath = join67(ctx.tmpdir, "infraweaver-stop-hook.sh");
302075
302508
  writeFileSync22(stopHookPath, buildStopHookScript(), { mode: 493 });
302076
302509
  const pretoolGate = writePretoolGateAssets(ctx, stopHookPath);
302077
302510
  installManagedSettings({
@@ -302237,7 +302670,7 @@ var claude = agent({
302237
302670
  init_core3();
302238
302671
  import { spawn as nodeSpawn2 } from "node:child_process";
302239
302672
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync24 } from "node:fs";
302240
- import { join as join68 } from "node:path";
302673
+ import { join as join69 } from "node:path";
302241
302674
  import { performance as performance7 } from "node:perf_hooks";
302242
302675
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
302243
302676
  import { setTimeout as sleep7 } from "node:timers/promises";
@@ -307920,7 +308353,7 @@ function autoSelectModel() {
307920
308353
  import { execFileSync as execFileSync4 } from "node:child_process";
307921
308354
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync23 } from "node:fs";
307922
308355
  import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
307923
- import { join as join67 } from "node:path";
308356
+ import { join as join68 } from "node:path";
307924
308357
 
307925
308358
  // src/utils/agent/codexOAuth.ts
307926
308359
  init_webapi();
@@ -307974,8 +308407,8 @@ function installCodexAuth() {
307974
308407
  }
307975
308408
  const expiresMs = decodeJwtExpMs(body2.tokens.access_token) ?? 0;
307976
308409
  const xdgDataHome = resolveDataHome();
307977
- const opencodeDir = join67(xdgDataHome, "opencode");
307978
- const authPath = join67(opencodeDir, "auth.json");
308410
+ const opencodeDir = join68(xdgDataHome, "opencode");
308411
+ const authPath = join68(opencodeDir, "auth.json");
307979
308412
  const opencodeAuth = {
307980
308413
  openai: {
307981
308414
  type: "oauth",
@@ -307999,7 +308432,7 @@ function installCodexAuth() {
307999
308432
  };
308000
308433
  }
308001
308434
  function resolveDataHome() {
308002
- if (process.env.CI !== "true") return join67(homedir3(), ".local", "share");
308435
+ if (process.env.CI !== "true") return join68(homedir3(), ".local", "share");
308003
308436
  bootstrapInfraweaverDataDir();
308004
308437
  return INFRAWEAVER_DATA_DIR;
308005
308438
  }
@@ -308263,7 +308696,7 @@ function newTurn() {
308263
308696
  turnCounter += 1;
308264
308697
  return {
308265
308698
  finalText: "",
308266
- tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
308699
+ tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0 },
308267
308700
  costUsd: 0,
308268
308701
  sessionError: null,
308269
308702
  lastToolError: null,
@@ -308273,7 +308706,7 @@ function newTurn() {
308273
308706
  function chargeTokenQuota2(ctx) {
308274
308707
  const quota = ctx.tokenQuota;
308275
308708
  if (!quota || !ctx.currentTurn || ctx.tokenQuotaExceeded) return;
308276
- if (!quota.observe(ctx.currentTurn.quotaSource, quotaTokens(ctx.currentTurn.tokens))) return;
308709
+ if (!quota.observe(ctx.currentTurn.quotaSource, quotaTokens2(ctx.currentTurn.tokens))) return;
308277
308710
  ctx.tokenQuotaExceeded = true;
308278
308711
  log2.info(`\xBB ${ctx.label}: ${quota.gap()}`);
308279
308712
  ctx.abortRun?.(new TokenQuotaExceededError(quota.gap()));
@@ -308382,6 +308815,7 @@ async function onPartUpdated(ctx, part) {
308382
308815
  if (t) {
308383
308816
  ctx.currentTurn.tokens.input += t.input || 0;
308384
308817
  ctx.currentTurn.tokens.output += t.output || 0;
308818
+ ctx.currentTurn.tokens.reasoning += t.reasoning || 0;
308385
308819
  ctx.currentTurn.tokens.cacheRead += t.cache?.read || 0;
308386
308820
  ctx.currentTurn.tokens.cacheWrite += t.cache?.write || 0;
308387
308821
  }
@@ -308740,10 +309174,10 @@ var opencode = agent({
308740
309174
  const openAiCompatModel = resolveOpenAiCompatibleOpenCodeModel(rawModel);
308741
309175
  const model = isBedrockRoute ? `amazon-bedrock/${rawModel}` : vertexModel ?? openAiCompatModel ?? rawModel;
308742
309176
  const homeEnv = agentHomeEnv(ctx.tmpdir);
308743
- const opencodePluginDir = join68(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
309177
+ const opencodePluginDir = join69(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
308744
309178
  mkdirSync12(opencodePluginDir, { recursive: true });
308745
309179
  writeFileSync24(
308746
- join68(opencodePluginDir, INFRAWEAVER_OPENCODE_GATE_PLUGIN_FILENAME),
309180
+ join69(opencodePluginDir, INFRAWEAVER_OPENCODE_GATE_PLUGIN_FILENAME),
308747
309181
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools, writePolicyPath2(ctx.tmpdir))
308748
309182
  );
308749
309183
  installBundledSkills({ home: homeEnv.HOME });
@@ -308986,10 +309420,10 @@ var DispatchRecorder = class {
308986
309420
  * orchestrator counts as subagent — including a session the labeler could not
308987
309421
  * name, because an unnamed session is by definition not the orchestrator's.
308988
309422
  */
308989
- recordTokens(label, billableTokens2) {
308990
- if (!Number.isFinite(billableTokens2) || billableTokens2 <= 0) return;
308991
- if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens += billableTokens2;
308992
- else this.#subagentTokens += billableTokens2;
309423
+ recordTokens(label, billableTokens) {
309424
+ if (!Number.isFinite(billableTokens) || billableTokens <= 0) return;
309425
+ if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens += billableTokens;
309426
+ else this.#subagentTokens += billableTokens;
308993
309427
  }
308994
309428
  tally() {
308995
309429
  return {
@@ -309779,7 +310213,7 @@ async function fetchRunContext(params) {
309779
310213
  // src/utils/github/github.ts
309780
310214
  init_core3();
309781
310215
  import { rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
309782
- import { dirname as dirname12, join as join69 } from "node:path";
310216
+ import { dirname as dirname12, join as join70 } from "node:path";
309783
310217
 
309784
310218
  // node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
309785
310219
  function getUserAgent2() {
@@ -309878,8 +310312,8 @@ function addQueryParameters(url5, parameters) {
309878
310312
  }).join("&");
309879
310313
  }
309880
310314
  var urlVariableRegex = /\{[^{}}]+\}/g;
309881
- function removeNonChars(variableName) {
309882
- return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
310315
+ function removeNonChars(variableName2) {
310316
+ return variableName2.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
309883
310317
  }
309884
310318
  function extractUrlVariableNames(url5) {
309885
310319
  const matches2 = url5.match(urlVariableRegex);
@@ -315595,7 +316029,7 @@ function getGitHubUsageSummary() {
315595
316029
  }
315596
316030
  async function writeGitHubUsageSummaryToFile(path15) {
315597
316031
  const summary2 = getGitHubUsageSummary();
315598
- const tmpPath = join69(dirname12(path15), `.usage-summary-${process.pid}.tmp`);
316032
+ const tmpPath = join70(dirname12(path15), `.usage-summary-${process.pid}.tmp`);
315599
316033
  await writeFile2(tmpPath, JSON.stringify(summary2));
315600
316034
  await rename2(tmpPath, path15);
315601
316035
  }
@@ -316140,7 +316574,7 @@ async function resolveModelAndAuth(params) {
316140
316574
  // src/utils/run/runLifecycle.ts
316141
316575
  init_core3();
316142
316576
  import { writeFile as writeFile5 } from "node:fs/promises";
316143
- import { join as join72, resolve as resolve10 } from "node:path";
316577
+ import { join as join73, resolve as resolve10 } from "node:path";
316144
316578
 
316145
316579
  // src/utils/cloud/cloudReport.ts
316146
316580
  init_core3();
@@ -316534,7 +316968,7 @@ async function reportToCloud(input2) {
316534
316968
 
316535
316969
  // src/utils/cloud/learnings.ts
316536
316970
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
316537
- import { dirname as dirname13, join as join70 } from "node:path";
316971
+ import { dirname as dirname13, join as join71 } from "node:path";
316538
316972
 
316539
316973
  // src/utils/cloud/learningsTruncate.ts
316540
316974
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -316551,7 +316985,7 @@ function truncateAtLineBoundary(body2, cap2) {
316551
316985
  // src/utils/cloud/learnings.ts
316552
316986
  var LEARNINGS_FILE_NAME = LEARNINGS_FILE;
316553
316987
  function learningsFilePath(tmpdir5) {
316554
- return join70(tmpdir5, LEARNINGS_FILE_NAME);
316988
+ return join71(tmpdir5, LEARNINGS_FILE_NAME);
316555
316989
  }
316556
316990
  async function seedLearningsFile(params) {
316557
316991
  const path15 = learningsFilePath(params.tmpdir);
@@ -316682,7 +317116,7 @@ ${ctx.error}` : ctx.error;
316682
317116
 
316683
317117
  // src/utils/github/prSummary.ts
316684
317118
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
316685
- import { dirname as dirname14, join as join71 } from "node:path";
317119
+ import { dirname as dirname14, join as join72 } from "node:path";
316686
317120
  var SUMMARY_FILE_NAME = SUMMARY_FILE;
316687
317121
  var SUMMARY_SCAFFOLD = `# PR summary
316688
317122
 
@@ -316692,7 +317126,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
316692
317126
  var MIN_SNAPSHOT_LENGTH = 60;
316693
317127
  var MAX_SNAPSHOT_LENGTH = 32768;
316694
317128
  function summaryFilePath(tmpdir5) {
316695
- return join71(tmpdir5, SUMMARY_FILE_NAME);
317129
+ return join72(tmpdir5, SUMMARY_FILE_NAME);
316696
317130
  }
316697
317131
  async function seedSummaryFile(params) {
316698
317132
  const path15 = summaryFilePath(params.tmpdir);
@@ -317226,7 +317660,7 @@ async function emitFindingsOutputs(toolState, requiredControls) {
317226
317660
  if (a.level === "error") error62(a.message, props);
317227
317661
  else warning(a.message, props);
317228
317662
  }
317229
- const sarifPath = join72(workspace, SARIF_FILE);
317663
+ const sarifPath = join73(workspace, SARIF_FILE);
317230
317664
  if (session.emittedSarifPath && resolve10(session.emittedSarifPath) === resolve10(sarifPath)) {
317231
317665
  log2.info(`\xBB findings output: replacing the agent-emitted ${SARIF_FILE} with the gate's`);
317232
317666
  }
@@ -317558,7 +317992,7 @@ async function runAgentWithWatchdogs(params) {
317558
317992
  const tokenQuota = reserved ? quotas.token : createTokenQuota(quotas.tokenLimit.full);
317559
317993
  const costQuota = reserved ? quotas.cost : createCostQuota(quotas.costLimit.full);
317560
317994
  log2.info(
317561
- quotas.tokenLimit.full > 0 ? `\xBB run token quota: ${quotas.tokenLimit.full.toLocaleString("en-GB")} billable tokens` + (reserved ? ` (${tokenQuota.limit.toLocaleString("en-GB")} for the run, the rest reserved to deliver findings if it runs out)` : "") : `\xBB run token quota: disabled (${RUN_TOKEN_QUOTA_INPUT}: 0)`
317995
+ quotas.tokenLimit.full > 0 ? `\xBB run token quota: ${quotas.tokenLimit.full.toLocaleString("en-GB")} tokens` + (reserved ? ` (${tokenQuota.limit.toLocaleString("en-GB")} for the run, the rest reserved to deliver findings if it runs out)` : "") + "; a token read from the prompt cache counts as a tenth" : `\xBB run token quota: disabled (${RUN_TOKEN_QUOTA_INPUT}: 0)`
317562
317996
  );
317563
317997
  log2.info(
317564
317998
  quotas.costLimit.full > 0 ? `\xBB run cost quota: $${quotas.costLimit.full.toFixed(2)}` + (reserved ? ` ($${costQuota.limit.toFixed(2)} for the run, the rest reserved to deliver findings if it runs out)` : "") : `\xBB run cost quota: disabled (${RUN_COST_QUOTA_INPUT}: 0)`
@@ -317656,9 +318090,9 @@ async function runAgentWithWatchdogs(params) {
317656
318090
  }
317657
318091
 
317658
318092
  // src/phases/startToolPlane.ts
317659
- import { existsSync as existsSync19, readdirSync as readdirSync10 } from "node:fs";
318093
+ import { existsSync as existsSync18, readdirSync as readdirSync10 } from "node:fs";
317660
318094
  import { readFile as readFile5 } from "node:fs/promises";
317661
- import { join as join73 } from "node:path";
318095
+ import { join as join74 } from "node:path";
317662
318096
 
317663
318097
  // src/utils/cloud/runTerminalStatus.ts
317664
318098
  var TERMINAL_STATE_KEY = "run_terminal";
@@ -318486,8 +318920,8 @@ ${instructions.user}` : null,
318486
318920
  log2.info(instructions.full);
318487
318921
  });
318488
318922
  if (toolContext.agentId === "opencode") {
318489
- const pluginDir = join73(process.cwd(), ".opencode", "plugin");
318490
- const hasPlugins = existsSync19(pluginDir) && readdirSync10(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
318923
+ const pluginDir = join74(process.cwd(), ".opencode", "plugin");
318924
+ const hasPlugins = existsSync18(pluginDir) && readdirSync10(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
318491
318925
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
318492
318926
  log2.info(
318493
318927
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"