tamperward 2.23.10 → 2.23.12
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/dist/cli/index.js +509 -228
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1026,7 +1026,7 @@ var init_trusted = __esm({
|
|
|
1026
1026
|
|
|
1027
1027
|
// src/repo-context.ts
|
|
1028
1028
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1029
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
1029
|
+
import { accessSync, constants as constants2, existsSync as existsSync2, statSync } from "node:fs";
|
|
1030
1030
|
import { isAbsolute as isAbsolute3, resolve } from "node:path";
|
|
1031
1031
|
function rev(cwd, flag) {
|
|
1032
1032
|
try {
|
|
@@ -1059,6 +1059,47 @@ function repoContext(cwd) {
|
|
|
1059
1059
|
function repoRoot(cwd) {
|
|
1060
1060
|
return repoContext(cwd)?.root ?? cwd;
|
|
1061
1061
|
}
|
|
1062
|
+
function outsideRepository(cwd) {
|
|
1063
|
+
const key2 = resolve(cwd);
|
|
1064
|
+
let isDir;
|
|
1065
|
+
try {
|
|
1066
|
+
isDir = statSync(key2).isDirectory();
|
|
1067
|
+
} catch (e) {
|
|
1068
|
+
return `cwd ${key2} cannot be read: ${errCode2(e)}`;
|
|
1069
|
+
}
|
|
1070
|
+
if (!isDir) return `cwd ${key2} is not a directory`;
|
|
1071
|
+
try {
|
|
1072
|
+
accessSync(key2, constants2.R_OK | constants2.X_OK);
|
|
1073
|
+
} catch (e) {
|
|
1074
|
+
return `cwd ${key2} cannot be read: ${errCode2(e)}`;
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
execFileSync2("git", ["rev-parse", "--git-dir"], {
|
|
1078
|
+
cwd: key2,
|
|
1079
|
+
encoding: "utf8",
|
|
1080
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1081
|
+
});
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
const detail = errDetail(e);
|
|
1084
|
+
if (/not a git repository/i.test(detail)) return null;
|
|
1085
|
+
return `git could not resolve cwd ${key2}: ${detail || "unknown failure"}`;
|
|
1086
|
+
}
|
|
1087
|
+
return `cwd ${key2} lies in a repository with no working tree the gate can judge`;
|
|
1088
|
+
}
|
|
1089
|
+
function errCode2(e) {
|
|
1090
|
+
if (typeof e === "object" && e !== null) {
|
|
1091
|
+
const code2 = Reflect.get(e, "code");
|
|
1092
|
+
if (typeof code2 === "string") return code2;
|
|
1093
|
+
}
|
|
1094
|
+
return e instanceof Error ? e.message : String(e);
|
|
1095
|
+
}
|
|
1096
|
+
function errDetail(e) {
|
|
1097
|
+
if (typeof e === "object" && e !== null) {
|
|
1098
|
+
const stderr = Reflect.get(e, "stderr");
|
|
1099
|
+
if (typeof stderr === "string" && stderr.trim()) return stderr.replace(/\s+/g, " ").trim();
|
|
1100
|
+
}
|
|
1101
|
+
return errCode2(e);
|
|
1102
|
+
}
|
|
1062
1103
|
var cache, CACHE_MAX;
|
|
1063
1104
|
var init_repo_context = __esm({
|
|
1064
1105
|
"src/repo-context.ts"() {
|
|
@@ -4890,13 +4931,7 @@ var init_hook_wiring = __esm({
|
|
|
4890
4931
|
}
|
|
4891
4932
|
});
|
|
4892
4933
|
|
|
4893
|
-
// src/detectors/
|
|
4894
|
-
function isActiveWorkflow(path) {
|
|
4895
|
-
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
4896
|
-
}
|
|
4897
|
-
function commandCore(line) {
|
|
4898
|
-
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
4899
|
-
}
|
|
4934
|
+
// src/detectors/gh-expression.ts
|
|
4900
4935
|
function foldExpressions(s) {
|
|
4901
4936
|
return s.replace(/\$\{\{([^}]*)\}\}/g, (_m, e) => {
|
|
4902
4937
|
const v = foldConst(e.trim());
|
|
@@ -4904,90 +4939,6 @@ function foldExpressions(s) {
|
|
|
4904
4939
|
return /\b(?:matrix|strategy|inputs)\./.test(e) ? MATRIX_TOKEN : EXPR_TOKEN;
|
|
4905
4940
|
});
|
|
4906
4941
|
}
|
|
4907
|
-
function canonical(core) {
|
|
4908
|
-
let s = foldExpressions(core.replace(/^(['"])(.*)\1$/, "$2").trim()).replace(MATRIX_FLAG, " ").replace(/\s+/g, " ").trim();
|
|
4909
|
-
s = s.replace(/^(?:cd|pushd)\s+\S+\s*&&\s*/, "");
|
|
4910
|
-
s = s.replace(/^(?:npx|pnpm\s+(?:exec|dlx)|yarn\s+(?:exec|dlx)|bunx|bun\s+x)\s+/, "exec ");
|
|
4911
|
-
s = s.replace(/^(?:npm|pnpm|yarn|bun)\s+(?:run(?:-script)?\s+)?/, "exec ");
|
|
4912
|
-
s = s.replace(/^exec\s+(?:t|tst)(?=\s|$)/, "exec test");
|
|
4913
|
-
s = s.replace(/^exec\s+/, "");
|
|
4914
|
-
return s.replace(/\s+--\s*$/, "").trim();
|
|
4915
|
-
}
|
|
4916
|
-
function runnerNarrowed(cmd, args) {
|
|
4917
|
-
const t = RUNNER_NARROWINGS.find((x) => x.runner.test(cmd));
|
|
4918
|
-
return t !== void 0 && t.flags.test(" " + args.trim());
|
|
4919
|
-
}
|
|
4920
|
-
function coverageFloorLowered(removed, added) {
|
|
4921
|
-
const was = removed.match(COVERAGE_FLOOR);
|
|
4922
|
-
const now = added.match(COVERAGE_FLOOR);
|
|
4923
|
-
return was !== null && now !== null && Number(now[1]) < Number(was[1]);
|
|
4924
|
-
}
|
|
4925
|
-
function inCommandPosition(core) {
|
|
4926
|
-
return canonical(core).split(/\s*(?:&&|\|\||;|\|)\s*/).some((seg) => CHECK_HEAD.test(canonical(seg.replace(COMMAND_PREFIX, ""))));
|
|
4927
|
-
}
|
|
4928
|
-
function narrowedByPositional(args) {
|
|
4929
|
-
const toks = args.trim().split(/\s+/).filter(Boolean);
|
|
4930
|
-
for (let i = 0; i < toks.length; i++) {
|
|
4931
|
-
const t = toks[i];
|
|
4932
|
-
if (t === "--" || t.startsWith("-")) continue;
|
|
4933
|
-
const prev = toks[i - 1];
|
|
4934
|
-
if (prev && (prev.startsWith("-") && prev !== "--" && !prev.includes("=") || /[<>]/.test(prev))) continue;
|
|
4935
|
-
if (/[<>|;&]/.test(t)) continue;
|
|
4936
|
-
if (PATH_SHAPED.test(t)) return true;
|
|
4937
|
-
}
|
|
4938
|
-
return false;
|
|
4939
|
-
}
|
|
4940
|
-
function checkKind(core) {
|
|
4941
|
-
if (!invokesCheck(core)) return null;
|
|
4942
|
-
const s = canonical(core);
|
|
4943
|
-
if (/\btamperward\b/.test(s)) return "gate";
|
|
4944
|
-
if (/\b(?:lint|eslint|biome|oxlint|golangci-lint|ruff)\b/i.test(s)) return "lint";
|
|
4945
|
-
if (/\b(?:typecheck|type-check|tsc|tsgo|mypy)\b|\bdeno\s+check\b/i.test(s)) return "types";
|
|
4946
|
-
return "test";
|
|
4947
|
-
}
|
|
4948
|
-
function survives(removedCore, isUses, afterCores, addedCores) {
|
|
4949
|
-
if (isUses) {
|
|
4950
|
-
const ref = usesRef(removedCore);
|
|
4951
|
-
return afterCores.some((a) => usesRef(a) === ref) ? { state: "kept" } : { state: "gone" };
|
|
4952
|
-
}
|
|
4953
|
-
const r = canonical(removedCore);
|
|
4954
|
-
const kind = checkKind(removedCore);
|
|
4955
|
-
const narrowed = (args) => kind === "test" && narrowedByPositional(args);
|
|
4956
|
-
let neutralised;
|
|
4957
|
-
for (const raw of afterCores) {
|
|
4958
|
-
const a = canonical(raw);
|
|
4959
|
-
if (a === r) return { state: "kept" };
|
|
4960
|
-
if (a.startsWith(r + " ")) {
|
|
4961
|
-
const rest = a.slice(r.length);
|
|
4962
|
-
if (NEUTRALISING_SUFFIX.test(rest) || narrowed(rest) || runnerNarrowed(a, rest)) neutralised ??= raw;
|
|
4963
|
-
else return { state: "kept" };
|
|
4964
|
-
} else if (r.startsWith(a + " ") && invokesCheck(a)) {
|
|
4965
|
-
return { state: "kept" };
|
|
4966
|
-
} else {
|
|
4967
|
-
const w = a.match(TIMEOUT_WRAP);
|
|
4968
|
-
if (w && (canonical(w[1]) === r || canonical(w[1]).startsWith(r + " "))) neutralised ??= raw;
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
let respelled = false;
|
|
4972
|
-
for (const raw of addedCores) {
|
|
4973
|
-
if (!kind || checkKind(raw) !== kind) continue;
|
|
4974
|
-
const a = canonical(raw);
|
|
4975
|
-
if (a.match(TIMEOUT_WRAP)) {
|
|
4976
|
-
neutralised ??= raw;
|
|
4977
|
-
continue;
|
|
4978
|
-
}
|
|
4979
|
-
if (!inCommandPosition(raw)) continue;
|
|
4980
|
-
if (coverageFloorLowered(r, a)) {
|
|
4981
|
-
neutralised ??= raw;
|
|
4982
|
-
continue;
|
|
4983
|
-
}
|
|
4984
|
-
const args = a.slice(a.indexOf(" ") + 1 || a.length);
|
|
4985
|
-
if (a.includes(" ") && (NEUTRALISING_SUFFIX.test(" " + args) || narrowed(args) || runnerNarrowed(a, args))) neutralised ??= raw;
|
|
4986
|
-
else respelled = true;
|
|
4987
|
-
}
|
|
4988
|
-
if (respelled) return { state: "kept" };
|
|
4989
|
-
return neutralised ? { state: "neutralised", by: neutralised } : { state: "gone" };
|
|
4990
|
-
}
|
|
4991
4942
|
function lex(src) {
|
|
4992
4943
|
const out3 = [];
|
|
4993
4944
|
let i = 0;
|
|
@@ -5177,6 +5128,329 @@ function callConst(name, args) {
|
|
|
5177
5128
|
return void 0;
|
|
5178
5129
|
}
|
|
5179
5130
|
}
|
|
5131
|
+
var EXPR_TOKEN, MATRIX_TOKEN, TRUTHY, truthy;
|
|
5132
|
+
var init_gh_expression = __esm({
|
|
5133
|
+
"src/detectors/gh-expression.ts"() {
|
|
5134
|
+
"use strict";
|
|
5135
|
+
EXPR_TOKEN = "__expr__";
|
|
5136
|
+
MATRIX_TOKEN = "__matrix__";
|
|
5137
|
+
TRUTHY = Symbol("truthy");
|
|
5138
|
+
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5139
|
+
}
|
|
5140
|
+
});
|
|
5141
|
+
|
|
5142
|
+
// src/detectors/invocation.ts
|
|
5143
|
+
function canonical(core) {
|
|
5144
|
+
let s = foldExpressions(core.replace(/^(['"])(.*)\1$/, "$2").trim()).replace(MATRIX_FLAG, " ").replace(/\s+/g, " ").trim();
|
|
5145
|
+
s = s.replace(/^(?:cd|pushd)\s+\S+\s*&&\s*/, "");
|
|
5146
|
+
s = s.replace(/^(?:npx|pnpm\s+(?:exec|dlx)|yarn\s+(?:exec|dlx)|bunx|bun\s+x)\s+/, "exec ");
|
|
5147
|
+
s = s.replace(/^(?:npm|pnpm|yarn|bun)\s+(?:run(?:-script)?\s+)?/, "exec ");
|
|
5148
|
+
s = s.replace(/^exec\s+(?:t|tst)(?=\s|$)/, "exec test");
|
|
5149
|
+
s = s.replace(/^exec\s+/, "");
|
|
5150
|
+
return s.replace(/\s+--\s*$/, "").trim();
|
|
5151
|
+
}
|
|
5152
|
+
function runnerNarrowed(cmd, args) {
|
|
5153
|
+
return runnerNarrowing(cmd, args) !== null;
|
|
5154
|
+
}
|
|
5155
|
+
function runnerNarrowing(cmd, args) {
|
|
5156
|
+
const t = RUNNER_NARROWINGS.find((x) => x.runner.test(cmd));
|
|
5157
|
+
const m = t ? (" " + args.trim()).match(t.flags) : null;
|
|
5158
|
+
return m ? m[0].trim().replace(/[=\s].*$/, "") : null;
|
|
5159
|
+
}
|
|
5160
|
+
function coverageFloorLowered(removed, added) {
|
|
5161
|
+
const was = removed.match(COVERAGE_FLOOR);
|
|
5162
|
+
const now = added.match(COVERAGE_FLOOR);
|
|
5163
|
+
return was !== null && now !== null && Number(now[1]) < Number(was[1]);
|
|
5164
|
+
}
|
|
5165
|
+
function inCommandPosition(core) {
|
|
5166
|
+
return canonical(core).split(/\s*(?:&&|\|\||;|\|)\s*/).some((seg) => CHECK_HEAD.test(canonical(seg.replace(COMMAND_PREFIX, ""))));
|
|
5167
|
+
}
|
|
5168
|
+
function narrowedByPositional(args) {
|
|
5169
|
+
return positionalOf(args) !== null;
|
|
5170
|
+
}
|
|
5171
|
+
function positionalOf(args) {
|
|
5172
|
+
const toks = args.trim().split(/\s+/).filter(Boolean);
|
|
5173
|
+
for (let i = 0; i < toks.length; i++) {
|
|
5174
|
+
const t = toks[i];
|
|
5175
|
+
if (t === "--" || t.startsWith("-")) continue;
|
|
5176
|
+
const prev = toks[i - 1];
|
|
5177
|
+
if (prev && (prev.startsWith("-") && prev !== "--" && !prev.includes("=") || /[<>]/.test(prev))) continue;
|
|
5178
|
+
if (/[<>|;&]/.test(t)) continue;
|
|
5179
|
+
if (PATH_SHAPED.test(t)) return t;
|
|
5180
|
+
}
|
|
5181
|
+
return null;
|
|
5182
|
+
}
|
|
5183
|
+
function checkKind(core) {
|
|
5184
|
+
if (!invokesCheck(core)) return null;
|
|
5185
|
+
const s = canonical(core);
|
|
5186
|
+
if (/\btamperward\b/.test(s)) return "gate";
|
|
5187
|
+
if (/\b(?:lint|eslint|biome|oxlint|golangci-lint|ruff)\b/i.test(s)) return "lint";
|
|
5188
|
+
if (/\b(?:typecheck|type-check|tsc|tsgo|mypy)\b|\bdeno\s+check\b/i.test(s)) return "types";
|
|
5189
|
+
return "test";
|
|
5190
|
+
}
|
|
5191
|
+
function survives(removedCore, isUses, afterCores, addedCores) {
|
|
5192
|
+
if (isUses) {
|
|
5193
|
+
const ref = usesRef(removedCore);
|
|
5194
|
+
return afterCores.some((a) => usesRef(a) === ref) ? { state: "kept" } : { state: "gone" };
|
|
5195
|
+
}
|
|
5196
|
+
const r = canonical(removedCore);
|
|
5197
|
+
const kind = checkKind(removedCore);
|
|
5198
|
+
const narrowed = (args) => kind === "test" && narrowedByPositional(args);
|
|
5199
|
+
let neutralised;
|
|
5200
|
+
for (const raw of afterCores) {
|
|
5201
|
+
const a = canonical(raw);
|
|
5202
|
+
if (a === r) return { state: "kept" };
|
|
5203
|
+
if (a.startsWith(r + " ")) {
|
|
5204
|
+
const rest = a.slice(r.length);
|
|
5205
|
+
if (NEUTRALISING_SUFFIX.test(rest) || narrowed(rest) || runnerNarrowed(a, rest)) neutralised ??= raw;
|
|
5206
|
+
else return { state: "kept" };
|
|
5207
|
+
} else if (r.startsWith(a + " ") && invokesCheck(a)) {
|
|
5208
|
+
return { state: "kept" };
|
|
5209
|
+
} else {
|
|
5210
|
+
const w = a.match(TIMEOUT_WRAP);
|
|
5211
|
+
if (w && (canonical(w[1]) === r || canonical(w[1]).startsWith(r + " "))) neutralised ??= raw;
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
let respelled = false;
|
|
5215
|
+
for (const raw of addedCores) {
|
|
5216
|
+
if (!kind || checkKind(raw) !== kind) continue;
|
|
5217
|
+
const a = canonical(raw);
|
|
5218
|
+
if (a.match(TIMEOUT_WRAP)) {
|
|
5219
|
+
neutralised ??= raw;
|
|
5220
|
+
continue;
|
|
5221
|
+
}
|
|
5222
|
+
if (!inCommandPosition(raw)) continue;
|
|
5223
|
+
if (coverageFloorLowered(r, a)) {
|
|
5224
|
+
neutralised ??= raw;
|
|
5225
|
+
continue;
|
|
5226
|
+
}
|
|
5227
|
+
const args = a.slice(a.indexOf(" ") + 1 || a.length);
|
|
5228
|
+
if (a.includes(" ") && (NEUTRALISING_SUFFIX.test(" " + args) || narrowed(args) || runnerNarrowed(a, args))) neutralised ??= raw;
|
|
5229
|
+
else respelled = true;
|
|
5230
|
+
}
|
|
5231
|
+
if (respelled) return { state: "kept" };
|
|
5232
|
+
return neutralised ? { state: "neutralised", by: neutralised } : { state: "gone" };
|
|
5233
|
+
}
|
|
5234
|
+
function unwrap(seg) {
|
|
5235
|
+
const s = seg.trim().replace(PYTHON_M, "").replace(NODE_BIN, "$1").replace(BIN_OF, "$1$2");
|
|
5236
|
+
if (invokesCheck(s) || PRINTER.test(s) || /^["']/.test(s)) return s;
|
|
5237
|
+
const toks = s.split(/\s+/);
|
|
5238
|
+
for (let i = 1; i < toks.length && i <= 16; i++) {
|
|
5239
|
+
const rest = toks.slice(i).join(" ");
|
|
5240
|
+
if (invokesCheck(rest)) return rest;
|
|
5241
|
+
}
|
|
5242
|
+
return s;
|
|
5243
|
+
}
|
|
5244
|
+
function scriptKind3(inner) {
|
|
5245
|
+
const kind = checkKind(inner);
|
|
5246
|
+
if (!kind) return null;
|
|
5247
|
+
const head = canonical(inner).match(/^(tests?|lint|type-?check|coverage)(?=[\s:]|$)/i)?.[1];
|
|
5248
|
+
if (!head) return kind;
|
|
5249
|
+
return /^type/i.test(head) ? "types" : /^lint/i.test(head) ? "lint" : "test";
|
|
5250
|
+
}
|
|
5251
|
+
function runnerOfSegment(canon) {
|
|
5252
|
+
const m = canon.match(RUNNER_NAME);
|
|
5253
|
+
if (!m) return null;
|
|
5254
|
+
const n = m[1];
|
|
5255
|
+
return n === "jest" || n === "vitest" || n === "mocha" || n === "pytest" || n === "go" || n === "cargo" ? n : "node";
|
|
5256
|
+
}
|
|
5257
|
+
function statements(raw) {
|
|
5258
|
+
const out3 = [];
|
|
5259
|
+
let single = false;
|
|
5260
|
+
let double = false;
|
|
5261
|
+
let last = 0;
|
|
5262
|
+
for (let i = 0; i < raw.length; i++) {
|
|
5263
|
+
const ch = raw[i];
|
|
5264
|
+
if (ch === "\\" && !single) {
|
|
5265
|
+
i++;
|
|
5266
|
+
continue;
|
|
5267
|
+
}
|
|
5268
|
+
if (ch === "'" && !double) {
|
|
5269
|
+
single = !single;
|
|
5270
|
+
continue;
|
|
5271
|
+
}
|
|
5272
|
+
if (ch === '"' && !single) {
|
|
5273
|
+
double = !double;
|
|
5274
|
+
continue;
|
|
5275
|
+
}
|
|
5276
|
+
if (single || double) continue;
|
|
5277
|
+
const prev = raw[i - 1];
|
|
5278
|
+
const next = raw[i + 1];
|
|
5279
|
+
let op = null;
|
|
5280
|
+
if (ch === "&" && next === "&") op = "&&";
|
|
5281
|
+
else if (ch === "|" && next === "|") op = "||";
|
|
5282
|
+
else if (ch === "|" && next === "&") op = "|&";
|
|
5283
|
+
else if (ch === ";" || ch === "\n") op = ";";
|
|
5284
|
+
else if (ch === "|" && prev !== ">") op = "|";
|
|
5285
|
+
else if (ch === "&" && prev !== ">" && next !== ">") op = "&";
|
|
5286
|
+
if (!op) continue;
|
|
5287
|
+
out3.push({ seg: raw.slice(last, i).trim(), op });
|
|
5288
|
+
i += op.length - 1;
|
|
5289
|
+
last = i + 1;
|
|
5290
|
+
}
|
|
5291
|
+
out3.push({ seg: raw.slice(last).trim(), op: null });
|
|
5292
|
+
return out3.filter((s) => s.seg !== "");
|
|
5293
|
+
}
|
|
5294
|
+
function descriptors(cmd, runner, opts) {
|
|
5295
|
+
const out3 = [];
|
|
5296
|
+
const stmts = statements(cmd);
|
|
5297
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
5298
|
+
const { seg, op } = stmts[i];
|
|
5299
|
+
const wrapped = seg.match(TIMEOUT_WRAP);
|
|
5300
|
+
const inner = unwrap(wrapped ? wrapped[1] : seg);
|
|
5301
|
+
if (!invokesCheck(inner)) continue;
|
|
5302
|
+
const kind = scriptKind3(inner);
|
|
5303
|
+
const canon = canonical(inner);
|
|
5304
|
+
const rn = runner ?? runnerOfSegment(canon);
|
|
5305
|
+
const args = canon.includes(" ") ? " " + canon.slice(canon.indexOf(" ") + 1) : " ";
|
|
5306
|
+
if (wrapped) out3.push({ key: "timeout", what: "a timeout wrapper", direction: "added" });
|
|
5307
|
+
const following = stmts[i + 1]?.seg ?? "";
|
|
5308
|
+
if (op === "||" || op === "|" || op === "&" || op === ";" && !invokesCheck(unwrap(following))) {
|
|
5309
|
+
out3.push({ key: `op:${op}`, what: `${op} ${following.split(/\s+/).slice(0, 2).join(" ")}`.trim(), direction: "added" });
|
|
5310
|
+
}
|
|
5311
|
+
for (const re of MASKING_FLAGS) {
|
|
5312
|
+
const f = spelled(re, args);
|
|
5313
|
+
if (f) out3.push({ key: `flag:${f}`, what: f, direction: "added" });
|
|
5314
|
+
}
|
|
5315
|
+
for (const re of GATE_FLAGS) {
|
|
5316
|
+
const f = spelled(re, " " + seg);
|
|
5317
|
+
if (f) out3.push({ key: `gate:${f}`, what: f, direction: "removed" });
|
|
5318
|
+
}
|
|
5319
|
+
if (kind !== "test" || opts.sliceByDesign || opts.extended?.has(kind)) continue;
|
|
5320
|
+
const flags = [SUITE_NARROWING_FLAGS, ...NARROWING_BY_RUNNER["*"], ...rn ? NARROWING_BY_RUNNER[rn] : []];
|
|
5321
|
+
for (const re of flags) {
|
|
5322
|
+
const f = spelled(re, args);
|
|
5323
|
+
if (f && !opts.excuseFlag?.(f, canon)) out3.push({ key: `flag:${f}`, what: f, direction: "added" });
|
|
5324
|
+
}
|
|
5325
|
+
if (rn === "cargo" && CARGO_SKIP.test(args)) out3.push({ key: "flag:-- --skip", what: "-- --skip", direction: "added" });
|
|
5326
|
+
const shared = runnerNarrowing(canon, args);
|
|
5327
|
+
if (shared && !opts.excuseFlag?.(shared, canon)) out3.push({ key: `flag:${shared}`, what: shared, direction: "added" });
|
|
5328
|
+
if (opts.fresh) continue;
|
|
5329
|
+
const cfg = args.match(CONFIG_FLAG);
|
|
5330
|
+
if (cfg && !opts.configReviewed?.(cfg[3])) out3.push({ key: `config:${cfg[3]}`, what: `${cfg[1]} ${cfg[3]}`, direction: "added" });
|
|
5331
|
+
const pos = positionalOf(args);
|
|
5332
|
+
if (pos) out3.push({ key: "positional", what: `a path positional (${pos})`, direction: "added" });
|
|
5333
|
+
}
|
|
5334
|
+
return out3;
|
|
5335
|
+
}
|
|
5336
|
+
function checkStatements(cmd) {
|
|
5337
|
+
const out3 = [];
|
|
5338
|
+
for (const seg of segments(cmd)) {
|
|
5339
|
+
const w = seg.match(TIMEOUT_WRAP);
|
|
5340
|
+
const inner = unwrap(w ? w[1] : seg);
|
|
5341
|
+
const kind = scriptKind3(inner);
|
|
5342
|
+
if (kind) out3.push({ kind, canon: canonical(inner) });
|
|
5343
|
+
}
|
|
5344
|
+
return out3;
|
|
5345
|
+
}
|
|
5346
|
+
function checkKinds(cmd) {
|
|
5347
|
+
return new Set(checkStatements(cmd).map((s) => s.kind));
|
|
5348
|
+
}
|
|
5349
|
+
function failsLoudly(cmd) {
|
|
5350
|
+
const last = segments(cmd).pop();
|
|
5351
|
+
return last !== void 0 && FAILS_LOUDLY.test(last.trim());
|
|
5352
|
+
}
|
|
5353
|
+
function invocationWeakening(before, after, runner, opts = {}) {
|
|
5354
|
+
const was = checkKinds(before);
|
|
5355
|
+
const now = checkKinds(after);
|
|
5356
|
+
if (now.size === 0 && failsLoudly(after)) return { state: "kept" };
|
|
5357
|
+
for (const k of was) {
|
|
5358
|
+
if (now.has(k) || opts.relocated?.(k)) continue;
|
|
5359
|
+
const instead = [...now].filter((x) => !was.has(x));
|
|
5360
|
+
return { state: "removed", kind: k, what: instead.length ? `runs a ${instead.join("/")} check instead` : now.size ? "the check is gone" : "no check is run" };
|
|
5361
|
+
}
|
|
5362
|
+
const survived = new Set(checkStatements(before).map((s) => s.canon));
|
|
5363
|
+
const extended = new Set(checkStatements(after).filter((s) => survived.has(s.canon)).map((s) => s.kind));
|
|
5364
|
+
const read = { ...opts, fresh: was.size === 0, extended };
|
|
5365
|
+
const beforeDs = descriptors(before, runner, read);
|
|
5366
|
+
const afterDs = descriptors(after, runner, read);
|
|
5367
|
+
const seenBefore = new Set(beforeDs.map((d) => d.key));
|
|
5368
|
+
const added = afterDs.find((d) => d.direction === "added" && !seenBefore.has(d.key));
|
|
5369
|
+
if (added) return { state: "neutralised", what: added.what, direction: "added" };
|
|
5370
|
+
if (coverageFloorLowered(before, after)) return { state: "neutralised", what: "a lowered --cov-fail-under", direction: "added" };
|
|
5371
|
+
const seenAfter = new Set(afterDs.map((d) => d.key));
|
|
5372
|
+
const dropped = beforeDs.find((d) => d.direction === "removed" && !seenAfter.has(d.key));
|
|
5373
|
+
if (dropped) return { state: "neutralised", what: dropped.what, direction: "removed" };
|
|
5374
|
+
return { state: "kept" };
|
|
5375
|
+
}
|
|
5376
|
+
var INVOKES_TOOL, INVOKES_SCRIPT, invokesCheck, MATRIX_FLAG, usesRef, SUITE_NARROWING_FLAGS, NEUTRALISING_SUFFIX, TIMEOUT_WRAP, RUNNER_NARROWINGS, COVERAGE_FLOOR, COMMAND_PREFIX, CHECK_HEAD, PATH_SHAPED, NARROWING_BY_RUNNER, CARGO_SKIP, MASKING_FLAGS, GATE_FLAGS, CONFIG_FLAG, PRINTER, PYTHON_M, NODE_BIN, BIN_OF, RUNNER_NAME, spelled, FAILS_LOUDLY;
|
|
5377
|
+
var init_invocation = __esm({
|
|
5378
|
+
"src/detectors/invocation.ts"() {
|
|
5379
|
+
"use strict";
|
|
5380
|
+
init_gh_expression();
|
|
5381
|
+
init_command();
|
|
5382
|
+
INVOKES_TOOL = /(?:^\s*|[;&|`]\s*|\$\(\s*|\b(?:npx|yarn|pnpm|bunx?)\s+)(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint|node\s+--test|biome\s+(?:ci|check|lint)|deno\s+(?:test|lint|check)|ruff\s+check)\b/;
|
|
5383
|
+
INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
|
|
5384
|
+
invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line);
|
|
5385
|
+
MATRIX_FLAG = new RegExp(`(?:^|\\s)-{1,2}[\\w-]+(?:=|\\s+)${MATRIX_TOKEN}(?:/\\S*)?(?=\\s|$)`, "g");
|
|
5386
|
+
usesRef = (core) => core.replace(/@.*$/, "");
|
|
5387
|
+
SUITE_NARROWING_FLAGS = /--testPathIgnorePatterns\b|--testPathPattern\b|--testNamePattern\b|(?:^|\s)-t\s|--onlyChanged\b|--changed\b|--findRelatedTests\b|--exclude\b|--dir\b|--project\b/;
|
|
5388
|
+
NEUTRALISING_SUFFIX = new RegExp(
|
|
5389
|
+
`\\|\\||;|(?<!&)&(?!&)|\\|(?!\\|)|--passWithNoTests\\b|--coverage=false\\b|--coverageThreshold\\b|${SUITE_NARROWING_FLAGS.source}`
|
|
5390
|
+
);
|
|
5391
|
+
TIMEOUT_WRAP = /^timeout\s+(?:-{1,2}\S+\s+)*\d\S*\s+(.+)$/;
|
|
5392
|
+
RUNNER_NARROWINGS = [
|
|
5393
|
+
{
|
|
5394
|
+
// a package-manager script: `test --prefix dir`, `-w pkg`, `--workspace=pkg`,
|
|
5395
|
+
// `--if-present` (no script, no failure), pnpm's `--filter` / `-F`, npm's `-C`
|
|
5396
|
+
runner: /^(?:test|tests|coverage)(?::\S+)?(?=\s|$)/,
|
|
5397
|
+
flags: /\s--if-present(?=\s|$)|\s--prefix(?:=|\s)|\s-C(?:=|\s)|\s-w(?:=|\s)|\s--workspace(?:=|\s)|\s--filter(?:=|\s)|\s-F(?:=|\s)/
|
|
5398
|
+
},
|
|
5399
|
+
{
|
|
5400
|
+
runner: /^(?:jest|vitest)\b/,
|
|
5401
|
+
flags: /\s--shard(?:=|\s)|\s--root(?:Dir)?(?:=|\s)|\s--testMatch(?:=|\s)|\s--testRegex(?:=|\s)|\s--modulePathIgnorePatterns(?:=|\s)|\s--selectProjects(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)/
|
|
5402
|
+
},
|
|
5403
|
+
{ runner: /^playwright\b/, flags: /\s--shard(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--grep-invert(?:=|\s)/ },
|
|
5404
|
+
{ runner: /^mocha\b/, flags: /\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--fgrep(?:=|\s)|\s-f(?:=|\s)/ },
|
|
5405
|
+
{ runner: /^ava\b/, flags: /\s--match(?:=|\s)|\s-m(?:=|\s)/ },
|
|
5406
|
+
{ runner: /^node --test\b/, flags: /\s--test-name-pattern(?:=|\s)|\s--test-skip-pattern(?:=|\s)|\s--test-only(?=\s|$)/ },
|
|
5407
|
+
{
|
|
5408
|
+
// `-p no:<plugin>` disables collection when the plugin is the collector
|
|
5409
|
+
// (`python`, `doctest`, `cov`); the cache, warning and output plugins select nothing
|
|
5410
|
+
runner: /^pytest\b/,
|
|
5411
|
+
flags: /\s-k(?:=|\s)|\s-m(?:=|\s)|\s--deselect(?:=|\s)|\s--ignore(?:-glob)?(?:=|\s)|\s-p\s*no:(?!(?:cacheprovider|warnings|faulthandler|randomly|logging|legacypath|pastebin|stepwise|tmpdir|junitxml|terminal|terminalreporter)(?=\s|$))|\s--collect-only(?=\s|$)|\s--co(?=\s|$)/
|
|
5412
|
+
},
|
|
5413
|
+
{ runner: /^cargo test\b/, flags: /\s--\s(?:\S+\s+)*--skip(?:=|\s)|\s--exclude(?:=|\s)/ },
|
|
5414
|
+
{ runner: /^go test\b/, flags: /\s-run(?:=|\s)|\s-skip(?:=|\s)/ }
|
|
5415
|
+
];
|
|
5416
|
+
COVERAGE_FLOOR = /--cov-fail-under(?:=|\s+)(\d+(?:\.\d+)?)/;
|
|
5417
|
+
COMMAND_PREFIX = /^(?:(?:[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S*)|sudo|env|nice|time|exec|command|nohup|xvfb-run|dbus-launch|cross-env|dotenv|poetry\s+run|pipenv\s+run|uv\s+run|hatch\s+run|pdm\s+run|bundle\s+exec)\s+(?:-{1,2}\S+\s+)*)*/;
|
|
5418
|
+
CHECK_HEAD = /^(?:(?:test|tests|lint|typecheck|type-check|coverage)(?::\S+)?(?=\s|$)|(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint)\b|node --test\b|biome (?:ci|check|lint)\b|deno (?:test|lint|check)\b|ruff check\b|(?:make|cargo|go) test\b|gradle\w* (?:test|check)\b)/;
|
|
5419
|
+
PATH_SHAPED = /\/|\.(?:test|spec)\./;
|
|
5420
|
+
NARROWING_BY_RUNNER = {
|
|
5421
|
+
"*": [/--shard\b/],
|
|
5422
|
+
jest: [/--root\b/, /--rootDir\b/, /--testMatch\b/, /--testRegex\b/, /--modulePathIgnorePatterns\b/, /--selectProjects\b/, /--ignoreProjects\b/, /--filter\b/],
|
|
5423
|
+
vitest: [/--grep\b/],
|
|
5424
|
+
mocha: [/--grep\b/, /(?:^|\s)-g(?:\s|=)/, /--ignore\b/, /(?:^|\s)-f(?:\s|=)/, /--fgrep\b/],
|
|
5425
|
+
pytest: [/(?:^|\s)-k(?:\s|=)/, /(?:^|\s)-m(?:\s|=)/, /--deselect\b/, /--ignore\b/, /--ignore-glob\b/, /--last-failed\b/, /(?:^|\s)--lf(?:\s|$)/, /--collect-only\b/, /(?:^|\s)--co(?:\s|$)/],
|
|
5426
|
+
go: [/(?:^|\s)-run(?:\s|=)/, /(?:^|\s)-skip(?:\s|=)/],
|
|
5427
|
+
cargo: [],
|
|
5428
|
+
node: [/--test-name-pattern\b/, /--test-skip-pattern\b/]
|
|
5429
|
+
};
|
|
5430
|
+
CARGO_SKIP = /\s--\s+(?:\S+\s+)*--skip\b/;
|
|
5431
|
+
MASKING_FLAGS = [/--passWithNoTests\b/, /--coverage=false\b/, /--coverageThreshold\b/, /--if-present\b/];
|
|
5432
|
+
GATE_FLAGS = [/--check-coverage\b/];
|
|
5433
|
+
CONFIG_FLAG = /(?:^|\s)(--config|-c)(?:=|\s+)(['"]?)([^\s'"]+)\2(?=\s|$)/;
|
|
5434
|
+
PRINTER = /^(?:echo|printf|true|false|:|cat|exit|return|sleep|test|\[|node|git|curl|wget)(?=\s|$)/;
|
|
5435
|
+
PYTHON_M = /^python\d?(?:\.\d+)?\s+-m\s+(?=pytest\b)/;
|
|
5436
|
+
NODE_BIN = /^node(?:\s+-\S+)*\s+\S*node_modules\/(?:\.bin\/)?(jest|vitest|mocha|ava)\b\S*/;
|
|
5437
|
+
BIN_OF = /\$\((?:yarn|npm|pnpm)\s+bin\s+(\w+)\)|\$\((?:yarn|npm|pnpm)\s+bin\)\/(\w+)/g;
|
|
5438
|
+
RUNNER_NAME = /^(jest|vitest|mocha|pytest|go|cargo|node)\b/;
|
|
5439
|
+
spelled = (re, s) => {
|
|
5440
|
+
const m = s.match(re);
|
|
5441
|
+
return m ? m[0].trim().replace(/[=\s].*$/, "") : null;
|
|
5442
|
+
};
|
|
5443
|
+
FAILS_LOUDLY = /^(?:exit\s+(?:[1-9]\d*)|false)\s*$/;
|
|
5444
|
+
}
|
|
5445
|
+
});
|
|
5446
|
+
|
|
5447
|
+
// src/detectors/ci-tampering.ts
|
|
5448
|
+
function isActiveWorkflow(path) {
|
|
5449
|
+
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
5450
|
+
}
|
|
5451
|
+
function commandCore(line) {
|
|
5452
|
+
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
5453
|
+
}
|
|
5180
5454
|
function expressionOf(raw) {
|
|
5181
5455
|
const v = uncommented(raw).replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
5182
5456
|
const m = v.match(/^\$\{\{\s*(.+?)\s*\}\}$/);
|
|
@@ -5425,7 +5699,7 @@ function ancestorOf(lines, i) {
|
|
|
5425
5699
|
while (h >= 0 && (lines[h].trim() === "" || /^\s*#/.test(lines[h]) || indentOf(lines[h]) >= ind)) h--;
|
|
5426
5700
|
return h >= 0 ? h : null;
|
|
5427
5701
|
}
|
|
5428
|
-
var RULE6, USES, CHECK, YAML_KEY,
|
|
5702
|
+
var RULE6, USES, CHECK, YAML_KEY, invokesCheck2, uncommented, SHELL_ERREXIT, indentOf, STEP_START, REPORTER_ACTION, usesCheck, stepHasCheck, jobHasCheck, NEUTRALISERS, LIST_KEYS, DEFAULT_BRANCH, ALL, CODE_EVENTS, PR_CODE_TYPES, pathsMatchSource, ignoresEverySource, CODE_PROBES, REUSED_WORKFLOW, MANIFEST, WORKDIR_LINE, REF_LINE, CHECKOUT, ciTampering;
|
|
5429
5703
|
var init_ci_tampering = __esm({
|
|
5430
5704
|
"src/detectors/ci-tampering.ts"() {
|
|
5431
5705
|
"use strict";
|
|
@@ -5435,60 +5709,22 @@ var init_ci_tampering = __esm({
|
|
|
5435
5709
|
init_files();
|
|
5436
5710
|
init_hook_wiring();
|
|
5437
5711
|
init_repo();
|
|
5712
|
+
init_gh_expression();
|
|
5713
|
+
init_invocation();
|
|
5714
|
+
init_invocation();
|
|
5438
5715
|
RULE6 = "ci-tampering";
|
|
5439
5716
|
USES = /^\s*-\s*uses:/;
|
|
5440
5717
|
CHECK = /\b(test|tests|lint|typecheck|type-check|tsc|eslint|jest|vitest|playwright|coverage|tamperward)\b/i;
|
|
5441
5718
|
YAML_KEY = /^\s*-?\s*[A-Za-z_][\w-]*:\s*(?:$|\S)/;
|
|
5442
|
-
|
|
5443
|
-
INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
|
|
5444
|
-
invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line) || INVOKES_TOOL.test(commandCore(line));
|
|
5445
|
-
EXPR_TOKEN = "__expr__";
|
|
5446
|
-
MATRIX_TOKEN = "__matrix__";
|
|
5447
|
-
MATRIX_FLAG = new RegExp(`(?:^|\\s)-{1,2}[\\w-]+(?:=|\\s+)${MATRIX_TOKEN}(?:/\\S*)?(?=\\s|$)`, "g");
|
|
5448
|
-
usesRef = (core) => core.replace(/@.*$/, "");
|
|
5449
|
-
SUITE_NARROWING_FLAGS = /--testPathIgnorePatterns\b|--testPathPattern\b|--testNamePattern\b|(?:^|\s)-t\s|--onlyChanged\b|--changed\b|--findRelatedTests\b|--exclude\b|--dir\b|--project\b/;
|
|
5450
|
-
NEUTRALISING_SUFFIX = new RegExp(
|
|
5451
|
-
`\\|\\||;|(?<!&)&(?!&)|\\|(?!\\|)|--passWithNoTests\\b|--coverage=false\\b|--coverageThreshold\\b|${SUITE_NARROWING_FLAGS.source}`
|
|
5452
|
-
);
|
|
5453
|
-
TIMEOUT_WRAP = /^timeout\s+(?:-{1,2}\S+\s+)*\d\S*\s+(.+)$/;
|
|
5454
|
-
RUNNER_NARROWINGS = [
|
|
5455
|
-
{
|
|
5456
|
-
// a package-manager script: `test --prefix dir`, `-w pkg`, `--workspace=pkg`,
|
|
5457
|
-
// `--if-present` (no script, no failure), pnpm's `--filter` / `-F`, npm's `-C`
|
|
5458
|
-
runner: /^(?:test|tests|coverage)(?::\S+)?(?=\s|$)/,
|
|
5459
|
-
flags: /\s--if-present(?=\s|$)|\s--prefix(?:=|\s)|\s-C(?:=|\s)|\s-w(?:=|\s)|\s--workspace(?:=|\s)|\s--filter(?:=|\s)|\s-F(?:=|\s)/
|
|
5460
|
-
},
|
|
5461
|
-
{
|
|
5462
|
-
runner: /^(?:jest|vitest)\b/,
|
|
5463
|
-
flags: /\s--shard(?:=|\s)|\s--root(?:Dir)?(?:=|\s)|\s--testMatch(?:=|\s)|\s--testRegex(?:=|\s)|\s--modulePathIgnorePatterns(?:=|\s)|\s--selectProjects(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)/
|
|
5464
|
-
},
|
|
5465
|
-
{ runner: /^playwright\b/, flags: /\s--shard(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--grep-invert(?:=|\s)/ },
|
|
5466
|
-
{ runner: /^mocha\b/, flags: /\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--fgrep(?:=|\s)|\s-f(?:=|\s)/ },
|
|
5467
|
-
{ runner: /^ava\b/, flags: /\s--match(?:=|\s)|\s-m(?:=|\s)/ },
|
|
5468
|
-
{ runner: /^node --test\b/, flags: /\s--test-name-pattern(?:=|\s)|\s--test-skip-pattern(?:=|\s)|\s--test-only(?=\s|$)/ },
|
|
5469
|
-
{
|
|
5470
|
-
// `-p no:<plugin>` disables collection when the plugin is the collector
|
|
5471
|
-
// (`python`, `doctest`, `cov`); the cache, warning and output plugins select nothing
|
|
5472
|
-
runner: /^pytest\b/,
|
|
5473
|
-
flags: /\s-k(?:=|\s)|\s-m(?:=|\s)|\s--deselect(?:=|\s)|\s--ignore(?:-glob)?(?:=|\s)|\s-p\s*no:(?!(?:cacheprovider|warnings|faulthandler|randomly|logging|legacypath|pastebin|stepwise|tmpdir|junitxml|terminal|terminalreporter)(?=\s|$))|\s--collect-only(?=\s|$)|\s--co(?=\s|$)/
|
|
5474
|
-
},
|
|
5475
|
-
{ runner: /^cargo test\b/, flags: /\s--\s(?:\S+\s+)*--skip(?:=|\s)|\s--exclude(?:=|\s)/ },
|
|
5476
|
-
{ runner: /^go test\b/, flags: /\s-run(?:=|\s)|\s-skip(?:=|\s)/ }
|
|
5477
|
-
];
|
|
5478
|
-
COVERAGE_FLOOR = /--cov-fail-under(?:=|\s+)(\d+(?:\.\d+)?)/;
|
|
5479
|
-
COMMAND_PREFIX = /^(?:(?:[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S*)|sudo|env|nice|time|exec|command|nohup|xvfb-run|dbus-launch|cross-env|dotenv|poetry\s+run|pipenv\s+run|uv\s+run|hatch\s+run|pdm\s+run|bundle\s+exec)\s+(?:-{1,2}\S+\s+)*)*/;
|
|
5480
|
-
CHECK_HEAD = /^(?:(?:test|tests|lint|typecheck|type-check|coverage)(?::\S+)?(?=\s|$)|(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint)\b|node --test\b|biome (?:ci|check|lint)\b|deno (?:test|lint|check)\b|ruff check\b|(?:make|cargo|go) test\b|gradle\w* (?:test|check)\b)/;
|
|
5481
|
-
PATH_SHAPED = /\/|\.(?:test|spec)\./;
|
|
5719
|
+
invokesCheck2 = (line) => invokesCheck(line) || INVOKES_TOOL.test(commandCore(line));
|
|
5482
5720
|
uncommented = (v) => v.replace(/\s+#.*$/, "").trim();
|
|
5483
|
-
TRUTHY = Symbol("truthy");
|
|
5484
|
-
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5485
5721
|
SHELL_ERREXIT = /(?:^|\s)-[a-zA-Z]*e[a-zA-Z]*(?:\s|$)|-o\s+errexit\b/;
|
|
5486
5722
|
indentOf = (l) => l.length - l.trimStart().length;
|
|
5487
5723
|
STEP_START = /^\s*-\s+(?:name|uses|run|if|id|with|env|shell|continue-on-error|working-directory|timeout-minutes):/;
|
|
5488
5724
|
REPORTER_ACTION = /^\s*-?\s*uses:\s*\S*?(?:[\w-]*-reporter|[\w-]*-results?[\w-]*|upload-[\w-]*|codecov[\w-]*|coveralls[\w-]*)(?:@|\s|$)/i;
|
|
5489
5725
|
usesCheck = (l) => USES.test(l) && CHECK.test(l) && !REPORTER_ACTION.test(l);
|
|
5490
|
-
stepHasCheck = (lines, range) => range === null || lines.slice(range[0], range[1]).some((l) =>
|
|
5491
|
-
jobHasCheck = (lines, range) => lines.slice(range[0], range[1]).some((l) =>
|
|
5726
|
+
stepHasCheck = (lines, range) => range === null || lines.slice(range[0], range[1]).some((l) => invokesCheck2(l) || usesCheck(l));
|
|
5727
|
+
jobHasCheck = (lines, range) => lines.slice(range[0], range[1]).some((l) => invokesCheck2(l) || usesCheck(l) || /^\s*uses:\s*\S/.test(l));
|
|
5492
5728
|
NEUTRALISERS = [
|
|
5493
5729
|
{ re: /^\s*set\s+\+e\b/, what: "set +e \u2014 a failing check no longer fails the step" },
|
|
5494
5730
|
{ re: /^\s*exit\s+0\s*(?:$|[;#])/, what: "exit 0 \u2014 the step ends green whatever the check did" },
|
|
@@ -5574,7 +5810,7 @@ var init_ci_tampering = __esm({
|
|
|
5574
5810
|
}
|
|
5575
5811
|
if (afterLines) {
|
|
5576
5812
|
for (const b of runBlocks(afterLines)) {
|
|
5577
|
-
if (!b.body.some(
|
|
5813
|
+
if (!b.body.some(invokesCheck2)) continue;
|
|
5578
5814
|
const propagates = b.body.some((l) => /\bexit\s+"?\$/.test(l));
|
|
5579
5815
|
for (let k = 0; k < b.body.length; k++) {
|
|
5580
5816
|
const idx = b.start + 1 + k;
|
|
@@ -5621,7 +5857,7 @@ var init_ci_tampering = __esm({
|
|
|
5621
5857
|
const d = defaultsOf(afterLines, i);
|
|
5622
5858
|
if (d === null) continue;
|
|
5623
5859
|
const job = jobOf(afterLines, d);
|
|
5624
|
-
if (job ? !jobHasCheck(afterLines, job) : !afterLines.some((x) =>
|
|
5860
|
+
if (job ? !jobHasCheck(afterLines, job) : !afterLines.some((x) => invokesCheck2(x) || usesCheck(x))) continue;
|
|
5625
5861
|
}
|
|
5626
5862
|
out3.push(
|
|
5627
5863
|
makeFinding(RULE6, policy, {
|
|
@@ -5678,7 +5914,7 @@ var init_ci_tampering = __esm({
|
|
|
5678
5914
|
for (const l of removedLines(c)) {
|
|
5679
5915
|
if (/^\s*#/.test(l.content)) continue;
|
|
5680
5916
|
const isUsesLine = usesCheck(l.content);
|
|
5681
|
-
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? /^\s*-?\s*run:/.test(l.content) : true) &&
|
|
5917
|
+
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? /^\s*-?\s*run:/.test(l.content) : true) && invokesCheck2(l.content);
|
|
5682
5918
|
if (!isUsesLine && !isCheckCommand) continue;
|
|
5683
5919
|
const s = survives(commandCore(l.content), isUsesLine, afterCores, addedCores);
|
|
5684
5920
|
if (s.state === "kept") continue;
|
|
@@ -7324,33 +7560,43 @@ function excludesOtherRunner(flag, cmd) {
|
|
|
7324
7560
|
const values = [...cmd.matchAll(/--exclude(?:=|\s+)(['"]?)(\S+?)\1(?=\s|$)/g)].map((m) => m[2]);
|
|
7325
7561
|
return values.length > 0 && values.every((v) => OTHER_RUNNER_DIR.test(v.replace(/^\.\//, "").replace(/(?:\/\*+)*\/?$/, "") + "/"));
|
|
7326
7562
|
}
|
|
7327
|
-
function
|
|
7563
|
+
function scriptsOf(src) {
|
|
7564
|
+
try {
|
|
7565
|
+
const pkg = JSON.parse(src ?? "{}");
|
|
7566
|
+
const s = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts : {};
|
|
7567
|
+
return Object.fromEntries(Object.entries(s).filter((e) => typeof e[1] === "string"));
|
|
7568
|
+
} catch {
|
|
7569
|
+
return {};
|
|
7570
|
+
}
|
|
7571
|
+
}
|
|
7572
|
+
function scriptWeakenings(before, after, path, policy, addedPaths) {
|
|
7328
7573
|
if ((path.split("/").pop() ?? path) !== "package.json") return [];
|
|
7329
|
-
const scriptsOf = (src) => {
|
|
7330
|
-
try {
|
|
7331
|
-
const pkg = JSON.parse(src ?? "{}");
|
|
7332
|
-
const s = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts : {};
|
|
7333
|
-
return Object.fromEntries(Object.entries(s).filter((e) => typeof e[1] === "string"));
|
|
7334
|
-
} catch {
|
|
7335
|
-
return {};
|
|
7336
|
-
}
|
|
7337
|
-
};
|
|
7338
7574
|
const was = scriptsOf(before);
|
|
7339
7575
|
const now = scriptsOf(after);
|
|
7576
|
+
const dir = path.includes("/") ? path.slice(0, path.lastIndexOf("/") + 1) : "";
|
|
7577
|
+
const configReviewed = (file) => {
|
|
7578
|
+
const f = dir + file.replace(/^\.\//, "");
|
|
7579
|
+
return isProtected(f, policy, "config") && !addedPaths.has(f);
|
|
7580
|
+
};
|
|
7581
|
+
const relocated = (kind) => Object.keys(now).some((n) => !(n in was) && checkKinds(now[n]).has(kind));
|
|
7340
7582
|
const out3 = [];
|
|
7341
|
-
for (const
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
const
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
if (
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7583
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])) {
|
|
7584
|
+
const whole = WHOLE_SUITE_SCRIPT.test(name);
|
|
7585
|
+
if (!whole && !SLICE_SCRIPT.test(name)) continue;
|
|
7586
|
+
const prev = was[name];
|
|
7587
|
+
const cmd = now[name];
|
|
7588
|
+
if (prev === cmd) continue;
|
|
7589
|
+
if (prev === void 0 && !whole) continue;
|
|
7590
|
+
if (cmd === void 0) {
|
|
7591
|
+
if (name !== "pretest" || prev === void 0) continue;
|
|
7592
|
+
const gone = [...checkKinds(prev)].filter((k) => !relocated(k));
|
|
7593
|
+
if (gone.length === 0) continue;
|
|
7594
|
+
out3.push({ script: name, weakening: { state: "removed", kind: gone[0], what: "the script is gone" }, before: prev, after: "" });
|
|
7595
|
+
continue;
|
|
7353
7596
|
}
|
|
7597
|
+
const w = invocationWeakening(prev ?? "", cmd, void 0, { configReviewed, excuseFlag: excludesOtherRunner, sliceByDesign: !whole, relocated });
|
|
7598
|
+
if (w.state === "kept") continue;
|
|
7599
|
+
out3.push({ script: name, weakening: w, before: prev ?? "", after: cmd });
|
|
7354
7600
|
}
|
|
7355
7601
|
return out3;
|
|
7356
7602
|
}
|
|
@@ -7412,7 +7658,7 @@ function pytestPrecedenceFindings(changes, policy, ctx) {
|
|
|
7412
7658
|
})
|
|
7413
7659
|
);
|
|
7414
7660
|
}
|
|
7415
|
-
var RULE8, JVM_TEST, TEST_DEFS, TABLE_METHOD, isEachOf, isDescribeEach, OTHER_RUNNER_DIR, WHOLE_SUITE_SCRIPT, SED_INPLACE, testDeletion;
|
|
7661
|
+
var RULE8, JVM_TEST, TEST_DEFS, TABLE_METHOD, isEachOf, isDescribeEach, OTHER_RUNNER_DIR, WHOLE_SUITE_SCRIPT, SLICE_SCRIPT, SED_INPLACE, testDeletion;
|
|
7416
7662
|
var init_test_deletion = __esm({
|
|
7417
7663
|
"src/detectors/test-deletion.ts"() {
|
|
7418
7664
|
"use strict";
|
|
@@ -7423,7 +7669,7 @@ var init_test_deletion = __esm({
|
|
|
7423
7669
|
init_files();
|
|
7424
7670
|
init_repo();
|
|
7425
7671
|
init_suite_config();
|
|
7426
|
-
|
|
7672
|
+
init_invocation();
|
|
7427
7673
|
init_narrow();
|
|
7428
7674
|
RULE8 = "test-deletion";
|
|
7429
7675
|
JVM_TEST = /^\s*(?:@\w+(?:\([^)]*\))?\s+)*@(?:Test|ParameterizedTest|RepeatedTest|TestFactory|TestTemplate)\b/;
|
|
@@ -7441,7 +7687,8 @@ var init_test_deletion = __esm({
|
|
|
7441
7687
|
isEachOf = (expr) => ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && (expr.expression.text === "it" || expr.expression.text === "test") && TABLE_METHOD.test(expr.name.text);
|
|
7442
7688
|
isDescribeEach = (expr) => ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === "describe" && TABLE_METHOD.test(expr.name.text);
|
|
7443
7689
|
OTHER_RUNNER_DIR = /(?:^|\/)(?:e2e|cypress|playwright|fixtures|__fixtures__|__mocks__)\//;
|
|
7444
|
-
WHOLE_SUITE_SCRIPT = /^test
|
|
7690
|
+
WHOLE_SUITE_SCRIPT = /^(?:test|test:ci|check|pretest)$/;
|
|
7691
|
+
SLICE_SCRIPT = /^test:(?:unit|integration)$/;
|
|
7445
7692
|
SED_INPLACE = /(?:^|\s)(?:-i\b|--in-place\b|-[a-zA-Z]*i[a-zA-Z]*\b)/;
|
|
7446
7693
|
testDeletion = {
|
|
7447
7694
|
id: RULE8,
|
|
@@ -7450,6 +7697,7 @@ var init_test_deletion = __esm({
|
|
|
7450
7697
|
run(changes, policy, _view, ctx) {
|
|
7451
7698
|
const out3 = [];
|
|
7452
7699
|
const isSpec = (p) => isProtected(p, policy, "tests") && !isProtected(p, policy, "snapshots");
|
|
7700
|
+
const addedPaths = new Set(changes.filter((c) => c.kind === "file" && c.op === "add").map((c) => c.path));
|
|
7453
7701
|
const addedTestLines = /* @__PURE__ */ new Set();
|
|
7454
7702
|
let addedTestBlocks = 0;
|
|
7455
7703
|
for (const c of changes) {
|
|
@@ -7529,13 +7777,20 @@ var init_test_deletion = __esm({
|
|
|
7529
7777
|
})
|
|
7530
7778
|
);
|
|
7531
7779
|
}
|
|
7532
|
-
for (const n of
|
|
7780
|
+
for (const n of scriptWeakenings(c.before, c.after, c.path, policy, addedPaths)) {
|
|
7781
|
+
const w = n.weakening;
|
|
7782
|
+
const evidence = `"${n.script}": ${JSON.stringify(n.before)} \u2192 ${n.after === "" ? "deleted" : JSON.stringify(n.after)}`;
|
|
7533
7783
|
out3.push(
|
|
7534
|
-
makeFinding(RULE8, policy, {
|
|
7784
|
+
w.state === "removed" ? makeFinding(RULE8, policy, {
|
|
7785
|
+
file: c.path,
|
|
7786
|
+
message: `The test script no longer runs the ${w.kind} check: scripts.${n.script} ${w.what}.`,
|
|
7787
|
+
evidence,
|
|
7788
|
+
remediation: "Keep the script running its check. Replacing the runner in package.json removes every test from every `npm test` while the workflow line stays untouched."
|
|
7789
|
+
}) : makeFinding(RULE8, policy, {
|
|
7535
7790
|
file: c.path,
|
|
7536
|
-
message: `The test script now narrows the suite: ${
|
|
7537
|
-
evidence
|
|
7538
|
-
remediation: "Keep the test script running the whole suite; a runner flag that selects fewer specs removes the rest from every run."
|
|
7791
|
+
message: `The test script now narrows the suite: ${w.what} ${w.direction} ${w.direction === "added" ? "to" : "from"} scripts.${n.script}.`,
|
|
7792
|
+
evidence,
|
|
7793
|
+
remediation: "Keep the test script running the whole suite and letting it decide; a masked status, a cut-short run or a runner flag that selects fewer specs removes the rest from every run."
|
|
7539
7794
|
})
|
|
7540
7795
|
);
|
|
7541
7796
|
}
|
|
@@ -8902,8 +9157,22 @@ var init_deny = __esm({
|
|
|
8902
9157
|
});
|
|
8903
9158
|
|
|
8904
9159
|
// src/session.ts
|
|
8905
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9160
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8906
9161
|
import { dirname as dirname3, join as join10 } from "node:path";
|
|
9162
|
+
function writeBaseline(p, sha3) {
|
|
9163
|
+
mkdirSync2(dirname3(p), { recursive: true });
|
|
9164
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
9165
|
+
try {
|
|
9166
|
+
writeFileSync2(tmp, sha3);
|
|
9167
|
+
renameSync(tmp, p);
|
|
9168
|
+
} catch (e) {
|
|
9169
|
+
try {
|
|
9170
|
+
rmSync2(tmp, { force: true });
|
|
9171
|
+
} catch {
|
|
9172
|
+
}
|
|
9173
|
+
throw e;
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
8907
9176
|
function baselinePath(cwd, sessionId) {
|
|
8908
9177
|
const gd = gitDir(cwd);
|
|
8909
9178
|
if (!gd) return null;
|
|
@@ -8911,21 +9180,23 @@ function baselinePath(cwd, sessionId) {
|
|
|
8911
9180
|
}
|
|
8912
9181
|
function turnBaseline(cwd, sessionId) {
|
|
8913
9182
|
if (!sessionId) return null;
|
|
9183
|
+
const p = baselinePath(cwd, sessionId);
|
|
9184
|
+
if (!p) return null;
|
|
8914
9185
|
try {
|
|
8915
|
-
const p = baselinePath(cwd, sessionId);
|
|
8916
|
-
if (!p) return null;
|
|
8917
9186
|
if (existsSync7(p)) {
|
|
8918
9187
|
const v = readFileSync7(p, "utf8").trim();
|
|
8919
9188
|
if (SHA.test(v)) return v;
|
|
8920
9189
|
}
|
|
8921
|
-
const head = headSha(cwd);
|
|
8922
|
-
if (!head) return null;
|
|
8923
|
-
mkdirSync2(dirname3(p), { recursive: true });
|
|
8924
|
-
writeFileSync2(p, head);
|
|
8925
|
-
return head;
|
|
8926
9190
|
} catch {
|
|
8927
|
-
return null;
|
|
8928
9191
|
}
|
|
9192
|
+
const head = headSha(cwd);
|
|
9193
|
+
if (!head) return null;
|
|
9194
|
+
try {
|
|
9195
|
+
writeBaseline(p, head);
|
|
9196
|
+
} catch (e) {
|
|
9197
|
+
throw new BaselineWriteError(p, e);
|
|
9198
|
+
}
|
|
9199
|
+
return head;
|
|
8929
9200
|
}
|
|
8930
9201
|
function advanceTurnBaseline(cwd, sessionId) {
|
|
8931
9202
|
if (!sessionId) return;
|
|
@@ -8933,24 +9204,30 @@ function advanceTurnBaseline(cwd, sessionId) {
|
|
|
8933
9204
|
const p = baselinePath(cwd, sessionId);
|
|
8934
9205
|
const head = headSha(cwd);
|
|
8935
9206
|
if (!p || !head) return;
|
|
8936
|
-
|
|
8937
|
-
writeFileSync2(p, head);
|
|
9207
|
+
writeBaseline(p, head);
|
|
8938
9208
|
} catch {
|
|
8939
9209
|
}
|
|
8940
9210
|
}
|
|
8941
|
-
var UNSAFE, SHA;
|
|
9211
|
+
var UNSAFE, SHA, BaselineWriteError;
|
|
8942
9212
|
var init_session = __esm({
|
|
8943
9213
|
"src/session.ts"() {
|
|
8944
9214
|
"use strict";
|
|
8945
9215
|
init_build();
|
|
8946
9216
|
UNSAFE = /[^A-Za-z0-9_-]/g;
|
|
8947
|
-
SHA = /^[0-9a-f]{
|
|
9217
|
+
SHA = /^[0-9a-f]{40}$/;
|
|
9218
|
+
BaselineWriteError = class extends Error {
|
|
9219
|
+
constructor(path, cause) {
|
|
9220
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
9221
|
+
super(`turn baseline could not be recorded at ${path}: ${detail}`);
|
|
9222
|
+
this.name = "BaselineWriteError";
|
|
9223
|
+
}
|
|
9224
|
+
};
|
|
8948
9225
|
}
|
|
8949
9226
|
});
|
|
8950
9227
|
|
|
8951
9228
|
// src/effect.ts
|
|
8952
9229
|
import { createHash as createHash3 } from "node:crypto";
|
|
8953
|
-
import { existsSync as existsSync8, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync, rmSync as
|
|
9230
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8954
9231
|
import { join as join11 } from "node:path";
|
|
8955
9232
|
function ptreeFrom(value) {
|
|
8956
9233
|
if (!isRecord(value)) return null;
|
|
@@ -9055,14 +9332,14 @@ function saveTree(p, tree) {
|
|
|
9055
9332
|
mkdirSync3(join11(p, ".."), { recursive: true });
|
|
9056
9333
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
9057
9334
|
writeFileSync3(tmp, JSON.stringify(tree));
|
|
9058
|
-
|
|
9335
|
+
renameSync2(tmp, p);
|
|
9059
9336
|
} catch {
|
|
9060
9337
|
}
|
|
9061
9338
|
}
|
|
9062
9339
|
function dropTree(p) {
|
|
9063
9340
|
if (!p) return;
|
|
9064
9341
|
try {
|
|
9065
|
-
|
|
9342
|
+
rmSync3(p, { force: true });
|
|
9066
9343
|
} catch {
|
|
9067
9344
|
}
|
|
9068
9345
|
}
|
|
@@ -9361,7 +9638,7 @@ var init_watch = __esm({
|
|
|
9361
9638
|
});
|
|
9362
9639
|
|
|
9363
9640
|
// src/detectors/fs-events.ts
|
|
9364
|
-
import { closeSync as closeSync2, openSync as openSync2, readSync as readSync2, statSync } from "node:fs";
|
|
9641
|
+
import { closeSync as closeSync2, openSync as openSync2, readSync as readSync2, statSync as statSync2 } from "node:fs";
|
|
9365
9642
|
function fsEventFrom(line) {
|
|
9366
9643
|
let value;
|
|
9367
9644
|
try {
|
|
@@ -9381,7 +9658,7 @@ function fsEventFrom(line) {
|
|
|
9381
9658
|
function readEvents(log, offset, maxBytes = MAX_EVENT_READ_BYTES, io = DEFAULT_EVENT_READ_IO) {
|
|
9382
9659
|
let size = 0;
|
|
9383
9660
|
try {
|
|
9384
|
-
size =
|
|
9661
|
+
size = statSync2(log).size;
|
|
9385
9662
|
} catch {
|
|
9386
9663
|
return {
|
|
9387
9664
|
events: [],
|
|
@@ -9972,8 +10249,12 @@ function recordObserverHealth(state, reason) {
|
|
|
9972
10249
|
function stopVerdict(input, defaultCwd) {
|
|
9973
10250
|
if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
|
|
9974
10251
|
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
9975
|
-
|
|
9976
|
-
|
|
10252
|
+
const ctx = repoContext(sessionCwd);
|
|
10253
|
+
if (!ctx) {
|
|
10254
|
+
const why = outsideRepository(sessionCwd);
|
|
10255
|
+
return why ? failClosed("Stop", why) : { exitCode: 0, stdout: "" };
|
|
10256
|
+
}
|
|
10257
|
+
const cwd = ctx.root;
|
|
9977
10258
|
let blocks2;
|
|
9978
10259
|
let commitCursor = () => {
|
|
9979
10260
|
};
|
|
@@ -10163,7 +10444,7 @@ var init_ptree_cache = __esm({
|
|
|
10163
10444
|
});
|
|
10164
10445
|
|
|
10165
10446
|
// src/cli/hook-service.ts
|
|
10166
|
-
import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as
|
|
10447
|
+
import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
10167
10448
|
import { createConnection as createConnection2, createServer } from "node:net";
|
|
10168
10449
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
10169
10450
|
import { sep } from "node:path";
|
|
@@ -10246,7 +10527,7 @@ function withEnv(env, fn) {
|
|
|
10246
10527
|
}
|
|
10247
10528
|
function removeQuietly(p) {
|
|
10248
10529
|
try {
|
|
10249
|
-
|
|
10530
|
+
rmSync4(p, { force: true });
|
|
10250
10531
|
} catch {
|
|
10251
10532
|
}
|
|
10252
10533
|
}
|
|
@@ -10588,7 +10869,7 @@ var init_verifier_limits = __esm({
|
|
|
10588
10869
|
// src/cli/init.ts
|
|
10589
10870
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
10590
10871
|
import { createHash as createHash6 } from "node:crypto";
|
|
10591
|
-
import { existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as
|
|
10872
|
+
import { existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync3 } from "node:fs";
|
|
10592
10873
|
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve4 } from "node:path";
|
|
10593
10874
|
function gitConfig(cwd, key2) {
|
|
10594
10875
|
try {
|
|
@@ -10951,7 +11232,7 @@ function planPreCommit(cwd) {
|
|
|
10951
11232
|
}
|
|
10952
11233
|
}
|
|
10953
11234
|
const rel = display(cwd, path);
|
|
10954
|
-
if (existsSync11(path) && !
|
|
11235
|
+
if (existsSync11(path) && !statSync3(path).isFile()) {
|
|
10955
11236
|
return { item: "pre-commit", path: rel, status: "error", detail: "exists but is not a regular file \u2014 refusing to write" };
|
|
10956
11237
|
}
|
|
10957
11238
|
const existing = existsSync11(path) ? readFileSync12(path, "utf8") : null;
|
|
@@ -11393,7 +11674,7 @@ import {
|
|
|
11393
11674
|
readFileSync as readFileSync13,
|
|
11394
11675
|
readlinkSync as readlinkSync2,
|
|
11395
11676
|
realpathSync as realpathSync5,
|
|
11396
|
-
statSync as
|
|
11677
|
+
statSync as statSync4
|
|
11397
11678
|
} from "node:fs";
|
|
11398
11679
|
import {
|
|
11399
11680
|
basename as basename3,
|
|
@@ -11435,7 +11716,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11435
11716
|
root.kind + " root moved from " + JSON.stringify(root.realPath) + " to " + JSON.stringify(currentReal)
|
|
11436
11717
|
);
|
|
11437
11718
|
}
|
|
11438
|
-
if (!
|
|
11719
|
+
if (!statSync4(root.path).isDirectory()) {
|
|
11439
11720
|
throw new Unattestable(root.kind + " root " + JSON.stringify(root.path) + " is not a directory");
|
|
11440
11721
|
}
|
|
11441
11722
|
h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
|
|
@@ -11445,7 +11726,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11445
11726
|
const hashFile = (path, label) => {
|
|
11446
11727
|
let st;
|
|
11447
11728
|
try {
|
|
11448
|
-
st =
|
|
11729
|
+
st = statSync4(path);
|
|
11449
11730
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
11450
11731
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
11451
11732
|
h.update(readFileSync13(path));
|
|
@@ -11511,7 +11792,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11511
11792
|
} catch {
|
|
11512
11793
|
continue;
|
|
11513
11794
|
}
|
|
11514
|
-
const followed =
|
|
11795
|
+
const followed = statSync4(path);
|
|
11515
11796
|
if (followed.isFile()) {
|
|
11516
11797
|
hashFile(resolved, "@target:" + rel);
|
|
11517
11798
|
continue;
|
|
@@ -11559,7 +11840,7 @@ function measuredSnapshot(roots, probes, cwd, metrics) {
|
|
|
11559
11840
|
}
|
|
11560
11841
|
function freezeRoot(kind, path) {
|
|
11561
11842
|
try {
|
|
11562
|
-
if (!
|
|
11843
|
+
if (!statSync4(path).isDirectory()) {
|
|
11563
11844
|
throw new Error("not a directory");
|
|
11564
11845
|
}
|
|
11565
11846
|
return { kind, path: resolve5(path), realPath: realpathSync5(path) };
|
|
@@ -11608,7 +11889,7 @@ function resolveExecutable(token, cwd, env) {
|
|
|
11608
11889
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
11609
11890
|
const candidate = resolve5(entry, token);
|
|
11610
11891
|
try {
|
|
11611
|
-
if (existsSync12(candidate) &&
|
|
11892
|
+
if (existsSync12(candidate) && statSync4(candidate).isFile()) return realpathSync5(candidate);
|
|
11612
11893
|
} catch {
|
|
11613
11894
|
}
|
|
11614
11895
|
}
|
|
@@ -11795,7 +12076,7 @@ var init_dependency_env = __esm({
|
|
|
11795
12076
|
|
|
11796
12077
|
// src/suite-diagnostics.ts
|
|
11797
12078
|
import { spawnSync } from "node:child_process";
|
|
11798
|
-
import { mkdtempSync as mkdtempSync2, rmSync as
|
|
12079
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
11799
12080
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
11800
12081
|
import { join as join16 } from "node:path";
|
|
11801
12082
|
function emptySuiteDiagnostics() {
|
|
@@ -11924,7 +12205,7 @@ function runCapturedProcessSync(executable, args, opts) {
|
|
|
11924
12205
|
}
|
|
11925
12206
|
return parsed;
|
|
11926
12207
|
} finally {
|
|
11927
|
-
|
|
12208
|
+
rmSync5(stateDir, { recursive: true, force: true });
|
|
11928
12209
|
}
|
|
11929
12210
|
}
|
|
11930
12211
|
var DIAGNOSTIC_TAIL_BYTES, EMPTY_STREAM, CAPTURE_SUPERVISOR;
|
|
@@ -12114,7 +12395,7 @@ if (child) {
|
|
|
12114
12395
|
// src/verifier-backend.ts
|
|
12115
12396
|
import { createHash as createHash9, randomUUID } from "node:crypto";
|
|
12116
12397
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12117
|
-
import { accessSync, constants as
|
|
12398
|
+
import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as statSync5 } from "node:fs";
|
|
12118
12399
|
import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve6 } from "node:path";
|
|
12119
12400
|
function identity() {
|
|
12120
12401
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
@@ -12123,7 +12404,7 @@ function identity() {
|
|
|
12123
12404
|
}
|
|
12124
12405
|
function writableByCaller(path) {
|
|
12125
12406
|
try {
|
|
12126
|
-
const st =
|
|
12407
|
+
const st = statSync5(path);
|
|
12127
12408
|
const { uid, groups } = identity();
|
|
12128
12409
|
if (uid === 0) return true;
|
|
12129
12410
|
if ((st.mode & 2) !== 0) return true;
|
|
@@ -12151,7 +12432,7 @@ function findExecutable(name) {
|
|
|
12151
12432
|
if (!raw) continue;
|
|
12152
12433
|
const candidate = join17(raw, name);
|
|
12153
12434
|
try {
|
|
12154
|
-
|
|
12435
|
+
accessSync2(candidate, constants3.X_OK);
|
|
12155
12436
|
return realpathSync6(candidate);
|
|
12156
12437
|
} catch {
|
|
12157
12438
|
}
|
|
@@ -12593,7 +12874,7 @@ var init_verifier_backend = __esm({
|
|
|
12593
12874
|
// src/cli/verify.ts
|
|
12594
12875
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
12595
12876
|
import { createHash as createHash10 } from "node:crypto";
|
|
12596
|
-
import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as
|
|
12877
|
+
import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync6, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "node:fs";
|
|
12597
12878
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
12598
12879
|
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve7, sep as sep3 } from "node:path";
|
|
12599
12880
|
function oracleAssuranceReport() {
|
|
@@ -12641,7 +12922,7 @@ function safeSymlink(target, out3, root, label) {
|
|
|
12641
12922
|
if (rootedLinkTarget(target) || !inside2(root, resolve7(dirname7(out3), target))) {
|
|
12642
12923
|
throw linkEscape(label, target);
|
|
12643
12924
|
}
|
|
12644
|
-
|
|
12925
|
+
rmSync6(out3, { force: true });
|
|
12645
12926
|
symlinkSync(target, out3);
|
|
12646
12927
|
}
|
|
12647
12928
|
function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
@@ -12752,7 +13033,7 @@ function probeFilesystemCaseSensitivity(dir) {
|
|
|
12752
13033
|
writeFileSync9(upper, "", { flag: "wx" });
|
|
12753
13034
|
return !existsSync13(lower);
|
|
12754
13035
|
} finally {
|
|
12755
|
-
|
|
13036
|
+
rmSync6(upper, { force: true });
|
|
12756
13037
|
}
|
|
12757
13038
|
}
|
|
12758
13039
|
function foldCase(p) {
|
|
@@ -12770,7 +13051,7 @@ function caseFoldCollision(paths) {
|
|
|
12770
13051
|
}
|
|
12771
13052
|
function dropSymlink(p) {
|
|
12772
13053
|
try {
|
|
12773
|
-
if (lstatSync9(p).isSymbolicLink())
|
|
13054
|
+
if (lstatSync9(p).isSymbolicLink()) rmSync6(p, { force: true });
|
|
12774
13055
|
} catch {
|
|
12775
13056
|
}
|
|
12776
13057
|
}
|
|
@@ -12846,7 +13127,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
12846
13127
|
const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
12847
13128
|
const out3 = join18(dest, e.path);
|
|
12848
13129
|
mkdirSync7(dirname7(out3), { recursive: true });
|
|
12849
|
-
|
|
13130
|
+
rmSync6(out3, { force: true });
|
|
12850
13131
|
if (e.mode === "120000") {
|
|
12851
13132
|
const target = content.toString("utf8");
|
|
12852
13133
|
safeSymlink(target, out3, dest, `the base's ${e.path}`);
|
|
@@ -12861,7 +13142,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
12861
13142
|
const inCopy = workingTreePaths(cwd);
|
|
12862
13143
|
for (const rel of inCopy) {
|
|
12863
13144
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
12864
|
-
|
|
13145
|
+
rmSync6(join18(dest, rel), { force: true });
|
|
12865
13146
|
removed++;
|
|
12866
13147
|
}
|
|
12867
13148
|
for (const link of restoredLinks) {
|
|
@@ -12948,7 +13229,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
12948
13229
|
}
|
|
12949
13230
|
return { exit: r.exit, secs, diagnostics: r.diagnostics };
|
|
12950
13231
|
} finally {
|
|
12951
|
-
|
|
13232
|
+
rmSync6(scratch, { recursive: true, force: true });
|
|
12952
13233
|
}
|
|
12953
13234
|
}
|
|
12954
13235
|
function diagnosticsJson(diagnostics, includeTail) {
|
|
@@ -13101,7 +13382,7 @@ function runVerify(opts) {
|
|
|
13101
13382
|
const visDir = join18(visRoot, "t");
|
|
13102
13383
|
const cleanup = (dirs) => {
|
|
13103
13384
|
if (opts.keep) return;
|
|
13104
|
-
for (const d of dirs)
|
|
13385
|
+
for (const d of dirs) rmSync6(d, { recursive: true, force: true });
|
|
13105
13386
|
};
|
|
13106
13387
|
filesystemCaseSensitive = (opts.probeCaseSensitivity ?? probeFilesystemCaseSensitivity)(visRoot);
|
|
13107
13388
|
if (!filesystemCaseSensitive) {
|
|
@@ -13468,7 +13749,7 @@ var init_verify = __esm({
|
|
|
13468
13749
|
// src/cli/run.ts
|
|
13469
13750
|
import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
13470
13751
|
import { randomBytes } from "node:crypto";
|
|
13471
|
-
import { accessSync as
|
|
13752
|
+
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
13472
13753
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
13473
13754
|
import { dirname as dirname8, join as join19, resolve as resolve8 } from "node:path";
|
|
13474
13755
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
@@ -13503,7 +13784,7 @@ function writableByCaller2(path) {
|
|
|
13503
13784
|
let cur = path;
|
|
13504
13785
|
for (; ; ) {
|
|
13505
13786
|
try {
|
|
13506
|
-
|
|
13787
|
+
accessSync3(cur, fsConstants.W_OK);
|
|
13507
13788
|
return true;
|
|
13508
13789
|
} catch {
|
|
13509
13790
|
}
|
|
@@ -13524,7 +13805,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
13524
13805
|
for (const candidate of candidates) {
|
|
13525
13806
|
try {
|
|
13526
13807
|
const real = realpathSync8(candidate);
|
|
13527
|
-
const st =
|
|
13808
|
+
const st = statSync6(real);
|
|
13528
13809
|
if (!st.isFile() || writableByCaller2(real)) continue;
|
|
13529
13810
|
const probe = spawnSync4(
|
|
13530
13811
|
real,
|
|
@@ -13647,7 +13928,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
13647
13928
|
...state.failure ? { failure: state.failure } : {}
|
|
13648
13929
|
};
|
|
13649
13930
|
} finally {
|
|
13650
|
-
|
|
13931
|
+
rmSync7(stateDir, { recursive: true, force: true });
|
|
13651
13932
|
}
|
|
13652
13933
|
}
|
|
13653
13934
|
function waitMs(ms) {
|
|
@@ -15020,7 +15301,7 @@ import {
|
|
|
15020
15301
|
readFileSync as readFileSync19,
|
|
15021
15302
|
readdirSync as readdirSync6,
|
|
15022
15303
|
realpathSync as realpathSync9,
|
|
15023
|
-
rmSync as
|
|
15304
|
+
rmSync as rmSync8,
|
|
15024
15305
|
symlinkSync as symlinkSync2
|
|
15025
15306
|
} from "node:fs";
|
|
15026
15307
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
@@ -15218,8 +15499,8 @@ function runOneTrace(cwd, base, command, budget) {
|
|
|
15218
15499
|
accesses: rewriteTraceRoot(accesses, root)
|
|
15219
15500
|
};
|
|
15220
15501
|
} finally {
|
|
15221
|
-
|
|
15222
|
-
|
|
15502
|
+
rmSync8(root, { recursive: true, force: true });
|
|
15503
|
+
rmSync8(traceDir, { recursive: true, force: true });
|
|
15223
15504
|
}
|
|
15224
15505
|
}
|
|
15225
15506
|
function renderText2(report2) {
|
|
@@ -15404,7 +15685,7 @@ var init_trace_verify = __esm({
|
|
|
15404
15685
|
// src/cli/onboard.ts
|
|
15405
15686
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
15406
15687
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
15407
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, renameSync as
|
|
15688
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
15408
15689
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
15409
15690
|
import { join as join22, relative as relative7, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15410
15691
|
import { createInterface } from "node:readline";
|
|
@@ -15477,9 +15758,9 @@ function atomicReplaceFile(path, content, mode) {
|
|
|
15477
15758
|
const tmp = `${path}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
|
|
15478
15759
|
try {
|
|
15479
15760
|
writeFileSync11(tmp, content, { encoding: "utf8", flag: "wx", mode });
|
|
15480
|
-
|
|
15761
|
+
renameSync3(tmp, path);
|
|
15481
15762
|
} finally {
|
|
15482
|
-
|
|
15763
|
+
rmSync9(tmp, { force: true });
|
|
15483
15764
|
}
|
|
15484
15765
|
}
|
|
15485
15766
|
function writeVerifyCommand(cwd, command) {
|
|
@@ -15506,7 +15787,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
15506
15787
|
if (loaded2.verify?.command !== command) throw new Error("verify.command did not round-trip");
|
|
15507
15788
|
return null;
|
|
15508
15789
|
} catch (e) {
|
|
15509
|
-
if (original === null)
|
|
15790
|
+
if (original === null) rmSync9(path, { force: true });
|
|
15510
15791
|
else atomicReplaceFile(path, original, mode);
|
|
15511
15792
|
return `the updated ${POLICY_FILE} does not load (${errorMessage(e)}); restored the previous file`;
|
|
15512
15793
|
}
|
|
@@ -15906,7 +16187,7 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
15906
16187
|
} finally {
|
|
15907
16188
|
git5(cwd, ["worktree", "remove", "--force", wt]);
|
|
15908
16189
|
git5(cwd, ["worktree", "prune"]);
|
|
15909
|
-
|
|
16190
|
+
rmSync9(wt, { recursive: true, force: true });
|
|
15910
16191
|
}
|
|
15911
16192
|
const after = treeFingerprint(cwd);
|
|
15912
16193
|
out3(`working tree fingerprint after: ${after.slice(0, 16)}`);
|
|
@@ -16321,7 +16602,7 @@ var init_record = __esm({
|
|
|
16321
16602
|
|
|
16322
16603
|
// src/research/run.ts
|
|
16323
16604
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
16324
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as
|
|
16605
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync4, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "node:fs";
|
|
16325
16606
|
import { join as join23, resolve as resolve14 } from "node:path";
|
|
16326
16607
|
function git6(args, cwd) {
|
|
16327
16608
|
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
@@ -16370,11 +16651,11 @@ function resumableRecord(path, expected) {
|
|
|
16370
16651
|
function writeRecordAtomically(path, text) {
|
|
16371
16652
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
16372
16653
|
writeFileSync12(tmp, text);
|
|
16373
|
-
|
|
16654
|
+
renameSync4(tmp, path);
|
|
16374
16655
|
}
|
|
16375
16656
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
16376
16657
|
const ws2 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
16377
|
-
|
|
16658
|
+
rmSync10(ws2, { recursive: true, force: true });
|
|
16378
16659
|
mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
|
|
16379
16660
|
try {
|
|
16380
16661
|
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws2], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.12",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|