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/cli.mjs CHANGED
@@ -80148,7 +80148,7 @@ var require_polyfill = __commonJS({
80148
80148
  var {
80149
80149
  dirname: dirname16,
80150
80150
  isAbsolute: isAbsolute10,
80151
- join: join77,
80151
+ join: join78,
80152
80152
  parse: parse8,
80153
80153
  resolve: resolve15,
80154
80154
  sep: sep12,
@@ -80405,8 +80405,8 @@ var require_polyfill = __commonJS({
80405
80405
  const dir = await readdir2(src);
80406
80406
  for (let i = 0; i < dir.length; i++) {
80407
80407
  const item = dir[i];
80408
- const srcItem = join77(src, item);
80409
- const destItem = join77(dest, item);
80408
+ const srcItem = join78(src, item);
80409
+ const destItem = join78(dest, item);
80410
80410
  const { destStat } = await checkPaths2(srcItem, destItem, opts);
80411
80411
  await startCopy(destStat, srcItem, destItem, opts);
80412
80412
  }
@@ -80479,7 +80479,7 @@ var require_cp = __commonJS({
80479
80479
  // node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/with-temp-dir.js
80480
80480
  var require_with_temp_dir = __commonJS({
80481
80481
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/with-temp-dir.js"(exports2, module) {
80482
- var { join: join77, sep: sep12 } = __require("path");
80482
+ var { join: join78, sep: sep12 } = __require("path");
80483
80483
  var getOptions2 = require_get_options();
80484
80484
  var { mkdir: mkdir5, mkdtemp, rm: rm2 } = __require("fs/promises");
80485
80485
  var withTempDir = async (root, fn2, opts) => {
@@ -80487,7 +80487,7 @@ var require_with_temp_dir = __commonJS({
80487
80487
  copy: ["tmpPrefix"]
80488
80488
  });
80489
80489
  await mkdir5(root, { recursive: true });
80490
- const target = await mkdtemp(join77(`${root}${sep12}`, options.tmpPrefix || ""));
80490
+ const target = await mkdtemp(join78(`${root}${sep12}`, options.tmpPrefix || ""));
80491
80491
  let err;
80492
80492
  let result;
80493
80493
  try {
@@ -80512,13 +80512,13 @@ var require_with_temp_dir = __commonJS({
80512
80512
  var require_readdir_scoped = __commonJS({
80513
80513
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/readdir-scoped.js"(exports2, module) {
80514
80514
  var { readdir: readdir2 } = __require("fs/promises");
80515
- var { join: join77 } = __require("path");
80515
+ var { join: join78 } = __require("path");
80516
80516
  var readdirScoped = async (dir) => {
80517
80517
  const results = [];
80518
80518
  for (const item of await readdir2(dir)) {
80519
80519
  if (item.startsWith("@")) {
80520
- for (const scopedItem of await readdir2(join77(dir, item))) {
80521
- results.push(join77(item, scopedItem));
80520
+ for (const scopedItem of await readdir2(join78(dir, item))) {
80521
+ results.push(join78(item, scopedItem));
80522
80522
  }
80523
80523
  } else {
80524
80524
  results.push(item);
@@ -80533,7 +80533,7 @@ var require_readdir_scoped = __commonJS({
80533
80533
  // node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/move-file.js
80534
80534
  var require_move_file = __commonJS({
80535
80535
  "node_modules/.pnpm/@npmcli+fs@6.0.0/node_modules/@npmcli/fs/lib/move-file.js"(exports2, module) {
80536
- var { dirname: dirname16, join: join77, resolve: resolve15, relative: relative10, isAbsolute: isAbsolute10 } = __require("path");
80536
+ var { dirname: dirname16, join: join78, resolve: resolve15, relative: relative10, isAbsolute: isAbsolute10 } = __require("path");
80537
80537
  var fs12 = __require("fs/promises");
80538
80538
  var pathExists2 = async (path15) => {
80539
80539
  try {
@@ -80563,7 +80563,7 @@ var require_move_file = __commonJS({
80563
80563
  if (sourceStat.isDirectory()) {
80564
80564
  const files = await fs12.readdir(source);
80565
80565
  await Promise.all(files.map(
80566
- (file2) => moveFile(join77(source, file2), join77(destination, file2), options, false, symlinks)
80566
+ (file2) => moveFile(join78(source, file2), join78(destination, file2), options, false, symlinks)
80567
80567
  ));
80568
80568
  } else if (sourceStat.isSymbolicLink()) {
80569
80569
  symlinks.push({ source, destination });
@@ -90400,23 +90400,23 @@ var require_helpers = __commonJS({
90400
90400
  "node_modules/.pnpm/ip-address@10.7.3/node_modules/ip-address/dist/v6/helpers.js"(exports2) {
90401
90401
  "use strict";
90402
90402
  Object.defineProperty(exports2, "__esModule", { value: true });
90403
- exports2.escapeHtml = escapeHtml;
90403
+ exports2.escapeHtml = escapeHtml2;
90404
90404
  exports2.spanAllZeroes = spanAllZeroes;
90405
90405
  exports2.spanAll = spanAll;
90406
90406
  exports2.spanLeadingZeroes = spanLeadingZeroes;
90407
90407
  exports2.simpleGroup = simpleGroup;
90408
- function escapeHtml(s) {
90408
+ function escapeHtml2(s) {
90409
90409
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
90410
90410
  }
90411
90411
  function spanAllZeroes(s) {
90412
- return escapeHtml(s).replace(/(0+)/g, '<span class="zero">$1</span>');
90412
+ return escapeHtml2(s).replace(/(0+)/g, '<span class="zero">$1</span>');
90413
90413
  }
90414
90414
  function spanAll(s, offset = 0) {
90415
90415
  const letters = s.split("");
90416
- return letters.map((n, i) => `<span class="digit value-${escapeHtml(n)} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
90416
+ return letters.map((n, i) => `<span class="digit value-${escapeHtml2(n)} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
90417
90417
  }
90418
90418
  function spanLeadingZeroesSimple(group2) {
90419
- return escapeHtml(group2).replace(/^(0+)/, '<span class="zero">$1</span>');
90419
+ return escapeHtml2(group2).replace(/^(0+)/, '<span class="zero">$1</span>');
90420
90420
  }
90421
90421
  function spanLeadingZeroes(address) {
90422
90422
  const groups2 = address.split(":");
@@ -112581,14 +112581,14 @@ var require_turndown_cjs = __commonJS({
112581
112581
  } else if (node3.nodeType === 1) {
112582
112582
  replacement = replacementForNode.call(self2, node3);
112583
112583
  }
112584
- return join77(output2, replacement);
112584
+ return join78(output2, replacement);
112585
112585
  }, "");
112586
112586
  }
112587
112587
  function postProcess(output2) {
112588
112588
  var self2 = this;
112589
112589
  this.rules.forEach(function(rule) {
112590
112590
  if (typeof rule.append === "function") {
112591
- output2 = join77(output2, rule.append(self2.options));
112591
+ output2 = join78(output2, rule.append(self2.options));
112592
112592
  }
112593
112593
  });
112594
112594
  return output2.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -112600,7 +112600,7 @@ var require_turndown_cjs = __commonJS({
112600
112600
  if (whitespace.leading || whitespace.trailing) content = content.trim();
112601
112601
  return whitespace.leading + rule.replacement(content, node3, this.options) + whitespace.trailing;
112602
112602
  }
112603
- function join77(output2, replacement) {
112603
+ function join78(output2, replacement) {
112604
112604
  var s1 = trimTrailingNewlines(output2);
112605
112605
  var s2 = trimLeadingNewlines(replacement);
112606
112606
  var nls = Math.max(output2.length - s1.length, replacement.length - s2.length);
@@ -160536,7 +160536,7 @@ var init_cacheTwirpClient = __esm({
160536
160536
  });
160537
160537
 
160538
160538
  // node_modules/.pnpm/@actions+cache@6.3.0_supports-color@7.2.0/node_modules/@actions/cache/lib/internal/tar.js
160539
- import { existsSync as existsSync19, writeFileSync as writeFileSync21 } from "fs";
160539
+ import { existsSync as existsSync18, writeFileSync as writeFileSync21 } from "fs";
160540
160540
  import * as path13 from "path";
160541
160541
  function getTarPath() {
160542
160542
  return __awaiter18(this, void 0, void 0, function* () {
@@ -160546,7 +160546,7 @@ function getTarPath() {
160546
160546
  const systemTar = SystemTarPathOnWindows;
160547
160547
  if (gnuTar) {
160548
160548
  return { path: gnuTar, type: ArchiveToolType.GNU };
160549
- } else if (existsSync19(systemTar)) {
160549
+ } else if (existsSync18(systemTar)) {
160550
160550
  return { path: systemTar, type: ArchiveToolType.BSD };
160551
160551
  }
160552
160552
  break;
@@ -191031,7 +191031,7 @@ var BOT_EMAIL = `${BOT_LOGIN}@users.noreply.github.com`;
191031
191031
  var OIDC_AUDIENCE = `${PRODUCT_SLUG}-api`;
191032
191032
 
191033
191033
  // src/commands/assess.ts
191034
- import { existsSync as existsSync10, statSync as statSync8 } from "node:fs";
191034
+ import { existsSync as existsSync9, statSync as statSync8 } from "node:fs";
191035
191035
  import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
191036
191036
  var import_arg = __toESM(require_arg(), 1);
191037
191037
 
@@ -199939,7 +199939,7 @@ var log = {
199939
199939
  debug: (...args2) => current.debug(...args2)
199940
199940
  };
199941
199941
 
199942
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/usage.js
199942
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/usage.js
199943
199943
  function formatCostUsd(costUsd) {
199944
199944
  return costUsd.toFixed(4);
199945
199945
  }
@@ -200490,7 +200490,7 @@ import { randomBytes } from "node:crypto";
200490
200490
  // package.json
200491
200491
  var package_default = {
200492
200492
  name: "infraweaver",
200493
- version: "0.3.11",
200493
+ version: "0.3.13",
200494
200494
  description: "GitHub Action that remediates Terraform to best practices and opens one scoped pull request per concern.",
200495
200495
  keywords: [
200496
200496
  "ai-coding-agent",
@@ -200593,7 +200593,7 @@ var package_default = {
200593
200593
  "@infraweaver-io/ci": "^0.1.8",
200594
200594
  "@infraweaver-io/core": "^0.1.5",
200595
200595
  "@infraweaver-io/crosswalk": "^1.2.0",
200596
- "@infraweaver-io/models": "^0.1.6",
200596
+ "@infraweaver-io/models": "^0.1.7",
200597
200597
  "@infraweaver-io/policy-spec": "^1.2.0",
200598
200598
  "@infraweaver-io/report-contract": "^0.1.5",
200599
200599
  "@infraweaver-io/sandbox": "^0.1.8",
@@ -207495,7 +207495,18 @@ var SECRET_WORDS = /* @__PURE__ */ new Set([
207495
207495
  "credential",
207496
207496
  "credentials"
207497
207497
  ]);
207498
- var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set(["api", "secret", "private", "signing"]);
207498
+ var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set([
207499
+ "api",
207500
+ "secret",
207501
+ "private",
207502
+ "signing",
207503
+ "access",
207504
+ "account",
207505
+ "client",
207506
+ "shared",
207507
+ "preshared",
207508
+ "master"
207509
+ ]);
207499
207510
  var NOT_SECRET_TOKENS = /* @__PURE__ */ new Set(["creation", "idempotency", "client", "request", "continuation"]);
207500
207511
  function words(name) {
207501
207512
  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_-]+/).filter(Boolean);
@@ -207507,24 +207518,70 @@ function isSecretName(name) {
207507
207518
  if (last === void 0) return false;
207508
207519
  if (last === "key") return prev !== void 0 && SECRET_KEY_PREFIXES.has(prev);
207509
207520
  if (last === "token" && prev !== void 0 && NOT_SECRET_TOKENS.has(prev)) return false;
207521
+ if (last === "string") return prev === "connection";
207522
+ if (last === "secret" && w.length === 1) return false;
207510
207523
  return SECRET_WORDS.has(last);
207511
207524
  }
207512
207525
  function isPlaceholder(inner) {
207513
- return inner.trim() === "" || /^[*xX.]+$/.test(inner) || /^<[^>]*>$/.test(inner) || // the dummy credentials offline plans and LocalStack are given (`secret_key = "test"`)
207514
- /^(?:test|mock|fake|dummy|fixture|example|placeholder|sample)(?:[_-].*)?$/i.test(inner) || // a token's lifetime unit: `token_validity_units { access_token = "hours" }`
207526
+ 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 }"`
207527
+ inner.includes("%{") || // the dummy credentials offline plans and LocalStack are given (`secret_key = "test"`,
207528
+ // `"example-secret"`) — but only when that is the whole value: a lowercase tail at
207529
+ // most, so `"Test-Pr0dDbPassw0rd!"` is a password that happens to start with a word
207530
+ isDummy(inner) || // a token's lifetime unit: `token_validity_units { access_token = "hours" }`
207515
207531
  /^(?:seconds|minutes|hours|days)$/.test(inner);
207516
207532
  }
207517
- var HEADER = /^(resource|data|provider|module|locals|variable|output|terraform)\b([^{]*)\{\s*$/;
207533
+ function isDummy(inner) {
207534
+ const m = /^(?:test|mock|fake|dummy|fixture|example|placeholder|sample)([_-].*)?$/i.exec(inner);
207535
+ return m !== null && (m[1] === void 0 || /^[_-][a-z0-9_-]*$/.test(m[1]));
207536
+ }
207537
+ function isSecretReference(inner) {
207538
+ return inner.startsWith("arn:") || /^projects\/[^/]+\/secrets\//.test(inner) || /^https:\/\/[\w-]+\.vault\.azure\.net\/secrets\//i.test(inner) || /^\/[\w./-]+$/.test(inner);
207539
+ }
207540
+ var HEADER = /^(resource|data|provider|module|locals|variable|output|terraform)\b([^{]*)\{(.*)$/;
207518
207541
  var ASSIGNMENT = /^\s*(?:"([A-Za-z_][\w-]*)"|([A-Za-z_][\w-]*))\s*=(?!=)\s*(.*)$/;
207519
207542
  var HEREDOC = /<<-?([A-Za-z_]\w*)\s*$/;
207520
207543
  var INLINE_PAIR = /(?:"([A-Za-z_][\w-]*)"|\b([A-Za-z_][\w-]*))\s*=(?!=)\s*("(?:[^"\\]|\\.)*")/g;
207521
- function literalValue(raw2) {
207544
+ function literalValue(name, raw2) {
207522
207545
  const value2 = stripTrailingComment(raw2).replace(/,$/, "").trim();
207523
- return isStringLiteral(value2) && !isPlaceholder(value2.slice(1, -1));
207546
+ if (!isStringLiteral(value2)) return false;
207547
+ const inner = value2.slice(1, -1);
207548
+ if (isPlaceholder(inner) || isSecretReference(inner)) return false;
207549
+ return !(words(name).at(-1) === "auth" && /^[A-Z]+(?:_[A-Z]+)*$/.test(inner));
207550
+ }
207551
+ function variableName(holder) {
207552
+ return /^variable\s+"?([\w-]+)"?/.exec(holder)?.[1] ?? "";
207553
+ }
207554
+ function opensBlockComment(line) {
207555
+ for (let i = 0; i < line.length; i++) {
207556
+ const span = skipNonCode(line, i);
207557
+ if (!span) continue;
207558
+ if (line.startsWith("/*", i) && !line.includes("*/", i + 2)) return true;
207559
+ i = span.end;
207560
+ }
207561
+ return false;
207562
+ }
207563
+ function closesOnLine(rest) {
207564
+ let depth = 1;
207565
+ for (let i = 0; i < rest.length; i++) {
207566
+ const span = skipNonCode(rest, i);
207567
+ if (span) {
207568
+ i = span.end;
207569
+ continue;
207570
+ }
207571
+ if (rest[i] === "{") depth++;
207572
+ else if (rest[i] === "}" && --depth === 0) return true;
207573
+ }
207574
+ return false;
207524
207575
  }
207525
207576
  function findHardcodedSecrets(hcl2) {
207577
+ return findInLines(hcl2, null);
207578
+ }
207579
+ function findTfvarsSecrets(hcl2, path15) {
207580
+ return findInLines(hcl2, `tfvars input ${path15}`);
207581
+ }
207582
+ function findInLines(hcl2, fileHolder) {
207526
207583
  const out = [];
207527
- let holder = null;
207584
+ let holder = fileHolder;
207528
207585
  let heredoc = null;
207529
207586
  let inComment = false;
207530
207587
  const lines = hcl2.split("\n");
@@ -207544,12 +207601,28 @@ function findHardcodedSecrets(hcl2) {
207544
207601
  continue;
207545
207602
  }
207546
207603
  if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
207547
- const header = HEADER.exec(line);
207604
+ if (opensBlockComment(line)) inComment = true;
207605
+ const header = fileHolder === null ? HEADER.exec(line) : null;
207548
207606
  if (header) {
207549
207607
  holder = `${header[1]}${header[2].trimEnd() ? ` ${header[2].trim()}` : ""}`;
207608
+ const rest = header[3];
207609
+ const opens2 = HEREDOC.exec(rest);
207610
+ if (opens2) heredoc = opens2[1];
207611
+ if (!holder.startsWith("output ") && !holder.startsWith("terraform")) {
207612
+ for (const pair of rest.matchAll(INLINE_PAIR)) {
207613
+ const key = pair[1] ?? pair[2];
207614
+ const isVariable = holder.startsWith("variable ");
207615
+ if (isVariable && key !== "default") continue;
207616
+ const name2 = isVariable ? variableName(holder) : key;
207617
+ if (isSecretName(name2) && literalValue(name2, pair[3])) {
207618
+ out.push({ name: name2, holder, line: i + 1 });
207619
+ }
207620
+ }
207621
+ }
207622
+ if (closesOnLine(rest)) holder = null;
207550
207623
  continue;
207551
207624
  }
207552
- if (line.startsWith("}")) {
207625
+ if (fileHolder === null && line.startsWith("}")) {
207553
207626
  holder = null;
207554
207627
  continue;
207555
207628
  }
@@ -207561,30 +207634,34 @@ function findHardcodedSecrets(hcl2) {
207561
207634
  const name = m[1] ?? m[2];
207562
207635
  const value2 = m[3];
207563
207636
  if (holder.startsWith("variable ")) {
207564
- const variable = /^variable\s+"?([\w-]+)"?/.exec(holder)?.[1] ?? "";
207565
- if (name === "default" && isSecretName(variable) && literalValue(value2)) {
207637
+ const variable = variableName(holder);
207638
+ if (name === "default" && isSecretName(variable) && literalValue(variable, value2)) {
207566
207639
  out.push({ name: variable, holder, line: i + 1 });
207567
207640
  }
207568
207641
  continue;
207569
207642
  }
207570
207643
  if (holder.startsWith("output ") || holder.startsWith("terraform")) continue;
207571
- if (isSecretName(name) && literalValue(value2)) {
207644
+ if (isSecretName(name) && literalValue(name, value2)) {
207572
207645
  out.push({ name, holder, line: i + 1 });
207573
207646
  continue;
207574
207647
  }
207575
207648
  for (const pair of value2.matchAll(INLINE_PAIR)) {
207576
207649
  const key = pair[1] ?? pair[2];
207577
- if (isSecretName(key) && literalValue(pair[3])) {
207650
+ if (isSecretName(key) && literalValue(key, pair[3])) {
207578
207651
  out.push({ name: key, holder, line: i + 1 });
207579
207652
  }
207580
207653
  }
207581
207654
  }
207582
207655
  return out;
207583
207656
  }
207584
- function scanHardcodedSecrets(files) {
207657
+ function scanHardcodedSecrets(files, tfvarsFiles = []) {
207585
207658
  const concerns = [];
207586
- for (const { path: path15, hcl: hcl2 } of files) {
207587
- for (const hit of findHardcodedSecrets(hcl2)) {
207659
+ const hits = [
207660
+ ...files.map((f) => ({ path: f.path, found: findHardcodedSecrets(f.hcl) })),
207661
+ ...tfvarsFiles.map((f) => ({ path: f.path, found: findTfvarsSecrets(f.hcl, f.path) }))
207662
+ ];
207663
+ for (const { path: path15, found } of hits) {
207664
+ for (const hit of found) {
207588
207665
  concerns.push(
207589
207666
  buildConcern({
207590
207667
  source: NATIVE_SOURCE,
@@ -209587,7 +209664,8 @@ var NATIVE_RULE_SCANNERS = [
209587
209664
  name: "hardcoded-secrets",
209588
209665
  rules: [HARDCODED_SECRET_ARGUMENT],
209589
209666
  // Per file: the argument's name and its literal are both on the line itself.
209590
- scan: (ctx) => ({ concerns: scanHardcodedSecrets(ctx.files) })
209667
+ // The only rule that reads `.tfvars`: an input file holds values, not config.
209668
+ scan: (ctx) => ({ concerns: scanHardcodedSecrets(ctx.files, ctx.tfvarsFiles) })
209591
209669
  },
209592
209670
  {
209593
209671
  name: "write-only",
@@ -209672,6 +209750,7 @@ function createRuleContext(input2) {
209672
209750
  cwd: input2.cwd,
209673
209751
  files: input2.files,
209674
209752
  tofuFiles: input2.tofuFiles ?? [],
209753
+ tfvarsFiles: input2.tfvarsFiles ?? [],
209675
209754
  truncated: input2.truncated ?? null,
209676
209755
  repeatedLiteralThreshold: input2.repeatedLiteralThreshold
209677
209756
  };
@@ -209723,6 +209802,9 @@ function readTfTree(cwd, limits) {
209723
209802
  function readTofuTree(cwd, limits) {
209724
209803
  return readTree(cwd, ".tofu", limits);
209725
209804
  }
209805
+ function readTfvarsTree(cwd, limits) {
209806
+ return readTree(cwd, ".tfvars", limits);
209807
+ }
209726
209808
  var OVERSIZE_NAMED = 5;
209727
209809
  function describeOversize(paths) {
209728
209810
  const named = paths.slice(0, OVERSIZE_NAMED).join(", ");
@@ -209751,6 +209833,9 @@ function scanNative(cwd, opts = {}) {
209751
209833
  // walk over a suffix most repos do not use at all, and threading it through
209752
209834
  // the context keeps the scanners uniform.
209753
209835
  tofuFiles: readTofuTree(cwd).files,
209836
+ // likewise only for the hardcoded-secret rule: a value kept out of the `.tf`
209837
+ // source is most often committed here instead
209838
+ tfvarsFiles: readTfvarsTree(cwd).files,
209754
209839
  truncated: tree.truncated,
209755
209840
  repeatedLiteralThreshold: opts.repeatedLiteralThreshold ?? DEFAULT_REPEAT_THRESHOLD
209756
209841
  });
@@ -214109,7 +214194,7 @@ function TerraformAssessTool(ctx) {
214109
214194
  // src/mcp/localServer.ts
214110
214195
  import { mkdtempSync as mkdtempSync4 } from "node:fs";
214111
214196
  import { tmpdir as tmpdir3 } from "node:os";
214112
- import { join as join40 } from "node:path";
214197
+ import { join as join41 } from "node:path";
214113
214198
 
214114
214199
  // node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/external.js
214115
214200
  var external_exports = {};
@@ -271571,7 +271656,7 @@ var FastMCP = class extends FastMCPEventEmitter {
271571
271656
  }
271572
271657
  };
271573
271658
 
271574
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/catalog.js
271659
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/catalog.js
271575
271660
  function provider(config3) {
271576
271661
  return config3;
271577
271662
  }
@@ -272932,12 +273017,47 @@ function readTfDir(cwd, dir) {
272932
273017
  }
272933
273018
 
272934
273019
  // src/mcp/terraform/refactorScope.ts
272935
- import { existsSync as existsSync6, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync5 } from "node:fs";
273020
+ import { readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync5 } from "node:fs";
272936
273021
  import { join as join22 } from "node:path";
272937
273022
  function isExamplePath(path15) {
272938
- return /(?:^|\/)examples?\//.test(path15.replace(/\\/g, "/"));
273023
+ return /(?:^|\/)examples\/|^example\//i.test(path15.replace(/\\/g, "/"));
273024
+ }
273025
+ var PROVIDER_OR_BACKEND = new RegExp(
273026
+ String.raw`(?:^|\n)\s*(?:provider|backend)\s+${BLOCK_LABEL}\s*\{`
273027
+ );
273028
+ var CLOUD_BLOCK = /(?:^|\n)\s*cloud\s*\{/;
273029
+ function configuresDeployment(name, text) {
273030
+ if (name.endsWith(".tf.json")) {
273031
+ let doc;
273032
+ try {
273033
+ doc = JSON.parse(text);
273034
+ } catch {
273035
+ return false;
273036
+ }
273037
+ if (typeof doc !== "object" || doc === null) return false;
273038
+ const { provider: provider2, terraform } = doc;
273039
+ if (provider2 !== void 0) return true;
273040
+ return [terraform].flat().some((t) => typeof t === "object" && t !== null && ("backend" in t || "cloud" in t));
273041
+ }
273042
+ if (PROVIDER_OR_BACKEND.test(text)) return true;
273043
+ return parseBlocks(text, ["terraform"]).some((b) => CLOUD_BLOCK.test(b.body));
273044
+ }
273045
+ var isTerraformFile2 = (name) => name.endsWith(".tf") || name.endsWith(".tf.json");
273046
+ function deploysFrom(dir) {
273047
+ let names;
273048
+ try {
273049
+ names = readdirSync6(dir).filter(isTerraformFile2);
273050
+ } catch {
273051
+ return false;
273052
+ }
273053
+ return names.some((name) => {
273054
+ try {
273055
+ return configuresDeployment(name, readFileSync12(join22(dir, name), "utf8"));
273056
+ } catch {
273057
+ return false;
273058
+ }
273059
+ });
272939
273060
  }
272940
- var PROVIDER_OR_BACKEND = /^\s*(?:provider\s+"|backend\s+")/m;
272941
273061
  function holdsTerraform(dir, depth) {
272942
273062
  let entries;
272943
273063
  try {
@@ -272955,26 +273075,35 @@ function holdsTerraform(dir, depth) {
272955
273075
  return false;
272956
273076
  }
272957
273077
  function libraryRepoReason(cwd) {
272958
- let rootFiles;
273078
+ let rootEntries;
272959
273079
  try {
272960
- rootFiles = readdirSync6(cwd).filter((f) => f.endsWith(".tf"));
273080
+ rootEntries = readdirSync6(cwd);
272961
273081
  } catch {
272962
273082
  return null;
272963
273083
  }
272964
- if (rootFiles.length === 0) return null;
272965
- for (const f of rootFiles) {
272966
- let text;
272967
- try {
272968
- text = readFileSync12(join22(cwd, f), "utf8");
272969
- } catch {
272970
- continue;
272971
- }
272972
- if (PROVIDER_OR_BACKEND.test(text)) return null;
272973
- }
272974
- const examples = ["examples", "example"].find((d) => existsSync6(join22(cwd, d)));
273084
+ if (!rootEntries.some(isTerraformFile2) || deploysFrom(cwd)) return null;
273085
+ const examples = rootEntries.find((d) => /^examples?$/i.test(d));
272975
273086
  if (examples === void 0 || !holdsTerraform(join22(cwd, examples), 2)) return null;
272976
273087
  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`;
272977
273088
  }
273089
+ function libraryScope(cwd) {
273090
+ const reason = libraryRepoReason(cwd);
273091
+ if (reason === null) return () => null;
273092
+ const deploys = /* @__PURE__ */ new Map();
273093
+ return (file2) => {
273094
+ const dirs = file2.replace(/\\/g, "/").split("/").slice(0, -1);
273095
+ for (let i = dirs.length; i > 0; i--) {
273096
+ const dir = dirs.slice(0, i).join("/");
273097
+ let d = deploys.get(dir);
273098
+ if (d === void 0) {
273099
+ d = deploysFrom(join22(cwd, dir));
273100
+ deploys.set(dir, d);
273101
+ }
273102
+ if (d) return null;
273103
+ }
273104
+ return reason;
273105
+ };
273106
+ }
272978
273107
  function reportOnly(reason) {
272979
273108
  return reason === null ? {} : { report_only: true, report_only_reason: reason };
272980
273109
  }
@@ -273332,12 +273461,12 @@ function ModuleExtractionCandidatesTool(ctx) {
273332
273461
  parameters: ModuleExtractionCandidatesParams,
273333
273462
  execute: execute(async () => {
273334
273463
  const cwd = ctx.payload.cwd ?? process.cwd();
273464
+ const library = libraryScope(cwd);
273335
273465
  const candidates = findExtractionCandidates(
273336
273466
  cwd,
273337
273467
  ctx.payload.moduleCatalogue,
273338
273468
  ctx.toolState.orgModuleHcl
273339
- ).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file));
273340
- const library = libraryRepoReason(cwd);
273469
+ ).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file)).map((c) => ({ ...reportOnly(library(c.cluster.file)), ...c }));
273341
273470
  const matched = candidates.filter((c) => c.candidates.length > 0);
273342
273471
  const reasons = {};
273343
273472
  for (const c of candidates)
@@ -273351,7 +273480,6 @@ function ModuleExtractionCandidatesTool(ctx) {
273351
273480
  `\xBB module_extraction_candidates: ${candidates.length} cluster(s), ${matched.length} with a module match` + (partialInterfaces.length > 0 ? `, ${partialInterfaces.length} partial-interface block(s)` : "")
273352
273481
  );
273353
273482
  return toolOk({
273354
- ...reportOnly(library),
273355
273483
  cluster_count: candidates.length,
273356
273484
  matched_count: matched.length,
273357
273485
  candidates,
@@ -273375,7 +273503,7 @@ function ModuleExtractionCandidatesTool(ctx) {
273375
273503
  }
273376
273504
 
273377
273505
  // src/mcp/moduleTests.ts
273378
- import { existsSync as existsSync7, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
273506
+ import { existsSync as existsSync6, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
273379
273507
  import { join as join24 } from "node:path";
273380
273508
  var MODULE_META_ARGS = /* @__PURE__ */ new Set([
273381
273509
  "source",
@@ -273496,7 +273624,7 @@ function resolveSourceDir(fromDir, source) {
273496
273624
  }
273497
273625
  function isDir(p) {
273498
273626
  try {
273499
- return existsSync7(p) && statSync6(p).isDirectory();
273627
+ return existsSync6(p) && statSync6(p).isDirectory();
273500
273628
  } catch {
273501
273629
  return false;
273502
273630
  }
@@ -273630,7 +273758,7 @@ function TerraformModuleTestsTool(ctx) {
273630
273758
  }
273631
273759
 
273632
273760
  // src/mcp/terraform/tools/advise.ts
273633
- import { existsSync as existsSync8, readFileSync as readFileSync16 } from "node:fs";
273761
+ import { existsSync as existsSync7, readFileSync as readFileSync16 } from "node:fs";
273634
273762
  import { join as join26 } from "node:path";
273635
273763
 
273636
273764
  // src/mcp/terraform/fixMemory.ts
@@ -273789,7 +273917,7 @@ function inlineWaivers(content, file2) {
273789
273917
  }
273790
273918
  function readLearnings(tmpdir6) {
273791
273919
  const path15 = join26(tmpdir6, LEARNINGS_FILE);
273792
- if (!existsSync8(path15)) return [];
273920
+ if (!existsSync7(path15)) return [];
273793
273921
  try {
273794
273922
  return readFileSync16(path15, "utf-8").split("\n");
273795
273923
  } catch {
@@ -275148,8 +275276,11 @@ function ConsolidationCandidatesTool(ctx) {
275148
275276
  }
275149
275277
 
275150
275278
  // src/mcp/terraform/tools/detectPlanDrift.ts
275151
- import { readFileSync as readFileSync17 } from "node:fs";
275152
- import { join as join27 } from "node:path";
275279
+ import { readFileSync as readFileSync18 } from "node:fs";
275280
+ import { join as join28 } from "node:path";
275281
+
275282
+ // src/mcp/staleFix.ts
275283
+ import { createHash as createHash6 } from "node:crypto";
275153
275284
 
275154
275285
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/progressComment.js
275155
275286
  function parseProgressComment(raw2) {
@@ -276206,7 +276337,31 @@ function groupIdFromBranch(branch) {
276206
276337
  }
276207
276338
  function remediationBranchSuffix(base, defaultBranch) {
276208
276339
  if (!base || !defaultBranch || base === defaultBranch) return "";
276209
- return `--${base.replace(/[^A-Za-z0-9._-]+/g, "-")}`;
276340
+ const slug2 = base.replace(/[^A-Za-z0-9._-]+/g, "-");
276341
+ if (slug2 === base) return `--${slug2}`;
276342
+ return `--${slug2}-${createHash6("sha1").update(base).digest("hex").slice(0, 8)}`;
276343
+ }
276344
+ function remediationBranchFor(id, base, defaultBranch, open3) {
276345
+ const computed = `remediate/${id}${remediationBranchSuffix(base, defaultBranch)}`;
276346
+ 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));
276347
+ return heads.includes(computed) ? computed : heads[0] ?? computed;
276348
+ }
276349
+ async function listOpenRemediationPrs(ctx) {
276350
+ const { octokit, repo } = ctx;
276351
+ if (ctx.toolState?.selectedMode !== "Remediate") return [];
276352
+ if (!octokit || !repo?.owner || !repo.name) return [];
276353
+ try {
276354
+ const open3 = await octokit.paginate(octokit.rest.pulls.list, {
276355
+ owner: repo.owner,
276356
+ repo: repo.name,
276357
+ state: "open",
276358
+ per_page: 100
276359
+ });
276360
+ 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 }));
276361
+ } catch (e) {
276362
+ log2.warning(`\xBB could not list open remediation PRs: ${errorMessage(e)}`);
276363
+ return [];
276364
+ }
276210
276365
  }
276211
276366
  function matchesBotIdentity(identity) {
276212
276367
  const normalized = identity.replace(/\[bot\]$/i, "").toLowerCase();
@@ -276897,6 +277052,68 @@ function dedupeCrossScanner(concerns) {
276897
277052
  return [...merged, ...passthrough];
276898
277053
  }
276899
277054
 
277055
+ // src/mcp/terraform/verifySites.ts
277056
+ import { readFileSync as readFileSync17 } from "node:fs";
277057
+ import { join as join27 } from "node:path";
277058
+ function fileReader(cwd) {
277059
+ const cache3 = /* @__PURE__ */ new Map();
277060
+ return (file2) => {
277061
+ if (!cache3.has(file2)) {
277062
+ let text;
277063
+ try {
277064
+ text = readFileSync17(join27(cwd, file2), "utf8");
277065
+ } catch {
277066
+ text = null;
277067
+ }
277068
+ cache3.set(file2, text);
277069
+ }
277070
+ return cache3.get(file2) ?? null;
277071
+ };
277072
+ }
277073
+ function siteOf(c, read3) {
277074
+ const text = read3(c.location.file);
277075
+ return text === null ? null : resourceAddressAt(text, c.location.line);
277076
+ }
277077
+ function recordConcernSites(session, concerns, cwd, linesMatchTree) {
277078
+ const read3 = fileReader(cwd);
277079
+ session.siteById ??= /* @__PURE__ */ new Map();
277080
+ session.firstSitesByKey ??= /* @__PURE__ */ new Map();
277081
+ const first = session.firstSitesByKey;
277082
+ const fresh = /* @__PURE__ */ new Map();
277083
+ for (const c of concerns) {
277084
+ const site = linesMatchTree ? siteOf(c, read3) : null;
277085
+ const prior = session.siteById.get(c.id);
277086
+ session.siteById.set(c.id, prior === void 0 || prior === site ? site : null);
277087
+ if (!linesMatchTree) continue;
277088
+ const key = concernKeyOf(c);
277089
+ if (first.has(key)) continue;
277090
+ const sites = fresh.get(key) ?? /* @__PURE__ */ new Set();
277091
+ sites.add(site);
277092
+ fresh.set(key, sites);
277093
+ }
277094
+ for (const [key, sites] of fresh) first.set(key, sites);
277095
+ }
277096
+ function resolvedAtSite(params) {
277097
+ const firingByKey = /* @__PURE__ */ new Map();
277098
+ for (const c of params.current) {
277099
+ const list = firingByKey.get(c.key) ?? [];
277100
+ list.push(c.site);
277101
+ firingByKey.set(c.key, list);
277102
+ }
277103
+ const out = [];
277104
+ for (const r of params.requested) {
277105
+ if (r.site === null || r.file === void 0) continue;
277106
+ const firing = firingByKey.get(r.key) ?? [];
277107
+ const known = params.firstSites.get(r.key);
277108
+ if (known === void 0) continue;
277109
+ if (firing.some((s) => s === null || !known.has(s))) continue;
277110
+ if (firing.includes(r.site)) continue;
277111
+ if (!params.declared(r.file).has(r.site)) continue;
277112
+ out.push(r.id);
277113
+ }
277114
+ return out;
277115
+ }
277116
+
276900
277117
  // src/mcp/terraform/concernResult.ts
276901
277118
  function annotateConcerns(rawConcerns, opts) {
276902
277119
  const inScope = opts.inScope ?? (() => true);
@@ -276923,6 +277140,7 @@ function buildConcernResult(ctx, rawConcerns, opts) {
276923
277140
  fullBaseline.map((c) => concernKeyOf(c))
276924
277141
  );
276925
277142
  recordConcernKeys(session, fullBaseline);
277143
+ recordConcernSites(session, fullBaseline, opts.cwd, opts.linesMatchTree ?? false);
276926
277144
  session.baselineFmtDriftFiles = union3(
276927
277145
  session.baselineFmtDriftFiles,
276928
277146
  fullBaseline.filter((c) => c.source === "terraform-fmt").map((c) => c.location.file)
@@ -276937,13 +277155,13 @@ function buildConcernResult(ctx, rawConcerns, opts) {
276937
277155
  active,
276938
277156
  opts.autonomyThreshold
276939
277157
  );
276940
- const suffix2 = remediationBranchSuffix(
276941
- remediationBaseOf(ctx),
276942
- ctx.repo?.data.default_branch?.trim() || void 0
276943
- );
276944
- const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: `remediate/${g.id}${suffix2}` }));
277158
+ const base = remediationBaseOf(ctx);
277159
+ const defaultBranch = ctx.repo?.data.default_branch?.trim() || void 0;
277160
+ const branchFor = (id) => remediationBranchFor(id, base, defaultBranch, opts.openRemediationPrs);
277161
+ const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: branchFor(g.id) }));
276945
277162
  const planned = planBatches(groups2);
276946
- const batchPlan = planned.batch_branch ? { ...planned, batch_branch: `${planned.batch_branch}${suffix2}` } : planned;
277163
+ const batchId = planned.batch_branch ? groupIdFromBranch(planned.batch_branch) : null;
277164
+ const batchPlan = batchId ? { ...planned, batch_branch: branchFor(batchId) } : planned;
276947
277165
  const bySeverity = {};
276948
277166
  for (const c of active) bySeverity[c.severity] = (bySeverity[c.severity] ?? 0) + 1;
276949
277167
  return {
@@ -277373,7 +277591,7 @@ function readTfFiles(cwd) {
277373
277591
  const out = [];
277374
277592
  for (const file2 of walkTfFiles(cwd)) {
277375
277593
  try {
277376
- out.push({ file: file2, hcl: readFileSync17(join27(cwd, file2), "utf8") });
277594
+ out.push({ file: file2, hcl: readFileSync18(join28(cwd, file2), "utf8") });
277377
277595
  } catch {
277378
277596
  }
277379
277597
  }
@@ -277390,7 +277608,7 @@ function DetectPlanDriftTool(ctx) {
277390
277608
  const planPath = path15 || !envPlanPath ? resolveWithinCwd(cwd, path15 ?? "plan.json") : envPlanPath;
277391
277609
  let raw2;
277392
277610
  try {
277393
- raw2 = readFileSync17(planPath, "utf8");
277611
+ raw2 = readFileSync18(planPath, "utf8");
277394
277612
  } catch {
277395
277613
  return skipResult(
277396
277614
  "plan_artifact_not_found",
@@ -277460,7 +277678,8 @@ function DetectPlanDriftTool(ctx) {
277460
277678
  autonomyThreshold,
277461
277679
  // drift is a narrow concern source — join any prior scan baseline so a
277462
277680
  // later ✗→✓ verify doesn't read the whole workspace as regressions.
277463
- baselineMode: "merge"
277681
+ baselineMode: "merge",
277682
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
277464
277683
  });
277465
277684
  log2.info(
277466
277685
  `\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)` : "")
@@ -277522,22 +277741,22 @@ function DetectPlanDriftTool(ctx) {
277522
277741
  }
277523
277742
 
277524
277743
  // src/mcp/terraform/tools/emitCost.ts
277525
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "node:fs";
277526
- import { join as join29 } from "node:path";
277744
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "node:fs";
277745
+ import { join as join30 } from "node:path";
277527
277746
 
277528
277747
  // src/mcp/guardrails.ts
277529
277748
  import { spawnSync as spawnSync5 } from "node:child_process";
277530
- import { readFileSync as readFileSync18, rmSync as rmSync2 } from "node:fs";
277531
- import { join as join28 } from "node:path";
277749
+ import { readFileSync as readFileSync19, rmSync as rmSync2 } from "node:fs";
277750
+ import { join as join29 } from "node:path";
277532
277751
 
277533
277752
  // src/mcp/terraform/emittedArtefacts.ts
277534
- import { createHash as createHash6 } from "node:crypto";
277753
+ import { createHash as createHash7 } from "node:crypto";
277535
277754
  import { writeFileSync as writeFileSync3 } from "node:fs";
277536
277755
  import { resolve as resolve5 } from "node:path";
277537
277756
  var ARTEFACT_DIR = "compliance/";
277538
277757
  function gitBlobId(bytes) {
277539
277758
  const content = Buffer.from(bytes, "utf8");
277540
- return createHash6("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
277759
+ return createHash7("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
277541
277760
  }
277542
277761
  function writeEmittedArtefact(toolState, path15, bytes) {
277543
277762
  writeFileSync3(path15, bytes);
@@ -277830,7 +278049,7 @@ function parseGitleaksReport(json5) {
277830
278049
  return hits;
277831
278050
  }
277832
278051
  function scanWithGitleaks(ctx, base) {
277833
- const reportPath = join28(ctx.tmpdir, `gitleaks-report-${process.pid}.json`);
278052
+ const reportPath = join29(ctx.tmpdir, `gitleaks-report-${process.pid}.json`);
277834
278053
  const cwd = ctx.payload.cwd ?? process.cwd();
277835
278054
  const result = spawnSync5(
277836
278055
  "gitleaks",
@@ -277864,7 +278083,7 @@ function scanWithGitleaks(ctx, base) {
277864
278083
  return null;
277865
278084
  }
277866
278085
  try {
277867
- const report2 = readFileSync18(reportPath, "utf8");
278086
+ const report2 = readFileSync19(reportPath, "utf8");
277868
278087
  return parseGitleaksReport(report2);
277869
278088
  } catch {
277870
278089
  return [];
@@ -277936,7 +278155,7 @@ function assertNoForeignDialectIntroduced(ctx) {
277936
278155
  const file2 = rel.replace(/\\/g, "/");
277937
278156
  if (changedSet.has(file2)) continue;
277938
278157
  try {
277939
- established.push(...findDialectMarkers(readFileSync18(join28(cwd, rel), "utf8"), file2));
278158
+ established.push(...findDialectMarkers(readFileSync19(join29(cwd, rel), "utf8"), file2));
277940
278159
  } catch {
277941
278160
  }
277942
278161
  }
@@ -278310,7 +278529,7 @@ var OpenPrOverlapError = class extends Error {
278310
278529
  // src/mcp/terraform/awsPrices.ts
278311
278530
  var AWS_PRICE_REGION = "eu-west-2";
278312
278531
  var AWS_PRICE_CURRENCY = "USD";
278313
- var AWS_PRICE_FETCHED_AT = "2026-09-23";
278532
+ var AWS_PRICE_FETCHED_AT = "2026-10-03";
278314
278533
  var AWS_HOURLY_PRICES = {
278315
278534
  "eks-cluster-standard": 0.1,
278316
278535
  "elb-application": 0.02646,
@@ -278328,30 +278547,30 @@ var AWS_PER_GB_PRICES = {
278328
278547
  // src/mcp/terraform/azurePrices.ts
278329
278548
  var AZURE_PRICE_REGION = "westeurope";
278330
278549
  var AZURE_PRICE_CURRENCY = "EUR";
278331
- var AZURE_PRICE_FETCHED_AT = "2026-09-03";
278550
+ var AZURE_PRICE_FETCHED_AT = "2026-10-03";
278332
278551
  var AZURE_HOURLY_PRICES = {
278333
- "app-service-p0v3": 0.0764,
278334
- "app-service-p1v3": 0.1528,
278335
- "app-service-p2v3": 0.3057,
278336
- "app-service-p3v3": 0.6114,
278337
- "bastion-basic": 0.1631,
278338
- "bastion-premium": 0.3864,
278339
- "bastion-standard": 0.249,
278340
- "firewall-basic": 0.3392,
278341
- "firewall-premium": 1.5027,
278342
- "firewall-standard": 1.0733,
278343
- "redis-c0": 0.0236,
278344
- "redis-c1": 0.0592,
278345
- "redis-c2": 0.0962,
278346
- "redis-c3": 0.1932,
278347
- "redis-p1": 0.2378,
278348
- "redis-p2": 0.4766,
278349
- "redis-p3": 0.9523,
278350
- "vpn-gateway-vpngw1": 0.1631,
278351
- "vpn-gateway-vpngw2": 0.4207,
278352
- "vpn-gateway-vpngw3": 1.0733,
278353
- "vpn-gateway-vpngw4": 1.8032,
278354
- "vpn-gateway-vpngw5": 3.1341
278552
+ "app-service-p0v3": 0.0783,
278553
+ "app-service-p1v3": 0.1566,
278554
+ "app-service-p2v3": 0.3133,
278555
+ "app-service-p3v3": 0.6265,
278556
+ "bastion-basic": 0.1672,
278557
+ "bastion-premium": 0.396,
278558
+ "bastion-standard": 0.2552,
278559
+ "firewall-basic": 0.3476,
278560
+ "firewall-premium": 1.54,
278561
+ "firewall-standard": 1.1,
278562
+ "redis-c0": 0.0242,
278563
+ "redis-c1": 0.0607,
278564
+ "redis-c2": 0.0986,
278565
+ "redis-c3": 0.198,
278566
+ "redis-p1": 0.2438,
278567
+ "redis-p2": 0.4884,
278568
+ "redis-p3": 0.9759,
278569
+ "vpn-gateway-vpngw1": 0.1672,
278570
+ "vpn-gateway-vpngw2": 0.4312,
278571
+ "vpn-gateway-vpngw3": 1.1,
278572
+ "vpn-gateway-vpngw4": 1.8479,
278573
+ "vpn-gateway-vpngw5": 3.2119
278355
278574
  };
278356
278575
 
278357
278576
  // src/mcp/terraform/idleFloor.ts
@@ -278629,7 +278848,7 @@ function readTfFiles2(cwd) {
278629
278848
  const out = [];
278630
278849
  for (const file2 of walkTfFiles(cwd)) {
278631
278850
  try {
278632
- out.push({ file: file2, hcl: readFileSync19(join29(cwd, file2), "utf8") });
278851
+ out.push({ file: file2, hcl: readFileSync20(join30(cwd, file2), "utf8") });
278633
278852
  } catch {
278634
278853
  }
278635
278854
  }
@@ -278734,7 +278953,7 @@ function buildSuppressionSummary(waivers) {
278734
278953
  }
278735
278954
 
278736
278955
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/signing.js
278737
- import { createHash as createHash7, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
278956
+ import { createHash as createHash8, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
278738
278957
  var SIGNATURE_ALGORITHM = "Ed25519";
278739
278958
  var DEFAULT_KEY_LABEL = "the signing key";
278740
278959
  function loadSigningKey(raw2, options) {
@@ -278761,7 +278980,7 @@ function loadSigningKey(raw2, options) {
278761
278980
  return {
278762
278981
  privateKey,
278763
278982
  publicKeyPem,
278764
- fingerprint: createHash7("sha256").update(spkiDer).digest("hex")
278983
+ fingerprint: createHash8("sha256").update(spkiDer).digest("hex")
278765
278984
  };
278766
278985
  }
278767
278986
  function signBytes(bytes, key) {
@@ -278782,7 +279001,7 @@ function publicKeyFingerprint(publicKeyPem) {
278782
279001
  type: "spki",
278783
279002
  format: "der"
278784
279003
  });
278785
- return createHash7("sha256").update(der).digest("hex");
279004
+ return createHash8("sha256").update(der).digest("hex");
278786
279005
  }
278787
279006
  function signatureMetadata(key, sigFile) {
278788
279007
  return {
@@ -279012,7 +279231,7 @@ function buildEvidenceBundle(args2) {
279012
279231
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keyless.js
279013
279232
  var import_bundle = __toESM(require_dist6(), 1);
279014
279233
  var import_sign = __toESM(require_dist9(), 1);
279015
- import { createHash as createHash8 } from "node:crypto";
279234
+ import { createHash as createHash9 } from "node:crypto";
279016
279235
 
279017
279236
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keylessOidc.js
279018
279237
  var SIGSTORE_OIDC_AUDIENCE = "sigstore";
@@ -279041,7 +279260,7 @@ function keylessSigningPossible(env2, envName) {
279041
279260
  }
279042
279261
  function buildInTotoStatement(args2) {
279043
279262
  const data = typeof args2.artifactBytes === "string" ? Buffer.from(args2.artifactBytes, "utf8") : args2.artifactBytes;
279044
- const sha2562 = createHash8("sha256").update(data).digest("hex");
279263
+ const sha2562 = createHash9("sha256").update(data).digest("hex");
279045
279264
  return {
279046
279265
  _type: "https://in-toto.io/Statement/v1",
279047
279266
  subject: [{ name: args2.subjectName, digest: { sha256: sha2562 } }],
@@ -279251,12 +279470,12 @@ import { mkdirSync as mkdirSync4 } from "node:fs";
279251
279470
  import { dirname as dirname6 } from "node:path";
279252
279471
 
279253
279472
  // src/mcp/terraform/oscal.ts
279254
- import { createHash as createHash9 } from "node:crypto";
279473
+ import { createHash as createHash10 } from "node:crypto";
279255
279474
  var OSCAL_VERSION = "1.1.2";
279256
279475
  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.";
279257
279476
  var NS = "https://infraweaver.io/ns/oscal";
279258
279477
  function deterministicUuid(seed) {
279259
- const hex4 = createHash9("sha256").update(seed).digest("hex").slice(0, 32).split("");
279478
+ const hex4 = createHash10("sha256").update(seed).digest("hex").slice(0, 32).split("");
279260
279479
  hex4[12] = "5";
279261
279480
  hex4[16] = (Number.parseInt(hex4[16] ?? "0", 16) & 3 | 8).toString(16);
279262
279481
  const u = hex4.join("");
@@ -279395,20 +279614,20 @@ function TerraformEmitOscalTool(ctx) {
279395
279614
  import { writeFileSync as writeFileSync5 } from "node:fs";
279396
279615
 
279397
279616
  // src/mcp/terraform/lineage.ts
279398
- import { createHash as createHash10 } from "node:crypto";
279399
- import { readFileSync as readFileSync20 } from "node:fs";
279400
- import { join as join30 } from "node:path";
279617
+ import { createHash as createHash11 } from "node:crypto";
279618
+ import { readFileSync as readFileSync21 } from "node:fs";
279619
+ import { join as join31 } from "node:path";
279401
279620
  function repoFileReader(cwd) {
279402
279621
  return (rel) => {
279403
279622
  try {
279404
- return readFileSync20(join30(cwd, rel), "utf-8");
279623
+ return readFileSync21(join31(cwd, rel), "utf-8");
279405
279624
  } catch {
279406
279625
  return null;
279407
279626
  }
279408
279627
  };
279409
279628
  }
279410
279629
  function lineageKeyOf(c, address) {
279411
- return createHash10("sha1").update(`${c.source}|${bareRuleOf(c.source, c.rule_id)}|${c.location.file}|${address ?? ""}`).digest("hex").slice(0, 16);
279630
+ return createHash11("sha1").update(`${c.source}|${bareRuleOf(c.source, c.rule_id)}|${c.location.file}|${address ?? ""}`).digest("hex").slice(0, 16);
279412
279631
  }
279413
279632
  function lineageResolver(read3) {
279414
279633
  const files = /* @__PURE__ */ new Map();
@@ -279954,11 +280173,11 @@ function TerraformEmitVexTool(ctx) {
279954
280173
  // src/mcp/terraform/tools/equivalenceCheck.ts
279955
280174
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
279956
280175
  import { tmpdir as tmpdir2 } from "node:os";
279957
- import { join as join32 } from "node:path";
280176
+ import { join as join33 } from "node:path";
279958
280177
 
279959
280178
  // src/mcp/terraform/initArtefacts.ts
279960
280179
  import { rmSync as rmSync3 } from "node:fs";
279961
- import { join as join31, posix as posix9 } from "node:path";
280180
+ import { join as join32, posix as posix9 } from "node:path";
279962
280181
  var INIT_ARTEFACTS = /* @__PURE__ */ new Set([".terraform", ".terraform.lock.hcl"]);
279963
280182
  function initArtefacts(cwd) {
279964
280183
  const r = run("git", ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], cwd);
@@ -279981,7 +280200,7 @@ function removeNewInitArtefacts(cwd, before, by) {
279981
280200
  const created = [...after?.untracked ?? []].filter((p) => !before.untracked.has(p));
279982
280201
  const changed = [...after?.modifiedLocks ?? []].filter((p) => !before.modifiedLocks.has(p));
279983
280202
  if (!top || created.length + changed.length === 0) return;
279984
- for (const p of created) rmSync3(join31(top, p), { recursive: true, force: true });
280203
+ for (const p of created) rmSync3(join32(top, p), { recursive: true, force: true });
279985
280204
  if (changed.length > 0) run("git", ["checkout", "--", ...changed], top);
279986
280205
  resetTerraformInitCache();
279987
280206
  log2.info(
@@ -280450,16 +280669,32 @@ function uncoveredInstanceKeys(p) {
280450
280669
  if (expected === null) return null;
280451
280670
  const kind = level[0].kind;
280452
280671
  const written = (k) => kind === "count" ? k : `"${k}"`;
280672
+ const fromAt = level[0].at;
280673
+ const toAt = p.after.multiplicity[0]?.at;
280674
+ if (fromAt === void 0 || toAt === void 0) return null;
280453
280675
  const declared2 = /* @__PURE__ */ new Set();
280454
280676
  for (const blk of p.blocks) {
280677
+ if (stripIndices(normaliseAddress(blk.from)) !== p.before.address || stripIndices(normaliseAddress(blk.to)) !== p.after.address)
280678
+ return null;
280455
280679
  const from = instanceKeysOf(blk.from);
280456
280680
  const to = instanceKeysOf(blk.to);
280457
280681
  if (from.length > 1 || to.length > 1) return null;
280458
280682
  if (from.length === 0 && to.length === 0) return null;
280459
- if (from.length === 1 && to.length === 1 && from[0] === to[0]) declared2.add(from[0]);
280683
+ if (from.length !== 1 || to.length !== 1 || from[0] !== to[0]) continue;
280684
+ const fromPlaced = keyAtLevel(blk.from, fromAt);
280685
+ const toPlaced = keyAtLevel(blk.to, toAt);
280686
+ if (fromPlaced === false || toPlaced === false) continue;
280687
+ if (fromPlaced === null || toPlaced === null) return null;
280688
+ declared2.add(from[0]);
280460
280689
  }
280461
280690
  return expected.filter((k) => !declared2.has(written(k)));
280462
280691
  }
280692
+ function keyAtLevel(address, at) {
280693
+ const onResource = instanceKeysOf(stripModuleHops(normaliseAddress(address))).length > 0;
280694
+ if (at === "resource") return onResource;
280695
+ if (onResource) return false;
280696
+ return moduleHops(address).length === 1 ? true : null;
280697
+ }
280463
280698
  function computeEquivalence(before, after, movedBlocks, ambiguous = [], planPairs, opts = {}) {
280464
280699
  const reasons = [];
280465
280700
  const corr = correspond(before, after, movedBlocks);
@@ -281779,12 +282014,12 @@ async function baselineValidateErrors(cwd, base, env2, session) {
281779
282014
  }
281780
282015
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd);
281781
282016
  if (prefix2.status !== 0) return null;
281782
- const tree = mkdtempSync2(join32(tmpdir2(), "iw-baseline-"));
282017
+ const tree = mkdtempSync2(join33(tmpdir2(), "iw-baseline-"));
281783
282018
  try {
281784
282019
  if (run("git", ["worktree", "add", "--detach", "--quiet", tree, base], cwd).status !== 0) {
281785
282020
  return null;
281786
282021
  }
281787
- const outcome2 = await scanValidate(join32(tree, prefix2.stdout.trim()), env2);
282022
+ const outcome2 = await scanValidate(join33(tree, prefix2.stdout.trim()), env2);
281788
282023
  if (!outcome2.ran || outcome2.unvalidated) return null;
281789
282024
  const errors = outcome2.concerns.map(validateErrorKey);
281790
282025
  session.baselineValidateErrors = { base, errors };
@@ -281885,7 +282120,7 @@ function TerraformGenerateMovedTool(ctx) {
281885
282120
 
281886
282121
  // src/mcp/terraform/cost.ts
281887
282122
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync5 } from "node:fs";
281888
- import { join as join33 } from "node:path";
282123
+ import { join as join34 } from "node:path";
281889
282124
  function parseInfracostBreakdown(stdout) {
281890
282125
  const parsed2 = parseScannerJson(InfracostBreakdownOutput, stdout, "infracost");
281891
282126
  const raw2 = parsed2.totalMonthlyCost;
@@ -281967,15 +282202,15 @@ async function infracostBaseline(cwd, key, tmpdir6) {
281967
282202
  if (!baseRef) return null;
281968
282203
  const prefixResult = run("git", ["rev-parse", "--show-prefix"], cwd);
281969
282204
  const prefix2 = prefixResult.status === 0 ? prefixResult.stdout.trim() : "";
281970
- const baseDir = mkdtempSync3(join33(tmpdir6, "infracost-base-"));
281971
- const worktree = join33(baseDir, "wt");
282205
+ const baseDir = mkdtempSync3(join34(tmpdir6, "infracost-base-"));
282206
+ const worktree = join34(baseDir, "wt");
281972
282207
  const add = run("git", ["worktree", "add", "--detach", worktree, baseRef], cwd);
281973
282208
  if (add.status !== 0) {
281974
282209
  rmSync5(baseDir, { recursive: true, force: true });
281975
282210
  return null;
281976
282211
  }
281977
282212
  try {
281978
- const scanCwd = prefix2 ? join33(worktree, prefix2) : worktree;
282213
+ const scanCwd = prefix2 ? join34(worktree, prefix2) : worktree;
281979
282214
  const r = await runInfracostBreakdown(scanCwd, key);
281980
282215
  if (r.missing || r.status !== 0) return null;
281981
282216
  return parseInfracostBreakdown(r.stdout);
@@ -282046,7 +282281,7 @@ function InfracostDiffTool(ctx) {
282046
282281
 
282047
282282
  // src/mcp/terraform/tools/ingestExternalFindings.ts
282048
282283
  import { statSync as statSync7 } from "node:fs";
282049
- import { join as join34 } from "node:path";
282284
+ import { join as join35 } from "node:path";
282050
282285
 
282051
282286
  // src/mcp/terraform/prowlerOcsf.ts
282052
282287
  var DETECTION_FINDING_CREATE = 200401;
@@ -282272,7 +282507,7 @@ var MAX_FEED_BYTES = 100 * 1024 * 1024;
282272
282507
  function readTfFiles3(cwd) {
282273
282508
  const out = [];
282274
282509
  for (const file2 of walkTfFiles(cwd)) {
282275
- const hcl2 = readTextCapped(join34(cwd, file2));
282510
+ const hcl2 = readTextCapped(join35(cwd, file2));
282276
282511
  if (hcl2 !== null) out.push({ file: file2, hcl: hcl2 });
282277
282512
  }
282278
282513
  return out;
@@ -282386,7 +282621,8 @@ function IngestExternalFindingsTool(ctx) {
282386
282621
  // an external feed is a narrow concern source — join any prior scan
282387
282622
  // baseline so a later ✗→✓ verify doesn't read the workspace as regressions.
282388
282623
  baselineMode: "merge",
282389
- seedCaps: seedCapsFrom(input2)
282624
+ seedCaps: seedCapsFrom(input2),
282625
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
282390
282626
  });
282391
282627
  const cappedBlock = cappedSummary(capped);
282392
282628
  log2.info(
@@ -282429,8 +282665,8 @@ function IngestExternalFindingsTool(ctx) {
282429
282665
  }
282430
282666
 
282431
282667
  // src/mcp/terraform/tools/moduleDocs.ts
282432
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync6 } from "node:fs";
282433
- import { join as join35 } from "node:path";
282668
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync6 } from "node:fs";
282669
+ import { join as join36 } from "node:path";
282434
282670
 
282435
282671
  // src/mcp/terraform/moduleDocs.ts
282436
282672
  var VERDICT_CLAIM = {
@@ -282544,7 +282780,7 @@ function TerraformModuleDocsTool(ctx) {
282544
282780
  );
282545
282781
  }
282546
282782
  const absDir = resolveWithinCwd(cwd, module_dir);
282547
- const target = join35(absDir, "README.md");
282783
+ const target = join36(absDir, "README.md");
282548
282784
  let gitHead = null;
282549
282785
  try {
282550
282786
  gitHead = $("git", ["rev-parse", "HEAD"], { cwd, log: false });
@@ -282552,7 +282788,7 @@ function TerraformModuleDocsTool(ctx) {
282552
282788
  }
282553
282789
  let existing = null;
282554
282790
  try {
282555
- existing = readFileSync21(target, "utf8");
282791
+ existing = readFileSync22(target, "utf8");
282556
282792
  } catch {
282557
282793
  }
282558
282794
  const priorHead = readFooterGitHead(existing);
@@ -282856,15 +283092,13 @@ function NormalizationCandidatesTool(ctx) {
282856
283092
  parameters: NormalizationCandidatesParams,
282857
283093
  execute: execute(async () => {
282858
283094
  const cwd = ctx.payload.cwd ?? process.cwd();
282859
- const files = findNormalizationCandidates(cwd).filter(
282860
- (f) => ctx.payload.refactorExamples || !isExamplePath(f.file)
282861
- );
283095
+ const library = libraryScope(cwd);
283096
+ const files = findNormalizationCandidates(cwd).filter((f) => ctx.payload.refactorExamples || !isExamplePath(f.file)).map((f) => ({ ...reportOnly(library(f.file)), ...f }));
282862
283097
  const total = files.reduce((n, f) => n + f.count, 0);
282863
283098
  log2.info(
282864
283099
  `\xBB terraform_normalization_candidates: ${total} site(s) across ${files.length} file(s)`
282865
283100
  );
282866
283101
  return toolOk({
282867
- ...reportOnly(libraryRepoReason(cwd)),
282868
283102
  file_count: files.length,
282869
283103
  total,
282870
283104
  files,
@@ -282876,7 +283110,7 @@ function NormalizationCandidatesTool(ctx) {
282876
283110
 
282877
283111
  // src/mcp/terraform/tools/plan.ts
282878
283112
  import { rmSync as rmSync6 } from "node:fs";
282879
- import { join as join36 } from "node:path";
283113
+ import { join as join37 } from "node:path";
282880
283114
  var PLAN_JSON_ARGS = ["plan", "-input=false", "-no-color", "-lock=false", "-json"];
282881
283115
  var planFileSeq = 0;
282882
283116
  async function capturePlanPairs(absDir, planFile, creds) {
@@ -282899,7 +283133,7 @@ async function planOneRoot(absDir, creds, tmpdir6) {
282899
283133
  skipReason: `terraform init failed: ${init.stderr.trim().slice(0, 200) || "unknown error"}`
282900
283134
  };
282901
283135
  }
282902
- const planFile = tmpdir6 ? join36(tmpdir6, `plan-${process.pid}-${planFileSeq++}.tfplan`) : void 0;
283136
+ const planFile = tmpdir6 ? join37(tmpdir6, `plan-${process.pid}-${planFileSeq++}.tfplan`) : void 0;
282903
283137
  const plan = await runTfAsync(
282904
283138
  planFile ? [...PLAN_JSON_ARGS, `-out=${planFile}`] : PLAN_JSON_ARGS,
282905
283139
  absDir,
@@ -283269,8 +283503,8 @@ function TerraformProviderUpgradeTool(ctx) {
283269
283503
  }
283270
283504
 
283271
283505
  // src/mcp/terraform/tfquery.ts
283272
- import { readFileSync as readFileSync22 } from "node:fs";
283273
- import { join as join37 } from "node:path";
283506
+ import { readFileSync as readFileSync23 } from "node:fs";
283507
+ import { join as join38 } from "node:path";
283274
283508
  var TFQUERY_SUFFIX = ".tfquery.hcl";
283275
283509
  function parseQueryFile(hcl2) {
283276
283510
  return parseBlocks(hcl2, ["list"]).map((block) => {
@@ -283296,7 +283530,7 @@ function collectQueryInventory(cwd) {
283296
283530
  for (const rel of walkFilesBySuffix(cwd, TFQUERY_SUFFIX)) {
283297
283531
  let text;
283298
283532
  try {
283299
- text = readFileSync22(join37(cwd, rel), "utf8");
283533
+ text = readFileSync23(join38(cwd, rel), "utf8");
283300
283534
  } catch {
283301
283535
  continue;
283302
283536
  }
@@ -283331,7 +283565,7 @@ function TerraformQueryInventoryTool(ctx) {
283331
283565
  }
283332
283566
 
283333
283567
  // src/mcp/terraform/tools/readFindings.ts
283334
- import { readFileSync as readFileSync23 } from "node:fs";
283568
+ import { readFileSync as readFileSync24 } from "node:fs";
283335
283569
  var ReadFindingsParams = type({
283336
283570
  "path?": type.string.describe(
283337
283571
  "path to the Assessor's findings.json. Defaults to $INFRAWEAVER_FINDINGS_PATH, then ./findings.json in the workspace."
@@ -283356,7 +283590,7 @@ function ReadFindingsTool(ctx) {
283356
283590
  const findingsPath = path15 || !envFindingsPath ? resolveWithinCwd(cwd, path15 ?? "findings.json") : envFindingsPath;
283357
283591
  let raw2;
283358
283592
  try {
283359
- raw2 = readFileSync23(findingsPath, "utf8");
283593
+ raw2 = readFileSync24(findingsPath, "utf8");
283360
283594
  } catch {
283361
283595
  return skipResult(
283362
283596
  "findings_not_found",
@@ -283399,7 +283633,8 @@ function ReadFindingsTool(ctx) {
283399
283633
  minRank,
283400
283634
  grouping,
283401
283635
  autonomyThreshold,
283402
- seedCaps: seedCapsFrom(input2)
283636
+ seedCaps: seedCapsFrom(input2),
283637
+ openRemediationPrs: await listOpenRemediationPrs(ctx)
283403
283638
  });
283404
283639
  const cappedBlock = cappedSummary(capped);
283405
283640
  log2.info(
@@ -283578,23 +283813,54 @@ function computeScanDelta(input2) {
283578
283813
  };
283579
283814
  }
283580
283815
  function markPossibleRenames(introduced, resolved, headAddresses) {
283581
- const vanished = resolved.filter((r) => {
283582
- if (r.address === null) return false;
283583
- return !headAddresses(dirOf3(r.concern.location.file)).has(r.address);
283584
- });
283816
+ const byResource = (findings) => {
283817
+ const out = /* @__PURE__ */ new Map();
283818
+ for (const f of [...findings].sort((a, b) => byLocation(a.concern, b.concern))) {
283819
+ if (f.address === null) continue;
283820
+ const dir = dirOf3(f.concern.location.file);
283821
+ const key = `${dir}|${f.address}`;
283822
+ const entry4 = out.get(key);
283823
+ if (entry4) entry4.findings.push(f);
283824
+ else out.set(key, { dir, address: f.address, findings: [f] });
283825
+ }
283826
+ return [...out.values()];
283827
+ };
283828
+ const vanished = byResource(resolved).filter((r) => !headAddresses(r.dir).has(r.address));
283829
+ const appeared = byResource(introduced);
283830
+ const shared = (from, to) => {
283831
+ const left = /* @__PURE__ */ new Map();
283832
+ for (const f of from.findings)
283833
+ left.set(ruleKey(f.concern), (left.get(ruleKey(f.concern)) ?? 0) + 1);
283834
+ return to.findings.filter((f) => {
283835
+ const n = left.get(ruleKey(f.concern)) ?? 0;
283836
+ if (n === 0) return false;
283837
+ left.set(ruleKey(f.concern), n - 1);
283838
+ return true;
283839
+ });
283840
+ };
283841
+ const order2 = (a, b) => a.dir.localeCompare(b.dir) || a.address.localeCompare(b.address);
283842
+ const candidates = vanished.flatMap(
283843
+ (from) => appeared.filter(
283844
+ (to) => (
283845
+ // a `moved` block cannot change a resource's type
283846
+ typeOf(from.address) === typeOf(to.address) && // renamed in place, or moved elsewhere under its own name
283847
+ from.dir === to.dir !== (from.address === to.address)
283848
+ )
283849
+ ).map((to) => ({ from, to, findings: shared(from, to) })).filter((p) => p.findings.length > 0)
283850
+ );
283851
+ candidates.sort(
283852
+ (a, b) => b.findings.length - a.findings.length || order2(a.from, b.from) || order2(a.to, b.to)
283853
+ );
283585
283854
  const used = /* @__PURE__ */ new Set();
283586
- for (const finding of introduced) {
283587
- if (finding.address === null) continue;
283588
- const dir = dirOf3(finding.concern.location.file);
283589
- const match5 = vanished.find(
283590
- (r) => !used.has(r) && ruleKey(r.concern) === ruleKey(finding.concern) && // a `moved` block cannot change a resource's type
283591
- typeOf(r.address) === typeOf(finding.address) && // renamed in place, or moved elsewhere under its own name
283592
- dirOf3(r.concern.location.file) === dir !== (r.address === finding.address)
283593
- );
283594
- if (!match5) continue;
283595
- used.add(match5);
283596
- finding.possiblyPreexisting = true;
283597
- finding.renamedFrom = { address: match5.address, file: match5.concern.location.file };
283855
+ for (const { from, to, findings } of candidates) {
283856
+ if (used.has(from) || used.has(to)) continue;
283857
+ used.add(from);
283858
+ used.add(to);
283859
+ const file2 = from.findings[0]?.concern.location.file;
283860
+ for (const finding of findings) {
283861
+ finding.possiblyPreexisting = true;
283862
+ finding.renamedFrom = { address: from.address, file: file2 };
283863
+ }
283598
283864
  }
283599
283865
  }
283600
283866
  function typeOf(address) {
@@ -283637,7 +283903,8 @@ function deltaVerdict(delta, requiredControls) {
283637
283903
  return {
283638
283904
  pass: gate.pass && required3.length === 0,
283639
283905
  gating: gate.gating.length,
283640
- required: required3.length
283906
+ required: required3.length,
283907
+ blocking: (/* @__PURE__ */ new Set([...gate.gating, ...required3])).size
283641
283908
  };
283642
283909
  }
283643
283910
  function failsGate(finding, requiredControls) {
@@ -283755,8 +284022,8 @@ function describe3(f) {
283755
284022
  }
283756
284023
 
283757
284024
  // src/mcp/terraform/tools/scan.ts
283758
- import { readFileSync as readFileSync24 } from "node:fs";
283759
- import { join as join38 } from "node:path";
284025
+ import { readFileSync as readFileSync25 } from "node:fs";
284026
+ import { join as join39 } from "node:path";
283760
284027
 
283761
284028
  // src/mcp/terraform/noiseProfile.ts
283762
284029
  var MIN_DISMISSAL_REPOS = 3;
@@ -283930,7 +284197,9 @@ async function runScan(ctx, {
283930
284197
  grouping,
283931
284198
  autonomyThreshold,
283932
284199
  inScope,
283933
- priority
284200
+ priority,
284201
+ openRemediationPrs: await listOpenRemediationPrs(ctx),
284202
+ linesMatchTree: true
283934
284203
  }
283935
284204
  );
283936
284205
  const { active, suppressed, groups: groups2, batchPlan, bySeverity: by_severity } = result;
@@ -283942,7 +284211,7 @@ async function runScan(ctx, {
283942
284211
  outcomes.flatMap((o) => o.concerns),
283943
284212
  (rel) => {
283944
284213
  try {
283945
- return readFileSync24(join38(cwd, rel), "utf-8");
284214
+ return readFileSync25(join39(cwd, rel), "utf-8");
283946
284215
  } catch {
283947
284216
  return null;
283948
284217
  }
@@ -284459,9 +284728,9 @@ function TerraformStandardsReportTool(ctx) {
284459
284728
  }
284460
284729
 
284461
284730
  // src/mcp/terraform/tools/tag.ts
284462
- import { createHash as createHash11 } from "node:crypto";
284463
- import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync25, realpathSync as realpathSync2, rmSync as rmSync7 } from "node:fs";
284464
- import { isAbsolute as isAbsolute3, join as join39, relative as relative5, resolve as resolve6, sep as sep2 } from "node:path";
284731
+ import { createHash as createHash12 } from "node:crypto";
284732
+ import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync26, realpathSync as realpathSync2, rmSync as rmSync7 } from "node:fs";
284733
+ import { isAbsolute as isAbsolute3, join as join40, relative as relative5, resolve as resolve6, sep as sep2 } from "node:path";
284465
284734
  var TerraformTagParams = type({
284466
284735
  tags: type({ "[string]": "string" }).describe(
284467
284736
  'the tag set to apply, key \u2192 value (e.g. {"Owner":"platform","CostCentre":"1234"}); merged into every taggable resource\'s `tags`'
@@ -284482,24 +284751,24 @@ var TerraformTagParams = type({
284482
284751
  var realRunners3 = { run };
284483
284752
  function sha(path15) {
284484
284753
  try {
284485
- return createHash11("sha256").update(readFileSync25(path15)).digest("hex");
284754
+ return createHash12("sha256").update(readFileSync26(path15)).digest("hex");
284486
284755
  } catch {
284487
284756
  return null;
284488
284757
  }
284489
284758
  }
284490
284759
  function snapshot2(cwd, files) {
284491
- return new Map(files.map((f) => [f, sha(join39(cwd, f))]));
284760
+ return new Map(files.map((f) => [f, sha(join40(cwd, f))]));
284492
284761
  }
284493
284762
  function toPosix(p) {
284494
284763
  return p.split(sep2).join("/");
284495
284764
  }
284496
284765
  function terratagFiles(dir) {
284497
284766
  const dirs = [dir];
284498
- const manifest = join39(dir, ".terraform", "modules", "modules.json");
284499
- if (existsSync9(manifest)) {
284767
+ const manifest = join40(dir, ".terraform", "modules", "modules.json");
284768
+ if (existsSync8(manifest)) {
284500
284769
  let modules;
284501
284770
  try {
284502
- modules = JSON.parse(readFileSync25(manifest, "utf8")).Modules;
284771
+ modules = JSON.parse(readFileSync26(manifest, "utf8")).Modules;
284503
284772
  } catch {
284504
284773
  throw new Error(`terraform_tag refused: cannot read ${manifest} (re-run terraform init)`);
284505
284774
  }
@@ -284507,7 +284776,7 @@ function terratagFiles(dir) {
284507
284776
  const moduleDir = m?.Dir;
284508
284777
  if (typeof moduleDir !== "string") continue;
284509
284778
  try {
284510
- dirs.push(realpathSync2(join39(dir, moduleDir)));
284779
+ dirs.push(realpathSync2(join40(dir, moduleDir)));
284511
284780
  } catch {
284512
284781
  }
284513
284782
  }
@@ -284515,7 +284784,7 @@ function terratagFiles(dir) {
284515
284784
  const files = /* @__PURE__ */ new Set();
284516
284785
  for (const d of dirs) {
284517
284786
  for (const name of readdirSync8(d)) {
284518
- if (name.endsWith(".tf")) files.add(realpathSync2(join39(d, name)));
284787
+ if (name.endsWith(".tf")) files.add(realpathSync2(join40(d, name)));
284519
284788
  }
284520
284789
  }
284521
284790
  return [...files];
@@ -284539,12 +284808,12 @@ function runTag(ctx, params, runners = realRunners3) {
284539
284808
  return rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute3(rel);
284540
284809
  };
284541
284810
  const requested = resolve6(cwd, params.dir ?? ".");
284542
- const target = existsSync9(requested) ? realpathSync2(requested) : requested;
284811
+ const target = existsSync8(requested) ? realpathSync2(requested) : requested;
284543
284812
  if (!inside(target)) {
284544
284813
  throw new Error(`terraform_tag: \`dir\` must stay inside the workspace (got ${params.dir})`);
284545
284814
  }
284546
284815
  const relDir = toPosix(relative5(cwd, target)) || ".";
284547
- if (!existsSync9(target)) {
284816
+ if (!existsSync8(target)) {
284548
284817
  return toolSkip("no_terraform_files", `no .tf files under ${relDir}`);
284549
284818
  }
284550
284819
  const absFiles = terratagFiles(target);
@@ -284582,7 +284851,7 @@ function runTag(ctx, params, runners = realRunners3) {
284582
284851
  if (params.filter) args2.push(`-filter=${params.filter}`);
284583
284852
  if (params.skip) args2.push(`-skip=${params.skip}`);
284584
284853
  const before = snapshot2(cwd, files);
284585
- const priorBackups = new Set(files.filter((f) => existsSync9(join39(cwd, `${f}.bak`))));
284854
+ const priorBackups = new Set(files.filter((f) => existsSync8(join40(cwd, `${f}.bak`))));
284586
284855
  const r = runners.run("terratag", args2, cwd);
284587
284856
  if (r.missing) {
284588
284857
  return toolSkip(
@@ -284596,7 +284865,7 @@ function runTag(ctx, params, runners = realRunners3) {
284596
284865
  ${tail}` : ""}`);
284597
284866
  }
284598
284867
  for (const f of files) {
284599
- if (!priorBackups.has(f)) rmSync7(join39(cwd, `${f}.bak`), { force: true });
284868
+ if (!priorBackups.has(f)) rmSync7(join40(cwd, `${f}.bak`), { force: true });
284600
284869
  }
284601
284870
  const failed = [
284602
284871
  ...`${r.stderr}
@@ -284767,7 +285036,25 @@ async function runVerifyRemediation(ctx, { concern_ids, download_external_module
284767
285036
  if (key !== void 0) keyed.push({ id, key });
284768
285037
  else unkeyed.push(id);
284769
285038
  }
284770
- const keyedVerdict = partitionByKey(keyed, currentKeys);
285039
+ const byKey = partitionByKey(keyed, currentKeys);
285040
+ const read3 = fileReader(cwd);
285041
+ const remainingKeyed = keyed.filter((k) => byKey.remaining.includes(k.id));
285042
+ const atSite = new Set(
285043
+ resolvedAtSite({
285044
+ requested: remainingKeyed.map((k) => ({
285045
+ ...k,
285046
+ site: session.siteById?.get(k.id) ?? null,
285047
+ file: reported.get(k.id)?.location.file
285048
+ })),
285049
+ current: currentConcerns.map((c) => ({ key: concernKeyOf(c), site: siteOf(c, read3) })),
285050
+ firstSites: session.firstSitesByKey ?? /* @__PURE__ */ new Map(),
285051
+ declared: (file2) => new Set(declaredAddresses(read3(file2) ?? ""))
285052
+ })
285053
+ );
285054
+ const keyedVerdict = {
285055
+ resolved: [...byKey.resolved, ...byKey.remaining.filter((id) => atSite.has(id))],
285056
+ remaining: byKey.remaining.filter((id) => !atSite.has(id))
285057
+ };
284771
285058
  const fallbackVerdict = computeRemediationVerdict(unkeyed, new Set(currentIds));
284772
285059
  const resolved = [...keyedVerdict.resolved, ...fallbackVerdict.resolved];
284773
285060
  const remaining = [...keyedVerdict.remaining, ...fallbackVerdict.remaining];
@@ -284987,10 +285274,10 @@ var DispatchRecorder = class {
284987
285274
  * orchestrator counts as subagent — including a session the labeler could not
284988
285275
  * name, because an unnamed session is by definition not the orchestrator's.
284989
285276
  */
284990
- recordTokens(label, billableTokens2) {
284991
- if (!Number.isFinite(billableTokens2) || billableTokens2 <= 0) return;
284992
- if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens += billableTokens2;
284993
- else this.#subagentTokens += billableTokens2;
285277
+ recordTokens(label, billableTokens) {
285278
+ if (!Number.isFinite(billableTokens) || billableTokens <= 0) return;
285279
+ if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens += billableTokens;
285280
+ else this.#subagentTokens += billableTokens;
284994
285281
  }
284995
285282
  tally() {
284996
285283
  return {
@@ -285117,7 +285404,7 @@ function buildLocalContext(options) {
285117
285404
  registryHost: options.registryHost
285118
285405
  },
285119
285406
  toolState: initToolState({ progressComment: void 0 }),
285120
- tmpdir: mkdtempSync4(join40(tmpdir3(), `${PRODUCT_SLUG}-mcp-`))
285407
+ tmpdir: mkdtempSync4(join41(tmpdir3(), `${PRODUCT_SLUG}-mcp-`))
285121
285408
  };
285122
285409
  }
285123
285410
  function buildLocalTools(ctx) {
@@ -285160,7 +285447,7 @@ function buildLocalTools(ctx) {
285160
285447
  ];
285161
285448
  }
285162
285449
  function cliVersion() {
285163
- const raw2 = "0.3.11";
285450
+ const raw2 = "0.3.13";
285164
285451
  if (raw2 !== void 0 && /^\d+\.\d+\.\d+$/.test(raw2)) {
285165
285452
  return raw2;
285166
285453
  }
@@ -285299,7 +285586,7 @@ async function runAssessCli(params) {
285299
285586
  }
285300
285587
  let cwd = process.cwd();
285301
285588
  if (rawCwd) cwd = isAbsolute4(rawCwd) ? rawCwd : resolve7(process.cwd(), rawCwd);
285302
- if (!existsSync10(cwd) || !statSync8(cwd).isDirectory()) {
285589
+ if (!existsSync9(cwd) || !statSync8(cwd).isDirectory()) {
285303
285590
  console.error(`assess: not a directory: ${cwd}`);
285304
285591
  process.exit(ASSESS_EXIT.USAGE);
285305
285592
  }
@@ -285391,15 +285678,15 @@ ${postureLine(scorecard.posture, scorecard.total)} \u2014 failing the gate (--fa
285391
285678
  }
285392
285679
 
285393
285680
  // src/commands/explain.ts
285394
- import { readFileSync as readFileSync26 } from "node:fs";
285395
- import { join as join41, relative as relative6, resolve as resolve8 } from "node:path";
285681
+ import { readFileSync as readFileSync27 } from "node:fs";
285682
+ import { join as join42, relative as relative6, resolve as resolve8 } from "node:path";
285396
285683
  var import_arg2 = __toESM(require_arg(), 1);
285397
285684
  var import_yaml3 = __toESM(require_dist(), 1);
285398
285685
  function readConfig(cwd) {
285399
285686
  for (const name of INFRAWEAVER_CONFIG_FILENAMES) {
285400
285687
  let raw2;
285401
285688
  try {
285402
- raw2 = readFileSync26(join41(cwd, name), "utf-8");
285689
+ raw2 = readFileSync27(join42(cwd, name), "utf-8");
285403
285690
  } catch {
285404
285691
  continue;
285405
285692
  }
@@ -285573,8 +285860,8 @@ var import_arg3 = __toESM(require_arg(), 1);
285573
285860
 
285574
285861
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/gitAuth.js
285575
285862
  import { execSync } from "node:child_process";
285576
- import { createHash as createHash12 } from "node:crypto";
285577
- import { readFileSync as readFileSync27, realpathSync as realpathSync3, unlinkSync } from "node:fs";
285863
+ import { createHash as createHash13 } from "node:crypto";
285864
+ import { readFileSync as readFileSync28, realpathSync as realpathSync3, unlinkSync } from "node:fs";
285578
285865
 
285579
285866
  // node_modules/.pnpm/@infraweaver-io+sandbox@0.1.8/node_modules/@infraweaver-io/sandbox/dist/subprocess.js
285580
285867
  import { spawn as nodeSpawn } from "node:child_process";
@@ -285900,7 +286187,7 @@ ${errMsg}` : errMsg;
285900
286187
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/gitAuth.js
285901
286188
  var gitBinary;
285902
286189
  function hashFile(path15) {
285903
- return createHash12("sha256").update(readFileSync27(path15)).digest("hex");
286190
+ return createHash13("sha256").update(readFileSync28(path15)).digest("hex");
285904
286191
  }
285905
286192
  function resolveGit() {
285906
286193
  const lookup3 = process.platform === "win32" ? "where git" : "which git";
@@ -286022,7 +286309,7 @@ async function $gitFetchWithDeepen(args2, options, label) {
286022
286309
  import { randomUUID as randomUUID3 } from "node:crypto";
286023
286310
  import { writeFileSync as writeFileSync7 } from "node:fs";
286024
286311
  import { createServer } from "node:http";
286025
- import { join as join42 } from "node:path";
286312
+ import { join as join43 } from "node:path";
286026
286313
  var REVOKED_TRAP_MS = 6e4;
286027
286314
  function revokeGitHubToken(token, userAgent2) {
286028
286315
  const apiBase = process.env.GITHUB_API_URL || "https://api.github.com";
@@ -286094,7 +286381,7 @@ async function startGitAuthServer(tmpdir6, options) {
286094
286381
  function writeAskpassScript(code2) {
286095
286382
  const scriptId = randomUUID3();
286096
286383
  const scriptName = `askpass-${scriptId}.js`;
286097
- const scriptPath = join42(tmpdir6, scriptName);
286384
+ const scriptPath = join43(tmpdir6, scriptName);
286098
286385
  const content = [
286099
286386
  `#!/usr/bin/env node`,
286100
286387
  `var a=process.argv[2]||"";`,
@@ -286273,9 +286560,9 @@ function TerraformChangeSummaryTool(ctx) {
286273
286560
  }
286274
286561
 
286275
286562
  // src/mcp/checkout.ts
286276
- import { createHash as createHash13 } from "node:crypto";
286563
+ import { createHash as createHash14 } from "node:crypto";
286277
286564
  import { statSync as statSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
286278
- import { join as join52 } from "node:path";
286565
+ import { join as join53 } from "node:path";
286279
286566
 
286280
286567
  // node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/diffCoverage.js
286281
286568
  import { isAbsolute as isAbsolute5, normalize as normalize4, resolve as resolve9 } from "node:path";
@@ -286571,7 +286858,7 @@ function readNumber(params) {
286571
286858
  // src/mcp/git.ts
286572
286859
  import { randomUUID as randomUUID5 } from "node:crypto";
286573
286860
  import { writeFileSync as writeFileSync9 } from "node:fs";
286574
- import { join as join45 } from "node:path";
286861
+ import { join as join46 } from "node:path";
286575
286862
 
286576
286863
  // src/modes/address-reviews.ts
286577
286864
  function addressReviewsMode(t) {
@@ -287467,7 +287754,7 @@ This mode standardises STRUCTURE while preserving BEHAVIOUR \u2014 the equivalen
287467
287754
  - \`${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.
287468
287755
  - \`${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.
287469
287756
 
287470
- **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\`.
287757
+ **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\`.
287471
287758
 
287472
287759
  **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.
287473
287760
 
@@ -287600,7 +287887,7 @@ function remediateMode(t) {
287600
287887
 
287601
287888
  4. **for the chosen group**:
287602
287889
  - **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.
287603
- - **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.
287890
+ - **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.
287604
287891
  - **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.
287605
287892
  - **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.
287606
287893
  - **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.
@@ -287661,7 +287948,7 @@ This mode composes the **Remediate** and **Refactor** verbs into a single run. I
287661
287948
  - **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.
287662
287949
 
287663
287950
  3. **REFACTOR (phase 2) \u2014 modularise on top of the committed fix**: follow the **Refactor** mode's flow:
287664
- - \`${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\`).
287951
+ - \`${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\`).
287665
287952
  - 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")}\`).
287666
287953
  - \`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.
287667
287954
  - **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.
@@ -288074,14 +288361,14 @@ import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
288074
288361
  import { randomUUID as randomUUID4 } from "node:crypto";
288075
288362
  import { closeSync as closeSync3, openSync as openSync3, writeFileSync as writeFileSync8 } from "node:fs";
288076
288363
  import { constants as osConstants, userInfo } from "node:os";
288077
- import { join as join44 } from "node:path";
288364
+ import { join as join45 } from "node:path";
288078
288365
  import { setTimeout as sleep3 } from "node:timers/promises";
288079
288366
 
288080
288367
  // node_modules/.pnpm/@infraweaver-io+core@0.1.5/node_modules/@infraweaver-io/core/dist/packageManager.js
288081
288368
  var import_semver = __toESM(require_semver2(), 1);
288082
- import { existsSync as existsSync11 } from "node:fs";
288369
+ import { existsSync as existsSync10 } from "node:fs";
288083
288370
  import { mkdir as mkdir2, readFile } from "node:fs/promises";
288084
- import { delimiter as delimiter2, join as join43 } from "node:path";
288371
+ import { delimiter as delimiter2, join as join44 } from "node:path";
288085
288372
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
288086
288373
  var COREPACK_MANAGED = ["pnpm", "yarn"];
288087
288374
  function isSupported(name) {
@@ -288121,8 +288408,8 @@ function parseDevEnginesField(field) {
288121
288408
  };
288122
288409
  }
288123
288410
  async function resolvePackageManagerSpec(cwd) {
288124
- const pkgPath = join43(cwd, "package.json");
288125
- if (!existsSync11(pkgPath))
288411
+ const pkgPath = join44(cwd, "package.json");
288412
+ if (!existsSync10(pkgPath))
288126
288413
  return null;
288127
288414
  let pkg;
288128
288415
  try {
@@ -288180,7 +288467,7 @@ async function currentVersion(run4, name) {
288180
288467
  return result.stdout.trim();
288181
288468
  }
288182
288469
  function packageManagerBinDir(tmpdir6) {
288183
- return join43(tmpdir6, "pm-bin");
288470
+ return join44(tmpdir6, "pm-bin");
288184
288471
  }
288185
288472
  async function ensurePackageManager(params) {
288186
288473
  const spec = params.spec;
@@ -288421,7 +288708,7 @@ async function killProcessGroup(proc) {
288421
288708
  var MAX_OUTPUT_CHARS = 5e3;
288422
288709
  function capOutput(output2) {
288423
288710
  if (output2.length <= MAX_OUTPUT_CHARS) return output2;
288424
- const fullPath = join44(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
288711
+ const fullPath = join45(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
288425
288712
  writeFileSync8(fullPath, output2);
288426
288713
  const elided = output2.length - MAX_OUTPUT_CHARS;
288427
288714
  return `... [${elided} chars truncated; output saved to ${fullPath}] ...
@@ -288487,8 +288774,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
288487
288774
  if (params.background) {
288488
288775
  const tempDir = getTempDir();
288489
288776
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
288490
- const outputPath = join44(tempDir, `${handle}.log`);
288491
- const pidPath = join44(tempDir, `${handle}.pid`);
288777
+ const outputPath = join45(tempDir, `${handle}.log`);
288778
+ const pidPath = join45(tempDir, `${handle}.pid`);
288492
288779
  const logFd = openSync3(outputPath, "a");
288493
288780
  let proc;
288494
288781
  try {
@@ -289193,7 +289480,7 @@ function countAhead(head, base) {
289193
289480
  }
289194
289481
  function spillGitOutput(params) {
289195
289482
  const tempDir = getTempDir();
289196
- const outputPath = join45(tempDir, `git-${params.command}-${randomUUID5().slice(0, 8)}.txt`);
289483
+ const outputPath = join46(tempDir, `git-${params.command}-${randomUUID5().slice(0, 8)}.txt`);
289197
289484
  writeFileSync9(outputPath, params.output);
289198
289485
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
289199
289486
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
@@ -289214,15 +289501,52 @@ var Git = type({
289214
289501
  args: type.string.array().describe("Additional arguments for the git command").optional()
289215
289502
  });
289216
289503
  function splitWords(line) {
289217
- return [...line.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)].map((m) => m[1] ?? m[2] ?? m[3] ?? "");
289504
+ const words2 = [];
289505
+ let word = "";
289506
+ let inWord = false;
289507
+ let quote;
289508
+ for (let i = 0; i < line.length; i++) {
289509
+ const ch = line.charAt(i);
289510
+ if (quote === "'") {
289511
+ if (ch === "'") quote = void 0;
289512
+ else word += ch;
289513
+ } else if (quote === '"') {
289514
+ if (ch === '"') quote = void 0;
289515
+ else if (ch === "\\" && i + 1 < line.length && '"\\$`\n'.includes(line.charAt(i + 1))) {
289516
+ i += 1;
289517
+ if (line.charAt(i) !== "\n") word += line.charAt(i);
289518
+ } else word += ch;
289519
+ } else if (ch === " " || ch === " ") {
289520
+ if (inWord) words2.push(word);
289521
+ word = "";
289522
+ inWord = false;
289523
+ } else if (ch === "\n") {
289524
+ throw new Error(
289525
+ "command holds an unquoted newline \u2014 pass one git command per call, and quote a multi-line message"
289526
+ );
289527
+ } else if (ch === "\\") {
289528
+ if (i + 1 >= line.length) throw new Error("command ends in a backslash that escapes nothing");
289529
+ i += 1;
289530
+ if (line.charAt(i) === "\n") continue;
289531
+ word += line.charAt(i);
289532
+ inWord = true;
289533
+ } else if (ch === "'" || ch === '"') {
289534
+ quote = ch;
289535
+ inWord = true;
289536
+ } else {
289537
+ word += ch;
289538
+ inWord = true;
289539
+ }
289540
+ }
289541
+ if (quote) throw new Error(`command has an unterminated ${quote} quote`);
289542
+ if (inWord) words2.push(word);
289543
+ return words2;
289218
289544
  }
289219
289545
  function normalizeGitCall(params) {
289220
- let words2;
289221
- if (params.command === void 0) words2 = [...params.args ?? []];
289222
- else if (/^git\s/.test(params.command))
289223
- words2 = [...splitWords(params.command).slice(1), ...params.args ?? []];
289224
- else return { command: params.command, args: params.args ?? [] };
289225
- const [command, ...args2] = words2[0] === "git" ? words2.slice(1) : words2;
289546
+ const words2 = params.command === void 0 ? [] : /^git\s/.test(params.command) ? splitWords(params.command) : [params.command];
289547
+ words2.push(...params.args ?? []);
289548
+ while (words2[0] === "git") words2.shift();
289549
+ const [command, ...args2] = words2;
289226
289550
  if (command === void 0 || !/^[a-z][a-z0-9-]*$/.test(command)) {
289227
289551
  throw new Error(
289228
289552
  `command must be a Git subcommand such as 'status' or 'checkout' (was "${command ?? ""}")`
@@ -289408,7 +289732,7 @@ function PushTagsTool(ctx) {
289408
289732
  }
289409
289733
 
289410
289734
  // src/mcp/review.ts
289411
- import { readFileSync as readFileSync32 } from "node:fs";
289735
+ import { readFileSync as readFileSync33 } from "node:fs";
289412
289736
 
289413
289737
  // src/mcp/findingAudit.ts
289414
289738
  var COHERENCE_RULES = {
@@ -289658,7 +289982,7 @@ function claimsBlockingSeverity(body2) {
289658
289982
  init_core();
289659
289983
  import { isAbsolute as isAbsolute6, resolve as resolve10 } from "node:path";
289660
289984
 
289661
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/monotonicQuota.js
289985
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/monotonicQuota.js
289662
289986
  function createMonotonicQuota(params) {
289663
289987
  const { limit, unlimited, normalize: normalize5 } = params;
289664
289988
  let used = 0;
@@ -289683,7 +290007,7 @@ function createMonotonicQuota(params) {
289683
290007
  };
289684
290008
  }
289685
290009
 
289686
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/costQuota.js
290010
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/costQuota.js
289687
290011
  var CostQuotaExceededError = class extends Error {
289688
290012
  code = "COST_QUOTA_EXCEEDED";
289689
290013
  constructor(message2) {
@@ -289730,7 +290054,7 @@ function resolveRunCostQuota(parsed2) {
289730
290054
  return parsed2 === void 0 ? DEFAULT_RUN_COST_QUOTA_USD : parsed2;
289731
290055
  }
289732
290056
 
289733
- // node_modules/.pnpm/@infraweaver-io+models@0.1.6/node_modules/@infraweaver-io/models/dist/tokenQuota.js
290057
+ // node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/tokenQuota.js
289734
290058
  var TokenQuotaExceededError = class extends Error {
289735
290059
  code = "TOKEN_QUOTA_EXCEEDED";
289736
290060
  constructor(message2) {
@@ -289740,19 +290064,16 @@ var TokenQuotaExceededError = class extends Error {
289740
290064
  };
289741
290065
  var DEFAULT_RUN_TOKEN_QUOTA = 1e7;
289742
290066
  var RUN_TOKEN_QUOTA_INPUT = "max_run_tokens";
289743
- function billableTokens(t) {
289744
- let total = 0;
289745
- for (const v of [t.input, t.output, t.cacheRead, t.cacheWrite]) {
289746
- if (typeof v === "number" && Number.isFinite(v) && v > 0)
289747
- total += v;
289748
- }
289749
- return Math.floor(total);
290067
+ var CACHE_READ_WEIGHT = 0.1;
290068
+ function quotaTokens(t) {
290069
+ const part = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
290070
+ return Math.floor(part(t.input) + part(t.output) + part(t.cacheWrite) + part(t.cacheRead) * CACHE_READ_WEIGHT);
289750
290071
  }
289751
290072
  function normalizeTotal(total) {
289752
- return typeof total === "number" ? Math.max(0, Math.floor(total)) : billableTokens(total);
290073
+ return typeof total === "number" ? Math.max(0, Math.floor(total)) : quotaTokens(total);
289753
290074
  }
289754
290075
  function tokenGap(used, ceiling) {
289755
- 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.`;
290076
+ 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.`;
289756
290077
  }
289757
290078
  function unlimitedTokenQuota() {
289758
290079
  return createMonotonicQuota({
@@ -289807,7 +290128,7 @@ function validateCompatibility(payloadVersion, actionVersion) {
289807
290128
  }
289808
290129
 
289809
290130
  // src/utils/github/prCommentEvent.ts
289810
- import { readFileSync as readFileSync28 } from "node:fs";
290131
+ import { readFileSync as readFileSync29 } from "node:fs";
289811
290132
  var obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
289812
290133
  function parsePrCommentEvent(eventName, raw2) {
289813
290134
  if (eventName !== "issue_comment") return void 0;
@@ -289836,7 +290157,7 @@ function readPrCommentEvent(env2 = process.env) {
289836
290157
  try {
289837
290158
  return parsePrCommentEvent(
289838
290159
  env2.GITHUB_EVENT_NAME,
289839
- JSON.parse(readFileSync28(env2.GITHUB_EVENT_PATH, "utf-8"))
290160
+ JSON.parse(readFileSync29(env2.GITHUB_EVENT_PATH, "utf-8"))
289840
290161
  );
289841
290162
  } catch {
289842
290163
  return void 0;
@@ -289844,7 +290165,7 @@ function readPrCommentEvent(env2 = process.env) {
289844
290165
  }
289845
290166
 
289846
290167
  // src/utils/github/pullRequestEvent.ts
289847
- import { readFileSync as readFileSync29 } from "node:fs";
290168
+ import { readFileSync as readFileSync30 } from "node:fs";
289848
290169
  var obj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
289849
290170
  var str = (v) => typeof v === "string" && v ? v : void 0;
289850
290171
  function parsePullRequestEvent(eventName, raw2) {
@@ -289904,7 +290225,7 @@ function readPullRequestEvent(env2 = process.env) {
289904
290225
  if (env2.GITHUB_EVENT_NAME !== "pull_request" || !env2.GITHUB_EVENT_PATH) return void 0;
289905
290226
  let raw2;
289906
290227
  try {
289907
- raw2 = JSON.parse(readFileSync29(env2.GITHUB_EVENT_PATH, "utf-8"));
290228
+ raw2 = JSON.parse(readFileSync30(env2.GITHUB_EVENT_PATH, "utf-8"));
289908
290229
  } catch {
289909
290230
  return void 0;
289910
290231
  }
@@ -290895,6 +291216,12 @@ function parseCommentMarker(body2) {
290895
291216
  return null;
290896
291217
  }
290897
291218
  }
291219
+ function findingKeysIn(body2) {
291220
+ return [...body2.matchAll(new RegExp(MARKER.source, "g"))].flatMap((m) => {
291221
+ const marker2 = parseCommentMarker(m[0]);
291222
+ return marker2?.kind === "finding" ? [marker2.key] : [];
291223
+ });
291224
+ }
290898
291225
  var SUGGESTION = /```suggestion\r?\n([\s\S]*?)```/;
290899
291226
  function suggestionText(body2) {
290900
291227
  const m = SUGGESTION.exec(body2);
@@ -291144,44 +291471,120 @@ function formatDuplicateCommentsNote(duplicates) {
291144
291471
  var MAX_DUPLICATE_COMMENT_LINES = 50;
291145
291472
 
291146
291473
  // src/mcp/reviewCommentLimit.ts
291147
- var MAX_OVERFLOW_LINES = 50;
291474
+ var MAX_NOTE_CHARS = 3e4;
291475
+ var TRAILER_RESERVE = 400;
291148
291476
  var PROVEN_MARK = "\u2705 **Proven by re-scan**";
291149
- function rank(c) {
291477
+ var SEVERITY_LEVEL = {
291478
+ critical: 0,
291479
+ high: 1,
291480
+ medium: 2,
291481
+ low: 3,
291482
+ info: 4
291483
+ };
291484
+ var MARKER_LEVEL = { "\u{1F6A8}": 1, "\u26A0\uFE0F": 2, "\u26A0": 2, "\u2139\uFE0F": 3, \u2139: 3 };
291485
+ var UNMARKED_LEVEL = 2;
291486
+ function rank(c, severity) {
291150
291487
  const body2 = c.body ?? "";
291151
- const marker2 = leadingMarker(body2);
291152
- const severity = marker2 === "\u{1F6A8}" ? 0 : marker2 === "\u26A0\uFE0F" || marker2 === "\u26A0" ? 2 : 4;
291153
- return severity + (body2.includes(PROVEN_MARK) ? 0 : 1);
291488
+ const level = severity ? SEVERITY_LEVEL[severity] : MARKER_LEVEL[leadingMarker(body2) ?? ""] ?? UNMARKED_LEVEL;
291489
+ return level * 2 + (body2.includes(PROVEN_MARK) ? 0 : 1);
291154
291490
  }
291155
- function limitInlineComments(comments, limit) {
291491
+ function limitInlineComments(comments, limit, severityOf) {
291156
291492
  if (!limit || comments.length <= limit) return { kept: [...comments], overflow: [] };
291157
291493
  const keep = new Set(
291158
- 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)
291494
+ 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)
291159
291495
  );
291160
291496
  return {
291161
291497
  kept: comments.filter((_, i) => keep.has(i)),
291162
291498
  overflow: comments.filter((_, i) => !keep.has(i))
291163
291499
  };
291164
291500
  }
291165
- function formatOverflowNote(overflow, limit) {
291501
+ var escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
291502
+ var whereOf = (c) => c.line ? `${c.path}:${c.line}` : c.path;
291503
+ function closesItself(body2) {
291504
+ const occurrences = (re) => body2.match(re)?.length ?? 0;
291505
+ const fences = body2.split("\n").filter((l) => /^\s*(```|~~~)/.test(l)).length;
291506
+ return fences % 2 === 0 && occurrences(/<details\b/gi) === occurrences(/<\/details\s*>/gi) && occurrences(/<!--/g) === occurrences(/-->/g);
291507
+ }
291508
+ function asText(body2) {
291509
+ const longest = Math.max(0, ...(body2.match(/`+/g) ?? []).map((run4) => run4.length));
291510
+ const fence = "`".repeat(Math.max(3, longest + 1));
291511
+ return `${fence}text
291512
+ ${body2}
291513
+ ${fence}`;
291514
+ }
291515
+ function renderWhole(c, linkOf) {
291516
+ const where4 = escapeHtml(whereOf(c));
291517
+ const gist = gistOf(c.body);
291518
+ const href = linkOf?.(c);
291519
+ const body2 = c.body ?? "";
291520
+ return [
291521
+ `<details><summary><code>${where4}</code>${gist ? ` \u2014 ${gist}` : ""}</summary>`,
291522
+ ...href ? [`<a href="${escapeHtml(href)}">${where4}</a>`] : [],
291523
+ ...body2 ? [closesItself(body2) ? body2 : asText(body2)] : [],
291524
+ "</details>"
291525
+ ].join("\n\n");
291526
+ }
291527
+ function renderBrief(c) {
291528
+ const gist = gistOf(c.body);
291529
+ const markers = findingKeysIn(c.body ?? "").map(
291530
+ (key) => renderCommentMarker({ kind: "finding", key, proven: false })
291531
+ );
291532
+ return `- \`${whereOf(c).replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}${markers.map((m) => ` ${m}`).join("")}`;
291533
+ }
291534
+ function formatOverflowNote(overflow, limit, linkOf) {
291166
291535
  if (overflow.length === 0) return "";
291167
- const shown = overflow.slice(0, MAX_OVERFLOW_LINES).map((c) => {
291168
- const where4 = c.line ? `${c.path}:${c.line}` : c.path;
291169
- const gist = gistOf(c.body);
291170
- return `- \`${where4.replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}`;
291171
- });
291172
- const rest = overflow.length - shown.length;
291173
- if (rest > 0) shown.push(`- \u2026and ${rest} more not shown`);
291174
- return `
291536
+ const header = `
291175
291537
 
291176
291538
  ---
291177
291539
 
291178
- **Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead:
291179
- ` + shown.join("\n");
291540
+ **Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead:`;
291541
+ const SEP = 2;
291542
+ let room = MAX_NOTE_CHARS - TRAILER_RESERVE - header.length;
291543
+ const items = [];
291544
+ for (const c of overflow) {
291545
+ const brief2 = renderBrief(c);
291546
+ if (brief2.length + SEP > room) break;
291547
+ items.push(brief2);
291548
+ room -= brief2.length + SEP;
291549
+ }
291550
+ let brief = items.length;
291551
+ for (const [i, c] of overflow.slice(0, items.length).entries()) {
291552
+ const whole = renderWhole(c, linkOf);
291553
+ const extra = whole.length - items[i].length;
291554
+ if (extra > room) continue;
291555
+ items[i] = whole;
291556
+ room -= extra;
291557
+ brief--;
291558
+ }
291559
+ const omitted = overflow.length - items.length;
291560
+ if (omitted > 0) items.push(`- \u2026and ${omitted} more not shown`);
291561
+ const cut = [
291562
+ brief > 0 ? `${brief} of them ${brief === 1 ? "is" : "are"} shown by place and first line only` : "",
291563
+ omitted > 0 ? `${omitted} ${omitted === 1 ? "is" : "are"} not shown at all` : ""
291564
+ ].filter(Boolean);
291565
+ if (cut.length > 0) {
291566
+ items.push(`${cut.join(" and ")}, to keep this review within GitHub's size limit.`);
291567
+ }
291568
+ return `${header}
291569
+
291570
+ ${items.join("\n\n")}`;
291571
+ }
291572
+ function foldedFindingsStillIntroduced(input2) {
291573
+ const folded = new Set(
291574
+ input2.reviews.flatMap(
291575
+ (r) => r.body && isOwnReviewComment(r.user, r.body) ? findingKeysIn(r.body) : []
291576
+ )
291577
+ );
291578
+ if (folded.size === 0) return [];
291579
+ const dismissed = new Set(input2.dismissed.map((d) => d.finding_id));
291580
+ return input2.delta.introduced.filter(
291581
+ (f) => folded.has(findingKey(f)) && !dismissed.has(f.concern.id)
291582
+ );
291180
291583
  }
291181
291584
 
291182
291585
  // src/mcp/reviewComments.ts
291183
291586
  import { writeFileSync as writeFileSync10 } from "node:fs";
291184
- import { join as join46 } from "node:path";
291587
+ import { join as join47 } from "node:path";
291185
291588
 
291186
291589
  // src/utils/github/body.ts
291187
291590
  var import_turndown = __toESM(require_turndown_cjs(), 1);
@@ -291865,7 +292268,7 @@ function GetReviewCommentsTool(ctx) {
291865
292268
  const { threadBlocks, reviewer, formatted, truncated } = result;
291866
292269
  const tempDir = getTempDir();
291867
292270
  const filename = `review-${params.review_id}-threads.md`;
291868
- const commentsPath = join46(tempDir, filename);
292271
+ const commentsPath = join47(tempDir, filename);
291869
292272
  writeFileSync10(commentsPath, formatted.content);
291870
292273
  log2.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
291871
292274
  const truncationNote = truncated ? ` WARNING: this PR has more review threads/comments than a single fetch returns, so the set above is INCOMPLETE \u2014 some threads or comments are not included. Address what's here, but do not treat the list as exhaustive.` : "";
@@ -292056,7 +292459,7 @@ function where2(f) {
292056
292459
  function renderReviewVerdict(input2) {
292057
292460
  const { delta, verdict: verdict2 } = input2;
292058
292461
  const introduced = delta.introduced.length;
292059
- const blocking = verdict2.gating + verdict2.required;
292462
+ const { blocking } = verdict2;
292060
292463
  const renamedBlocking = delta.introduced.filter(
292061
292464
  (f) => f.possiblyPreexisting && failsGate(f, [...input2.requiredControls ?? []])
292062
292465
  ).length;
@@ -292161,8 +292564,8 @@ function renderReviewSummarySection(input2) {
292161
292564
  }
292162
292565
 
292163
292566
  // src/mcp/suggestionProver.ts
292164
- import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync30, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
292165
- import { join as join47, posix as posix14 } from "node:path";
292567
+ import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync31, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
292568
+ import { join as join48, posix as posix14 } from "node:path";
292166
292569
  var MAX_SINGLE_PROOFS = 4;
292167
292570
  async function proveSuggestions(input2, deps) {
292168
292571
  const out = /* @__PURE__ */ new Map();
@@ -292174,8 +292577,8 @@ async function proveSuggestions(input2, deps) {
292174
292577
  else ready.push(c);
292175
292578
  }
292176
292579
  if (ready.length === 0) return input2.candidates.map((c) => out.get(c.index));
292177
- const parent = mkdtempSync5(join47(input2.tmpdir, "suggestion-proof-"));
292178
- const worktree = join47(parent, "wt");
292580
+ const parent = mkdtempSync5(join48(input2.tmpdir, "suggestion-proof-"));
292581
+ const worktree = join48(parent, "wt");
292179
292582
  if (run("git", ["worktree", "add", "--detach", worktree, "HEAD"], input2.repoCwd).status !== 0) {
292180
292583
  rmSync8(parent, { recursive: true, force: true });
292181
292584
  for (const c of ready) {
@@ -292187,7 +292590,7 @@ async function proveSuggestions(input2, deps) {
292187
292590
  }
292188
292591
  return input2.candidates.map((c) => out.get(c.index));
292189
292592
  }
292190
- const scanCwd = prefix2 ? join47(worktree, prefix2) : worktree;
292593
+ const scanCwd = prefix2 ? join48(worktree, prefix2) : worktree;
292191
292594
  const files = [...new Set(ready.map((c) => c.path.slice(prefix2.length)))];
292192
292595
  try {
292193
292596
  const before = await deps.scan(scanCwd, files);
@@ -292283,16 +292686,16 @@ function snapshot3(worktree, prefix2, cs) {
292283
292686
  const m = /* @__PURE__ */ new Map();
292284
292687
  for (const c of cs) {
292285
292688
  const rel = c.path.slice(prefix2.length);
292286
- if (!m.has(rel)) m.set(rel, readFileSync30(join47(worktree, c.path), "utf-8"));
292689
+ if (!m.has(rel)) m.set(rel, readFileSync31(join48(worktree, c.path), "utf-8"));
292287
292690
  }
292288
292691
  return m;
292289
292692
  }
292290
292693
  function restore(worktree, prefix2, pristine) {
292291
- for (const [rel, text] of pristine) writeFileSync11(join47(worktree, prefix2, rel), text);
292694
+ for (const [rel, text] of pristine) writeFileSync11(join48(worktree, prefix2, rel), text);
292292
292695
  }
292293
292696
  function apply(worktree, path15, cs) {
292294
- const file2 = join47(worktree, path15);
292295
- const text = readFileSync30(file2, "utf-8");
292697
+ const file2 = join48(worktree, path15);
292698
+ const text = readFileSync31(file2, "utf-8");
292296
292699
  const eol = text.includes("\r\n") ? "\r\n" : "\n";
292297
292700
  const lines = text.split(/\r?\n/);
292298
292701
  for (const c of [...cs].sort((a, b) => b.startLine - a.startLine)) {
@@ -292302,12 +292705,12 @@ function apply(worktree, path15, cs) {
292302
292705
  }
292303
292706
 
292304
292707
  // src/mcp/terraform/gateScan.ts
292305
- import { existsSync as existsSync12, mkdtempSync as mkdtempSync6, rmSync as rmSync10 } from "node:fs";
292306
- import { join as join49, posix as posix15 } from "node:path";
292708
+ import { existsSync as existsSync11, mkdtempSync as mkdtempSync6, rmSync as rmSync10 } from "node:fs";
292709
+ import { join as join50, posix as posix15 } from "node:path";
292307
292710
 
292308
292711
  // src/mcp/terraform/secretScan.ts
292309
- import { readFileSync as readFileSync31, rmSync as rmSync9 } from "node:fs";
292310
- import { join as join48 } from "node:path";
292712
+ import { readFileSync as readFileSync32, rmSync as rmSync9 } from "node:fs";
292713
+ import { join as join49 } from "node:path";
292311
292714
  function parseBetterleaksReport(json5, prefix2 = "") {
292312
292715
  const doc = JSON.parse(json5);
292313
292716
  if (Array.isArray(doc) || !doc || typeof doc !== "object" || !("findings" in doc)) {
@@ -292341,7 +292744,7 @@ async function scanAddedSecrets(cwd, mergeBase, tmp) {
292341
292744
  const top = run("git", ["rev-parse", "--show-toplevel"], cwd).stdout.trim();
292342
292745
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd).stdout.trim();
292343
292746
  if (!top) return { ran: false, reason: "not a git checkout", concerns: [] };
292344
- const report2 = join48(tmp, `betterleaks-${process.pid}.json`);
292747
+ const report2 = join49(tmp, `betterleaks-${process.pid}.json`);
292345
292748
  try {
292346
292749
  const r = await runAsync(
292347
292750
  "betterleaks",
@@ -292364,7 +292767,7 @@ async function scanAddedSecrets(cwd, mergeBase, tmp) {
292364
292767
  const why = (r.stderr || r.stdout).trim().split("\n").pop() ?? "";
292365
292768
  let parsed2;
292366
292769
  try {
292367
- parsed2 = parseBetterleaksReport(readFileSync31(report2, "utf-8"), prefix2);
292770
+ parsed2 = parseBetterleaksReport(readFileSync32(report2, "utf-8"), prefix2);
292368
292771
  } catch (error63) {
292369
292772
  const reason = r.status !== 0 ? `betterleaks exited ${r.status}: ${why}` : `${error63}`;
292370
292773
  return { ran: false, reason, concerns: [] };
@@ -292414,8 +292817,8 @@ function checkOutBase(cwd, pr, tmp) {
292414
292817
  }
292415
292818
  const mergeBase = run("git", ["merge-base", pr.baseSha, "HEAD"], cwd).stdout.trim() || pr.baseSha;
292416
292819
  const prefix2 = run("git", ["rev-parse", "--show-prefix"], cwd).stdout.trim();
292417
- const parent = mkdtempSync6(join49(tmp, "scan-delta-base-"));
292418
- const worktree = join49(parent, "wt");
292820
+ const parent = mkdtempSync6(join50(tmp, "scan-delta-base-"));
292821
+ const worktree = join50(parent, "wt");
292419
292822
  if (run("git", ["worktree", "add", "--detach", worktree, mergeBase], cwd).status !== 0) {
292420
292823
  rmSync10(parent, { recursive: true, force: true });
292421
292824
  log2.warning("\xBB scan delta: could not check out the base \u2014 every finding is treated as new.");
@@ -292423,7 +292826,7 @@ function checkOutBase(cwd, pr, tmp) {
292423
292826
  }
292424
292827
  return {
292425
292828
  mergeBase,
292426
- cwd: prefix2 ? join49(worktree, prefix2) : worktree,
292829
+ cwd: prefix2 ? join50(worktree, prefix2) : worktree,
292427
292830
  close: () => {
292428
292831
  run("git", ["worktree", "remove", "--force", worktree], cwd);
292429
292832
  rmSync10(parent, { recursive: true, force: true });
@@ -292459,7 +292862,7 @@ async function reviewScans(ctx, pr, scanners) {
292459
292862
  const changed = diffScope(ctx, cwd, scanners);
292460
292863
  const scope2 = base ? reviewScope(ctx, cwd, base) : void 0;
292461
292864
  const started = Date.now();
292462
- const scanBase = async (b) => existsSync12(b.cwd) ? scanTree(ctx, b.cwd, changed, scanners, reviewedConcern, scope2) : [];
292865
+ const scanBase = async (b) => existsSync11(b.cwd) ? scanTree(ctx, b.cwd, changed, scanners, reviewedConcern, scope2) : [];
292463
292866
  const [head, baseScan, secrets] = await Promise.allSettled([
292464
292867
  scanTree(ctx, cwd, changed, scanners, reviewedConcern, scope2),
292465
292868
  base ? scanBase(base) : Promise.resolve([]),
@@ -292891,6 +293294,22 @@ function CreatePullRequestReviewTool(ctx) {
292891
293294
  `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.`
292892
293295
  );
292893
293296
  }
293297
+ if (delta) {
293298
+ const reviews = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listReviews, {
293299
+ owner: ctx.repo.owner,
293300
+ repo: ctx.repo.name,
293301
+ pull_number,
293302
+ per_page: 100
293303
+ });
293304
+ const folded = foldedFindingsStillIntroduced({ reviews, delta, dismissed });
293305
+ if (folded.length > 0) {
293306
+ throw new Error(
293307
+ `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(
293308
+ (f) => `${f.concern.id} (${f.concern.rule_id} at ${f.concern.location.file}:${f.concern.location.line ?? "?"})`
293309
+ ).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.`
293310
+ );
293311
+ }
293312
+ }
292894
293313
  }
292895
293314
  const skip = reviewSkipDecision({
292896
293315
  approved: approved ?? false,
@@ -292997,6 +293416,7 @@ ${formatReanchoredNote(reanchored.moved)}`
292997
293416
  params.event = event;
292998
293417
  }
292999
293418
  const proofs = delta ? await proveReviewSuggestions(ctx, auditedComments) : /* @__PURE__ */ new Map();
293419
+ const findingSeverity = /* @__PURE__ */ new Map();
293000
293420
  const reviewComments = auditedComments.map((comment, index) => {
293001
293421
  let commentBody = fixDoubleEscapedString(comment.body || "");
293002
293422
  if (comment.suggestion !== void 0) {
@@ -293038,6 +293458,9 @@ ${marker2}`;
293038
293458
  reviewComment.start_line = comment.start_line;
293039
293459
  reviewComment.start_side = side;
293040
293460
  }
293461
+ if (comment.provenance?.kind === "scanner") {
293462
+ findingSeverity.set(reviewComment, comment.provenance.finding.concern.severity);
293463
+ }
293041
293464
  return reviewComment;
293042
293465
  });
293043
293466
  let droppedComments = [];
@@ -293073,7 +293496,11 @@ ${marker2}`;
293073
293496
  `withholding ${duplicateComments.length}/${reviewComments.length} inline comment(s) that duplicate open review threads`
293074
293497
  );
293075
293498
  }
293076
- const limited = limitInlineComments(deduped.kept, ctx.payload.reviewCommentLimit);
293499
+ const limited = limitInlineComments(
293500
+ deduped.kept,
293501
+ ctx.payload.reviewCommentLimit,
293502
+ (c) => findingSeverity.get(c)
293503
+ );
293077
293504
  overflowComments = limited.overflow;
293078
293505
  if (overflowComments.length > 0) {
293079
293506
  log2.info(
@@ -293105,7 +293532,13 @@ ${marker2}`;
293105
293532
  body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
293106
293533
  }
293107
293534
  if (overflowComments.length > 0) {
293108
- const note = formatOverflowNote(overflowComments, ctx.payload.reviewCommentLimit);
293535
+ const server = process.env.GITHUB_SERVER_URL || "https://github.com";
293536
+ const commit = params.commit_id;
293537
+ const note = formatOverflowNote(
293538
+ overflowComments,
293539
+ ctx.payload.reviewCommentLimit,
293540
+ 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
293541
+ );
293109
293542
  body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
293110
293543
  }
293111
293544
  let reproved = 0;
@@ -293358,7 +293791,7 @@ ${coverageState.lastBreakdown}`
293358
293791
  }
293359
293792
  function readReviewedFile(path15) {
293360
293793
  try {
293361
- return readFileSync32(resolveWithinCwd(resolveRepoRoot(), path15), "utf-8");
293794
+ return readFileSync33(resolveWithinCwd(resolveRepoRoot(), path15), "utf-8");
293362
293795
  } catch {
293363
293796
  return null;
293364
293797
  }
@@ -293673,7 +294106,7 @@ function fastForwardDiff(params, exec2) {
293673
294106
 
293674
294107
  // src/utils/prompt/changeImpact.ts
293675
294108
  import { writeFileSync as writeFileSync12 } from "node:fs";
293676
- import { join as join50 } from "node:path";
294109
+ import { join as join51 } from "node:path";
293677
294110
  var MAX_ATOMS = 20;
293678
294111
  var MAX_REFS_PER_ATOM = 6;
293679
294112
  var MAX_EXCERPT = 160;
@@ -293853,7 +294286,7 @@ function createChangeImpactArtifact(params) {
293853
294286
  fileCount: params.files.length,
293854
294287
  lookupError
293855
294288
  });
293856
- const path15 = join50(
294289
+ const path15 = join51(
293857
294290
  params.tmpdir,
293858
294291
  `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`
293859
294292
  );
@@ -293873,7 +294306,7 @@ function createChangeImpactArtifact(params) {
293873
294306
  import { execFileSync, execSync as execSync2 } from "node:child_process";
293874
294307
  import { mkdtempSync as mkdtempSync7, readdirSync as readdirSync9, realpathSync as realpathSync5, unlinkSync as unlinkSync2 } from "node:fs";
293875
294308
  import { tmpdir as tmpdir4 } from "node:os";
293876
- import { join as join51 } from "node:path";
294309
+ import { join as join52 } from "node:path";
293877
294310
 
293878
294311
  // src/utils/github/ghaCore.ts
293879
294312
  import { randomUUID as randomUUID6 } from "node:crypto";
@@ -293896,7 +294329,7 @@ var TEMP_DIR_STATE_KEY = "infraweaver_temp_dir";
293896
294329
 
293897
294330
  // src/utils/setup/setup.ts
293898
294331
  function createTempDirectory() {
293899
- const sharedTempDir = mkdtempSync7(join51(tmpdir4(), `${PRODUCT_SLUG}-`));
294332
+ const sharedTempDir = mkdtempSync7(join52(tmpdir4(), `${PRODUCT_SLUG}-`));
293900
294333
  process.env.INFRAWEAVER_TEMP_DIR = sharedTempDir;
293901
294334
  saveState2(TEMP_DIR_STATE_KEY, sharedTempDir);
293902
294335
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
@@ -293942,13 +294375,13 @@ function wipeRunnerLeakSurface() {
293942
294375
  return [];
293943
294376
  }
293944
294377
  };
293945
- const fileCommandsDir = join51(runnerTemp, "_runner_file_commands");
294378
+ const fileCommandsDir = join52(runnerTemp, "_runner_file_commands");
293946
294379
  for (const entry4 of listDir(fileCommandsDir)) {
293947
- tryUnlink(join51(fileCommandsDir, entry4));
294380
+ tryUnlink(join52(fileCommandsDir, entry4));
293948
294381
  }
293949
294382
  for (const entry4 of listDir(runnerTemp)) {
293950
294383
  if (entry4.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry4)) {
293951
- tryUnlink(join51(runnerTemp, entry4));
294384
+ tryUnlink(join52(runnerTemp, entry4));
293952
294385
  }
293953
294386
  }
293954
294387
  if (wiped.length > 0) {
@@ -294117,7 +294550,7 @@ function formatFilesWithLineNumbers(files) {
294117
294550
  }
294118
294551
  const tocLines = [`## Files (${files.length})`];
294119
294552
  for (const entry4 of tocEntries) {
294120
- const anchor2 = createHash13("sha256").update(entry4.filename).digest("hex");
294553
+ const anchor2 = createHash14("sha256").update(entry4.filename).digest("hex");
294121
294554
  tocLines.push(
294122
294555
  `- ${entry4.filename} \u2192 lines ${entry4.startLine}-${entry4.endLine} \xB7 diff-${anchor2}`
294123
294556
  );
@@ -294447,7 +294880,7 @@ function CheckoutPrTool(ctx) {
294447
294880
  headSha: ctx.toolState.checkoutSha
294448
294881
  });
294449
294882
  if (incremental) {
294450
- incrementalDiffPath = join52(
294883
+ incrementalDiffPath = join53(
294451
294884
  tempDir,
294452
294885
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
294453
294886
  );
@@ -294461,7 +294894,7 @@ function CheckoutPrTool(ctx) {
294461
294894
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
294462
294895
  log2.debug(`formatted diff preview (first 100 lines):
294463
294896
  ${diffPreview}`);
294464
- const diffPath = join52(tempDir, `pr-${pull_number}-${headShort}.diff`);
294897
+ const diffPath = join53(tempDir, `pr-${pull_number}-${headShort}.diff`);
294465
294898
  writeFileSync13(diffPath, formatResult.content);
294466
294899
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
294467
294900
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -294602,7 +295035,7 @@ ${dirty}`
294602
295035
 
294603
295036
  // src/mcp/checkSuite.ts
294604
295037
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync14 } from "node:fs";
294605
- import { join as join53 } from "node:path";
295038
+ import { join as join54 } from "node:path";
294606
295039
  import { stripVTControlCharacters } from "node:util";
294607
295040
  var GetCheckSuiteLogs = type({
294608
295041
  check_suite_id: type.number.describe("the id from check_suite.id")
@@ -294697,7 +295130,7 @@ function GetCheckSuiteLogsTool(ctx) {
294697
295130
  };
294698
295131
  }
294699
295132
  const tempDir = getTempDir();
294700
- const logsDir = join53(tempDir, "ci-logs");
295133
+ const logsDir = join54(tempDir, "ci-logs");
294701
295134
  mkdirSync6(logsDir, { recursive: true });
294702
295135
  const jobLists = await mapWithConcurrency(
294703
295136
  failedRuns,
@@ -294721,7 +295154,7 @@ function GetCheckSuiteLogsTool(ctx) {
294721
295154
  request: { signal: AbortSignal.timeout(1e4) }
294722
295155
  });
294723
295156
  const logsText = typeof data === "string" ? data : Buffer.from(data).toString("utf8");
294724
- const logPath = join53(logsDir, `job-${job.id}.log`);
295157
+ const logPath = join54(logsDir, `job-${job.id}.log`);
294725
295158
  writeFileSync14(logPath, logsText);
294726
295159
  const analysis = analyzeLog(logsText, 80);
294727
295160
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -294783,7 +295216,7 @@ function GetCheckSuiteLogsTool(ctx) {
294783
295216
 
294784
295217
  // src/mcp/commitInfo.ts
294785
295218
  import { writeFileSync as writeFileSync15 } from "node:fs";
294786
- import { join as join54 } from "node:path";
295219
+ import { join as join55 } from "node:path";
294787
295220
  var CommitInfo = type({
294788
295221
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
294789
295222
  });
@@ -294802,7 +295235,7 @@ function CommitInfoTool(ctx) {
294802
295235
  const files = data.files ?? [];
294803
295236
  const formatResult = formatFilesWithLineNumbers(files);
294804
295237
  const tempDir = getTempDir("get_commit_info");
294805
- const diffFile = join54(tempDir, `commit-${sha2.slice(0, 7)}.diff`);
295238
+ const diffFile = join55(tempDir, `commit-${sha2.slice(0, 7)}.diff`);
294806
295239
  writeFileSync15(diffFile, formatResult.content);
294807
295240
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
294808
295241
  return {
@@ -294963,8 +295396,8 @@ function UpsertRemediationDashboardTool(ctx) {
294963
295396
  import { performance as performance3 } from "node:perf_hooks";
294964
295397
 
294965
295398
  // src/prep/installNodeDependencies.ts
294966
- import { existsSync as existsSync13 } from "node:fs";
294967
- import { join as join55 } from "node:path";
295399
+ import { existsSync as existsSync12 } from "node:fs";
295400
+ import { join as join56 } from "node:path";
294968
295401
 
294969
295402
  // node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
294970
295403
  function splitRunArgs(args2, valueFlags = []) {
@@ -295350,7 +295783,7 @@ async function installFallback(name, installSpec) {
295350
295783
  return result.stderr || `failed to install ${name}`;
295351
295784
  }
295352
295785
  if (name === "deno") {
295353
- const denoPath = join55(process.env.HOME || "", ".deno", "bin");
295786
+ const denoPath = join56(process.env.HOME || "", ".deno", "bin");
295354
295787
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
295355
295788
  }
295356
295789
  log2.info(`\xBB installed ${name}`);
@@ -295359,8 +295792,8 @@ async function installFallback(name, installSpec) {
295359
295792
  var installNodeDependencies = {
295360
295793
  name: "installNodeDependencies",
295361
295794
  shouldRun: () => {
295362
- const packageJsonPath = join55(process.cwd(), "package.json");
295363
- return existsSync13(packageJsonPath);
295795
+ const packageJsonPath = join56(process.cwd(), "package.json");
295796
+ return existsSync12(packageJsonPath);
295364
295797
  },
295365
295798
  run: async (options) => {
295366
295799
  const declared2 = await resolvePackageManagerSpec(process.cwd());
@@ -295468,8 +295901,8 @@ ${errorMessage2}`]
295468
295901
  };
295469
295902
 
295470
295903
  // src/prep/installPythonDependencies.ts
295471
- import { existsSync as existsSync14, readFileSync as readFileSync33 } from "node:fs";
295472
- import { join as join56 } from "node:path";
295904
+ import { existsSync as existsSync13, readFileSync as readFileSync34 } from "node:fs";
295905
+ import { join as join57 } from "node:path";
295473
295906
  var PYTHON_CONFIGS = [
295474
295907
  {
295475
295908
  file: "requirements.txt",
@@ -295504,7 +295937,7 @@ var PYTHON_CONFIGS = [
295504
295937
  ];
295505
295938
  function pyprojectDeclaresBuildSystem(cwd) {
295506
295939
  try {
295507
- const content = readFileSync33(join56(cwd, "pyproject.toml"), "utf8");
295940
+ const content = readFileSync34(join57(cwd, "pyproject.toml"), "utf8");
295508
295941
  return /^\s*\[build-system\]/m.test(content);
295509
295942
  } catch {
295510
295943
  return false;
@@ -295554,13 +295987,13 @@ var installPythonDependencies = {
295554
295987
  }
295555
295988
  const cwd = process.cwd();
295556
295989
  return PYTHON_CONFIGS.some(
295557
- (config3) => existsSync14(join56(cwd, config3.file)) && configApplies(config3, cwd)
295990
+ (config3) => existsSync13(join57(cwd, config3.file)) && configApplies(config3, cwd)
295558
295991
  );
295559
295992
  },
295560
295993
  run: async (options) => {
295561
295994
  const cwd = process.cwd();
295562
295995
  const config3 = PYTHON_CONFIGS.find(
295563
- (c) => existsSync14(join56(cwd, c.file)) && configApplies(c, cwd)
295996
+ (c) => existsSync13(join57(cwd, c.file)) && configApplies(c, cwd)
295564
295997
  );
295565
295998
  if (!config3) {
295566
295999
  return {
@@ -296141,8 +296574,8 @@ function SetOutputTool(ctx, outputSchema) {
296141
296574
 
296142
296575
  // src/mcp/policy.ts
296143
296576
  import { spawnSync as spawnSync8 } from "node:child_process";
296144
- import { existsSync as existsSync15, readFileSync as readFileSync34 } from "node:fs";
296145
- import { join as join57 } from "node:path";
296577
+ import { existsSync as existsSync14, readFileSync as readFileSync35 } from "node:fs";
296578
+ import { join as join58 } from "node:path";
296146
296579
  function parseConftestOutput(stdout) {
296147
296580
  let parsed2;
296148
296581
  try {
@@ -296176,7 +296609,7 @@ var CONFTEST_CONFIG = "conftest.toml";
296176
296609
  function conftestSelectsNamespaces(conftestCwd) {
296177
296610
  let raw2;
296178
296611
  try {
296179
- raw2 = readFileSync34(join57(conftestCwd, CONFTEST_CONFIG), "utf-8");
296612
+ raw2 = readFileSync35(join58(conftestCwd, CONFTEST_CONFIG), "utf-8");
296180
296613
  } catch {
296181
296614
  return false;
296182
296615
  }
@@ -296192,8 +296625,8 @@ function namespaceArgs(conftestCwd) {
296192
296625
  }
296193
296626
  function findPlanJson(cwd) {
296194
296627
  for (const candidate of PLAN_JSON_NAMES) {
296195
- const abs = join57(cwd, candidate);
296196
- if (existsSync15(abs)) return abs;
296628
+ const abs = join58(cwd, candidate);
296629
+ if (existsSync14(abs)) return abs;
296197
296630
  }
296198
296631
  return null;
296199
296632
  }
@@ -296234,11 +296667,11 @@ function resolvePolicyDir(cwd, explicit) {
296234
296667
  } catch {
296235
296668
  return null;
296236
296669
  }
296237
- return existsSync15(abs) ? abs : null;
296670
+ return existsSync14(abs) ? abs : null;
296238
296671
  }
296239
296672
  for (const d of DEFAULT_POLICY_DIRS) {
296240
- const abs = join57(cwd, d);
296241
- if (existsSync15(abs)) return abs;
296673
+ const abs = join58(cwd, d);
296674
+ if (existsSync14(abs)) return abs;
296242
296675
  }
296243
296676
  return null;
296244
296677
  }
@@ -296272,7 +296705,7 @@ function PolicyCheckTool(ctx) {
296272
296705
  } catch {
296273
296706
  abs = null;
296274
296707
  }
296275
- targetFile = abs && existsSync15(abs) ? abs : null;
296708
+ targetFile = abs && existsSync14(abs) ? abs : null;
296276
296709
  if (!targetFile) {
296277
296710
  return toolSkip(
296278
296711
  "target_not_found",
@@ -296545,9 +296978,9 @@ function PullRequestInfoTool(ctx) {
296545
296978
 
296546
296979
  // node_modules/.pnpm/@infraweaver-io+sandbox@0.1.8/node_modules/@infraweaver-io/sandbox/dist/writePolicy.js
296547
296980
  import { renameSync, writeFileSync as writeFileSync16 } from "node:fs";
296548
- import { join as join58 } from "node:path";
296981
+ import { join as join59 } from "node:path";
296549
296982
  function writePolicyPath(tmpdir6, filename) {
296550
- return join58(tmpdir6, filename);
296983
+ return join59(tmpdir6, filename);
296551
296984
  }
296552
296985
  function publishWritePolicy(params) {
296553
296986
  try {
@@ -299863,15 +300296,15 @@ async function startMcpHttpServer(ctx, options) {
299863
300296
 
299864
300297
  // src/mcp/terraform/policyGate.ts
299865
300298
  import {
299866
- existsSync as existsSync16,
300299
+ existsSync as existsSync15,
299867
300300
  mkdirSync as mkdirSync7,
299868
300301
  mkdtempSync as mkdtempSync8,
299869
300302
  readdirSync as readdirSync10,
299870
- readFileSync as readFileSync36,
300303
+ readFileSync as readFileSync37,
299871
300304
  writeFileSync as writeFileSync17
299872
300305
  } from "node:fs";
299873
300306
  import { tmpdir as osTmpdir } from "node:os";
299874
- import { dirname as dirname8, isAbsolute as isAbsolute8, join as join60, relative as relative8, resolve as resolve11 } from "node:path";
300307
+ import { dirname as dirname8, isAbsolute as isAbsolute8, join as join61, relative as relative8, resolve as resolve11 } from "node:path";
299875
300308
  function coercePolicyRules(raw2) {
299876
300309
  const rules = [];
299877
300310
  const rejected = [];
@@ -300123,7 +300556,7 @@ function evaluateRulesStatically(cwd, rules, scope2 = { kind: "all", reason: ""
300123
300556
  const files = [];
300124
300557
  for (const rel of walk.files) {
300125
300558
  try {
300126
- files.push({ rel, text: readFileSync36(join60(cwd, rel), "utf-8") });
300559
+ files.push({ rel, text: readFileSync37(join61(cwd, rel), "utf-8") });
300127
300560
  } catch {
300128
300561
  }
300129
300562
  }
@@ -300238,16 +300671,16 @@ function cloudEmittedRego(rules) {
300238
300671
  }
300239
300672
  function readOrEmpty(path15) {
300240
300673
  try {
300241
- return readFileSync36(path15, "utf-8");
300674
+ return readFileSync37(path15, "utf-8");
300242
300675
  } catch {
300243
300676
  return "";
300244
300677
  }
300245
300678
  }
300246
300679
  function regoDirIn(root, skip) {
300247
300680
  for (const dir of DEFAULT_POLICY_DIRS) {
300248
- const abs = join60(root, dir);
300249
- if (existsSync16(abs) && walkFilesBySuffix(abs, ".rego").some(
300250
- (rel) => !skip(join60(dir, rel), readOrEmpty(join60(abs, rel)))
300681
+ const abs = join61(root, dir);
300682
+ if (existsSync15(abs) && walkFilesBySuffix(abs, ".rego").some(
300683
+ (rel) => !skip(join61(dir, rel), readOrEmpty(join61(abs, rel)))
300251
300684
  )) {
300252
300685
  return dir;
300253
300686
  }
@@ -300255,14 +300688,14 @@ function regoDirIn(root, skip) {
300255
300688
  return null;
300256
300689
  }
300257
300690
  function withoutSkipped(root, dir, skip, tmpdir6) {
300258
- const abs = join60(root, dir);
300259
- const files = readdirSync10(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile()).map((e) => relative8(abs, join60(e.parentPath, e.name)));
300260
- const kept = files.filter((rel) => !skip(join60(dir, rel), readOrEmpty(join60(abs, rel))));
300691
+ const abs = join61(root, dir);
300692
+ const files = readdirSync10(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile()).map((e) => relative8(abs, join61(e.parentPath, e.name)));
300693
+ const kept = files.filter((rel) => !skip(join61(dir, rel), readOrEmpty(join61(abs, rel))));
300261
300694
  if (kept.length === files.length) return abs;
300262
- const copy = join60(mkdtempSync8(join60(tmpdir6 ?? osTmpdir(), "policy-own-")), dir);
300695
+ const copy = join61(mkdtempSync8(join61(tmpdir6 ?? osTmpdir(), "policy-own-")), dir);
300263
300696
  for (const rel of kept) {
300264
- mkdirSync7(dirname8(join60(copy, rel)), { recursive: true });
300265
- writeFileSync17(join60(copy, rel), readFileSync36(join60(abs, rel)));
300697
+ mkdirSync7(dirname8(join61(copy, rel)), { recursive: true });
300698
+ writeFileSync17(join61(copy, rel), readFileSync37(join61(abs, rel)));
300266
300699
  }
300267
300700
  return copy;
300268
300701
  }
@@ -300284,18 +300717,18 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir6, notes, skip) {
300284
300717
  }
300285
300718
  const own2 = tree.files.filter((f) => !skip(f.path, f.raw));
300286
300719
  if (!own2.some((f) => f.path.endsWith(".rego"))) continue;
300287
- const root = mkdtempSync8(join60(tmpdir6 ?? osTmpdir(), "policy-base-"));
300720
+ const root = mkdtempSync8(join61(tmpdir6 ?? osTmpdir(), "policy-base-"));
300288
300721
  for (const f of own2) {
300289
- mkdirSync7(dirname8(join60(root, f.path)), { recursive: true });
300290
- writeFileSync17(join60(root, f.path), f.raw);
300722
+ mkdirSync7(dirname8(join61(root, f.path)), { recursive: true });
300723
+ writeFileSync17(join61(root, f.path), f.raw);
300291
300724
  }
300292
300725
  const toml = readFileAtBaseRef(cwd, baseRef, CONFTEST_CONFIG);
300293
- if (toml.kind === "file") writeFileSync17(join60(root, CONFTEST_CONFIG), toml.raw);
300294
- const same = head === dir && readdirSync10(join60(cwd, dir), { recursive: true, withFileTypes: true }).filter(
300726
+ if (toml.kind === "file") writeFileSync17(join61(root, CONFTEST_CONFIG), toml.raw);
300727
+ const same = head === dir && readdirSync10(join61(cwd, dir), { recursive: true, withFileTypes: true }).filter(
300295
300728
  (e) => e.isFile()
300296
300729
  ).length === tree.files.length && tree.files.every((f) => {
300297
300730
  try {
300298
- return readFileSync36(join60(cwd, f.path), "utf-8") === f.raw;
300731
+ return readFileSync37(join61(cwd, f.path), "utf-8") === f.raw;
300299
300732
  } catch {
300300
300733
  return false;
300301
300734
  }
@@ -300305,7 +300738,7 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir6, notes, skip) {
300305
300738
  `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.`
300306
300739
  );
300307
300740
  }
300308
- return { policyDir: join60(root, dir), conftestCwd: root };
300741
+ return { policyDir: join61(root, dir), conftestCwd: root };
300309
300742
  }
300310
300743
  if (head) {
300311
300744
  notes.push(
@@ -300315,7 +300748,7 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir6, notes, skip) {
300315
300748
  return null;
300316
300749
  }
300317
300750
  function findWorkflowPlanJson(cwd, notes) {
300318
- const present = PLAN_JSON_NAMES.filter((name) => existsSync16(join60(cwd, name)));
300751
+ const present = PLAN_JSON_NAMES.filter((name) => existsSync15(join61(cwd, name)));
300319
300752
  if (present.length === 0) return null;
300320
300753
  const listing = run("git", ["ls-files", "-z"], cwd);
300321
300754
  if (listing.status !== 0) {
@@ -300332,7 +300765,7 @@ function findWorkflowPlanJson(cwd, notes) {
300332
300765
  );
300333
300766
  continue;
300334
300767
  }
300335
- return join60(cwd, name);
300768
+ return join61(cwd, name);
300336
300769
  }
300337
300770
  return null;
300338
300771
  }
@@ -300362,7 +300795,7 @@ function evaluatePolicyGate(cwd, rawRules, opts = {}) {
300362
300795
  let plan;
300363
300796
  if (planPath) {
300364
300797
  try {
300365
- plan = asPlan(JSON.parse(readFileSync36(planPath, "utf-8")));
300798
+ plan = asPlan(JSON.parse(readFileSync37(planPath, "utf-8")));
300366
300799
  } catch {
300367
300800
  }
300368
300801
  if (!plan) notes.push(`${planPath} is not a Terraform plan JSON \u2014 ignored`);
@@ -300465,7 +300898,7 @@ function capturePolicyGate(ctx) {
300465
300898
  if (!result) return;
300466
300899
  const workspace = process.env.GITHUB_WORKSPACE ?? cwd;
300467
300900
  for (const o of [...result.violations, ...result.inconclusive]) {
300468
- if (o.file) o.file = posix16(relative8(workspace, join60(cwd, o.file)));
300901
+ if (o.file) o.file = posix16(relative8(workspace, join61(cwd, o.file)));
300469
300902
  }
300470
300903
  session.policyGate = result;
300471
300904
  for (const n of result.notes) log2.warning(`\xBB policy gate: ${n}`);
@@ -300800,7 +301233,7 @@ var ThinkingTimer = class {
300800
301233
  // src/agents/claude.ts
300801
301234
  import { execFileSync as execFileSync3 } from "node:child_process";
300802
301235
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync10, writeFileSync as writeFileSync22 } from "node:fs";
300803
- import { join as join69 } from "node:path";
301236
+ import { join as join70 } from "node:path";
300804
301237
  import { performance as performance6 } from "node:perf_hooks";
300805
301238
  import { setTimeout as sleep6 } from "node:timers/promises";
300806
301239
 
@@ -301160,7 +301593,7 @@ import { readFile as readFile2 } from "node:fs/promises";
301160
301593
 
301161
301594
  // src/agents/shared.ts
301162
301595
  import { execFileSync as execFileSync2 } from "node:child_process";
301163
- import { join as join61 } from "node:path";
301596
+ import { join as join62 } from "node:path";
301164
301597
  var MAX_STDERR_LINES = 20;
301165
301598
  var MAX_POST_RUN_RETRIES = 3;
301166
301599
  function getGitStatus() {
@@ -301197,7 +301630,7 @@ var agent = (input2) => {
301197
301630
  };
301198
301631
  };
301199
301632
  function agentHomeEnv(tmpdir6) {
301200
- return { HOME: tmpdir6, XDG_CONFIG_HOME: join61(tmpdir6, ".config") };
301633
+ return { HOME: tmpdir6, XDG_CONFIG_HOME: join62(tmpdir6, ".config") };
301201
301634
  }
301202
301635
  function baseAgentEnv(homeEnv, repoDir) {
301203
301636
  return { ...process.env, ...homeEnv, PWD: repoDir };
@@ -301674,9 +302107,9 @@ function shouldRunGraceTurn(params) {
301674
302107
  }
301675
302108
 
301676
302109
  // src/agents/quotaTokens.ts
301677
- var CACHE_READ_WEIGHT = 0.1;
301678
- function quotaTokens(t) {
301679
- return t.input + t.output + t.cacheWrite + t.cacheRead * CACHE_READ_WEIGHT;
302110
+ var CACHE_READ_WEIGHT2 = 0.1;
302111
+ function quotaTokens2(t) {
302112
+ return t.input + t.output + (t.reasoning ?? 0) + t.cacheWrite + t.cacheRead * CACHE_READ_WEIGHT2;
301680
302113
  }
301681
302114
 
301682
302115
  // src/agents/toolPlaneWatch.ts
@@ -301905,7 +302338,7 @@ function matchesRouteSentinel(envVar, model) {
301905
302338
  import { randomUUID as randomUUID9 } from "node:crypto";
301906
302339
  import { mkdirSync as mkdirSync8, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "node:fs";
301907
302340
  import { homedir } from "node:os";
301908
- import { join as join62 } from "node:path";
302341
+ import { join as join63 } from "node:path";
301909
302342
  var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
301910
302343
  var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
301911
302344
  var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
@@ -301929,7 +302362,7 @@ function readProjectIdFromVertexServiceAccountJson() {
301929
302362
  }
301930
302363
  function createSecretDir() {
301931
302364
  const base = process.env.INFRAWEAVER_SECRET_HOME || process.env.HOME || homedir();
301932
- const secretDir = join62(base, `.${PRODUCT_SLUG}`, "secrets", randomUUID9());
302365
+ const secretDir = join63(base, `.${PRODUCT_SLUG}`, "secrets", randomUUID9());
301933
302366
  mkdirSync8(secretDir, { recursive: true, mode: 448 });
301934
302367
  return secretDir;
301935
302368
  }
@@ -301938,7 +302371,7 @@ function materializeVertexCredentials(params) {
301938
302371
  const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
301939
302372
  if (!blob) return void 0;
301940
302373
  const secretDir = createSecretDir();
301941
- const credentialsPath = join62(secretDir, "vertex-sa.json");
302374
+ const credentialsPath = join63(secretDir, "vertex-sa.json");
301942
302375
  writeFileSync18(credentialsPath, blob, { mode: 384 });
301943
302376
  process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
301944
302377
  const projectId = readProjectIdFromVertexServiceAccountJson();
@@ -302074,29 +302507,29 @@ function createProcessOutputActivityTimeout(ctx) {
302074
302507
  }
302075
302508
 
302076
302509
  // src/utils/prompt/skills.ts
302077
- import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
302078
- import { dirname as dirname9, join as join63 } from "node:path";
302510
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync38, writeFileSync as writeFileSync19 } from "node:fs";
302511
+ import { dirname as dirname9, join as join64 } from "node:path";
302079
302512
  import { fileURLToPath } from "node:url";
302080
302513
  var BUNDLED_SKILL_NAMES = ["terraform-best-practices"];
302081
302514
  function resolveSkillPath(name) {
302082
302515
  const here = dirname9(fileURLToPath(import.meta.url));
302083
302516
  const candidates = [
302084
- join63(here, "..", "..", "skills", name, "SKILL.md"),
302085
- join63(here, "skills", name, "SKILL.md")
302517
+ join64(here, "..", "..", "skills", name, "SKILL.md"),
302518
+ join64(here, "skills", name, "SKILL.md")
302086
302519
  ];
302087
302520
  for (const candidate of candidates) {
302088
- if (existsSync17(candidate)) return candidate;
302521
+ if (existsSync16(candidate)) return candidate;
302089
302522
  }
302090
302523
  throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
302091
302524
  }
302092
302525
  var SKILL_TARGET_DIRS = [".opencode/skills", ".claude/skills", ".agents/skills"];
302093
302526
  function installBundledSkills(params) {
302094
302527
  for (const name of BUNDLED_SKILL_NAMES) {
302095
- const content = readFileSync37(resolveSkillPath(name), "utf8");
302528
+ const content = readFileSync38(resolveSkillPath(name), "utf8");
302096
302529
  for (const targetDir of SKILL_TARGET_DIRS) {
302097
- const skillDir = join63(params.home, targetDir, name);
302530
+ const skillDir = join64(params.home, targetDir, name);
302098
302531
  mkdirSync9(skillDir, { recursive: true });
302099
- writeFileSync19(join63(skillDir, "SKILL.md"), content);
302532
+ writeFileSync19(join64(skillDir, "SKILL.md"), content);
302100
302533
  }
302101
302534
  }
302102
302535
  log2.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -302104,9 +302537,9 @@ function installBundledSkills(params) {
302104
302537
 
302105
302538
  // src/utils/setup/install.ts
302106
302539
  import { spawnSync as spawnSync9 } from "node:child_process";
302107
- import { createHash as createHash15 } from "node:crypto";
302108
- import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as existsSync20, readFileSync as readFileSync38 } from "node:fs";
302109
- import { join as join68 } from "node:path";
302540
+ import { createHash as createHash16 } from "node:crypto";
302541
+ import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as existsSync19, readFileSync as readFileSync39 } from "node:fs";
302542
+ import { join as join69 } from "node:path";
302110
302543
  import { pipeline as pipeline2 } from "node:stream/promises";
302111
302544
 
302112
302545
  // src/utils/setup/tarballCache.ts
@@ -302161,7 +302594,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
302161
302594
  `Refusing to install ${descriptor}: the registry published no integrity digest (neither dist.integrity nor dist.shasum), so the download cannot be verified.`
302162
302595
  );
302163
302596
  }
302164
- const bytes = readFileSync38(tarballPath);
302597
+ const bytes = readFileSync39(tarballPath);
302165
302598
  if (integrity2) {
302166
302599
  const candidates = integrity2.trim().split(/\s+/).filter(Boolean);
302167
302600
  for (const candidate of candidates) {
@@ -302171,7 +302604,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
302171
302604
  const expected = candidate.slice(separator2 + 1);
302172
302605
  let actual;
302173
302606
  try {
302174
- actual = createHash15(algorithm).update(bytes).digest("base64");
302607
+ actual = createHash16(algorithm).update(bytes).digest("base64");
302175
302608
  } catch {
302176
302609
  continue;
302177
302610
  }
@@ -302181,7 +302614,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
302181
302614
  `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published digest (${integrity2}). Refusing to execute it.`
302182
302615
  );
302183
302616
  }
302184
- const actualShasum = createHash15("sha1").update(bytes).digest("hex");
302617
+ const actualShasum = createHash16("sha1").update(bytes).digest("hex");
302185
302618
  if (actualShasum !== shasum) {
302186
302619
  throw new Error(
302187
302620
  `Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published shasum (expected ${shasum}, got ${actualShasum}). Refusing to execute it.`
@@ -302190,9 +302623,9 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
302190
302623
  }
302191
302624
  async function installFromNpmTarball(params) {
302192
302625
  const tempDir = getTempDir();
302193
- const extractedDir = join68(tempDir, "package");
302194
- const cliPath = join68(extractedDir, params.executablePath);
302195
- if (existsSync20(cliPath)) {
302626
+ const extractedDir = join69(tempDir, "package");
302627
+ const cliPath = join69(extractedDir, params.executablePath);
302628
+ if (existsSync19(cliPath)) {
302196
302629
  log2.debug(`\xBB using cached binary at ${cliPath}`);
302197
302630
  return cliPath;
302198
302631
  }
@@ -302243,7 +302676,7 @@ async function installFromNpmTarball(params) {
302243
302676
  dist = (await manifestResponse.json()).dist;
302244
302677
  }
302245
302678
  log2.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
302246
- const tarballPath = join68(tempDir, "package.tgz");
302679
+ const tarballPath = join69(tempDir, "package.tgz");
302247
302680
  const tarballFileName = params.packageName.startsWith("@") ? params.packageName.split("/")[1] : params.packageName;
302248
302681
  const tarballUrl = `${npmRegistry}/${encodedName}/-/${tarballFileName}-${resolvedVersion}.tgz`;
302249
302682
  const restored = await restoreTarball({
@@ -302283,7 +302716,7 @@ async function installFromNpmTarball(params) {
302283
302716
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
302284
302717
  );
302285
302718
  }
302286
- if (!existsSync20(cliPath)) {
302719
+ if (!existsSync19(cliPath)) {
302287
302720
  throw new Error(`Executable not found in extracted package at ${cliPath}`);
302288
302721
  }
302289
302722
  if (params.installDependencies) {
@@ -302394,9 +302827,9 @@ var CLAUDE_DISALLOWED_TOOLS = CLAUDE_EXEC_TOOL_DENY_RULES.join(",");
302394
302827
  var INFRAWEAVER_MCP_AUTH_HEADER = `Bearer \${${MCP_SERVER_TOKEN_ENV}}`;
302395
302828
  var CLAUDE_MAX_CONCURRENT_SUBAGENTS_DEFAULT = "4";
302396
302829
  function writeMcpConfig(ctx) {
302397
- const configDir = join69(ctx.tmpdir, ".claude");
302830
+ const configDir = join70(ctx.tmpdir, ".claude");
302398
302831
  mkdirSync10(configDir, { recursive: true });
302399
- const configPath = join69(configDir, "mcp.json");
302832
+ const configPath = join70(configDir, "mcp.json");
302400
302833
  const terraformMcp = resolveTerraformMcp(ctx.payload);
302401
302834
  if (terraformMcp.kind === "docker_missing") log2.info(`\xBB ${terraformMcp.note}`);
302402
302835
  const opentofuMcp = resolveOpenTofuMcp(ctx.payload);
@@ -302428,13 +302861,13 @@ function writeMcpConfig(ctx) {
302428
302861
  return configPath;
302429
302862
  }
302430
302863
  function writePretoolGateAssets(ctx, stopHookPath) {
302431
- const scriptPath = join69(ctx.tmpdir, CLAUDE_PRETOOL_GATE_FILENAME);
302864
+ const scriptPath = join70(ctx.tmpdir, CLAUDE_PRETOOL_GATE_FILENAME);
302432
302865
  writeFileSync22(
302433
302866
  scriptPath,
302434
302867
  buildClaudePretoolGateSource(ctx.subagentDeniedTools, writePolicyPath2(ctx.tmpdir))
302435
302868
  );
302436
302869
  chmodSync3(scriptPath, 493);
302437
- const settingsPath = join69(ctx.tmpdir, "infraweaver-claude-settings.json");
302870
+ const settingsPath = join70(ctx.tmpdir, "infraweaver-claude-settings.json");
302438
302871
  const settings = buildClaudePretoolGateSettings(scriptPath, CLAUDE_EXEC_TOOL_DENY_RULES);
302439
302872
  settings.permissions.deny.push(...harnessTmpdirWriteDenyClaude(ctx.tmpdir));
302440
302873
  if (stopHookPath) {
@@ -302523,7 +302956,7 @@ function timerFor(state5, label) {
302523
302956
  function chargeTokenQuota(state5) {
302524
302957
  const quota = state5.params.tokenQuota;
302525
302958
  if (!quota || state5.stopRun.signal.aborted) return;
302526
- if (!quota.observe(state5.quotaSource, quotaTokens(state5.accumulatedTokens))) return;
302959
+ if (!quota.observe(state5.quotaSource, quotaTokens2(state5.accumulatedTokens))) return;
302527
302960
  log2.info(`\xBB ${state5.params.label}: ${quota.gap()}`);
302528
302961
  state5.stopRun.abort(new TokenQuotaExceededError(quota.gap()));
302529
302962
  }
@@ -303059,11 +303492,11 @@ var claude = agent({
303059
303492
  const onVertex = matchesRouteSentinel(VERTEX_MODEL_ID_ENV, specifier) && specifier !== void 0 && isVertexAnthropicId(specifier);
303060
303493
  const model = !specifier ? void 0 : onBedrock ? specifier : onVertex ? void 0 : stripProviderPrefix(specifier);
303061
303494
  const homeEnv = agentHomeEnv(ctx.tmpdir);
303062
- mkdirSync10(join69(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
303495
+ mkdirSync10(join70(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
303063
303496
  installBundledSkills({ home: homeEnv.HOME });
303064
303497
  const mcpConfigPath = writeMcpConfig(ctx);
303065
303498
  const effort = resolveEffort(model);
303066
- const stopHookPath = join69(ctx.tmpdir, "infraweaver-stop-hook.sh");
303499
+ const stopHookPath = join70(ctx.tmpdir, "infraweaver-stop-hook.sh");
303067
303500
  writeFileSync22(stopHookPath, buildStopHookScript(), { mode: 493 });
303068
303501
  const pretoolGate = writePretoolGateAssets(ctx, stopHookPath);
303069
303502
  installManagedSettings({
@@ -303229,7 +303662,7 @@ var claude = agent({
303229
303662
  init_core();
303230
303663
  import { spawn as nodeSpawn2 } from "node:child_process";
303231
303664
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync24 } from "node:fs";
303232
- import { join as join71 } from "node:path";
303665
+ import { join as join72 } from "node:path";
303233
303666
  import { performance as performance7 } from "node:perf_hooks";
303234
303667
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
303235
303668
  import { setTimeout as sleep7 } from "node:timers/promises";
@@ -308912,7 +309345,7 @@ function autoSelectModel() {
308912
309345
  import { execFileSync as execFileSync4 } from "node:child_process";
308913
309346
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync23 } from "node:fs";
308914
309347
  import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
308915
- import { join as join70 } from "node:path";
309348
+ import { join as join71 } from "node:path";
308916
309349
 
308917
309350
  // src/utils/agent/codexOAuth.ts
308918
309351
  init_webapi();
@@ -308966,8 +309399,8 @@ function installCodexAuth() {
308966
309399
  }
308967
309400
  const expiresMs = decodeJwtExpMs(body2.tokens.access_token) ?? 0;
308968
309401
  const xdgDataHome = resolveDataHome();
308969
- const opencodeDir = join70(xdgDataHome, "opencode");
308970
- const authPath = join70(opencodeDir, "auth.json");
309402
+ const opencodeDir = join71(xdgDataHome, "opencode");
309403
+ const authPath = join71(opencodeDir, "auth.json");
308971
309404
  const opencodeAuth = {
308972
309405
  openai: {
308973
309406
  type: "oauth",
@@ -308991,7 +309424,7 @@ function installCodexAuth() {
308991
309424
  };
308992
309425
  }
308993
309426
  function resolveDataHome() {
308994
- if (process.env.CI !== "true") return join70(homedir3(), ".local", "share");
309427
+ if (process.env.CI !== "true") return join71(homedir3(), ".local", "share");
308995
309428
  bootstrapInfraweaverDataDir();
308996
309429
  return INFRAWEAVER_DATA_DIR;
308997
309430
  }
@@ -309255,7 +309688,7 @@ function newTurn() {
309255
309688
  turnCounter += 1;
309256
309689
  return {
309257
309690
  finalText: "",
309258
- tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
309691
+ tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0 },
309259
309692
  costUsd: 0,
309260
309693
  sessionError: null,
309261
309694
  lastToolError: null,
@@ -309265,7 +309698,7 @@ function newTurn() {
309265
309698
  function chargeTokenQuota2(ctx) {
309266
309699
  const quota = ctx.tokenQuota;
309267
309700
  if (!quota || !ctx.currentTurn || ctx.tokenQuotaExceeded) return;
309268
- if (!quota.observe(ctx.currentTurn.quotaSource, quotaTokens(ctx.currentTurn.tokens))) return;
309701
+ if (!quota.observe(ctx.currentTurn.quotaSource, quotaTokens2(ctx.currentTurn.tokens))) return;
309269
309702
  ctx.tokenQuotaExceeded = true;
309270
309703
  log2.info(`\xBB ${ctx.label}: ${quota.gap()}`);
309271
309704
  ctx.abortRun?.(new TokenQuotaExceededError(quota.gap()));
@@ -309374,6 +309807,7 @@ async function onPartUpdated(ctx, part) {
309374
309807
  if (t) {
309375
309808
  ctx.currentTurn.tokens.input += t.input || 0;
309376
309809
  ctx.currentTurn.tokens.output += t.output || 0;
309810
+ ctx.currentTurn.tokens.reasoning += t.reasoning || 0;
309377
309811
  ctx.currentTurn.tokens.cacheRead += t.cache?.read || 0;
309378
309812
  ctx.currentTurn.tokens.cacheWrite += t.cache?.write || 0;
309379
309813
  }
@@ -309732,10 +310166,10 @@ var opencode = agent({
309732
310166
  const openAiCompatModel = resolveOpenAiCompatibleOpenCodeModel(rawModel);
309733
310167
  const model = isBedrockRoute ? `amazon-bedrock/${rawModel}` : vertexModel ?? openAiCompatModel ?? rawModel;
309734
310168
  const homeEnv = agentHomeEnv(ctx.tmpdir);
309735
- const opencodePluginDir = join71(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
310169
+ const opencodePluginDir = join72(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
309736
310170
  mkdirSync12(opencodePluginDir, { recursive: true });
309737
310171
  writeFileSync24(
309738
- join71(opencodePluginDir, INFRAWEAVER_OPENCODE_GATE_PLUGIN_FILENAME),
310172
+ join72(opencodePluginDir, INFRAWEAVER_OPENCODE_GATE_PLUGIN_FILENAME),
309739
310173
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools, writePolicyPath2(ctx.tmpdir))
309740
310174
  );
309741
310175
  installBundledSkills({ home: homeEnv.HOME });
@@ -310671,7 +311105,7 @@ async function fetchRunContext(params) {
310671
311105
  // src/utils/github/github.ts
310672
311106
  init_core();
310673
311107
  import { rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
310674
- import { dirname as dirname12, join as join72 } from "node:path";
311108
+ import { dirname as dirname12, join as join73 } from "node:path";
310675
311109
 
310676
311110
  // node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
310677
311111
  function getUserAgent2() {
@@ -310770,8 +311204,8 @@ function addQueryParameters(url5, parameters) {
310770
311204
  }).join("&");
310771
311205
  }
310772
311206
  var urlVariableRegex = /\{[^{}}]+\}/g;
310773
- function removeNonChars(variableName) {
310774
- return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
311207
+ function removeNonChars(variableName2) {
311208
+ return variableName2.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
310775
311209
  }
310776
311210
  function extractUrlVariableNames(url5) {
310777
311211
  const matches2 = url5.match(urlVariableRegex);
@@ -316487,7 +316921,7 @@ function getGitHubUsageSummary() {
316487
316921
  }
316488
316922
  async function writeGitHubUsageSummaryToFile(path15) {
316489
316923
  const summary2 = getGitHubUsageSummary();
316490
- const tmpPath = join72(dirname12(path15), `.usage-summary-${process.pid}.tmp`);
316924
+ const tmpPath = join73(dirname12(path15), `.usage-summary-${process.pid}.tmp`);
316491
316925
  await writeFile2(tmpPath, JSON.stringify(summary2));
316492
316926
  await rename2(tmpPath, path15);
316493
316927
  }
@@ -317032,7 +317466,7 @@ async function resolveModelAndAuth(params) {
317032
317466
  // src/utils/run/runLifecycle.ts
317033
317467
  init_core();
317034
317468
  import { writeFile as writeFile5 } from "node:fs/promises";
317035
- import { join as join75, resolve as resolve13 } from "node:path";
317469
+ import { join as join76, resolve as resolve13 } from "node:path";
317036
317470
 
317037
317471
  // src/utils/cloud/cloudReport.ts
317038
317472
  init_core();
@@ -317426,7 +317860,7 @@ async function reportToCloud(input2) {
317426
317860
 
317427
317861
  // src/utils/cloud/learnings.ts
317428
317862
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
317429
- import { dirname as dirname13, join as join73 } from "node:path";
317863
+ import { dirname as dirname13, join as join74 } from "node:path";
317430
317864
 
317431
317865
  // src/utils/cloud/learningsTruncate.ts
317432
317866
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -317443,7 +317877,7 @@ function truncateAtLineBoundary(body2, cap2) {
317443
317877
  // src/utils/cloud/learnings.ts
317444
317878
  var LEARNINGS_FILE_NAME = LEARNINGS_FILE;
317445
317879
  function learningsFilePath(tmpdir6) {
317446
- return join73(tmpdir6, LEARNINGS_FILE_NAME);
317880
+ return join74(tmpdir6, LEARNINGS_FILE_NAME);
317447
317881
  }
317448
317882
  async function seedLearningsFile(params) {
317449
317883
  const path15 = learningsFilePath(params.tmpdir);
@@ -317574,7 +318008,7 @@ ${ctx.error}` : ctx.error;
317574
318008
 
317575
318009
  // src/utils/github/prSummary.ts
317576
318010
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
317577
- import { dirname as dirname14, join as join74 } from "node:path";
318011
+ import { dirname as dirname14, join as join75 } from "node:path";
317578
318012
  var SUMMARY_FILE_NAME = SUMMARY_FILE;
317579
318013
  var SUMMARY_SCAFFOLD = `# PR summary
317580
318014
 
@@ -317584,7 +318018,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
317584
318018
  var MIN_SNAPSHOT_LENGTH = 60;
317585
318019
  var MAX_SNAPSHOT_LENGTH = 32768;
317586
318020
  function summaryFilePath(tmpdir6) {
317587
- return join74(tmpdir6, SUMMARY_FILE_NAME);
318021
+ return join75(tmpdir6, SUMMARY_FILE_NAME);
317588
318022
  }
317589
318023
  async function seedSummaryFile(params) {
317590
318024
  const path15 = summaryFilePath(params.tmpdir);
@@ -318118,7 +318552,7 @@ async function emitFindingsOutputs(toolState, requiredControls) {
318118
318552
  if (a.level === "error") error(a.message, props);
318119
318553
  else warning(a.message, props);
318120
318554
  }
318121
- const sarifPath = join75(workspace, SARIF_FILE);
318555
+ const sarifPath = join76(workspace, SARIF_FILE);
318122
318556
  if (session.emittedSarifPath && resolve13(session.emittedSarifPath) === resolve13(sarifPath)) {
318123
318557
  log2.info(`\xBB findings output: replacing the agent-emitted ${SARIF_FILE} with the gate's`);
318124
318558
  }
@@ -318450,7 +318884,7 @@ async function runAgentWithWatchdogs(params) {
318450
318884
  const tokenQuota = reserved ? quotas.token : createTokenQuota(quotas.tokenLimit.full);
318451
318885
  const costQuota = reserved ? quotas.cost : createCostQuota(quotas.costLimit.full);
318452
318886
  log2.info(
318453
- 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)`
318887
+ 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)`
318454
318888
  );
318455
318889
  log2.info(
318456
318890
  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)`
@@ -318548,9 +318982,9 @@ async function runAgentWithWatchdogs(params) {
318548
318982
  }
318549
318983
 
318550
318984
  // src/phases/startToolPlane.ts
318551
- import { existsSync as existsSync21, readdirSync as readdirSync11 } from "node:fs";
318985
+ import { existsSync as existsSync20, readdirSync as readdirSync11 } from "node:fs";
318552
318986
  import { readFile as readFile5 } from "node:fs/promises";
318553
- import { join as join76 } from "node:path";
318987
+ import { join as join77 } from "node:path";
318554
318988
 
318555
318989
  // src/utils/cloud/runTerminalStatus.ts
318556
318990
  var TERMINAL_STATE_KEY = "run_terminal";
@@ -319378,8 +319812,8 @@ ${instructions.user}` : null,
319378
319812
  log2.info(instructions.full);
319379
319813
  });
319380
319814
  if (toolContext.agentId === "opencode") {
319381
- const pluginDir = join76(process.cwd(), ".opencode", "plugin");
319382
- const hasPlugins = existsSync21(pluginDir) && readdirSync11(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
319815
+ const pluginDir = join77(process.cwd(), ".opencode", "plugin");
319816
+ const hasPlugins = existsSync20(pluginDir) && readdirSync11(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
319383
319817
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
319384
319818
  log2.info(
319385
319819
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -319865,7 +320299,7 @@ async function run2(args2) {
319865
320299
  }
319866
320300
 
319867
320301
  // src/commands/mcp.ts
319868
- import { existsSync as existsSync22, statSync as statSync12 } from "node:fs";
320302
+ import { existsSync as existsSync21, statSync as statSync12 } from "node:fs";
319869
320303
  import { isAbsolute as isAbsolute9, resolve as resolve14 } from "node:path";
319870
320304
  var import_arg4 = __toESM(require_arg(), 1);
319871
320305
  var SEVERITIES5 = ["critical", "high", "medium", "low", "info"];
@@ -319938,7 +320372,7 @@ async function runMcpCli(params) {
319938
320372
  if (rawCwd) {
319939
320373
  cwd = isAbsolute9(rawCwd) ? rawCwd : resolve14(process.cwd(), rawCwd);
319940
320374
  }
319941
- if (!existsSync22(cwd) || !statSync12(cwd).isDirectory()) {
320375
+ if (!existsSync21(cwd) || !statSync12(cwd).isDirectory()) {
319942
320376
  console.error(`--cwd is not a directory: ${cwd}`);
319943
320377
  process.exit(1);
319944
320378
  }
@@ -319971,7 +320405,7 @@ async function runMcpCli(params) {
319971
320405
  }
319972
320406
 
319973
320407
  // node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/verifyEvidence.js
319974
- import { readFileSync as readFileSync39 } from "node:fs";
320408
+ import { readFileSync as readFileSync40 } from "node:fs";
319975
320409
  function usageLine(params) {
319976
320410
  const command = params.command ? `${params.command} ` : "";
319977
320411
  return `usage: ${params.prog} ${command}<bundle> <sig> <pubkey>`;
@@ -319989,7 +320423,7 @@ function printUsage(params, stream2) {
319989
320423
  }
319990
320424
  function readFileOrExit(path15, what) {
319991
320425
  try {
319992
- return readFileSync39(path15);
320426
+ return readFileSync40(path15);
319993
320427
  } catch (e) {
319994
320428
  console.error(`could not read the ${what} at ${path15}: ${errorMessage(e)}`);
319995
320429
  process.exit(1);
@@ -320037,7 +320471,7 @@ async function runVerifyEvidenceCli2(params) {
320037
320471
  }
320038
320472
 
320039
320473
  // src/cli.ts
320040
- var VERSION14 = "0.3.11";
320474
+ var VERSION14 = "0.3.13";
320041
320475
  var bin = basename9(process.argv[1] || "");
320042
320476
  var PROG = bin === "pf" || bin === PRODUCT_SLUG ? bin : PRODUCT_SLUG;
320043
320477
  var rawArgs = process.argv.slice(2);