infraweaver 0.3.12 → 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.
- package/README.md +1 -1
- package/dist/agents/opencode.d.ts +6 -0
- package/dist/agents/quotaTokens.d.ts +6 -0
- package/dist/cli.mjs +554 -199
- package/dist/index.js +531 -176
- package/dist/internal.js +4 -4
- package/dist/mcp/git.d.ts +6 -4
- package/dist/mcp/localContext.d.ts +7 -0
- package/dist/mcp/reviewCommentLimit.d.ts +55 -5
- package/dist/mcp/reviewMarkers.d.ts +5 -0
- package/dist/mcp/staleFix.d.ts +36 -1
- package/dist/mcp/terraform/concernResult.d.ts +9 -0
- package/dist/mcp/terraform/deltaSummary.d.ts +3 -0
- package/dist/mcp/terraform/hardcodedSecrets.d.ts +24 -10
- package/dist/mcp/terraform/refactorScope.d.ts +14 -2
- package/dist/mcp/terraform/ruleContext.d.ts +3 -0
- package/dist/mcp/terraform/tree.d.ts +5 -0
- package/dist/mcp/terraform/types.d.ts +2 -1
- package/dist/mcp/terraform/verifySites.d.ts +11 -3
- package/dist/vendor/@infraweaver-io/models/tokenQuota.d.ts +7 -6
- package/package.json +2 -2
- package/src/agents/opencode.ts +8 -1
- package/src/agents/quotaTokens.ts +7 -1
- package/src/mcp/git.ts +64 -12
- package/src/mcp/localContext.ts +6 -1
- package/src/mcp/moduleExtraction.ts +5 -4
- package/src/mcp/review.ts +52 -3
- package/src/mcp/reviewCommentLimit.ts +169 -26
- package/src/mcp/reviewMarkers.ts +11 -0
- package/src/mcp/reviewProvenance.ts +1 -1
- package/src/mcp/staleFix.ts +73 -2
- package/src/mcp/terraform/concernResult.ts +17 -10
- package/src/mcp/terraform/deltaSummary.ts +4 -0
- package/src/mcp/terraform/hardcodedSecrets.ts +148 -27
- package/src/mcp/terraform/nativeRuleScanners.ts +2 -1
- package/src/mcp/terraform/nativeScan.ts +4 -0
- package/src/mcp/terraform/refactor/equivalence.ts +42 -9
- package/src/mcp/terraform/refactorScope.ts +90 -21
- package/src/mcp/terraform/ruleContext.ts +4 -0
- package/src/mcp/terraform/scanDelta.ts +67 -26
- package/src/mcp/terraform/tools/detectPlanDrift.ts +2 -0
- package/src/mcp/terraform/tools/ingestExternalFindings.ts +2 -0
- package/src/mcp/terraform/tools/normalizationCandidates.ts +5 -5
- package/src/mcp/terraform/tools/readFindings.ts +2 -0
- package/src/mcp/terraform/tools/scan.ts +3 -0
- package/src/mcp/terraform/tree.ts +8 -0
- package/src/mcp/terraform/types.ts +2 -1
- package/src/mcp/terraform/verifySites.ts +23 -5
- package/src/modes/refactor.ts +1 -1
- package/src/modes/remediate-and-refactor.ts +1 -1
- package/src/modes/remediate.ts +1 -1
- package/src/phases/runAgentWithWatchdogs.ts +3 -2
package/dist/index.js
CHANGED
|
@@ -107552,23 +107552,23 @@ var require_helpers = __commonJS({
|
|
|
107552
107552
|
"node_modules/.pnpm/ip-address@10.7.3/node_modules/ip-address/dist/v6/helpers.js"(exports2) {
|
|
107553
107553
|
"use strict";
|
|
107554
107554
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
107555
|
-
exports2.escapeHtml =
|
|
107555
|
+
exports2.escapeHtml = escapeHtml2;
|
|
107556
107556
|
exports2.spanAllZeroes = spanAllZeroes;
|
|
107557
107557
|
exports2.spanAll = spanAll;
|
|
107558
107558
|
exports2.spanLeadingZeroes = spanLeadingZeroes;
|
|
107559
107559
|
exports2.simpleGroup = simpleGroup;
|
|
107560
|
-
function
|
|
107560
|
+
function escapeHtml2(s) {
|
|
107561
107561
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
107562
107562
|
}
|
|
107563
107563
|
function spanAllZeroes(s) {
|
|
107564
|
-
return
|
|
107564
|
+
return escapeHtml2(s).replace(/(0+)/g, '<span class="zero">$1</span>');
|
|
107565
107565
|
}
|
|
107566
107566
|
function spanAll(s, offset = 0) {
|
|
107567
107567
|
const letters = s.split("");
|
|
107568
|
-
return letters.map((n, i) => `<span class="digit value-${
|
|
107568
|
+
return letters.map((n, i) => `<span class="digit value-${escapeHtml2(n)} position-${i + offset}">${spanAllZeroes(n)}</span>`).join("");
|
|
107569
107569
|
}
|
|
107570
107570
|
function spanLeadingZeroesSimple(group2) {
|
|
107571
|
-
return
|
|
107571
|
+
return escapeHtml2(group2).replace(/^(0+)/, '<span class="zero">$1</span>');
|
|
107572
107572
|
}
|
|
107573
107573
|
function spanLeadingZeroes(address) {
|
|
107574
107574
|
const groups2 = address.split(":");
|
|
@@ -160384,7 +160384,7 @@ var init_cacheTwirpClient = __esm({
|
|
|
160384
160384
|
});
|
|
160385
160385
|
|
|
160386
160386
|
// node_modules/.pnpm/@actions+cache@6.3.0_supports-color@7.2.0/node_modules/@actions/cache/lib/internal/tar.js
|
|
160387
|
-
import { existsSync as
|
|
160387
|
+
import { existsSync as existsSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
160388
160388
|
import * as path13 from "path";
|
|
160389
160389
|
function getTarPath() {
|
|
160390
160390
|
return __awaiter18(this, void 0, void 0, function* () {
|
|
@@ -160394,7 +160394,7 @@ function getTarPath() {
|
|
|
160394
160394
|
const systemTar = SystemTarPathOnWindows;
|
|
160395
160395
|
if (gnuTar) {
|
|
160396
160396
|
return { path: gnuTar, type: ArchiveToolType.GNU };
|
|
160397
|
-
} else if (
|
|
160397
|
+
} else if (existsSync16(systemTar)) {
|
|
160398
160398
|
return { path: systemTar, type: ArchiveToolType.BSD };
|
|
160399
160399
|
}
|
|
160400
160400
|
break;
|
|
@@ -250325,7 +250325,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
250325
250325
|
}
|
|
250326
250326
|
};
|
|
250327
250327
|
|
|
250328
|
-
// node_modules/.pnpm/@infraweaver-io+models@0.1.
|
|
250328
|
+
// node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/catalog.js
|
|
250329
250329
|
function provider(config3) {
|
|
250330
250330
|
return config3;
|
|
250331
250331
|
}
|
|
@@ -258848,7 +258848,7 @@ function isGeminiRouted(ctx) {
|
|
|
258848
258848
|
init_core3();
|
|
258849
258849
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
258850
258850
|
|
|
258851
|
-
// node_modules/.pnpm/@infraweaver-io+models@0.1.
|
|
258851
|
+
// node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/usage.js
|
|
258852
258852
|
function formatCostUsd(costUsd) {
|
|
258853
258853
|
return costUsd.toFixed(4);
|
|
258854
258854
|
}
|
|
@@ -259203,7 +259203,7 @@ import { randomBytes as randomBytes2 } from "node:crypto";
|
|
|
259203
259203
|
// package.json
|
|
259204
259204
|
var package_default = {
|
|
259205
259205
|
name: "infraweaver",
|
|
259206
|
-
version: "0.3.
|
|
259206
|
+
version: "0.3.13",
|
|
259207
259207
|
description: "GitHub Action that remediates Terraform to best practices and opens one scoped pull request per concern.",
|
|
259208
259208
|
keywords: [
|
|
259209
259209
|
"ai-coding-agent",
|
|
@@ -259306,7 +259306,7 @@ var package_default = {
|
|
|
259306
259306
|
"@infraweaver-io/ci": "^0.1.8",
|
|
259307
259307
|
"@infraweaver-io/core": "^0.1.5",
|
|
259308
259308
|
"@infraweaver-io/crosswalk": "^1.2.0",
|
|
259309
|
-
"@infraweaver-io/models": "^0.1.
|
|
259309
|
+
"@infraweaver-io/models": "^0.1.7",
|
|
259310
259310
|
"@infraweaver-io/policy-spec": "^1.2.0",
|
|
259311
259311
|
"@infraweaver-io/report-contract": "^0.1.5",
|
|
259312
259312
|
"@infraweaver-io/sandbox": "^0.1.8",
|
|
@@ -266110,7 +266110,18 @@ var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
|
266110
266110
|
"credential",
|
|
266111
266111
|
"credentials"
|
|
266112
266112
|
]);
|
|
266113
|
-
var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set([
|
|
266113
|
+
var SECRET_KEY_PREFIXES = /* @__PURE__ */ new Set([
|
|
266114
|
+
"api",
|
|
266115
|
+
"secret",
|
|
266116
|
+
"private",
|
|
266117
|
+
"signing",
|
|
266118
|
+
"access",
|
|
266119
|
+
"account",
|
|
266120
|
+
"client",
|
|
266121
|
+
"shared",
|
|
266122
|
+
"preshared",
|
|
266123
|
+
"master"
|
|
266124
|
+
]);
|
|
266114
266125
|
var NOT_SECRET_TOKENS = /* @__PURE__ */ new Set(["creation", "idempotency", "client", "request", "continuation"]);
|
|
266115
266126
|
function words(name) {
|
|
266116
266127
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_-]+/).filter(Boolean);
|
|
@@ -266122,24 +266133,70 @@ function isSecretName(name) {
|
|
|
266122
266133
|
if (last === void 0) return false;
|
|
266123
266134
|
if (last === "key") return prev !== void 0 && SECRET_KEY_PREFIXES.has(prev);
|
|
266124
266135
|
if (last === "token" && prev !== void 0 && NOT_SECRET_TOKENS.has(prev)) return false;
|
|
266136
|
+
if (last === "string") return prev === "connection";
|
|
266137
|
+
if (last === "secret" && w.length === 1) return false;
|
|
266125
266138
|
return SECRET_WORDS.has(last);
|
|
266126
266139
|
}
|
|
266127
266140
|
function isPlaceholder(inner) {
|
|
266128
|
-
return inner.trim() === "" || /^[*xX.]+$/.test(inner) || /^<[^>]*>$/.test(inner) || //
|
|
266129
|
-
|
|
266141
|
+
return inner.trim() === "" || /^[*xX.]+$/.test(inner) || /^<[^>]*>$/.test(inner) || /^(?:true|false|null)$/.test(inner) || /^-?\d+(?:\.\d+)?$/.test(inner) || // a template directive is computed, like an interpolation: `"%{ if … }…%{ endif }"`
|
|
266142
|
+
inner.includes("%{") || // the dummy credentials offline plans and LocalStack are given (`secret_key = "test"`,
|
|
266143
|
+
// `"example-secret"`) — but only when that is the whole value: a lowercase tail at
|
|
266144
|
+
// most, so `"Test-Pr0dDbPassw0rd!"` is a password that happens to start with a word
|
|
266145
|
+
isDummy(inner) || // a token's lifetime unit: `token_validity_units { access_token = "hours" }`
|
|
266130
266146
|
/^(?:seconds|minutes|hours|days)$/.test(inner);
|
|
266131
266147
|
}
|
|
266132
|
-
|
|
266148
|
+
function isDummy(inner) {
|
|
266149
|
+
const m = /^(?:test|mock|fake|dummy|fixture|example|placeholder|sample)([_-].*)?$/i.exec(inner);
|
|
266150
|
+
return m !== null && (m[1] === void 0 || /^[_-][a-z0-9_-]*$/.test(m[1]));
|
|
266151
|
+
}
|
|
266152
|
+
function isSecretReference(inner) {
|
|
266153
|
+
return inner.startsWith("arn:") || /^projects\/[^/]+\/secrets\//.test(inner) || /^https:\/\/[\w-]+\.vault\.azure\.net\/secrets\//i.test(inner) || /^\/[\w./-]+$/.test(inner);
|
|
266154
|
+
}
|
|
266155
|
+
var HEADER = /^(resource|data|provider|module|locals|variable|output|terraform)\b([^{]*)\{(.*)$/;
|
|
266133
266156
|
var ASSIGNMENT = /^\s*(?:"([A-Za-z_][\w-]*)"|([A-Za-z_][\w-]*))\s*=(?!=)\s*(.*)$/;
|
|
266134
266157
|
var HEREDOC = /<<-?([A-Za-z_]\w*)\s*$/;
|
|
266135
266158
|
var INLINE_PAIR = /(?:"([A-Za-z_][\w-]*)"|\b([A-Za-z_][\w-]*))\s*=(?!=)\s*("(?:[^"\\]|\\.)*")/g;
|
|
266136
|
-
function literalValue(raw2) {
|
|
266159
|
+
function literalValue(name, raw2) {
|
|
266137
266160
|
const value2 = stripTrailingComment(raw2).replace(/,$/, "").trim();
|
|
266138
|
-
|
|
266161
|
+
if (!isStringLiteral(value2)) return false;
|
|
266162
|
+
const inner = value2.slice(1, -1);
|
|
266163
|
+
if (isPlaceholder(inner) || isSecretReference(inner)) return false;
|
|
266164
|
+
return !(words(name).at(-1) === "auth" && /^[A-Z]+(?:_[A-Z]+)*$/.test(inner));
|
|
266165
|
+
}
|
|
266166
|
+
function variableName(holder) {
|
|
266167
|
+
return /^variable\s+"?([\w-]+)"?/.exec(holder)?.[1] ?? "";
|
|
266168
|
+
}
|
|
266169
|
+
function opensBlockComment(line) {
|
|
266170
|
+
for (let i = 0; i < line.length; i++) {
|
|
266171
|
+
const span = skipNonCode(line, i);
|
|
266172
|
+
if (!span) continue;
|
|
266173
|
+
if (line.startsWith("/*", i) && !line.includes("*/", i + 2)) return true;
|
|
266174
|
+
i = span.end;
|
|
266175
|
+
}
|
|
266176
|
+
return false;
|
|
266177
|
+
}
|
|
266178
|
+
function closesOnLine(rest) {
|
|
266179
|
+
let depth = 1;
|
|
266180
|
+
for (let i = 0; i < rest.length; i++) {
|
|
266181
|
+
const span = skipNonCode(rest, i);
|
|
266182
|
+
if (span) {
|
|
266183
|
+
i = span.end;
|
|
266184
|
+
continue;
|
|
266185
|
+
}
|
|
266186
|
+
if (rest[i] === "{") depth++;
|
|
266187
|
+
else if (rest[i] === "}" && --depth === 0) return true;
|
|
266188
|
+
}
|
|
266189
|
+
return false;
|
|
266139
266190
|
}
|
|
266140
266191
|
function findHardcodedSecrets(hcl2) {
|
|
266192
|
+
return findInLines(hcl2, null);
|
|
266193
|
+
}
|
|
266194
|
+
function findTfvarsSecrets(hcl2, path15) {
|
|
266195
|
+
return findInLines(hcl2, `tfvars input ${path15}`);
|
|
266196
|
+
}
|
|
266197
|
+
function findInLines(hcl2, fileHolder) {
|
|
266141
266198
|
const out = [];
|
|
266142
|
-
let holder =
|
|
266199
|
+
let holder = fileHolder;
|
|
266143
266200
|
let heredoc = null;
|
|
266144
266201
|
let inComment = false;
|
|
266145
266202
|
const lines = hcl2.split("\n");
|
|
@@ -266159,12 +266216,28 @@ function findHardcodedSecrets(hcl2) {
|
|
|
266159
266216
|
continue;
|
|
266160
266217
|
}
|
|
266161
266218
|
if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
|
|
266162
|
-
|
|
266219
|
+
if (opensBlockComment(line)) inComment = true;
|
|
266220
|
+
const header = fileHolder === null ? HEADER.exec(line) : null;
|
|
266163
266221
|
if (header) {
|
|
266164
266222
|
holder = `${header[1]}${header[2].trimEnd() ? ` ${header[2].trim()}` : ""}`;
|
|
266223
|
+
const rest = header[3];
|
|
266224
|
+
const opens2 = HEREDOC.exec(rest);
|
|
266225
|
+
if (opens2) heredoc = opens2[1];
|
|
266226
|
+
if (!holder.startsWith("output ") && !holder.startsWith("terraform")) {
|
|
266227
|
+
for (const pair of rest.matchAll(INLINE_PAIR)) {
|
|
266228
|
+
const key = pair[1] ?? pair[2];
|
|
266229
|
+
const isVariable = holder.startsWith("variable ");
|
|
266230
|
+
if (isVariable && key !== "default") continue;
|
|
266231
|
+
const name2 = isVariable ? variableName(holder) : key;
|
|
266232
|
+
if (isSecretName(name2) && literalValue(name2, pair[3])) {
|
|
266233
|
+
out.push({ name: name2, holder, line: i + 1 });
|
|
266234
|
+
}
|
|
266235
|
+
}
|
|
266236
|
+
}
|
|
266237
|
+
if (closesOnLine(rest)) holder = null;
|
|
266165
266238
|
continue;
|
|
266166
266239
|
}
|
|
266167
|
-
if (line.startsWith("}")) {
|
|
266240
|
+
if (fileHolder === null && line.startsWith("}")) {
|
|
266168
266241
|
holder = null;
|
|
266169
266242
|
continue;
|
|
266170
266243
|
}
|
|
@@ -266176,30 +266249,34 @@ function findHardcodedSecrets(hcl2) {
|
|
|
266176
266249
|
const name = m[1] ?? m[2];
|
|
266177
266250
|
const value2 = m[3];
|
|
266178
266251
|
if (holder.startsWith("variable ")) {
|
|
266179
|
-
const variable =
|
|
266180
|
-
if (name === "default" && isSecretName(variable) && literalValue(value2)) {
|
|
266252
|
+
const variable = variableName(holder);
|
|
266253
|
+
if (name === "default" && isSecretName(variable) && literalValue(variable, value2)) {
|
|
266181
266254
|
out.push({ name: variable, holder, line: i + 1 });
|
|
266182
266255
|
}
|
|
266183
266256
|
continue;
|
|
266184
266257
|
}
|
|
266185
266258
|
if (holder.startsWith("output ") || holder.startsWith("terraform")) continue;
|
|
266186
|
-
if (isSecretName(name) && literalValue(value2)) {
|
|
266259
|
+
if (isSecretName(name) && literalValue(name, value2)) {
|
|
266187
266260
|
out.push({ name, holder, line: i + 1 });
|
|
266188
266261
|
continue;
|
|
266189
266262
|
}
|
|
266190
266263
|
for (const pair of value2.matchAll(INLINE_PAIR)) {
|
|
266191
266264
|
const key = pair[1] ?? pair[2];
|
|
266192
|
-
if (isSecretName(key) && literalValue(pair[3])) {
|
|
266265
|
+
if (isSecretName(key) && literalValue(key, pair[3])) {
|
|
266193
266266
|
out.push({ name: key, holder, line: i + 1 });
|
|
266194
266267
|
}
|
|
266195
266268
|
}
|
|
266196
266269
|
}
|
|
266197
266270
|
return out;
|
|
266198
266271
|
}
|
|
266199
|
-
function scanHardcodedSecrets(files) {
|
|
266272
|
+
function scanHardcodedSecrets(files, tfvarsFiles = []) {
|
|
266200
266273
|
const concerns = [];
|
|
266201
|
-
|
|
266202
|
-
|
|
266274
|
+
const hits = [
|
|
266275
|
+
...files.map((f) => ({ path: f.path, found: findHardcodedSecrets(f.hcl) })),
|
|
266276
|
+
...tfvarsFiles.map((f) => ({ path: f.path, found: findTfvarsSecrets(f.hcl, f.path) }))
|
|
266277
|
+
];
|
|
266278
|
+
for (const { path: path15, found } of hits) {
|
|
266279
|
+
for (const hit of found) {
|
|
266203
266280
|
concerns.push(
|
|
266204
266281
|
buildConcern({
|
|
266205
266282
|
source: NATIVE_SOURCE,
|
|
@@ -268202,7 +268279,8 @@ var NATIVE_RULE_SCANNERS = [
|
|
|
268202
268279
|
name: "hardcoded-secrets",
|
|
268203
268280
|
rules: [HARDCODED_SECRET_ARGUMENT],
|
|
268204
268281
|
// Per file: the argument's name and its literal are both on the line itself.
|
|
268205
|
-
|
|
268282
|
+
// The only rule that reads `.tfvars`: an input file holds values, not config.
|
|
268283
|
+
scan: (ctx) => ({ concerns: scanHardcodedSecrets(ctx.files, ctx.tfvarsFiles) })
|
|
268206
268284
|
},
|
|
268207
268285
|
{
|
|
268208
268286
|
name: "write-only",
|
|
@@ -268287,6 +268365,7 @@ function createRuleContext(input2) {
|
|
|
268287
268365
|
cwd: input2.cwd,
|
|
268288
268366
|
files: input2.files,
|
|
268289
268367
|
tofuFiles: input2.tofuFiles ?? [],
|
|
268368
|
+
tfvarsFiles: input2.tfvarsFiles ?? [],
|
|
268290
268369
|
truncated: input2.truncated ?? null,
|
|
268291
268370
|
repeatedLiteralThreshold: input2.repeatedLiteralThreshold
|
|
268292
268371
|
};
|
|
@@ -268338,6 +268417,9 @@ function readTfTree(cwd, limits) {
|
|
|
268338
268417
|
function readTofuTree(cwd, limits) {
|
|
268339
268418
|
return readTree(cwd, ".tofu", limits);
|
|
268340
268419
|
}
|
|
268420
|
+
function readTfvarsTree(cwd, limits) {
|
|
268421
|
+
return readTree(cwd, ".tfvars", limits);
|
|
268422
|
+
}
|
|
268341
268423
|
var OVERSIZE_NAMED = 5;
|
|
268342
268424
|
function describeOversize(paths) {
|
|
268343
268425
|
const named = paths.slice(0, OVERSIZE_NAMED).join(", ");
|
|
@@ -268366,6 +268448,9 @@ function scanNative(cwd, opts = {}) {
|
|
|
268366
268448
|
// walk over a suffix most repos do not use at all, and threading it through
|
|
268367
268449
|
// the context keeps the scanners uniform.
|
|
268368
268450
|
tofuFiles: readTofuTree(cwd).files,
|
|
268451
|
+
// likewise only for the hardcoded-secret rule: a value kept out of the `.tf`
|
|
268452
|
+
// source is most often committed here instead
|
|
268453
|
+
tfvarsFiles: readTfvarsTree(cwd).files,
|
|
268369
268454
|
truncated: tree.truncated,
|
|
268370
268455
|
repeatedLiteralThreshold: opts.repeatedLiteralThreshold ?? DEFAULT_REPEAT_THRESHOLD
|
|
268371
268456
|
});
|
|
@@ -272923,7 +273008,7 @@ function TerraformChangeSummaryTool(ctx) {
|
|
|
272923
273008
|
}
|
|
272924
273009
|
|
|
272925
273010
|
// src/mcp/checkout.ts
|
|
272926
|
-
import { createHash as
|
|
273011
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
272927
273012
|
import { statSync as statSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
272928
273013
|
import { join as join36 } from "node:path";
|
|
272929
273014
|
|
|
@@ -273331,6 +273416,9 @@ import { spawnSync as spawnSync5 } from "node:child_process";
|
|
|
273331
273416
|
import { readFileSync as readFileSync12, rmSync as rmSync2 } from "node:fs";
|
|
273332
273417
|
import { join as join22 } from "node:path";
|
|
273333
273418
|
|
|
273419
|
+
// src/mcp/staleFix.ts
|
|
273420
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
273421
|
+
|
|
273334
273422
|
// node_modules/.pnpm/@infraweaver-io+ci@0.1.8/node_modules/@infraweaver-io/ci/dist/progressComment.js
|
|
273335
273423
|
function parseProgressComment(raw2) {
|
|
273336
273424
|
if (!raw2?.id)
|
|
@@ -274283,7 +274371,31 @@ function groupIdFromBranch(branch) {
|
|
|
274283
274371
|
}
|
|
274284
274372
|
function remediationBranchSuffix(base, defaultBranch) {
|
|
274285
274373
|
if (!base || !defaultBranch || base === defaultBranch) return "";
|
|
274286
|
-
|
|
274374
|
+
const slug2 = base.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
274375
|
+
if (slug2 === base) return `--${slug2}`;
|
|
274376
|
+
return `--${slug2}-${createHash6("sha1").update(base).digest("hex").slice(0, 8)}`;
|
|
274377
|
+
}
|
|
274378
|
+
function remediationBranchFor(id, base, defaultBranch, open3) {
|
|
274379
|
+
const computed = `remediate/${id}${remediationBranchSuffix(base, defaultBranch)}`;
|
|
274380
|
+
const heads = (open3 ?? []).filter((pr) => pr.base === base && groupIdFromBranch(pr.head) === id).map((pr) => pr.head).filter((head) => !(open3 ?? []).some((pr) => pr.head === head && pr.base !== base));
|
|
274381
|
+
return heads.includes(computed) ? computed : heads[0] ?? computed;
|
|
274382
|
+
}
|
|
274383
|
+
async function listOpenRemediationPrs(ctx) {
|
|
274384
|
+
const { octokit, repo } = ctx;
|
|
274385
|
+
if (ctx.toolState?.selectedMode !== "Remediate") return [];
|
|
274386
|
+
if (!octokit || !repo?.owner || !repo.name) return [];
|
|
274387
|
+
try {
|
|
274388
|
+
const open3 = await octokit.paginate(octokit.rest.pulls.list, {
|
|
274389
|
+
owner: repo.owner,
|
|
274390
|
+
repo: repo.name,
|
|
274391
|
+
state: "open",
|
|
274392
|
+
per_page: 100
|
|
274393
|
+
});
|
|
274394
|
+
return open3.filter((pr) => pr.head.repo?.id === pr.base.repo.id && groupIdFromBranch(pr.head.ref)).map((pr) => ({ head: pr.head.ref, base: pr.base.ref }));
|
|
274395
|
+
} catch (e) {
|
|
274396
|
+
log2.warning(`\xBB could not list open remediation PRs: ${errorMessage(e)}`);
|
|
274397
|
+
return [];
|
|
274398
|
+
}
|
|
274287
274399
|
}
|
|
274288
274400
|
function matchesBotIdentity(identity) {
|
|
274289
274401
|
const normalized = identity.replace(/\[bot\]$/i, "").toLowerCase();
|
|
@@ -274539,13 +274651,13 @@ function ClosePullRequestTool(ctx) {
|
|
|
274539
274651
|
}
|
|
274540
274652
|
|
|
274541
274653
|
// src/mcp/terraform/emittedArtefacts.ts
|
|
274542
|
-
import { createHash as
|
|
274654
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
274543
274655
|
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
274544
274656
|
import { resolve as resolve6 } from "node:path";
|
|
274545
274657
|
var ARTEFACT_DIR = "compliance/";
|
|
274546
274658
|
function gitBlobId(bytes) {
|
|
274547
274659
|
const content = Buffer.from(bytes, "utf8");
|
|
274548
|
-
return
|
|
274660
|
+
return createHash7("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
274549
274661
|
}
|
|
274550
274662
|
function writeEmittedArtefact(toolState, path15, bytes) {
|
|
274551
274663
|
writeFileSync4(path15, bytes);
|
|
@@ -274895,7 +275007,7 @@ function parseResourcesWithArgs(hcl2) {
|
|
|
274895
275007
|
}
|
|
274896
275008
|
|
|
274897
275009
|
// src/mcp/terraform/refactor/workspace.ts
|
|
274898
|
-
import { createHash as
|
|
275010
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
274899
275011
|
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
|
|
274900
275012
|
import { join as join21, posix as posix6 } from "node:path";
|
|
274901
275013
|
function readTreeAtRef(cwd, ref) {
|
|
@@ -274925,7 +275037,7 @@ function readWorkingTree(cwd) {
|
|
|
274925
275037
|
return files;
|
|
274926
275038
|
}
|
|
274927
275039
|
function workingTreeFingerprint(files) {
|
|
274928
|
-
const h =
|
|
275040
|
+
const h = createHash8("sha256");
|
|
274929
275041
|
for (const f of [...files].sort((a, b) => a.path.localeCompare(b.path, "en"))) {
|
|
274930
275042
|
h.update(f.path);
|
|
274931
275043
|
h.update("\0");
|
|
@@ -276636,7 +276748,7 @@ This mode standardises STRUCTURE while preserving BEHAVIOUR \u2014 the equivalen
|
|
|
276636
276748
|
- \`${t("terraform_normalization_candidates")}\` \u2192 in-place idiomatic cleanups that change SYNTAX, not behaviour (redundant whole-string \`"\${expr}"\` interpolation; legacy HCL0.11 map-argument block syntax like \`vars {\` on a \`template_file\`). These relocate NOTHING \u2014 zero \`moved {}\` blocks \u2014 and are proven by the same equivalence check.
|
|
276637
276749
|
- \`${t("terraform_consolidation_candidates")}\` \u2192 the SAME resource shape declared in several root directories (\`env/dev\` + \`env/staging\` + \`env/prod\`), with the single parameterised module they could all call already derived: which attributes are identical (module content) and which differ (per-environment inputs). A member root of a group will ALSO show up as a zero-candidate cluster in \`${t("module_extraction_candidates")}\` \u2014 prefer the consolidation, which does that work once for every environment instead of once per environment. Its \`near_groups\` are the opposite case: roots recognisably the same stack that DIFFER (one carries a resource the other does not), where folding them together is a design decision this mode has no basis for. Do NOT extract a near-group root's shared resources into a module on its own either \u2014 a module one twin calls and the other does not widens exactly the drift the near-group reports. Leave those roots alone and name the near-group and its \`differences\` in your report.
|
|
276638
276750
|
|
|
276639
|
-
**A
|
|
276751
|
+
**A candidate marked \`report_only\` is an observation, not work.** It sits in the code of a module other configurations call (\`report_only_reason\` says why), and restructuring that is churn for every caller: name those candidates in your \`${t("report_progress")}\` summary and open no PR for them. \`examples/\` roots are left out of every candidate list unless the operator set \`refactor_examples\`.
|
|
276640
276752
|
|
|
276641
276753
|
**Before picking, check the interface.** \`${t("module_extraction_candidates")}\` reports \`unmapped_attributes\` per candidate and a top-level \`partial_interfaces\` list: arguments the raw resources SET that the target module does not. Adopting such a module DROPS them silently \u2014 and the equivalence check cannot catch it, because once the argument is gone it is absent from both sides. Two of these are unrecoverable rather than merely wrong (\`object_lock_enabled\` cannot be set after creation; \`force_destroy\` changes deletion semantics). So: **decline the candidate, or extend the module to expose the argument and say so in the PR body.** Deleting the argument to make the shapes match is never the fix. A candidate whose \`unmapped_attributes\` is \`null\` was NOT checked \u2014 the module is external and its code is not in this repo \u2014 which is not the same as clean.
|
|
276642
276754
|
|
|
@@ -276769,7 +276881,7 @@ function remediateMode(t) {
|
|
|
276769
276881
|
|
|
276770
276882
|
4. **for the chosen group**:
|
|
276771
276883
|
- **base branch**: this run's base branch is resolved deterministically \u2014 \`${t("create_pull_request")}\` targets the \`base_branch\` input if set, else the branch the run started on, else the repository's default branch (\`main\`, or \`master\`). You do not choose it; just **omit** the \`base\` argument when opening the PR (below) and it is filled in.
|
|
276772
|
-
- **idempotency**: the remediation branch is the group's \`branch\` (\`remediate/<group-id>\`, with a \`--<base>\` suffix when the PR targets a non-default branch) \u2014 use it exactly, never a name of your own. Before doing anything, check whether that branch or an open PR for it already exists (\`${t("git")}\` / \`${t("get_pull_request")}\`). If one exists, update it rather than opening a duplicate.
|
|
276884
|
+
- **idempotency**: the remediation branch is the group's \`branch\` (\`remediate/<group-id>\`, with a \`--<base>\` suffix when the PR targets a non-default branch, or the branch of the PR already open for this group) \u2014 use it exactly, never a name of your own. Before doing anything, check whether that branch or an open PR for it already exists (\`${t("git")}\` / \`${t("get_pull_request")}\`). If one exists, update it rather than opening a duplicate.
|
|
276773
276885
|
- **branch**: create the group's \`branch\` from the **current HEAD** (the checkout that was just scanned) via \`${t("git")}\` (\`git({ command: "checkout", args: ["-b", <branch>] })\`). Do NOT switch to a different base first \u2014 branching from the scanned checkout keeps the PR diff to exactly your fix.
|
|
276774
276886
|
- **honest refusal (decide BEFORE fixing)**: if the group's concerns appear in the scan's \`refusal_candidates\` (the fix needs a human decision \u2014 narrowing an IAM wildcard, a KMS key policy, a real ingress CIDR), do **not** guess a fix that could break the stack. Instead open a structured issue (\`${t("create_issue")}\`) describing the concern, why it isn't auto-fixed, and what a human should do, and skip the PR for that group. A proven fix or an honest refusal \u2014 never a guessed, unverifiable PR.
|
|
276775
276887
|
- **propose, then let me steer (when there's no single right fix)**: distinct from honest refusal (which refuses a fix a human must *decide*), this is for a finding with **2\u20133 genuinely distinct, defensible fixes** that differ in trade-offs, not correctness (e.g. encrypt with an AWS-managed key **vs** a customer-managed KMS key; a narrow security-group rule **vs** a prefix list **vs** a VPC endpoint). When such a fork exists **and the triggering comment did not already select a strategy**, do **not** silently pick for the reviewer: via \`${t("create_issue_comment")}\` post one short comment listing the options as **A / B / C** \u2014 each a single line (what it does + its trade-off) \u2014 and ask the reviewer to reply \`${COMMENT_COMMAND} fix #<concern-id> with strategy <A|B|C>\`. Then **skip the PR for this group** this run and note it in your final report (it resumes when the reviewer replies). When the comment **did** select one (\`fix #<id> with strategy B\`, or a bare \`strategy B\` reply on the proposal thread), apply **exactly** that strategy \u2014 don't second-guess it. Reserve this for real forks in the road; a fix with one obvious correct answer just gets made.
|
|
@@ -276830,7 +276942,7 @@ This mode composes the **Remediate** and **Refactor** verbs into a single run. I
|
|
|
276830
276942
|
- **commit the fix** (\`git add\` only the changed \`*.tf\`/\`*.tfvars\`, a \`fix(tf): \u2026\` message). Do NOT push yet. This committed state is the refactor's equivalence BASELINE.
|
|
276831
276943
|
|
|
276832
276944
|
3. **REFACTOR (phase 2) \u2014 modularise on top of the committed fix**: follow the **Refactor** mode's flow:
|
|
276833
|
-
- \`${t("module_extraction_candidates")}\` / \`${t("terraform_normalization_candidates")}\` \u2192 pick one behaviour-preserving refactor; a
|
|
276945
|
+
- \`${t("module_extraction_candidates")}\` / \`${t("terraform_normalization_candidates")}\` \u2192 pick one behaviour-preserving refactor; a candidate marked \`report_only\` (it sits in the code of a module others call) is named in the summary, never proposed. Respect a pinned \`refactor_source\` if one was supplied (it constrains which module source you may use; the behaviour-altering b.4 path is only available when the operator pinned \`third-party\`).
|
|
276834
276946
|
- resolve the module source, wire the \`module\` call against its real interface (\`${t("terraform_module_interface")}\` for a local module dir, \`${t("terraform_module_lookup")}\` for a registry-sourced one), and emit a \`moved {}\` block for EVERY relocated address (\`${t("terraform_generate_moved")}\`).
|
|
276835
276947
|
- \`terraform fmt\` + \`${t("terraform_validate")}\`, then \u2014 **with the refactor edits still UNCOMMITTED** \u2014 call \`${t("terraform_equivalence_check")}\`. In this mode it diffs the working tree against the COMMITTED fix (not the run-start commit), so it measures only the refactor. It must return \`equivalent: true\` (zero uncovered moves, resource set + arguments preserved, validate + fmt clean) before you proceed; \`${t("push_branch")}\` hard-blocks an unproven one \u2014 and, once this run has added a \`moved {}\` block, it also hard-blocks a push that never ran the check at all, so abandoning the refactor means REVERTING its edits rather than leaving them in with the fix. If it can't be proven equivalent, abandon the refactor and ship the fix alone.
|
|
276836
276948
|
- **document the module (only when the \`docs\` input is enabled)**: after the equivalence check passes, call \`${t("terraform_module_docs")}\` for the module you created/adopted (pass the relocated addresses as \`moves\`). Do not hand-write docs \u2014 the tool builds from the interface, moved blocks, and equivalence verdict.
|
|
@@ -278383,15 +278495,52 @@ var Git = type({
|
|
|
278383
278495
|
args: type.string.array().describe("Additional arguments for the git command").optional()
|
|
278384
278496
|
});
|
|
278385
278497
|
function splitWords(line) {
|
|
278386
|
-
|
|
278498
|
+
const words2 = [];
|
|
278499
|
+
let word = "";
|
|
278500
|
+
let inWord = false;
|
|
278501
|
+
let quote;
|
|
278502
|
+
for (let i = 0; i < line.length; i++) {
|
|
278503
|
+
const ch = line.charAt(i);
|
|
278504
|
+
if (quote === "'") {
|
|
278505
|
+
if (ch === "'") quote = void 0;
|
|
278506
|
+
else word += ch;
|
|
278507
|
+
} else if (quote === '"') {
|
|
278508
|
+
if (ch === '"') quote = void 0;
|
|
278509
|
+
else if (ch === "\\" && i + 1 < line.length && '"\\$`\n'.includes(line.charAt(i + 1))) {
|
|
278510
|
+
i += 1;
|
|
278511
|
+
if (line.charAt(i) !== "\n") word += line.charAt(i);
|
|
278512
|
+
} else word += ch;
|
|
278513
|
+
} else if (ch === " " || ch === " ") {
|
|
278514
|
+
if (inWord) words2.push(word);
|
|
278515
|
+
word = "";
|
|
278516
|
+
inWord = false;
|
|
278517
|
+
} else if (ch === "\n") {
|
|
278518
|
+
throw new Error(
|
|
278519
|
+
"command holds an unquoted newline \u2014 pass one git command per call, and quote a multi-line message"
|
|
278520
|
+
);
|
|
278521
|
+
} else if (ch === "\\") {
|
|
278522
|
+
if (i + 1 >= line.length) throw new Error("command ends in a backslash that escapes nothing");
|
|
278523
|
+
i += 1;
|
|
278524
|
+
if (line.charAt(i) === "\n") continue;
|
|
278525
|
+
word += line.charAt(i);
|
|
278526
|
+
inWord = true;
|
|
278527
|
+
} else if (ch === "'" || ch === '"') {
|
|
278528
|
+
quote = ch;
|
|
278529
|
+
inWord = true;
|
|
278530
|
+
} else {
|
|
278531
|
+
word += ch;
|
|
278532
|
+
inWord = true;
|
|
278533
|
+
}
|
|
278534
|
+
}
|
|
278535
|
+
if (quote) throw new Error(`command has an unterminated ${quote} quote`);
|
|
278536
|
+
if (inWord) words2.push(word);
|
|
278537
|
+
return words2;
|
|
278387
278538
|
}
|
|
278388
278539
|
function normalizeGitCall(params) {
|
|
278389
|
-
|
|
278390
|
-
|
|
278391
|
-
|
|
278392
|
-
|
|
278393
|
-
else return { command: params.command, args: params.args ?? [] };
|
|
278394
|
-
const [command, ...args2] = words2[0] === "git" ? words2.slice(1) : words2;
|
|
278540
|
+
const words2 = params.command === void 0 ? [] : /^git\s/.test(params.command) ? splitWords(params.command) : [params.command];
|
|
278541
|
+
words2.push(...params.args ?? []);
|
|
278542
|
+
while (words2[0] === "git") words2.shift();
|
|
278543
|
+
const [command, ...args2] = words2;
|
|
278395
278544
|
if (command === void 0 || !/^[a-z][a-z0-9-]*$/.test(command)) {
|
|
278396
278545
|
throw new Error(
|
|
278397
278546
|
`command must be a Git subcommand such as 'status' or 'checkout' (was "${command ?? ""}")`
|
|
@@ -278827,7 +278976,7 @@ function claimsBlockingSeverity(body2) {
|
|
|
278827
278976
|
init_core3();
|
|
278828
278977
|
import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
|
|
278829
278978
|
|
|
278830
|
-
// node_modules/.pnpm/@infraweaver-io+models@0.1.
|
|
278979
|
+
// node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/monotonicQuota.js
|
|
278831
278980
|
function createMonotonicQuota(params) {
|
|
278832
278981
|
const { limit, unlimited, normalize: normalize5 } = params;
|
|
278833
278982
|
let used = 0;
|
|
@@ -278852,7 +279001,7 @@ function createMonotonicQuota(params) {
|
|
|
278852
279001
|
};
|
|
278853
279002
|
}
|
|
278854
279003
|
|
|
278855
|
-
// node_modules/.pnpm/@infraweaver-io+models@0.1.
|
|
279004
|
+
// node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/costQuota.js
|
|
278856
279005
|
var CostQuotaExceededError = class extends Error {
|
|
278857
279006
|
code = "COST_QUOTA_EXCEEDED";
|
|
278858
279007
|
constructor(message2) {
|
|
@@ -278899,7 +279048,7 @@ function resolveRunCostQuota(parsed2) {
|
|
|
278899
279048
|
return parsed2 === void 0 ? DEFAULT_RUN_COST_QUOTA_USD : parsed2;
|
|
278900
279049
|
}
|
|
278901
279050
|
|
|
278902
|
-
// node_modules/.pnpm/@infraweaver-io+models@0.1.
|
|
279051
|
+
// node_modules/.pnpm/@infraweaver-io+models@0.1.7/node_modules/@infraweaver-io/models/dist/tokenQuota.js
|
|
278903
279052
|
var TokenQuotaExceededError = class extends Error {
|
|
278904
279053
|
code = "TOKEN_QUOTA_EXCEEDED";
|
|
278905
279054
|
constructor(message2) {
|
|
@@ -278909,19 +279058,16 @@ var TokenQuotaExceededError = class extends Error {
|
|
|
278909
279058
|
};
|
|
278910
279059
|
var DEFAULT_RUN_TOKEN_QUOTA = 1e7;
|
|
278911
279060
|
var RUN_TOKEN_QUOTA_INPUT = "max_run_tokens";
|
|
278912
|
-
|
|
278913
|
-
|
|
278914
|
-
|
|
278915
|
-
|
|
278916
|
-
total += v;
|
|
278917
|
-
}
|
|
278918
|
-
return Math.floor(total);
|
|
279061
|
+
var CACHE_READ_WEIGHT = 0.1;
|
|
279062
|
+
function quotaTokens(t) {
|
|
279063
|
+
const part = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
279064
|
+
return Math.floor(part(t.input) + part(t.output) + part(t.cacheWrite) + part(t.cacheRead) * CACHE_READ_WEIGHT);
|
|
278919
279065
|
}
|
|
278920
279066
|
function normalizeTotal(total) {
|
|
278921
|
-
return typeof total === "number" ? Math.max(0, Math.floor(total)) :
|
|
279067
|
+
return typeof total === "number" ? Math.max(0, Math.floor(total)) : quotaTokens(total);
|
|
278922
279068
|
}
|
|
278923
279069
|
function tokenGap(used, ceiling) {
|
|
278924
|
-
return `run token quota exhausted: ${used.toLocaleString("en-GB")} of ${ceiling.toLocaleString("en-GB")}
|
|
279070
|
+
return `run token quota exhausted: ${used.toLocaleString("en-GB")} of ${ceiling.toLocaleString("en-GB")} tokens counted against the quota (a token read from the prompt cache counts as a tenth), so the run was stopped before it finished. Its results are INCOMPLETE and must not be read as a clean assessment \u2014 a finding that would have been raised after this point was never looked for. Raise \`${RUN_TOKEN_QUOTA_INPUT}\` if the run legitimately needs more budget, or set \`${RUN_TOKEN_QUOTA_INPUT}: 0\` to remove the bound entirely. A run that reaches this limit without finishing is more often a remediation loop that cannot converge (a fix that re-breaks its own check) than a genuinely large repository.`;
|
|
278925
279071
|
}
|
|
278926
279072
|
function unlimitedTokenQuota() {
|
|
278927
279073
|
return createMonotonicQuota({
|
|
@@ -280064,6 +280210,12 @@ function parseCommentMarker(body2) {
|
|
|
280064
280210
|
return null;
|
|
280065
280211
|
}
|
|
280066
280212
|
}
|
|
280213
|
+
function findingKeysIn(body2) {
|
|
280214
|
+
return [...body2.matchAll(new RegExp(MARKER.source, "g"))].flatMap((m) => {
|
|
280215
|
+
const marker2 = parseCommentMarker(m[0]);
|
|
280216
|
+
return marker2?.kind === "finding" ? [marker2.key] : [];
|
|
280217
|
+
});
|
|
280218
|
+
}
|
|
280067
280219
|
var SUGGESTION = /```suggestion\r?\n([\s\S]*?)```/;
|
|
280068
280220
|
function suggestionText(body2) {
|
|
280069
280221
|
const m = SUGGESTION.exec(body2);
|
|
@@ -280220,23 +280372,54 @@ function computeScanDelta(input2) {
|
|
|
280220
280372
|
};
|
|
280221
280373
|
}
|
|
280222
280374
|
function markPossibleRenames(introduced, resolved, headAddresses) {
|
|
280223
|
-
const
|
|
280224
|
-
|
|
280225
|
-
|
|
280226
|
-
|
|
280375
|
+
const byResource = (findings) => {
|
|
280376
|
+
const out = /* @__PURE__ */ new Map();
|
|
280377
|
+
for (const f of [...findings].sort((a, b) => byLocation(a.concern, b.concern))) {
|
|
280378
|
+
if (f.address === null) continue;
|
|
280379
|
+
const dir = dirOf3(f.concern.location.file);
|
|
280380
|
+
const key = `${dir}|${f.address}`;
|
|
280381
|
+
const entry4 = out.get(key);
|
|
280382
|
+
if (entry4) entry4.findings.push(f);
|
|
280383
|
+
else out.set(key, { dir, address: f.address, findings: [f] });
|
|
280384
|
+
}
|
|
280385
|
+
return [...out.values()];
|
|
280386
|
+
};
|
|
280387
|
+
const vanished = byResource(resolved).filter((r) => !headAddresses(r.dir).has(r.address));
|
|
280388
|
+
const appeared = byResource(introduced);
|
|
280389
|
+
const shared = (from, to) => {
|
|
280390
|
+
const left = /* @__PURE__ */ new Map();
|
|
280391
|
+
for (const f of from.findings)
|
|
280392
|
+
left.set(ruleKey(f.concern), (left.get(ruleKey(f.concern)) ?? 0) + 1);
|
|
280393
|
+
return to.findings.filter((f) => {
|
|
280394
|
+
const n = left.get(ruleKey(f.concern)) ?? 0;
|
|
280395
|
+
if (n === 0) return false;
|
|
280396
|
+
left.set(ruleKey(f.concern), n - 1);
|
|
280397
|
+
return true;
|
|
280398
|
+
});
|
|
280399
|
+
};
|
|
280400
|
+
const order2 = (a, b) => a.dir.localeCompare(b.dir) || a.address.localeCompare(b.address);
|
|
280401
|
+
const candidates = vanished.flatMap(
|
|
280402
|
+
(from) => appeared.filter(
|
|
280403
|
+
(to) => (
|
|
280404
|
+
// a `moved` block cannot change a resource's type
|
|
280405
|
+
typeOf(from.address) === typeOf(to.address) && // renamed in place, or moved elsewhere under its own name
|
|
280406
|
+
from.dir === to.dir !== (from.address === to.address)
|
|
280407
|
+
)
|
|
280408
|
+
).map((to) => ({ from, to, findings: shared(from, to) })).filter((p) => p.findings.length > 0)
|
|
280409
|
+
);
|
|
280410
|
+
candidates.sort(
|
|
280411
|
+
(a, b) => b.findings.length - a.findings.length || order2(a.from, b.from) || order2(a.to, b.to)
|
|
280412
|
+
);
|
|
280227
280413
|
const used = /* @__PURE__ */ new Set();
|
|
280228
|
-
for (const
|
|
280229
|
-
if (
|
|
280230
|
-
|
|
280231
|
-
|
|
280232
|
-
|
|
280233
|
-
|
|
280234
|
-
|
|
280235
|
-
|
|
280236
|
-
|
|
280237
|
-
used.add(match5);
|
|
280238
|
-
finding.possiblyPreexisting = true;
|
|
280239
|
-
finding.renamedFrom = { address: match5.address, file: match5.concern.location.file };
|
|
280414
|
+
for (const { from, to, findings } of candidates) {
|
|
280415
|
+
if (used.has(from) || used.has(to)) continue;
|
|
280416
|
+
used.add(from);
|
|
280417
|
+
used.add(to);
|
|
280418
|
+
const file2 = from.findings[0]?.concern.location.file;
|
|
280419
|
+
for (const finding of findings) {
|
|
280420
|
+
finding.possiblyPreexisting = true;
|
|
280421
|
+
finding.renamedFrom = { address: from.address, file: file2 };
|
|
280422
|
+
}
|
|
280240
280423
|
}
|
|
280241
280424
|
}
|
|
280242
280425
|
function typeOf(address) {
|
|
@@ -280505,39 +280688,115 @@ function formatDuplicateCommentsNote(duplicates) {
|
|
|
280505
280688
|
var MAX_DUPLICATE_COMMENT_LINES = 50;
|
|
280506
280689
|
|
|
280507
280690
|
// src/mcp/reviewCommentLimit.ts
|
|
280508
|
-
var
|
|
280691
|
+
var MAX_NOTE_CHARS = 3e4;
|
|
280692
|
+
var TRAILER_RESERVE = 400;
|
|
280509
280693
|
var PROVEN_MARK = "\u2705 **Proven by re-scan**";
|
|
280510
|
-
|
|
280694
|
+
var SEVERITY_LEVEL = {
|
|
280695
|
+
critical: 0,
|
|
280696
|
+
high: 1,
|
|
280697
|
+
medium: 2,
|
|
280698
|
+
low: 3,
|
|
280699
|
+
info: 4
|
|
280700
|
+
};
|
|
280701
|
+
var MARKER_LEVEL = { "\u{1F6A8}": 1, "\u26A0\uFE0F": 2, "\u26A0": 2, "\u2139\uFE0F": 3, \u2139: 3 };
|
|
280702
|
+
var UNMARKED_LEVEL = 2;
|
|
280703
|
+
function rank(c, severity) {
|
|
280511
280704
|
const body2 = c.body ?? "";
|
|
280512
|
-
const
|
|
280513
|
-
|
|
280514
|
-
return severity + (body2.includes(PROVEN_MARK) ? 0 : 1);
|
|
280705
|
+
const level = severity ? SEVERITY_LEVEL[severity] : MARKER_LEVEL[leadingMarker(body2) ?? ""] ?? UNMARKED_LEVEL;
|
|
280706
|
+
return level * 2 + (body2.includes(PROVEN_MARK) ? 0 : 1);
|
|
280515
280707
|
}
|
|
280516
|
-
function limitInlineComments(comments, limit) {
|
|
280708
|
+
function limitInlineComments(comments, limit, severityOf) {
|
|
280517
280709
|
if (!limit || comments.length <= limit) return { kept: [...comments], overflow: [] };
|
|
280518
280710
|
const keep = new Set(
|
|
280519
|
-
comments.map((c, i) => ({ i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).slice(0, limit).map(({ i }) => i)
|
|
280711
|
+
comments.map((c, i) => ({ i, r: rank(c, severityOf?.(c)) })).sort((a, b) => a.r - b.r || a.i - b.i).slice(0, limit).map(({ i }) => i)
|
|
280520
280712
|
);
|
|
280521
280713
|
return {
|
|
280522
280714
|
kept: comments.filter((_, i) => keep.has(i)),
|
|
280523
280715
|
overflow: comments.filter((_, i) => !keep.has(i))
|
|
280524
280716
|
};
|
|
280525
280717
|
}
|
|
280526
|
-
|
|
280718
|
+
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
280719
|
+
var whereOf = (c) => c.line ? `${c.path}:${c.line}` : c.path;
|
|
280720
|
+
function closesItself(body2) {
|
|
280721
|
+
const occurrences = (re) => body2.match(re)?.length ?? 0;
|
|
280722
|
+
const fences = body2.split("\n").filter((l) => /^\s*(```|~~~)/.test(l)).length;
|
|
280723
|
+
return fences % 2 === 0 && occurrences(/<details\b/gi) === occurrences(/<\/details\s*>/gi) && occurrences(/<!--/g) === occurrences(/-->/g);
|
|
280724
|
+
}
|
|
280725
|
+
function asText(body2) {
|
|
280726
|
+
const longest = Math.max(0, ...(body2.match(/`+/g) ?? []).map((run2) => run2.length));
|
|
280727
|
+
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
280728
|
+
return `${fence}text
|
|
280729
|
+
${body2}
|
|
280730
|
+
${fence}`;
|
|
280731
|
+
}
|
|
280732
|
+
function renderWhole(c, linkOf) {
|
|
280733
|
+
const where4 = escapeHtml(whereOf(c));
|
|
280734
|
+
const gist = gistOf(c.body);
|
|
280735
|
+
const href = linkOf?.(c);
|
|
280736
|
+
const body2 = c.body ?? "";
|
|
280737
|
+
return [
|
|
280738
|
+
`<details><summary><code>${where4}</code>${gist ? ` \u2014 ${gist}` : ""}</summary>`,
|
|
280739
|
+
...href ? [`<a href="${escapeHtml(href)}">${where4}</a>`] : [],
|
|
280740
|
+
...body2 ? [closesItself(body2) ? body2 : asText(body2)] : [],
|
|
280741
|
+
"</details>"
|
|
280742
|
+
].join("\n\n");
|
|
280743
|
+
}
|
|
280744
|
+
function renderBrief(c) {
|
|
280745
|
+
const gist = gistOf(c.body);
|
|
280746
|
+
const markers = findingKeysIn(c.body ?? "").map(
|
|
280747
|
+
(key) => renderCommentMarker({ kind: "finding", key, proven: false })
|
|
280748
|
+
);
|
|
280749
|
+
return `- \`${whereOf(c).replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}${markers.map((m) => ` ${m}`).join("")}`;
|
|
280750
|
+
}
|
|
280751
|
+
function formatOverflowNote(overflow, limit, linkOf) {
|
|
280527
280752
|
if (overflow.length === 0) return "";
|
|
280528
|
-
const
|
|
280529
|
-
const where4 = c.line ? `${c.path}:${c.line}` : c.path;
|
|
280530
|
-
const gist = gistOf(c.body);
|
|
280531
|
-
return `- \`${where4.replace(/`/g, "'")}\`${gist ? ` \u2014 ${gist}` : ""}`;
|
|
280532
|
-
});
|
|
280533
|
-
const rest = overflow.length - shown.length;
|
|
280534
|
-
if (rest > 0) shown.push(`- \u2026and ${rest} more not shown`);
|
|
280535
|
-
return `
|
|
280753
|
+
const header = `
|
|
280536
280754
|
|
|
280537
280755
|
---
|
|
280538
280756
|
|
|
280539
|
-
**Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead
|
|
280540
|
-
|
|
280757
|
+
**Note:** this review posts at most ${limit} inline comments (\`review_comment_limit\`), so ${overflow.length} more ${overflow.length === 1 ? "is" : "are"} listed here instead:`;
|
|
280758
|
+
const SEP = 2;
|
|
280759
|
+
let room = MAX_NOTE_CHARS - TRAILER_RESERVE - header.length;
|
|
280760
|
+
const items = [];
|
|
280761
|
+
for (const c of overflow) {
|
|
280762
|
+
const brief2 = renderBrief(c);
|
|
280763
|
+
if (brief2.length + SEP > room) break;
|
|
280764
|
+
items.push(brief2);
|
|
280765
|
+
room -= brief2.length + SEP;
|
|
280766
|
+
}
|
|
280767
|
+
let brief = items.length;
|
|
280768
|
+
for (const [i, c] of overflow.slice(0, items.length).entries()) {
|
|
280769
|
+
const whole = renderWhole(c, linkOf);
|
|
280770
|
+
const extra = whole.length - items[i].length;
|
|
280771
|
+
if (extra > room) continue;
|
|
280772
|
+
items[i] = whole;
|
|
280773
|
+
room -= extra;
|
|
280774
|
+
brief--;
|
|
280775
|
+
}
|
|
280776
|
+
const omitted = overflow.length - items.length;
|
|
280777
|
+
if (omitted > 0) items.push(`- \u2026and ${omitted} more not shown`);
|
|
280778
|
+
const cut = [
|
|
280779
|
+
brief > 0 ? `${brief} of them ${brief === 1 ? "is" : "are"} shown by place and first line only` : "",
|
|
280780
|
+
omitted > 0 ? `${omitted} ${omitted === 1 ? "is" : "are"} not shown at all` : ""
|
|
280781
|
+
].filter(Boolean);
|
|
280782
|
+
if (cut.length > 0) {
|
|
280783
|
+
items.push(`${cut.join(" and ")}, to keep this review within GitHub's size limit.`);
|
|
280784
|
+
}
|
|
280785
|
+
return `${header}
|
|
280786
|
+
|
|
280787
|
+
${items.join("\n\n")}`;
|
|
280788
|
+
}
|
|
280789
|
+
function foldedFindingsStillIntroduced(input2) {
|
|
280790
|
+
const folded = new Set(
|
|
280791
|
+
input2.reviews.flatMap(
|
|
280792
|
+
(r) => r.body && isOwnReviewComment(r.user, r.body) ? findingKeysIn(r.body) : []
|
|
280793
|
+
)
|
|
280794
|
+
);
|
|
280795
|
+
if (folded.size === 0) return [];
|
|
280796
|
+
const dismissed = new Set(input2.dismissed.map((d) => d.finding_id));
|
|
280797
|
+
return input2.delta.introduced.filter(
|
|
280798
|
+
(f) => folded.has(findingKey(f)) && !dismissed.has(f.concern.id)
|
|
280799
|
+
);
|
|
280541
280800
|
}
|
|
280542
280801
|
|
|
280543
280802
|
// src/mcp/reviewComments.ts
|
|
@@ -281368,7 +281627,8 @@ function deltaVerdict(delta, requiredControls) {
|
|
|
281368
281627
|
return {
|
|
281369
281628
|
pass: gate.pass && required3.length === 0,
|
|
281370
281629
|
gating: gate.gating.length,
|
|
281371
|
-
required: required3.length
|
|
281630
|
+
required: required3.length,
|
|
281631
|
+
blocking: (/* @__PURE__ */ new Set([...gate.gating, ...required3])).size
|
|
281372
281632
|
};
|
|
281373
281633
|
}
|
|
281374
281634
|
function failsGate(finding, requiredControls) {
|
|
@@ -281495,7 +281755,7 @@ function where2(f) {
|
|
|
281495
281755
|
function renderReviewVerdict(input2) {
|
|
281496
281756
|
const { delta, verdict: verdict2 } = input2;
|
|
281497
281757
|
const introduced = delta.introduced.length;
|
|
281498
|
-
const blocking = verdict2
|
|
281758
|
+
const { blocking } = verdict2;
|
|
281499
281759
|
const renamedBlocking = delta.introduced.filter(
|
|
281500
281760
|
(f) => f.possiblyPreexisting && failsGate(f, [...input2.requiredControls ?? []])
|
|
281501
281761
|
).length;
|
|
@@ -281604,7 +281864,7 @@ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, rmSync as
|
|
|
281604
281864
|
import { join as join28, posix as posix8 } from "node:path";
|
|
281605
281865
|
|
|
281606
281866
|
// src/mcp/terraform/lineage.ts
|
|
281607
|
-
import { createHash as
|
|
281867
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
281608
281868
|
import { readFileSync as readFileSync15 } from "node:fs";
|
|
281609
281869
|
import { join as join27 } from "node:path";
|
|
281610
281870
|
function repoFileReader(cwd) {
|
|
@@ -281617,7 +281877,7 @@ function repoFileReader(cwd) {
|
|
|
281617
281877
|
};
|
|
281618
281878
|
}
|
|
281619
281879
|
function lineageKeyOf(c, address) {
|
|
281620
|
-
return
|
|
281880
|
+
return createHash9("sha1").update(`${c.source}|${bareRuleOf(c.source, c.rule_id)}|${c.location.file}|${address ?? ""}`).digest("hex").slice(0, 16);
|
|
281621
281881
|
}
|
|
281622
281882
|
function lineageResolver(read3) {
|
|
281623
281883
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -282249,15 +282509,17 @@ function siteOf(c, read3) {
|
|
|
282249
282509
|
const text = read3(c.location.file);
|
|
282250
282510
|
return text === null ? null : resourceAddressAt(text, c.location.line);
|
|
282251
282511
|
}
|
|
282252
|
-
function recordConcernSites(session, concerns, cwd) {
|
|
282512
|
+
function recordConcernSites(session, concerns, cwd, linesMatchTree) {
|
|
282253
282513
|
const read3 = fileReader(cwd);
|
|
282254
282514
|
session.siteById ??= /* @__PURE__ */ new Map();
|
|
282255
282515
|
session.firstSitesByKey ??= /* @__PURE__ */ new Map();
|
|
282256
282516
|
const first = session.firstSitesByKey;
|
|
282257
282517
|
const fresh = /* @__PURE__ */ new Map();
|
|
282258
282518
|
for (const c of concerns) {
|
|
282259
|
-
const site = siteOf(c, read3);
|
|
282260
|
-
session.siteById.
|
|
282519
|
+
const site = linesMatchTree ? siteOf(c, read3) : null;
|
|
282520
|
+
const prior = session.siteById.get(c.id);
|
|
282521
|
+
session.siteById.set(c.id, prior === void 0 || prior === site ? site : null);
|
|
282522
|
+
if (!linesMatchTree) continue;
|
|
282261
282523
|
const key = concernKeyOf(c);
|
|
282262
282524
|
if (first.has(key)) continue;
|
|
282263
282525
|
const sites = fresh.get(key) ?? /* @__PURE__ */ new Set();
|
|
@@ -282313,7 +282575,7 @@ function buildConcernResult(ctx, rawConcerns, opts) {
|
|
|
282313
282575
|
fullBaseline.map((c) => concernKeyOf(c))
|
|
282314
282576
|
);
|
|
282315
282577
|
recordConcernKeys(session, fullBaseline);
|
|
282316
|
-
recordConcernSites(session, fullBaseline, opts.cwd);
|
|
282578
|
+
recordConcernSites(session, fullBaseline, opts.cwd, opts.linesMatchTree ?? false);
|
|
282317
282579
|
session.baselineFmtDriftFiles = union3(
|
|
282318
282580
|
session.baselineFmtDriftFiles,
|
|
282319
282581
|
fullBaseline.filter((c) => c.source === "terraform-fmt").map((c) => c.location.file)
|
|
@@ -282328,13 +282590,13 @@ function buildConcernResult(ctx, rawConcerns, opts) {
|
|
|
282328
282590
|
active,
|
|
282329
282591
|
opts.autonomyThreshold
|
|
282330
282592
|
);
|
|
282331
|
-
const
|
|
282332
|
-
|
|
282333
|
-
|
|
282334
|
-
);
|
|
282335
|
-
const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: `remediate/${g.id}${suffix2}` }));
|
|
282593
|
+
const base = remediationBaseOf(ctx);
|
|
282594
|
+
const defaultBranch = ctx.repo?.data.default_branch?.trim() || void 0;
|
|
282595
|
+
const branchFor = (id) => remediationBranchFor(id, base, defaultBranch, opts.openRemediationPrs);
|
|
282596
|
+
const groups2 = (opts.priority ? prioritizeGroups(annotatedGroups, active, opts.priority) : annotatedGroups).map((g) => ({ ...g, branch: branchFor(g.id) }));
|
|
282336
282597
|
const planned = planBatches(groups2);
|
|
282337
|
-
const
|
|
282598
|
+
const batchId = planned.batch_branch ? groupIdFromBranch(planned.batch_branch) : null;
|
|
282599
|
+
const batchPlan = batchId ? { ...planned, batch_branch: branchFor(batchId) } : planned;
|
|
282338
282600
|
const bySeverity = {};
|
|
282339
282601
|
for (const c of active) bySeverity[c.severity] = (bySeverity[c.severity] ?? 0) + 1;
|
|
282340
282602
|
return {
|
|
@@ -282630,7 +282892,9 @@ async function runScan(ctx, {
|
|
|
282630
282892
|
grouping,
|
|
282631
282893
|
autonomyThreshold,
|
|
282632
282894
|
inScope,
|
|
282633
|
-
priority
|
|
282895
|
+
priority,
|
|
282896
|
+
openRemediationPrs: await listOpenRemediationPrs(ctx),
|
|
282897
|
+
linesMatchTree: true
|
|
282634
282898
|
}
|
|
282635
282899
|
);
|
|
282636
282900
|
const { active, suppressed, groups: groups2, batchPlan, bySeverity: by_severity } = result;
|
|
@@ -283271,6 +283535,22 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
283271
283535
|
`cannot approve: ${outstanding} unresolved ${PRODUCT_NAME} review thread(s) still open on this PR. approval requires every prior ${PRODUCT_NAME} finding to be resolved, not just the latest commits to be clean. inspect them with \`${listRef}\`; for each thread the current code genuinely addresses, reply then call \`${resolveRef2}\`, and retry this approval. if any thread is a real outstanding issue, do NOT approve \u2014 submit a non-approving review (omit \`approved\`, or set \`request_changes\` for a blocking one) that covers it instead.`
|
|
283272
283536
|
);
|
|
283273
283537
|
}
|
|
283538
|
+
if (delta) {
|
|
283539
|
+
const reviews = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listReviews, {
|
|
283540
|
+
owner: ctx.repo.owner,
|
|
283541
|
+
repo: ctx.repo.name,
|
|
283542
|
+
pull_number,
|
|
283543
|
+
per_page: 100
|
|
283544
|
+
});
|
|
283545
|
+
const folded = foldedFindingsStillIntroduced({ reviews, delta, dismissed });
|
|
283546
|
+
if (folded.length > 0) {
|
|
283547
|
+
throw new Error(
|
|
283548
|
+
`cannot approve: ${folded.length} introduced finding(s) an earlier ${PRODUCT_NAME} review listed in its body (past \`review_comment_limit\`, so with no thread to resolve) are still introduced by this head: ` + folded.slice(0, 20).map(
|
|
283549
|
+
(f) => `${f.concern.id} (${f.concern.rule_id} at ${f.concern.location.file}:${f.concern.location.line ?? "?"})`
|
|
283550
|
+
).join(", ") + `. approval requires every prior ${PRODUCT_NAME} finding to be addressed. if one genuinely needs no change, list it in \`dismissed\` with the reason and retry; otherwise do NOT approve \u2014 submit a non-approving review that covers it.`
|
|
283551
|
+
);
|
|
283552
|
+
}
|
|
283553
|
+
}
|
|
283274
283554
|
}
|
|
283275
283555
|
const skip = reviewSkipDecision({
|
|
283276
283556
|
approved: approved ?? false,
|
|
@@ -283377,6 +283657,7 @@ ${formatReanchoredNote(reanchored.moved)}`
|
|
|
283377
283657
|
params.event = event;
|
|
283378
283658
|
}
|
|
283379
283659
|
const proofs = delta ? await proveReviewSuggestions(ctx, auditedComments) : /* @__PURE__ */ new Map();
|
|
283660
|
+
const findingSeverity = /* @__PURE__ */ new Map();
|
|
283380
283661
|
const reviewComments = auditedComments.map((comment, index) => {
|
|
283381
283662
|
let commentBody = fixDoubleEscapedString(comment.body || "");
|
|
283382
283663
|
if (comment.suggestion !== void 0) {
|
|
@@ -283418,6 +283699,9 @@ ${marker2}`;
|
|
|
283418
283699
|
reviewComment.start_line = comment.start_line;
|
|
283419
283700
|
reviewComment.start_side = side;
|
|
283420
283701
|
}
|
|
283702
|
+
if (comment.provenance?.kind === "scanner") {
|
|
283703
|
+
findingSeverity.set(reviewComment, comment.provenance.finding.concern.severity);
|
|
283704
|
+
}
|
|
283421
283705
|
return reviewComment;
|
|
283422
283706
|
});
|
|
283423
283707
|
let droppedComments = [];
|
|
@@ -283453,7 +283737,11 @@ ${marker2}`;
|
|
|
283453
283737
|
`withholding ${duplicateComments.length}/${reviewComments.length} inline comment(s) that duplicate open review threads`
|
|
283454
283738
|
);
|
|
283455
283739
|
}
|
|
283456
|
-
const limited = limitInlineComments(
|
|
283740
|
+
const limited = limitInlineComments(
|
|
283741
|
+
deduped.kept,
|
|
283742
|
+
ctx.payload.reviewCommentLimit,
|
|
283743
|
+
(c) => findingSeverity.get(c)
|
|
283744
|
+
);
|
|
283457
283745
|
overflowComments = limited.overflow;
|
|
283458
283746
|
if (overflowComments.length > 0) {
|
|
283459
283747
|
log2.info(
|
|
@@ -283485,7 +283773,13 @@ ${marker2}`;
|
|
|
283485
283773
|
body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
|
|
283486
283774
|
}
|
|
283487
283775
|
if (overflowComments.length > 0) {
|
|
283488
|
-
const
|
|
283776
|
+
const server = process.env.GITHUB_SERVER_URL || "https://github.com";
|
|
283777
|
+
const commit = params.commit_id;
|
|
283778
|
+
const note = formatOverflowNote(
|
|
283779
|
+
overflowComments,
|
|
283780
|
+
ctx.payload.reviewCommentLimit,
|
|
283781
|
+
commit ? (c) => `${server}/${ctx.repo.owner}/${ctx.repo.name}/blob/${commit}/${c.path.split("/").map(encodeURIComponent).join("/")}${c.line && c.side !== "LEFT" ? `#L${c.line}` : ""}` : void 0
|
|
283782
|
+
);
|
|
283489
283783
|
body2 = body2 ? body2 + note : note.replace(/^\n\n/, "");
|
|
283490
283784
|
}
|
|
283491
283785
|
let reproved = 0;
|
|
@@ -284497,7 +284791,7 @@ function formatFilesWithLineNumbers(files) {
|
|
|
284497
284791
|
}
|
|
284498
284792
|
const tocLines = [`## Files (${files.length})`];
|
|
284499
284793
|
for (const entry4 of tocEntries) {
|
|
284500
|
-
const anchor2 =
|
|
284794
|
+
const anchor2 = createHash10("sha256").update(entry4.filename).digest("hex");
|
|
284501
284795
|
tocLines.push(
|
|
284502
284796
|
`- ${entry4.filename} \u2192 lines ${entry4.startLine}-${entry4.endLine} \xB7 diff-${anchor2}`
|
|
284503
284797
|
);
|
|
@@ -286526,12 +286820,47 @@ function findPartialInterfaces(cwd) {
|
|
|
286526
286820
|
}
|
|
286527
286821
|
|
|
286528
286822
|
// src/mcp/terraform/refactorScope.ts
|
|
286529
|
-
import {
|
|
286823
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync23, statSync as statSync6 } from "node:fs";
|
|
286530
286824
|
import { join as join42 } from "node:path";
|
|
286531
286825
|
function isExamplePath(path15) {
|
|
286532
|
-
return /(?:^|\/)examples
|
|
286826
|
+
return /(?:^|\/)examples\/|^example\//i.test(path15.replace(/\\/g, "/"));
|
|
286827
|
+
}
|
|
286828
|
+
var PROVIDER_OR_BACKEND = new RegExp(
|
|
286829
|
+
String.raw`(?:^|\n)\s*(?:provider|backend)\s+${BLOCK_LABEL}\s*\{`
|
|
286830
|
+
);
|
|
286831
|
+
var CLOUD_BLOCK = /(?:^|\n)\s*cloud\s*\{/;
|
|
286832
|
+
function configuresDeployment(name, text) {
|
|
286833
|
+
if (name.endsWith(".tf.json")) {
|
|
286834
|
+
let doc;
|
|
286835
|
+
try {
|
|
286836
|
+
doc = JSON.parse(text);
|
|
286837
|
+
} catch {
|
|
286838
|
+
return false;
|
|
286839
|
+
}
|
|
286840
|
+
if (typeof doc !== "object" || doc === null) return false;
|
|
286841
|
+
const { provider: provider2, terraform } = doc;
|
|
286842
|
+
if (provider2 !== void 0) return true;
|
|
286843
|
+
return [terraform].flat().some((t) => typeof t === "object" && t !== null && ("backend" in t || "cloud" in t));
|
|
286844
|
+
}
|
|
286845
|
+
if (PROVIDER_OR_BACKEND.test(text)) return true;
|
|
286846
|
+
return parseBlocks(text, ["terraform"]).some((b) => CLOUD_BLOCK.test(b.body));
|
|
286847
|
+
}
|
|
286848
|
+
var isTerraformFile2 = (name) => name.endsWith(".tf") || name.endsWith(".tf.json");
|
|
286849
|
+
function deploysFrom(dir) {
|
|
286850
|
+
let names;
|
|
286851
|
+
try {
|
|
286852
|
+
names = readdirSync7(dir).filter(isTerraformFile2);
|
|
286853
|
+
} catch {
|
|
286854
|
+
return false;
|
|
286855
|
+
}
|
|
286856
|
+
return names.some((name) => {
|
|
286857
|
+
try {
|
|
286858
|
+
return configuresDeployment(name, readFileSync23(join42(dir, name), "utf8"));
|
|
286859
|
+
} catch {
|
|
286860
|
+
return false;
|
|
286861
|
+
}
|
|
286862
|
+
});
|
|
286533
286863
|
}
|
|
286534
|
-
var PROVIDER_OR_BACKEND = /^\s*(?:provider\s+"|backend\s+")/m;
|
|
286535
286864
|
function holdsTerraform(dir, depth) {
|
|
286536
286865
|
let entries;
|
|
286537
286866
|
try {
|
|
@@ -286549,26 +286878,35 @@ function holdsTerraform(dir, depth) {
|
|
|
286549
286878
|
return false;
|
|
286550
286879
|
}
|
|
286551
286880
|
function libraryRepoReason(cwd) {
|
|
286552
|
-
let
|
|
286881
|
+
let rootEntries;
|
|
286553
286882
|
try {
|
|
286554
|
-
|
|
286883
|
+
rootEntries = readdirSync7(cwd);
|
|
286555
286884
|
} catch {
|
|
286556
286885
|
return null;
|
|
286557
286886
|
}
|
|
286558
|
-
if (
|
|
286559
|
-
|
|
286560
|
-
let text;
|
|
286561
|
-
try {
|
|
286562
|
-
text = readFileSync23(join42(cwd, f), "utf8");
|
|
286563
|
-
} catch {
|
|
286564
|
-
continue;
|
|
286565
|
-
}
|
|
286566
|
-
if (PROVIDER_OR_BACKEND.test(text)) return null;
|
|
286567
|
-
}
|
|
286568
|
-
const examples = ["examples", "example"].find((d) => existsSync10(join42(cwd, d)));
|
|
286887
|
+
if (!rootEntries.some(isTerraformFile2) || deploysFrom(cwd)) return null;
|
|
286888
|
+
const examples = rootEntries.find((d) => /^examples?$/i.test(d));
|
|
286569
286889
|
if (examples === void 0 || !holdsTerraform(join42(cwd, examples), 2)) return null;
|
|
286570
286890
|
return `the repository root configures no provider and no backend, and ships \`${examples}/\`: it is a module other configurations call, so restructuring it is churn for every caller`;
|
|
286571
286891
|
}
|
|
286892
|
+
function libraryScope(cwd) {
|
|
286893
|
+
const reason = libraryRepoReason(cwd);
|
|
286894
|
+
if (reason === null) return () => null;
|
|
286895
|
+
const deploys = /* @__PURE__ */ new Map();
|
|
286896
|
+
return (file2) => {
|
|
286897
|
+
const dirs = file2.replace(/\\/g, "/").split("/").slice(0, -1);
|
|
286898
|
+
for (let i = dirs.length; i > 0; i--) {
|
|
286899
|
+
const dir = dirs.slice(0, i).join("/");
|
|
286900
|
+
let d = deploys.get(dir);
|
|
286901
|
+
if (d === void 0) {
|
|
286902
|
+
d = deploysFrom(join42(cwd, dir));
|
|
286903
|
+
deploys.set(dir, d);
|
|
286904
|
+
}
|
|
286905
|
+
if (d) return null;
|
|
286906
|
+
}
|
|
286907
|
+
return reason;
|
|
286908
|
+
};
|
|
286909
|
+
}
|
|
286572
286910
|
function reportOnly(reason) {
|
|
286573
286911
|
return reason === null ? {} : { report_only: true, report_only_reason: reason };
|
|
286574
286912
|
}
|
|
@@ -286926,12 +287264,12 @@ function ModuleExtractionCandidatesTool(ctx) {
|
|
|
286926
287264
|
parameters: ModuleExtractionCandidatesParams,
|
|
286927
287265
|
execute: execute(async () => {
|
|
286928
287266
|
const cwd = ctx.payload.cwd ?? process.cwd();
|
|
287267
|
+
const library = libraryScope(cwd);
|
|
286929
287268
|
const candidates = findExtractionCandidates(
|
|
286930
287269
|
cwd,
|
|
286931
287270
|
ctx.payload.moduleCatalogue,
|
|
286932
287271
|
ctx.toolState.orgModuleHcl
|
|
286933
|
-
).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file));
|
|
286934
|
-
const library = libraryRepoReason(cwd);
|
|
287272
|
+
).filter((c) => ctx.payload.refactorExamples || !isExamplePath(c.cluster.file)).map((c) => ({ ...reportOnly(library(c.cluster.file)), ...c }));
|
|
286935
287273
|
const matched = candidates.filter((c) => c.candidates.length > 0);
|
|
286936
287274
|
const reasons = {};
|
|
286937
287275
|
for (const c of candidates)
|
|
@@ -286945,7 +287283,6 @@ function ModuleExtractionCandidatesTool(ctx) {
|
|
|
286945
287283
|
`\xBB module_extraction_candidates: ${candidates.length} cluster(s), ${matched.length} with a module match` + (partialInterfaces.length > 0 ? `, ${partialInterfaces.length} partial-interface block(s)` : "")
|
|
286946
287284
|
);
|
|
286947
287285
|
return toolOk({
|
|
286948
|
-
...reportOnly(library),
|
|
286949
287286
|
cluster_count: candidates.length,
|
|
286950
287287
|
matched_count: matched.length,
|
|
286951
287288
|
candidates,
|
|
@@ -286969,7 +287306,7 @@ function ModuleExtractionCandidatesTool(ctx) {
|
|
|
286969
287306
|
}
|
|
286970
287307
|
|
|
286971
287308
|
// src/mcp/moduleTests.ts
|
|
286972
|
-
import { existsSync as
|
|
287309
|
+
import { existsSync as existsSync10, readdirSync as readdirSync8, readFileSync as readFileSync25, statSync as statSync7 } from "node:fs";
|
|
286973
287310
|
import { join as join44 } from "node:path";
|
|
286974
287311
|
var MODULE_META_ARGS = /* @__PURE__ */ new Set([
|
|
286975
287312
|
"source",
|
|
@@ -287090,7 +287427,7 @@ function resolveSourceDir(fromDir, source) {
|
|
|
287090
287427
|
}
|
|
287091
287428
|
function isDir(p) {
|
|
287092
287429
|
try {
|
|
287093
|
-
return
|
|
287430
|
+
return existsSync10(p) && statSync7(p).isDirectory();
|
|
287094
287431
|
} catch {
|
|
287095
287432
|
return false;
|
|
287096
287433
|
}
|
|
@@ -287284,7 +287621,7 @@ function SetOutputTool(ctx, outputSchema) {
|
|
|
287284
287621
|
|
|
287285
287622
|
// src/mcp/policy.ts
|
|
287286
287623
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
287287
|
-
import { existsSync as
|
|
287624
|
+
import { existsSync as existsSync11, readFileSync as readFileSync26 } from "node:fs";
|
|
287288
287625
|
import { join as join45 } from "node:path";
|
|
287289
287626
|
function parseConftestOutput(stdout) {
|
|
287290
287627
|
let parsed2;
|
|
@@ -287336,7 +287673,7 @@ function namespaceArgs(conftestCwd) {
|
|
|
287336
287673
|
function findPlanJson(cwd) {
|
|
287337
287674
|
for (const candidate of PLAN_JSON_NAMES) {
|
|
287338
287675
|
const abs = join45(cwd, candidate);
|
|
287339
|
-
if (
|
|
287676
|
+
if (existsSync11(abs)) return abs;
|
|
287340
287677
|
}
|
|
287341
287678
|
return null;
|
|
287342
287679
|
}
|
|
@@ -287377,11 +287714,11 @@ function resolvePolicyDir(cwd, explicit) {
|
|
|
287377
287714
|
} catch {
|
|
287378
287715
|
return null;
|
|
287379
287716
|
}
|
|
287380
|
-
return
|
|
287717
|
+
return existsSync11(abs) ? abs : null;
|
|
287381
287718
|
}
|
|
287382
287719
|
for (const d of DEFAULT_POLICY_DIRS) {
|
|
287383
287720
|
const abs = join45(cwd, d);
|
|
287384
|
-
if (
|
|
287721
|
+
if (existsSync11(abs)) return abs;
|
|
287385
287722
|
}
|
|
287386
287723
|
return null;
|
|
287387
287724
|
}
|
|
@@ -287415,7 +287752,7 @@ function PolicyCheckTool(ctx) {
|
|
|
287415
287752
|
} catch {
|
|
287416
287753
|
abs = null;
|
|
287417
287754
|
}
|
|
287418
|
-
targetFile = abs &&
|
|
287755
|
+
targetFile = abs && existsSync11(abs) ? abs : null;
|
|
287419
287756
|
if (!targetFile) {
|
|
287420
287757
|
return toolSkip(
|
|
287421
287758
|
"target_not_found",
|
|
@@ -287861,7 +288198,7 @@ function SelectModeTool(ctx) {
|
|
|
287861
288198
|
}
|
|
287862
288199
|
|
|
287863
288200
|
// src/mcp/terraform/tools/advise.ts
|
|
287864
|
-
import { existsSync as
|
|
288201
|
+
import { existsSync as existsSync12, readFileSync as readFileSync28 } from "node:fs";
|
|
287865
288202
|
import { join as join48 } from "node:path";
|
|
287866
288203
|
|
|
287867
288204
|
// src/mcp/terraform/fixMemory.ts
|
|
@@ -288020,7 +288357,7 @@ function inlineWaivers(content, file2) {
|
|
|
288020
288357
|
}
|
|
288021
288358
|
function readLearnings(tmpdir5) {
|
|
288022
288359
|
const path15 = join48(tmpdir5, LEARNINGS_FILE);
|
|
288023
|
-
if (!
|
|
288360
|
+
if (!existsSync12(path15)) return [];
|
|
288024
288361
|
try {
|
|
288025
288362
|
return readFileSync28(path15, "utf-8").split("\n");
|
|
288026
288363
|
} catch {
|
|
@@ -289885,7 +290222,8 @@ function DetectPlanDriftTool(ctx) {
|
|
|
289885
290222
|
autonomyThreshold,
|
|
289886
290223
|
// drift is a narrow concern source — join any prior scan baseline so a
|
|
289887
290224
|
// later ✗→✓ verify doesn't read the whole workspace as regressions.
|
|
289888
|
-
baselineMode: "merge"
|
|
290225
|
+
baselineMode: "merge",
|
|
290226
|
+
openRemediationPrs: await listOpenRemediationPrs(ctx)
|
|
289889
290227
|
});
|
|
289890
290228
|
log2.info(
|
|
289891
290229
|
`\xBB detect_plan_drift: ${active.length} drifted resource(s) \u2265 ${threshold} from ${planPath}` + (noiseFiltered.length ? `, ${noiseFiltered.length} noise-filtered` : "") + (unlocated.length ? `, ${unlocated.length} unlocated` : "") + (statefulForgotten.length ? `, ${statefulForgotten.length} STATEFUL forget(s)` : "") + (suppressed.length ? ` (${suppressed.length} suppressed)` : "")
|
|
@@ -290377,7 +290715,7 @@ function buildSuppressionSummary(waivers) {
|
|
|
290377
290715
|
}
|
|
290378
290716
|
|
|
290379
290717
|
// node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/signing.js
|
|
290380
|
-
import { createHash as
|
|
290718
|
+
import { createHash as createHash11, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
|
|
290381
290719
|
var SIGNATURE_ALGORITHM = "Ed25519";
|
|
290382
290720
|
var DEFAULT_KEY_LABEL = "the signing key";
|
|
290383
290721
|
function loadSigningKey(raw2, options) {
|
|
@@ -290404,7 +290742,7 @@ function loadSigningKey(raw2, options) {
|
|
|
290404
290742
|
return {
|
|
290405
290743
|
privateKey,
|
|
290406
290744
|
publicKeyPem,
|
|
290407
|
-
fingerprint:
|
|
290745
|
+
fingerprint: createHash11("sha256").update(spkiDer).digest("hex")
|
|
290408
290746
|
};
|
|
290409
290747
|
}
|
|
290410
290748
|
function signBytes(bytes, key) {
|
|
@@ -290639,7 +290977,7 @@ function buildEvidenceBundle(args2) {
|
|
|
290639
290977
|
// node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keyless.js
|
|
290640
290978
|
var import_bundle = __toESM(require_dist6(), 1);
|
|
290641
290979
|
var import_sign = __toESM(require_dist9(), 1);
|
|
290642
|
-
import { createHash as
|
|
290980
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
290643
290981
|
|
|
290644
290982
|
// node_modules/.pnpm/@infraweaver-io+attest@0.1.7_supports-color@7.2.0/node_modules/@infraweaver-io/attest/dist/keylessOidc.js
|
|
290645
290983
|
var SIGSTORE_OIDC_AUDIENCE = "sigstore";
|
|
@@ -290668,7 +291006,7 @@ function keylessSigningPossible(env2, envName) {
|
|
|
290668
291006
|
}
|
|
290669
291007
|
function buildInTotoStatement(args2) {
|
|
290670
291008
|
const data = typeof args2.artifactBytes === "string" ? Buffer.from(args2.artifactBytes, "utf8") : args2.artifactBytes;
|
|
290671
|
-
const sha2562 =
|
|
291009
|
+
const sha2562 = createHash12("sha256").update(data).digest("hex");
|
|
290672
291010
|
return {
|
|
290673
291011
|
_type: "https://in-toto.io/Statement/v1",
|
|
290674
291012
|
subject: [{ name: args2.subjectName, digest: { sha256: sha2562 } }],
|
|
@@ -290878,12 +291216,12 @@ import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
|
290878
291216
|
import { dirname as dirname6 } from "node:path";
|
|
290879
291217
|
|
|
290880
291218
|
// src/mcp/terraform/oscal.ts
|
|
290881
|
-
import { createHash as
|
|
291219
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
290882
291220
|
var OSCAL_VERSION = "1.1.2";
|
|
290883
291221
|
var INDICATIVE_REMARK = "Indicative, crosswalk-derived assessment from a deterministic starter rule-pack \u2014 NOT a formal OSCAL assessment against an authored assessment plan. Statuses are code-verified only; absence of a finding is not proof of compliance.";
|
|
290884
291222
|
var NS = "https://infraweaver.io/ns/oscal";
|
|
290885
291223
|
function deterministicUuid(seed) {
|
|
290886
|
-
const hex4 =
|
|
291224
|
+
const hex4 = createHash13("sha256").update(seed).digest("hex").slice(0, 32).split("");
|
|
290887
291225
|
hex4[12] = "5";
|
|
290888
291226
|
hex4[16] = (Number.parseInt(hex4[16] ?? "0", 16) & 3 | 8).toString(16);
|
|
290889
291227
|
const u = hex4.join("");
|
|
@@ -291999,16 +292337,32 @@ function uncoveredInstanceKeys(p) {
|
|
|
291999
292337
|
if (expected === null) return null;
|
|
292000
292338
|
const kind = level[0].kind;
|
|
292001
292339
|
const written = (k) => kind === "count" ? k : `"${k}"`;
|
|
292340
|
+
const fromAt = level[0].at;
|
|
292341
|
+
const toAt = p.after.multiplicity[0]?.at;
|
|
292342
|
+
if (fromAt === void 0 || toAt === void 0) return null;
|
|
292002
292343
|
const declared2 = /* @__PURE__ */ new Set();
|
|
292003
292344
|
for (const blk of p.blocks) {
|
|
292345
|
+
if (stripIndices(normaliseAddress(blk.from)) !== p.before.address || stripIndices(normaliseAddress(blk.to)) !== p.after.address)
|
|
292346
|
+
return null;
|
|
292004
292347
|
const from = instanceKeysOf(blk.from);
|
|
292005
292348
|
const to = instanceKeysOf(blk.to);
|
|
292006
292349
|
if (from.length > 1 || to.length > 1) return null;
|
|
292007
292350
|
if (from.length === 0 && to.length === 0) return null;
|
|
292008
|
-
if (from.length
|
|
292351
|
+
if (from.length !== 1 || to.length !== 1 || from[0] !== to[0]) continue;
|
|
292352
|
+
const fromPlaced = keyAtLevel(blk.from, fromAt);
|
|
292353
|
+
const toPlaced = keyAtLevel(blk.to, toAt);
|
|
292354
|
+
if (fromPlaced === false || toPlaced === false) continue;
|
|
292355
|
+
if (fromPlaced === null || toPlaced === null) return null;
|
|
292356
|
+
declared2.add(from[0]);
|
|
292009
292357
|
}
|
|
292010
292358
|
return expected.filter((k) => !declared2.has(written(k)));
|
|
292011
292359
|
}
|
|
292360
|
+
function keyAtLevel(address, at) {
|
|
292361
|
+
const onResource = instanceKeysOf(stripModuleHops(normaliseAddress(address))).length > 0;
|
|
292362
|
+
if (at === "resource") return onResource;
|
|
292363
|
+
if (onResource) return false;
|
|
292364
|
+
return moduleHops(address).length === 1 ? true : null;
|
|
292365
|
+
}
|
|
292012
292366
|
function computeEquivalence(before, after, movedBlocks, ambiguous = [], planPairs, opts = {}) {
|
|
292013
292367
|
const reasons = [];
|
|
292014
292368
|
const corr = correspond(before, after, movedBlocks);
|
|
@@ -293935,7 +294289,8 @@ function IngestExternalFindingsTool(ctx) {
|
|
|
293935
294289
|
// an external feed is a narrow concern source — join any prior scan
|
|
293936
294290
|
// baseline so a later ✗→✓ verify doesn't read the workspace as regressions.
|
|
293937
294291
|
baselineMode: "merge",
|
|
293938
|
-
seedCaps: seedCapsFrom(input2)
|
|
294292
|
+
seedCaps: seedCapsFrom(input2),
|
|
294293
|
+
openRemediationPrs: await listOpenRemediationPrs(ctx)
|
|
293939
294294
|
});
|
|
293940
294295
|
const cappedBlock = cappedSummary(capped);
|
|
293941
294296
|
log2.info(
|
|
@@ -294405,15 +294760,13 @@ function NormalizationCandidatesTool(ctx) {
|
|
|
294405
294760
|
parameters: NormalizationCandidatesParams,
|
|
294406
294761
|
execute: execute(async () => {
|
|
294407
294762
|
const cwd = ctx.payload.cwd ?? process.cwd();
|
|
294408
|
-
const
|
|
294409
|
-
|
|
294410
|
-
);
|
|
294763
|
+
const library = libraryScope(cwd);
|
|
294764
|
+
const files = findNormalizationCandidates(cwd).filter((f) => ctx.payload.refactorExamples || !isExamplePath(f.file)).map((f) => ({ ...reportOnly(library(f.file)), ...f }));
|
|
294411
294765
|
const total = files.reduce((n, f) => n + f.count, 0);
|
|
294412
294766
|
log2.info(
|
|
294413
294767
|
`\xBB terraform_normalization_candidates: ${total} site(s) across ${files.length} file(s)`
|
|
294414
294768
|
);
|
|
294415
294769
|
return toolOk({
|
|
294416
|
-
...reportOnly(libraryRepoReason(cwd)),
|
|
294417
294770
|
file_count: files.length,
|
|
294418
294771
|
total,
|
|
294419
294772
|
files,
|
|
@@ -294948,7 +295301,8 @@ function ReadFindingsTool(ctx) {
|
|
|
294948
295301
|
minRank,
|
|
294949
295302
|
grouping,
|
|
294950
295303
|
autonomyThreshold,
|
|
294951
|
-
seedCaps: seedCapsFrom(input2)
|
|
295304
|
+
seedCaps: seedCapsFrom(input2),
|
|
295305
|
+
openRemediationPrs: await listOpenRemediationPrs(ctx)
|
|
294952
295306
|
});
|
|
294953
295307
|
const cappedBlock = cappedSummary(capped);
|
|
294954
295308
|
log2.info(
|
|
@@ -298855,7 +299209,7 @@ async function startMcpHttpServer(ctx, options) {
|
|
|
298855
299209
|
|
|
298856
299210
|
// src/mcp/terraform/policyGate.ts
|
|
298857
299211
|
import {
|
|
298858
|
-
existsSync as
|
|
299212
|
+
existsSync as existsSync13,
|
|
298859
299213
|
mkdirSync as mkdirSync7,
|
|
298860
299214
|
mkdtempSync as mkdtempSync7,
|
|
298861
299215
|
readdirSync as readdirSync9,
|
|
@@ -299238,7 +299592,7 @@ function readOrEmpty(path15) {
|
|
|
299238
299592
|
function regoDirIn(root, skip) {
|
|
299239
299593
|
for (const dir of DEFAULT_POLICY_DIRS) {
|
|
299240
299594
|
const abs = join58(root, dir);
|
|
299241
|
-
if (
|
|
299595
|
+
if (existsSync13(abs) && walkFilesBySuffix(abs, ".rego").some(
|
|
299242
299596
|
(rel) => !skip(join58(dir, rel), readOrEmpty(join58(abs, rel)))
|
|
299243
299597
|
)) {
|
|
299244
299598
|
return dir;
|
|
@@ -299307,7 +299661,7 @@ function resolveRepoPolicy(cwd, baseRef, tmpdir5, notes, skip) {
|
|
|
299307
299661
|
return null;
|
|
299308
299662
|
}
|
|
299309
299663
|
function findWorkflowPlanJson(cwd, notes) {
|
|
299310
|
-
const present = PLAN_JSON_NAMES.filter((name) =>
|
|
299664
|
+
const present = PLAN_JSON_NAMES.filter((name) => existsSync13(join58(cwd, name)));
|
|
299311
299665
|
if (present.length === 0) return null;
|
|
299312
299666
|
const listing = run("git", ["ls-files", "-z"], cwd);
|
|
299313
299667
|
if (listing.status !== 0) {
|
|
@@ -300666,9 +301020,9 @@ function shouldRunGraceTurn(params) {
|
|
|
300666
301020
|
}
|
|
300667
301021
|
|
|
300668
301022
|
// src/agents/quotaTokens.ts
|
|
300669
|
-
var
|
|
300670
|
-
function
|
|
300671
|
-
return t.input + t.output + t.cacheWrite + t.cacheRead *
|
|
301023
|
+
var CACHE_READ_WEIGHT2 = 0.1;
|
|
301024
|
+
function quotaTokens2(t) {
|
|
301025
|
+
return t.input + t.output + (t.reasoning ?? 0) + t.cacheWrite + t.cacheRead * CACHE_READ_WEIGHT2;
|
|
300672
301026
|
}
|
|
300673
301027
|
|
|
300674
301028
|
// src/agents/sessionLabeler.ts
|
|
@@ -301161,7 +301515,7 @@ function createProcessOutputActivityTimeout(ctx) {
|
|
|
301161
301515
|
}
|
|
301162
301516
|
|
|
301163
301517
|
// src/utils/prompt/skills.ts
|
|
301164
|
-
import { existsSync as
|
|
301518
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync36, writeFileSync as writeFileSync19 } from "node:fs";
|
|
301165
301519
|
import { dirname as dirname9, join as join61 } from "node:path";
|
|
301166
301520
|
import { fileURLToPath } from "node:url";
|
|
301167
301521
|
var BUNDLED_SKILL_NAMES = ["terraform-best-practices"];
|
|
@@ -301172,7 +301526,7 @@ function resolveSkillPath(name) {
|
|
|
301172
301526
|
join61(here, "skills", name, "SKILL.md")
|
|
301173
301527
|
];
|
|
301174
301528
|
for (const candidate of candidates) {
|
|
301175
|
-
if (
|
|
301529
|
+
if (existsSync14(candidate)) return candidate;
|
|
301176
301530
|
}
|
|
301177
301531
|
throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
|
|
301178
301532
|
}
|
|
@@ -301191,8 +301545,8 @@ function installBundledSkills(params) {
|
|
|
301191
301545
|
|
|
301192
301546
|
// src/utils/setup/install.ts
|
|
301193
301547
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
301194
|
-
import { createHash as
|
|
301195
|
-
import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as
|
|
301548
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
301549
|
+
import { chmodSync as chmodSync2, createWriteStream as createWriteStream2, existsSync as existsSync17, readFileSync as readFileSync37 } from "node:fs";
|
|
301196
301550
|
import { join as join66 } from "node:path";
|
|
301197
301551
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
301198
301552
|
|
|
@@ -301258,7 +301612,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
|
|
|
301258
301612
|
const expected = candidate.slice(separator2 + 1);
|
|
301259
301613
|
let actual;
|
|
301260
301614
|
try {
|
|
301261
|
-
actual =
|
|
301615
|
+
actual = createHash15(algorithm).update(bytes).digest("base64");
|
|
301262
301616
|
} catch {
|
|
301263
301617
|
continue;
|
|
301264
301618
|
}
|
|
@@ -301268,7 +301622,7 @@ function verifyTarballIntegrity(tarballPath, dist, descriptor) {
|
|
|
301268
301622
|
`Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published digest (${integrity2}). Refusing to execute it.`
|
|
301269
301623
|
);
|
|
301270
301624
|
}
|
|
301271
|
-
const actualShasum =
|
|
301625
|
+
const actualShasum = createHash15("sha1").update(bytes).digest("hex");
|
|
301272
301626
|
if (actualShasum !== shasum) {
|
|
301273
301627
|
throw new Error(
|
|
301274
301628
|
`Integrity check FAILED for ${descriptor}: the downloaded tarball does not match the registry's published shasum (expected ${shasum}, got ${actualShasum}). Refusing to execute it.`
|
|
@@ -301279,7 +301633,7 @@ async function installFromNpmTarball(params) {
|
|
|
301279
301633
|
const tempDir = getTempDir();
|
|
301280
301634
|
const extractedDir = join66(tempDir, "package");
|
|
301281
301635
|
const cliPath = join66(extractedDir, params.executablePath);
|
|
301282
|
-
if (
|
|
301636
|
+
if (existsSync17(cliPath)) {
|
|
301283
301637
|
log2.debug(`\xBB using cached binary at ${cliPath}`);
|
|
301284
301638
|
return cliPath;
|
|
301285
301639
|
}
|
|
@@ -301370,7 +301724,7 @@ async function installFromNpmTarball(params) {
|
|
|
301370
301724
|
`Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
|
|
301371
301725
|
);
|
|
301372
301726
|
}
|
|
301373
|
-
if (!
|
|
301727
|
+
if (!existsSync17(cliPath)) {
|
|
301374
301728
|
throw new Error(`Executable not found in extracted package at ${cliPath}`);
|
|
301375
301729
|
}
|
|
301376
301730
|
if (params.installDependencies) {
|
|
@@ -301610,7 +301964,7 @@ function timerFor(state5, label) {
|
|
|
301610
301964
|
function chargeTokenQuota(state5) {
|
|
301611
301965
|
const quota = state5.params.tokenQuota;
|
|
301612
301966
|
if (!quota || state5.stopRun.signal.aborted) return;
|
|
301613
|
-
if (!quota.observe(state5.quotaSource,
|
|
301967
|
+
if (!quota.observe(state5.quotaSource, quotaTokens2(state5.accumulatedTokens))) return;
|
|
301614
301968
|
log2.info(`\xBB ${state5.params.label}: ${quota.gap()}`);
|
|
301615
301969
|
state5.stopRun.abort(new TokenQuotaExceededError(quota.gap()));
|
|
301616
301970
|
}
|
|
@@ -308342,7 +308696,7 @@ function newTurn() {
|
|
|
308342
308696
|
turnCounter += 1;
|
|
308343
308697
|
return {
|
|
308344
308698
|
finalText: "",
|
|
308345
|
-
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
308699
|
+
tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0 },
|
|
308346
308700
|
costUsd: 0,
|
|
308347
308701
|
sessionError: null,
|
|
308348
308702
|
lastToolError: null,
|
|
@@ -308352,7 +308706,7 @@ function newTurn() {
|
|
|
308352
308706
|
function chargeTokenQuota2(ctx) {
|
|
308353
308707
|
const quota = ctx.tokenQuota;
|
|
308354
308708
|
if (!quota || !ctx.currentTurn || ctx.tokenQuotaExceeded) return;
|
|
308355
|
-
if (!quota.observe(ctx.currentTurn.quotaSource,
|
|
308709
|
+
if (!quota.observe(ctx.currentTurn.quotaSource, quotaTokens2(ctx.currentTurn.tokens))) return;
|
|
308356
308710
|
ctx.tokenQuotaExceeded = true;
|
|
308357
308711
|
log2.info(`\xBB ${ctx.label}: ${quota.gap()}`);
|
|
308358
308712
|
ctx.abortRun?.(new TokenQuotaExceededError(quota.gap()));
|
|
@@ -308461,6 +308815,7 @@ async function onPartUpdated(ctx, part) {
|
|
|
308461
308815
|
if (t) {
|
|
308462
308816
|
ctx.currentTurn.tokens.input += t.input || 0;
|
|
308463
308817
|
ctx.currentTurn.tokens.output += t.output || 0;
|
|
308818
|
+
ctx.currentTurn.tokens.reasoning += t.reasoning || 0;
|
|
308464
308819
|
ctx.currentTurn.tokens.cacheRead += t.cache?.read || 0;
|
|
308465
308820
|
ctx.currentTurn.tokens.cacheWrite += t.cache?.write || 0;
|
|
308466
308821
|
}
|
|
@@ -309065,10 +309420,10 @@ var DispatchRecorder = class {
|
|
|
309065
309420
|
* orchestrator counts as subagent — including a session the labeler could not
|
|
309066
309421
|
* name, because an unnamed session is by definition not the orchestrator's.
|
|
309067
309422
|
*/
|
|
309068
|
-
recordTokens(label,
|
|
309069
|
-
if (!Number.isFinite(
|
|
309070
|
-
if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens +=
|
|
309071
|
-
else this.#subagentTokens +=
|
|
309423
|
+
recordTokens(label, billableTokens) {
|
|
309424
|
+
if (!Number.isFinite(billableTokens) || billableTokens <= 0) return;
|
|
309425
|
+
if (label === ORCHESTRATOR_LABEL) this.#orchestratorTokens += billableTokens;
|
|
309426
|
+
else this.#subagentTokens += billableTokens;
|
|
309072
309427
|
}
|
|
309073
309428
|
tally() {
|
|
309074
309429
|
return {
|
|
@@ -309957,8 +310312,8 @@ function addQueryParameters(url5, parameters) {
|
|
|
309957
310312
|
}).join("&");
|
|
309958
310313
|
}
|
|
309959
310314
|
var urlVariableRegex = /\{[^{}}]+\}/g;
|
|
309960
|
-
function removeNonChars(
|
|
309961
|
-
return
|
|
310315
|
+
function removeNonChars(variableName2) {
|
|
310316
|
+
return variableName2.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
|
|
309962
310317
|
}
|
|
309963
310318
|
function extractUrlVariableNames(url5) {
|
|
309964
310319
|
const matches2 = url5.match(urlVariableRegex);
|
|
@@ -317637,7 +317992,7 @@ async function runAgentWithWatchdogs(params) {
|
|
|
317637
317992
|
const tokenQuota = reserved ? quotas.token : createTokenQuota(quotas.tokenLimit.full);
|
|
317638
317993
|
const costQuota = reserved ? quotas.cost : createCostQuota(quotas.costLimit.full);
|
|
317639
317994
|
log2.info(
|
|
317640
|
-
quotas.tokenLimit.full > 0 ? `\xBB run token quota: ${quotas.tokenLimit.full.toLocaleString("en-GB")}
|
|
317995
|
+
quotas.tokenLimit.full > 0 ? `\xBB run token quota: ${quotas.tokenLimit.full.toLocaleString("en-GB")} tokens` + (reserved ? ` (${tokenQuota.limit.toLocaleString("en-GB")} for the run, the rest reserved to deliver findings if it runs out)` : "") + "; a token read from the prompt cache counts as a tenth" : `\xBB run token quota: disabled (${RUN_TOKEN_QUOTA_INPUT}: 0)`
|
|
317641
317996
|
);
|
|
317642
317997
|
log2.info(
|
|
317643
317998
|
quotas.costLimit.full > 0 ? `\xBB run cost quota: $${quotas.costLimit.full.toFixed(2)}` + (reserved ? ` ($${costQuota.limit.toFixed(2)} for the run, the rest reserved to deliver findings if it runs out)` : "") : `\xBB run cost quota: disabled (${RUN_COST_QUOTA_INPUT}: 0)`
|
|
@@ -317735,7 +318090,7 @@ async function runAgentWithWatchdogs(params) {
|
|
|
317735
318090
|
}
|
|
317736
318091
|
|
|
317737
318092
|
// src/phases/startToolPlane.ts
|
|
317738
|
-
import { existsSync as
|
|
318093
|
+
import { existsSync as existsSync18, readdirSync as readdirSync10 } from "node:fs";
|
|
317739
318094
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
317740
318095
|
import { join as join74 } from "node:path";
|
|
317741
318096
|
|
|
@@ -318566,7 +318921,7 @@ ${instructions.user}` : null,
|
|
|
318566
318921
|
});
|
|
318567
318922
|
if (toolContext.agentId === "opencode") {
|
|
318568
318923
|
const pluginDir = join74(process.cwd(), ".opencode", "plugin");
|
|
318569
|
-
const hasPlugins =
|
|
318924
|
+
const hasPlugins = existsSync18(pluginDir) && readdirSync10(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
318570
318925
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
318571
318926
|
log2.info(
|
|
318572
318927
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|