tamperward 2.10.0 → 2.10.2
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 +347 -104
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -153,6 +153,18 @@ function defaultPolicy(version = 1) {
|
|
|
153
153
|
// as a strengthening). pyproject.toml carries [tool.coverage.report].
|
|
154
154
|
"**/.coveragerc",
|
|
155
155
|
"**/pyproject.toml",
|
|
156
|
+
// pytest's selection configuration. These decide WHICH TESTS RUN, so a
|
|
157
|
+
// narrowing in them removes a test as surely as deleting the file. The
|
|
158
|
+
// pristine boundary already refused to inherit them (verify's
|
|
159
|
+
// VERIFICATION_SURFACE, 1.14.2); this is the in-loop half, so the gate can
|
|
160
|
+
// say so at the tool call instead of only at adjudication. `tox.ini` and
|
|
161
|
+
// `setup.cfg` are shared files — the suite-config detector only treats them
|
|
162
|
+
// as pytest configs when they carry the section pytest reads, so ordinary
|
|
163
|
+
// packaging edits are untouched.
|
|
164
|
+
"**/pytest.ini",
|
|
165
|
+
"**/.pytest.ini",
|
|
166
|
+
"**/tox.ini",
|
|
167
|
+
"**/setup.cfg",
|
|
156
168
|
"**/.nycrc",
|
|
157
169
|
"**/.nycrc.*",
|
|
158
170
|
"**/nyc.config.*",
|
|
@@ -1334,6 +1346,8 @@ import ts2 from "typescript";
|
|
|
1334
1346
|
|
|
1335
1347
|
// src/detectors/repo.ts
|
|
1336
1348
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1349
|
+
import { readFileSync } from "node:fs";
|
|
1350
|
+
import { join as join2 } from "node:path";
|
|
1337
1351
|
var cache = /* @__PURE__ */ new Map();
|
|
1338
1352
|
function trackedFiles(ctx) {
|
|
1339
1353
|
if (!ctx) return null;
|
|
@@ -1355,6 +1369,23 @@ function trackedFiles(ctx) {
|
|
|
1355
1369
|
cache.set(ctx.cwd, files);
|
|
1356
1370
|
return files;
|
|
1357
1371
|
}
|
|
1372
|
+
var contentCache = /* @__PURE__ */ new Map();
|
|
1373
|
+
function trackedContent(path, ctx) {
|
|
1374
|
+
if (!ctx) return null;
|
|
1375
|
+
if (ctx.trackedContents) return ctx.trackedContents[path] ?? null;
|
|
1376
|
+
if (!ctx.cwd) return null;
|
|
1377
|
+
const key2 = ctx.cwd + "\0" + path;
|
|
1378
|
+
const hit = contentCache.get(key2);
|
|
1379
|
+
if (hit !== void 0) return hit;
|
|
1380
|
+
let src = null;
|
|
1381
|
+
try {
|
|
1382
|
+
src = readFileSync(join2(ctx.cwd, path), "utf8");
|
|
1383
|
+
} catch {
|
|
1384
|
+
src = null;
|
|
1385
|
+
}
|
|
1386
|
+
contentCache.set(key2, src);
|
|
1387
|
+
return src;
|
|
1388
|
+
}
|
|
1358
1389
|
var branchCache = /* @__PURE__ */ new Map();
|
|
1359
1390
|
var refsCache = /* @__PURE__ */ new Map();
|
|
1360
1391
|
function branchRefs(ctx) {
|
|
@@ -2660,20 +2691,20 @@ var ciTampering = {
|
|
|
2660
2691
|
};
|
|
2661
2692
|
|
|
2662
2693
|
// src/detectors/hook-tampering.ts
|
|
2663
|
-
import { existsSync as existsSync2, readFileSync as
|
|
2664
|
-
import { isAbsolute as isAbsolute3, join as
|
|
2694
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
2695
|
+
import { isAbsolute as isAbsolute3, join as join4, relative } from "node:path";
|
|
2665
2696
|
|
|
2666
2697
|
// src/wiring.ts
|
|
2667
|
-
import { readFileSync, realpathSync as realpathSync2 } from "node:fs";
|
|
2698
|
+
import { readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
2668
2699
|
import { homedir } from "node:os";
|
|
2669
|
-
import { basename, dirname, isAbsolute, join as
|
|
2700
|
+
import { basename, dirname, isAbsolute, join as join3, posix } from "node:path";
|
|
2670
2701
|
import { fileURLToPath } from "node:url";
|
|
2671
2702
|
function shippedVersion() {
|
|
2672
2703
|
try {
|
|
2673
2704
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
2674
2705
|
for (const rel of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
2675
2706
|
try {
|
|
2676
|
-
const pkg = JSON.parse(
|
|
2707
|
+
const pkg = JSON.parse(readFileSync2(join3(here, rel), "utf8"));
|
|
2677
2708
|
if (pkg.name === "tamperward" && pkg.version) return pkg.version;
|
|
2678
2709
|
} catch {
|
|
2679
2710
|
}
|
|
@@ -4551,8 +4582,8 @@ function lefthookBase(c, changes, ctx) {
|
|
|
4551
4582
|
if (!ctx?.cwd) return null;
|
|
4552
4583
|
for (const n of names) {
|
|
4553
4584
|
try {
|
|
4554
|
-
const p =
|
|
4555
|
-
if (existsSync2(p)) return parseDoc(
|
|
4585
|
+
const p = join4(ctx.cwd, n);
|
|
4586
|
+
if (existsSync2(p)) return parseDoc(readFileSync3(p, "utf8"));
|
|
4556
4587
|
} catch {
|
|
4557
4588
|
}
|
|
4558
4589
|
}
|
|
@@ -4626,6 +4657,61 @@ function repoRelative(path, ctx) {
|
|
|
4626
4657
|
const rel = relative(ctx.cwd, path);
|
|
4627
4658
|
return rel && !rel.startsWith("..") && !isAbsolute3(rel) ? rel : path;
|
|
4628
4659
|
}
|
|
4660
|
+
var HUSKY_SHIM = '#!/usr/bin/env sh\n. "$(dirname "$0")/h"';
|
|
4661
|
+
var HUSKY_SHIM_NAMES = /* @__PURE__ */ new Set(["applypatch-msg", "commit-msg", "post-applypatch", "post-checkout", "post-commit", "post-merge", "post-rewrite", "pre-applypatch", "pre-auto-gc", "pre-commit", "pre-merge-commit", "pre-push", "pre-rebase", "prepare-commit-msg"]);
|
|
4662
|
+
var HUSKY_H_KNOWN = [
|
|
4663
|
+
// husky 9.1.x
|
|
4664
|
+
`#!/usr/bin/env sh
|
|
4665
|
+
[ "$HUSKY" = "2" ] && set -x
|
|
4666
|
+
n=$(basename "$0")
|
|
4667
|
+
s=$(dirname "$(dirname "$0")")/$n
|
|
4668
|
+
|
|
4669
|
+
[ ! -f "$s" ] && exit 0
|
|
4670
|
+
|
|
4671
|
+
if [ -f "$HOME/.huskyrc" ]; then
|
|
4672
|
+
echo "husky - '~/.huskyrc' is DEPRECATED, please move your code to ~/.config/husky/init.sh"
|
|
4673
|
+
fi
|
|
4674
|
+
i="\${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh"
|
|
4675
|
+
[ -f "$i" ] && . "$i"
|
|
4676
|
+
|
|
4677
|
+
[ "\${HUSKY-}" = "0" ] && exit 0
|
|
4678
|
+
|
|
4679
|
+
export PATH="node_modules/.bin:$PATH"
|
|
4680
|
+
sh -e "$s" "$@"
|
|
4681
|
+
c=$?
|
|
4682
|
+
|
|
4683
|
+
[ $c != 0 ] && echo "husky - $n script failed (code $c)"
|
|
4684
|
+
[ $c = 127 ] && echo "husky - command not found in PATH=$PATH"
|
|
4685
|
+
exit $c
|
|
4686
|
+
`
|
|
4687
|
+
];
|
|
4688
|
+
var HUSKY_DEPRECATION = `echo "husky - DEPRECATED
|
|
4689
|
+
|
|
4690
|
+
Please remove the following two lines from $0:
|
|
4691
|
+
|
|
4692
|
+
#!/usr/bin/env sh
|
|
4693
|
+
. \\"\\$(dirname -- \\"\\$0\\")/_/husky.sh\\"
|
|
4694
|
+
|
|
4695
|
+
They WILL FAIL in v10.0.0
|
|
4696
|
+
"`;
|
|
4697
|
+
var stripTrailingNl = (s) => s.endsWith("\n") ? s.slice(0, -1) : s;
|
|
4698
|
+
function huskyRuntimeWrite(path, content, ctx) {
|
|
4699
|
+
const m = path.match(/(?:^|\/)\.husky\/_\/([^/]+)$/);
|
|
4700
|
+
if (!m || content == null) return false;
|
|
4701
|
+
const name = m[1];
|
|
4702
|
+
const body = stripTrailingNl(content);
|
|
4703
|
+
const ok = name === ".gitignore" ? body === "*" : name === "h" ? HUSKY_H_KNOWN.some((t) => stripTrailingNl(t) === body) : name === "husky.sh" ? stripTrailingNl(HUSKY_DEPRECATION) === body : HUSKY_SHIM_NAMES.has(name) && body === HUSKY_SHIM;
|
|
4704
|
+
if (!ok) return false;
|
|
4705
|
+
if (!ctx?.cwd) return false;
|
|
4706
|
+
const root = path.slice(0, path.length - m[0].length + (m[0].startsWith("/") ? 1 : 0));
|
|
4707
|
+
const entry = inspectRel(join4(ctx.cwd, root), ".husky/pre-commit");
|
|
4708
|
+
if (entry.kind !== "file") return false;
|
|
4709
|
+
const gate = textOf(entry);
|
|
4710
|
+
if (gate == null) return false;
|
|
4711
|
+
return invocations(gate.split("\n"), { errexit: true }).some(
|
|
4712
|
+
(i) => i.identity === "tamperward check --staged" && i.state === "live" && i.pin === TW_VERSION
|
|
4713
|
+
);
|
|
4714
|
+
}
|
|
4629
4715
|
var isHuskyScript = (path) => /(?:^|\/)\.husky\/[^/]+$/.test(path);
|
|
4630
4716
|
var isHookNote = (path) => /\.(?:md|markdown|txt)$|(?:^|\/)\.git(?:ignore|attributes)$/i.test(path);
|
|
4631
4717
|
var hookTampering = {
|
|
@@ -4700,6 +4786,7 @@ var hookTampering = {
|
|
|
4700
4786
|
} else if ((isLefthook(c.path) || isPreCommitConfig(c.path)) && (semantic = configFindings(c, changes, policy, ctx)) !== null) {
|
|
4701
4787
|
out2.push(...semantic);
|
|
4702
4788
|
} else if (!c.path.endsWith(POLICY_FILE)) {
|
|
4789
|
+
if ((c.op === "add" || c.op === "modify") && huskyRuntimeWrite(c.path, c.after, ctx)) continue;
|
|
4703
4790
|
const husky = isHuskyScript(c.path);
|
|
4704
4791
|
const opts = husky ? { errexit: true } : {};
|
|
4705
4792
|
const beforePin = c.before != null && c.after != null ? initScriptPin(c.before) : null;
|
|
@@ -4897,6 +4984,8 @@ import ts3 from "typescript";
|
|
|
4897
4984
|
var JEST_DEFAULT_MATCH = ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"];
|
|
4898
4985
|
var JEST_DEFAULT_IGNORE = ["/node_modules/"];
|
|
4899
4986
|
var VITEST_DEFAULT_INCLUDE = ["**/*.{test,spec}.?(c|m)[jt]s?(x)"];
|
|
4987
|
+
var PYTEST_DEFAULT_INCLUDE = ["**/test_*.py", "**/*_test.py"];
|
|
4988
|
+
var PYTEST_DEFAULT_IGNORE = ["**/build/**", "**/dist/**", "**/node_modules/**", "**/venv/**", "**/.*/**"];
|
|
4900
4989
|
var VITEST_DEFAULT_EXCLUDE = [
|
|
4901
4990
|
"**/node_modules/**",
|
|
4902
4991
|
"**/dist/**",
|
|
@@ -4914,6 +5003,13 @@ var CANONICAL_SAMPLES = [
|
|
|
4914
5003
|
"a.spec.js",
|
|
4915
5004
|
"test/a.test.mjs"
|
|
4916
5005
|
];
|
|
5006
|
+
var PYTEST_CANONICAL_SAMPLES = [
|
|
5007
|
+
"tests/test_a.py",
|
|
5008
|
+
"tests/a_test.py",
|
|
5009
|
+
"test_a.py",
|
|
5010
|
+
"src/tests/test_a.py",
|
|
5011
|
+
"tests/conftest.py"
|
|
5012
|
+
];
|
|
4917
5013
|
function keyName2(name) {
|
|
4918
5014
|
if (ts3.isIdentifier(name) || ts3.isStringLiteral(name)) return name.text;
|
|
4919
5015
|
return null;
|
|
@@ -4948,8 +5044,104 @@ function runnerOf(path, content) {
|
|
|
4948
5044
|
if (/^jest\.config\./.test(base) || base === "package.json") return "jest";
|
|
4949
5045
|
if (/^vitest\.(?:config|workspace)\./.test(base)) return "vitest";
|
|
4950
5046
|
if (/^vite\.config\./.test(base) && content != null && /(?:^|[\s,{])["']?test["']?\s*:/m.test(content)) return "vitest";
|
|
5047
|
+
if (base === "pytest.ini" || base === ".pytest.ini") return "pytest";
|
|
5048
|
+
if (content == null) return null;
|
|
5049
|
+
if (base === "tox.ini" && /^\s*\[pytest\]/m.test(content)) return "pytest";
|
|
5050
|
+
if (base === "setup.cfg" && /^\s*\[tool:pytest\]/m.test(content)) return "pytest";
|
|
5051
|
+
if (base === "pyproject.toml" && /^\s*\[tool\.pytest\.ini_options\]/m.test(content)) return "pytest";
|
|
5052
|
+
return null;
|
|
5053
|
+
}
|
|
5054
|
+
var PYTEST_INI_ORDER = ["pytest.ini", ".pytest.ini", "pyproject.toml", "tox.ini", "setup.cfg"];
|
|
5055
|
+
function claimsPytest(name, src) {
|
|
5056
|
+
if (name === "pytest.ini" || name === ".pytest.ini") return true;
|
|
5057
|
+
const section = name === "pyproject.toml" ? /^\s*\[tool\.pytest\.ini_options\]\s*$/m : name === "tox.ini" ? /^\s*\[pytest\]\s*$/m : name === "setup.cfg" ? /^\s*\[tool:pytest\]\s*$/m : null;
|
|
5058
|
+
return section !== null && section.test(src);
|
|
5059
|
+
}
|
|
5060
|
+
function effectivePytestFile(files) {
|
|
5061
|
+
for (const name of PYTEST_INI_ORDER) {
|
|
5062
|
+
const content = files.get(name);
|
|
5063
|
+
if (content == null) continue;
|
|
5064
|
+
if (claimsPytest(name, content)) return { path: name, content };
|
|
5065
|
+
}
|
|
4951
5066
|
return null;
|
|
4952
5067
|
}
|
|
5068
|
+
function effectivePytestConfig(path, ctx) {
|
|
5069
|
+
if (path.includes("/")) return false;
|
|
5070
|
+
const base = path;
|
|
5071
|
+
const ORDER = PYTEST_INI_ORDER;
|
|
5072
|
+
const rank = ORDER.indexOf(base);
|
|
5073
|
+
if (rank < 0) return false;
|
|
5074
|
+
const files = ctx?.trackedFiles;
|
|
5075
|
+
if (!files) return true;
|
|
5076
|
+
const rootNames = new Set(files.filter((f) => !f.includes("/")));
|
|
5077
|
+
return !ORDER.slice(0, rank).some((n) => rootNames.has(n));
|
|
5078
|
+
}
|
|
5079
|
+
function pytestBlock(src) {
|
|
5080
|
+
const lines = src.split(/\r?\n/);
|
|
5081
|
+
const head = /^\s*\[(pytest|tool:pytest|tool\.pytest\.ini_options)\]\s*$/;
|
|
5082
|
+
let i = lines.findIndex((l) => head.test(l));
|
|
5083
|
+
if (i < 0) return null;
|
|
5084
|
+
const out2 = {};
|
|
5085
|
+
let key2 = null;
|
|
5086
|
+
for (i += 1; i < lines.length; i++) {
|
|
5087
|
+
const line = lines[i];
|
|
5088
|
+
if (/^\s*\[/.test(line)) break;
|
|
5089
|
+
if (/^\s*[#;]/.test(line) || !line.trim()) {
|
|
5090
|
+
continue;
|
|
5091
|
+
}
|
|
5092
|
+
const m = line.match(/^(\w[\w.-]*)\s*[=:]\s*(.*)$/);
|
|
5093
|
+
if (m) {
|
|
5094
|
+
key2 = m[1];
|
|
5095
|
+
out2[key2] = m[2].trim();
|
|
5096
|
+
continue;
|
|
5097
|
+
}
|
|
5098
|
+
if (key2 && /^\s+\S/.test(line)) out2[key2] += " " + line.trim();
|
|
5099
|
+
}
|
|
5100
|
+
return out2;
|
|
5101
|
+
}
|
|
5102
|
+
function pytestTokens(raw) {
|
|
5103
|
+
const v = raw.trim();
|
|
5104
|
+
if (v.startsWith("[")) {
|
|
5105
|
+
return [...v.matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? "");
|
|
5106
|
+
}
|
|
5107
|
+
const unq2 = v.replace(/^"([\s\S]*)"$/, "$1").replace(/^'([\s\S]*)'$/, "$1");
|
|
5108
|
+
return [...unq2.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)].map((m) => m[1] ?? m[2] ?? m[3] ?? "").filter(Boolean);
|
|
5109
|
+
}
|
|
5110
|
+
var pathGlobs = (p) => {
|
|
5111
|
+
const clean = p.replace(/^\.\//, "").replace(/::.*$/, "").replace(/\/+$/, "");
|
|
5112
|
+
return clean ? [clean, clean + "/**"] : [];
|
|
5113
|
+
};
|
|
5114
|
+
function pytestSelection(src) {
|
|
5115
|
+
const sel = empty();
|
|
5116
|
+
const blk = pytestBlock(src);
|
|
5117
|
+
if (!blk) return sel;
|
|
5118
|
+
sel.present = true;
|
|
5119
|
+
const ignore = blk.norecursedirs !== void 0 ? pytestTokens(blk.norecursedirs).flatMap((d) => pathGlobs(d).map((pattern) => ({ pattern, key: "norecursedirs" }))) : PYTEST_DEFAULT_IGNORE.map((pattern) => ({ pattern, key: "norecursedirs" }));
|
|
5120
|
+
if (blk.testpaths !== void 0) sel.roots = pytestTokens(blk.testpaths).map((t) => t.replace(/^\.\//, ""));
|
|
5121
|
+
if (blk.python_files !== void 0) sel.include = pytestTokens(blk.python_files).map((g) => g.includes("/") ? g : "**/" + g);
|
|
5122
|
+
for (const key2 of ["python_classes", "python_functions"]) {
|
|
5123
|
+
if (blk[key2] !== void 0) ignore.push({ pattern: "\0" + key2, key: key2 });
|
|
5124
|
+
}
|
|
5125
|
+
const opts = blk.addopts !== void 0 ? pytestTokens(blk.addopts) : [];
|
|
5126
|
+
for (let j = 0; j < opts.length; j++) {
|
|
5127
|
+
const o = opts[j];
|
|
5128
|
+
const val = (inline) => o.includes("=") ? o.slice(o.indexOf("=") + 1) : opts[++j] ?? inline;
|
|
5129
|
+
if (o === "--ignore" || o.startsWith("--ignore=")) for (const g of pathGlobs(val(""))) ignore.push({ pattern: g, key: "--ignore" });
|
|
5130
|
+
else if (o === "--ignore-glob" || o.startsWith("--ignore-glob=")) {
|
|
5131
|
+
const g = val("");
|
|
5132
|
+
ignore.push({ pattern: g, key: "--ignore-glob" });
|
|
5133
|
+
if (!g.startsWith("**/") && !g.startsWith("/")) ignore.push({ pattern: "**/" + g, key: "--ignore-glob" });
|
|
5134
|
+
} else if (o === "--deselect" || o.startsWith("--deselect=")) for (const g of pathGlobs(val(""))) ignore.push({ pattern: g, key: "--deselect" });
|
|
5135
|
+
else if (o === "-k" || o.startsWith("-k=")) {
|
|
5136
|
+
for (const w of (val("").match(/\bnot\s+([A-Za-z_][\w]*)/g) ?? []).map((t) => t.replace(/^not\s+/, "")))
|
|
5137
|
+
ignore.push({ pattern: `**/*${w}*`, key: "-k" });
|
|
5138
|
+
} else if (o === "-m" || o.startsWith("-m=")) {
|
|
5139
|
+
ignore.push({ pattern: "\0-m " + val(""), key: "-m" });
|
|
5140
|
+
} else if (o === "--collect-only" || o === "--co") ignore.push({ pattern: "**", key: o });
|
|
5141
|
+
}
|
|
5142
|
+
sel.ignore = ignore;
|
|
5143
|
+
return sel;
|
|
5144
|
+
}
|
|
4953
5145
|
var isWorkspaceFile = (path) => /^vitest\.workspace\./.test(path.split("/").pop() ?? path);
|
|
4954
5146
|
var empty = () => ({ include: null, regex: null, ignore: null, roots: null, base: null, projects: null, opaque: false, present: false });
|
|
4955
5147
|
var stripRootDir = (g) => g.replace(/^<rootDir>\/?/, "");
|
|
@@ -5098,6 +5290,7 @@ function workspaceArray(sf) {
|
|
|
5098
5290
|
}
|
|
5099
5291
|
function parseSelection(src, runner, path = "") {
|
|
5100
5292
|
let sel = empty();
|
|
5293
|
+
if (runner === "pytest") return pytestSelection(src);
|
|
5101
5294
|
try {
|
|
5102
5295
|
const sf = ts3.createSourceFile("cfg.ts", asExpression2(src), ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
5103
5296
|
if (runner === "vitest" && isWorkspaceFile(path)) {
|
|
@@ -5146,10 +5339,14 @@ function predicate(sel, runner) {
|
|
|
5146
5339
|
why: (p) => ps.length ? `no project selects it (${ps[0].why(p)})` : "the projects list is empty"
|
|
5147
5340
|
};
|
|
5148
5341
|
}
|
|
5149
|
-
const includeList = sel.include ?? (runner === "jest" ? sel.regex ? null : JEST_DEFAULT_MATCH : VITEST_DEFAULT_INCLUDE);
|
|
5150
|
-
const ignoreList = sel.ignore ?? (runner === "jest" ? JEST_DEFAULT_IGNORE :
|
|
5342
|
+
const includeList = sel.include ?? (runner === "jest" ? sel.regex ? null : JEST_DEFAULT_MATCH : runner === "pytest" ? PYTEST_DEFAULT_INCLUDE : VITEST_DEFAULT_INCLUDE);
|
|
5343
|
+
const ignoreList = sel.ignore ?? (runner === "jest" ? JEST_DEFAULT_IGNORE : runner === "pytest" ? PYTEST_DEFAULT_IGNORE : VITEST_DEFAULT_EXCLUDE).map((pattern) => ({
|
|
5344
|
+
pattern,
|
|
5345
|
+
key: runner === "jest" ? "testPathIgnorePatterns" : runner === "pytest" ? "norecursedirs" : "test.exclude"
|
|
5346
|
+
}));
|
|
5347
|
+
const blockWide = ignoreList.filter((e) => e.pattern.startsWith("\0"));
|
|
5151
5348
|
const included = includeList ? globList2(includeList) : sel.regex ? regexes(sel.regex) : () => true;
|
|
5152
|
-
const ignoredBy = (p) => ignoreList.find((e) => (runner === "jest" ? regexes([e.pattern]) : globs3([e.pattern]))(p));
|
|
5349
|
+
const ignoredBy = (p) => blockWide[0] ?? ignoreList.filter((e) => !e.pattern.startsWith("\0")).find((e) => (runner === "jest" ? regexes([e.pattern]) : globs3([e.pattern]))(p));
|
|
5153
5350
|
const roots = sel.roots?.map((r) => r.replace(/^\.\//, "").replace(/\/+$/, "")).map((r) => r === "." ? "" : r) ?? null;
|
|
5154
5351
|
const rebase = (p) => sel.base === null ? p : underDir(p, sel.base) ? p.slice(sel.base.length + 1) : null;
|
|
5155
5352
|
const inRoots = (p) => roots === null || roots.some((r) => underDir(p, r));
|
|
@@ -5164,7 +5361,10 @@ function predicate(sel, runner) {
|
|
|
5164
5361
|
if (r === null) return `${baseKey} no longer covers it (${JSON.stringify(sel.base)})`;
|
|
5165
5362
|
if (!inRoots(r)) return `roots no longer covers it (${JSON.stringify(sel.roots)})`;
|
|
5166
5363
|
const hit = ignoredBy(r);
|
|
5167
|
-
if (hit)
|
|
5364
|
+
if (hit) {
|
|
5365
|
+
const pat = hit.pattern.startsWith("\0") ? hit.pattern.slice(1) : hit.pattern;
|
|
5366
|
+
return `${hit.key === "exclude" ? "test.exclude" : hit.key} now matches it (${JSON.stringify(pat)})`;
|
|
5367
|
+
}
|
|
5168
5368
|
const key2 = runner === "jest" ? sel.include ? "testMatch" : sel.regex ? "testRegex" : "testMatch (default)" : sel.include ? "test.include" : "test.include (default)";
|
|
5169
5369
|
return `${key2} no longer matches it (${JSON.stringify(sel.include ?? sel.regex ?? includeList ?? [])})`;
|
|
5170
5370
|
}
|
|
@@ -5314,12 +5514,14 @@ function significantLines(src, path) {
|
|
|
5314
5514
|
return out2;
|
|
5315
5515
|
}
|
|
5316
5516
|
var OTHER_RUNNER_DIR = /(?:^|\/)(?:e2e|cypress|playwright|fixtures|__fixtures__|__mocks__)\//;
|
|
5317
|
-
function runnerSamples(configPath, policy, ctx) {
|
|
5517
|
+
function runnerSamples(configPath, policy, ctx, runner) {
|
|
5518
|
+
const lang = runner === "pytest" ? "py" : "js";
|
|
5519
|
+
const fallback = runner === "pytest" ? PYTEST_CANONICAL_SAMPLES : CANONICAL_SAMPLES;
|
|
5318
5520
|
const files = trackedFiles(ctx);
|
|
5319
|
-
if (!files) return
|
|
5521
|
+
if (!files) return fallback;
|
|
5320
5522
|
const root = configPath.includes("/") ? configPath.slice(0, configPath.lastIndexOf("/") + 1) : "";
|
|
5321
|
-
const own = files.filter((f) => f.startsWith(root) && langOf(f) ===
|
|
5322
|
-
return own.length ? own :
|
|
5523
|
+
const own = files.filter((f) => f.startsWith(root) && langOf(f) === lang && isProtected(f, policy, "tests") && (lang !== "js" || !OTHER_RUNNER_DIR.test(f))).map((f) => f.slice(root.length));
|
|
5524
|
+
return own.length ? own : fallback;
|
|
5323
5525
|
}
|
|
5324
5526
|
var WHOLE_SUITE_SCRIPT = /^test(?::ci)?$/;
|
|
5325
5527
|
function excludesOtherRunner(flag, cmd) {
|
|
@@ -5440,7 +5642,9 @@ var testDeletion = {
|
|
|
5440
5642
|
);
|
|
5441
5643
|
}
|
|
5442
5644
|
} else if (c.op !== "delete" && c.after != null && !isTest && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
|
|
5443
|
-
|
|
5645
|
+
const rn = runnerOf(c.path, c.after);
|
|
5646
|
+
const readable = rn !== "pytest" || effectivePytestConfig(c.path, ctx);
|
|
5647
|
+
for (const n of readable ? suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx, rn)) : []) {
|
|
5444
5648
|
out2.push(
|
|
5445
5649
|
makeFinding(RULE7, policy, {
|
|
5446
5650
|
file: c.path,
|
|
@@ -5550,9 +5754,48 @@ var testDeletion = {
|
|
|
5550
5754
|
}
|
|
5551
5755
|
}
|
|
5552
5756
|
}
|
|
5757
|
+
out2.push(...pytestPrecedenceFindings(changes, policy, ctx));
|
|
5553
5758
|
return out2;
|
|
5554
5759
|
}
|
|
5555
5760
|
};
|
|
5761
|
+
function pytestPrecedenceFindings(changes, policy, ctx) {
|
|
5762
|
+
const order = PYTEST_INI_ORDER;
|
|
5763
|
+
const touched = /* @__PURE__ */ new Map();
|
|
5764
|
+
for (const c of changes) {
|
|
5765
|
+
if (c.kind !== "file" || c.oldPath) continue;
|
|
5766
|
+
if (order.includes(c.path)) touched.set(c.path, c);
|
|
5767
|
+
}
|
|
5768
|
+
if (touched.size === 0) return [];
|
|
5769
|
+
const listing = trackedFiles(ctx);
|
|
5770
|
+
if (!listing) return [];
|
|
5771
|
+
const rootNames = new Set(listing.filter((f) => !f.includes("/")));
|
|
5772
|
+
const base = /* @__PURE__ */ new Map();
|
|
5773
|
+
const head = /* @__PURE__ */ new Map();
|
|
5774
|
+
for (const name of order) {
|
|
5775
|
+
const c = touched.get(name);
|
|
5776
|
+
if (c) {
|
|
5777
|
+
base.set(name, c.before);
|
|
5778
|
+
head.set(name, c.after);
|
|
5779
|
+
continue;
|
|
5780
|
+
}
|
|
5781
|
+
if (!rootNames.has(name)) continue;
|
|
5782
|
+
const src = trackedContent(name, ctx);
|
|
5783
|
+
base.set(name, src);
|
|
5784
|
+
head.set(name, src);
|
|
5785
|
+
}
|
|
5786
|
+
const was = effectivePytestFile(base);
|
|
5787
|
+
const now = effectivePytestFile(head);
|
|
5788
|
+
if (!was || !now || was.path === now.path) return [];
|
|
5789
|
+
const samples = runnerSamples(now.path, policy, ctx, "pytest");
|
|
5790
|
+
return suiteNarrowings(was.content, now.content, now.path, samples).map(
|
|
5791
|
+
(n) => makeFinding(RULE7, policy, {
|
|
5792
|
+
file: now.path,
|
|
5793
|
+
message: `The effective pytest config moved from ${was.path} to ${now.path}, and ${n.path} is no longer selected: ${n.reason}.`,
|
|
5794
|
+
evidence: `inifile precedence ${was.path} \u2192 ${now.path} drops ${n.path} from the suite`,
|
|
5795
|
+
remediation: "pytest reads ONE config file. Adding a higher-precedence one, or deleting the one in force, changes which settings apply \u2014 keep the effective configuration selecting the whole suite."
|
|
5796
|
+
})
|
|
5797
|
+
);
|
|
5798
|
+
}
|
|
5556
5799
|
|
|
5557
5800
|
// src/detectors/test-content-removal.ts
|
|
5558
5801
|
import ts5 from "typescript";
|
|
@@ -5838,8 +6081,8 @@ function hasBlocking(findings) {
|
|
|
5838
6081
|
}
|
|
5839
6082
|
|
|
5840
6083
|
// src/policy-load.ts
|
|
5841
|
-
import { readFileSync as
|
|
5842
|
-
import { isAbsolute as isAbsolute4, join as
|
|
6084
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "node:fs";
|
|
6085
|
+
import { isAbsolute as isAbsolute4, join as join5, posix as posix2 } from "node:path";
|
|
5843
6086
|
import { parse as parse2 } from "yaml";
|
|
5844
6087
|
var PolicyError = class extends Error {
|
|
5845
6088
|
};
|
|
@@ -5963,9 +6206,9 @@ function parseOrThrow(src, where) {
|
|
|
5963
6206
|
return parsePolicy(raw, where);
|
|
5964
6207
|
}
|
|
5965
6208
|
function loadPolicy(cwd = process.cwd()) {
|
|
5966
|
-
const path =
|
|
6209
|
+
const path = join5(cwd, POLICY_FILE);
|
|
5967
6210
|
if (!existsSync3(path)) return defaultPolicy();
|
|
5968
|
-
return parseOrThrow(
|
|
6211
|
+
return parseOrThrow(readFileSync4(path, "utf8"), POLICY_FILE);
|
|
5969
6212
|
}
|
|
5970
6213
|
function loadPolicyAt(rev, cwd) {
|
|
5971
6214
|
const src = fileAt(rev, POLICY_FILE, { cwd });
|
|
@@ -5975,8 +6218,8 @@ function loadPolicyAt(rev, cwd) {
|
|
|
5975
6218
|
|
|
5976
6219
|
// src/signoff.ts
|
|
5977
6220
|
import { createHash } from "node:crypto";
|
|
5978
|
-
import { appendFileSync, mkdirSync, readFileSync as
|
|
5979
|
-
import { dirname as dirname2, join as
|
|
6221
|
+
import { appendFileSync, mkdirSync, readFileSync as readFileSync5, existsSync as existsSync4 } from "node:fs";
|
|
6222
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
5980
6223
|
var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5981
6224
|
function fingerprint(rule, file, evidence) {
|
|
5982
6225
|
return createHash("sha256").update(`${rule}\0${file ?? ""}\0${evidence}`).digest("hex").slice(0, 16);
|
|
@@ -5987,13 +6230,13 @@ function ledgerPath(cwd, policy) {
|
|
|
5987
6230
|
if (!ledgerInsideRepo(rel)) {
|
|
5988
6231
|
throw new PolicyError(`signoff.ledger must be a relative path inside the repository, got ${JSON.stringify(rel)}`);
|
|
5989
6232
|
}
|
|
5990
|
-
return
|
|
6233
|
+
return join6(cwd, rel);
|
|
5991
6234
|
}
|
|
5992
6235
|
function readLedger(cwd, policy) {
|
|
5993
6236
|
const p = ledgerPath(cwd, policy);
|
|
5994
6237
|
if (!existsSync4(p)) return [];
|
|
5995
6238
|
const out2 = [];
|
|
5996
|
-
for (const line of
|
|
6239
|
+
for (const line of readFileSync5(p, "utf8").split("\n")) {
|
|
5997
6240
|
if (!line.trim()) continue;
|
|
5998
6241
|
try {
|
|
5999
6242
|
const e = JSON.parse(line);
|
|
@@ -6363,14 +6606,14 @@ function runCheck(opts) {
|
|
|
6363
6606
|
}
|
|
6364
6607
|
|
|
6365
6608
|
// src/cli/hook.ts
|
|
6366
|
-
import { readFileSync as
|
|
6609
|
+
import { readFileSync as readFileSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
|
|
6367
6610
|
import { createHash as createHash4 } from "node:crypto";
|
|
6368
6611
|
|
|
6369
6612
|
// src/adapters/claude/changes.ts
|
|
6370
6613
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
6371
6614
|
import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
6372
6615
|
import { tmpdir } from "node:os";
|
|
6373
|
-
import { isAbsolute as isAbsolute5, join as
|
|
6616
|
+
import { isAbsolute as isAbsolute5, join as join7, relative as relative2, resolve as resolve2 } from "node:path";
|
|
6374
6617
|
function asStr(v) {
|
|
6375
6618
|
return typeof v === "string" ? v : "";
|
|
6376
6619
|
}
|
|
@@ -6395,11 +6638,11 @@ function synthFileChange(displayPath, before, after) {
|
|
|
6395
6638
|
if (before === null) op = "add";
|
|
6396
6639
|
else if (after === null) op = "delete";
|
|
6397
6640
|
else op = "modify";
|
|
6398
|
-
const dir = mkdtempSync(
|
|
6641
|
+
const dir = mkdtempSync(join7(tmpdir(), "hf-"));
|
|
6399
6642
|
let raw = "";
|
|
6400
6643
|
try {
|
|
6401
|
-
const a =
|
|
6402
|
-
const b =
|
|
6644
|
+
const a = join7(dir, "a");
|
|
6645
|
+
const b = join7(dir, "b");
|
|
6403
6646
|
writeFileSync(a, before ?? "");
|
|
6404
6647
|
writeFileSync(b, after ?? "");
|
|
6405
6648
|
try {
|
|
@@ -6484,14 +6727,14 @@ function formatDenial(blocks) {
|
|
|
6484
6727
|
}
|
|
6485
6728
|
|
|
6486
6729
|
// src/session.ts
|
|
6487
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as
|
|
6488
|
-
import { dirname as dirname3, join as
|
|
6730
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6731
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
6489
6732
|
var UNSAFE = /[^A-Za-z0-9_-]/g;
|
|
6490
6733
|
var SHA = /^[0-9a-f]{7,40}$/;
|
|
6491
6734
|
function baselinePath(cwd, sessionId) {
|
|
6492
6735
|
const gd = gitDir(cwd);
|
|
6493
6736
|
if (!gd) return null;
|
|
6494
|
-
return
|
|
6737
|
+
return join8(gd, "tamperward", `session-${sessionId.replace(UNSAFE, "")}`);
|
|
6495
6738
|
}
|
|
6496
6739
|
function turnBaseline(cwd, sessionId) {
|
|
6497
6740
|
if (!sessionId) return null;
|
|
@@ -6499,7 +6742,7 @@ function turnBaseline(cwd, sessionId) {
|
|
|
6499
6742
|
const p = baselinePath(cwd, sessionId);
|
|
6500
6743
|
if (!p) return null;
|
|
6501
6744
|
if (existsSync5(p)) {
|
|
6502
|
-
const v =
|
|
6745
|
+
const v = readFileSync6(p, "utf8").trim();
|
|
6503
6746
|
if (SHA.test(v)) return v;
|
|
6504
6747
|
}
|
|
6505
6748
|
const head = headSha(cwd);
|
|
@@ -6525,8 +6768,8 @@ function advanceTurnBaseline(cwd, sessionId) {
|
|
|
6525
6768
|
|
|
6526
6769
|
// src/effect.ts
|
|
6527
6770
|
import { createHash as createHash2 } from "node:crypto";
|
|
6528
|
-
import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as
|
|
6529
|
-
import { isAbsolute as isAbsolute6, join as
|
|
6771
|
+
import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync7, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6772
|
+
import { isAbsolute as isAbsolute6, join as join9 } from "node:path";
|
|
6530
6773
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
6531
6774
|
var UNSAFE2 = /[^A-Za-z0-9._-]/g;
|
|
6532
6775
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
|
|
@@ -6538,7 +6781,7 @@ function gitDir2(cwd) {
|
|
|
6538
6781
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6539
6782
|
}).trim();
|
|
6540
6783
|
if (!d) return null;
|
|
6541
|
-
return isAbsolute6(d) ? d :
|
|
6784
|
+
return isAbsolute6(d) ? d : join9(cwd, d);
|
|
6542
6785
|
} catch {
|
|
6543
6786
|
return null;
|
|
6544
6787
|
}
|
|
@@ -6547,8 +6790,8 @@ function statePath(cwd, sessionId, kind) {
|
|
|
6547
6790
|
if (!sessionId) return null;
|
|
6548
6791
|
const gd = gitDir2(cwd);
|
|
6549
6792
|
if (!gd) return null;
|
|
6550
|
-
const dir =
|
|
6551
|
-
return
|
|
6793
|
+
const dir = join9(gd, "tamperward");
|
|
6794
|
+
return join9(dir, `${kind}-${sessionId.replace(UNSAFE2, "")}.json`);
|
|
6552
6795
|
}
|
|
6553
6796
|
function ptreePath(cwd, sessionId) {
|
|
6554
6797
|
return statePath(cwd, sessionId, "ptree");
|
|
@@ -6577,7 +6820,7 @@ function snapshotProtected(cwd, policy, prev) {
|
|
|
6577
6820
|
} catch {
|
|
6578
6821
|
}
|
|
6579
6822
|
const walk2 = (rel) => {
|
|
6580
|
-
const abs2 = rel ?
|
|
6823
|
+
const abs2 = rel ? join9(cwd, rel) : cwd;
|
|
6581
6824
|
let names;
|
|
6582
6825
|
try {
|
|
6583
6826
|
names = readdirSync(abs2);
|
|
@@ -6590,7 +6833,7 @@ function snapshotProtected(cwd, policy, prev) {
|
|
|
6590
6833
|
if (pruned.has(r)) continue;
|
|
6591
6834
|
let st;
|
|
6592
6835
|
try {
|
|
6593
|
-
st = lstatSync2(
|
|
6836
|
+
st = lstatSync2(join9(cwd, r));
|
|
6594
6837
|
} catch {
|
|
6595
6838
|
continue;
|
|
6596
6839
|
}
|
|
@@ -6619,7 +6862,7 @@ function driftBetween(expected, current) {
|
|
|
6619
6862
|
function loadTree(p) {
|
|
6620
6863
|
if (!p || !existsSync6(p)) return null;
|
|
6621
6864
|
try {
|
|
6622
|
-
return JSON.parse(
|
|
6865
|
+
return JSON.parse(readFileSync7(p, "utf8"));
|
|
6623
6866
|
} catch {
|
|
6624
6867
|
return null;
|
|
6625
6868
|
}
|
|
@@ -6627,7 +6870,7 @@ function loadTree(p) {
|
|
|
6627
6870
|
function saveTree(p, tree) {
|
|
6628
6871
|
if (!p) return;
|
|
6629
6872
|
try {
|
|
6630
|
-
mkdirSync3(
|
|
6873
|
+
mkdirSync3(join9(p, ".."), { recursive: true });
|
|
6631
6874
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
6632
6875
|
writeFileSync3(tmp, JSON.stringify(tree));
|
|
6633
6876
|
renameSync(tmp, p);
|
|
@@ -6659,7 +6902,7 @@ function dropTurnTree(cwd, sessionId) {
|
|
|
6659
6902
|
|
|
6660
6903
|
// src/cli/watch.ts
|
|
6661
6904
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync, watch } from "node:fs";
|
|
6662
|
-
import { join as
|
|
6905
|
+
import { join as join10 } from "node:path";
|
|
6663
6906
|
import { createHash as createHash3 } from "node:crypto";
|
|
6664
6907
|
var SKIP2 = /(^|\/)(\.git|node_modules)(\/|$)/;
|
|
6665
6908
|
function snap(abs2) {
|
|
@@ -6671,7 +6914,7 @@ function snap(abs2) {
|
|
|
6671
6914
|
}
|
|
6672
6915
|
function defaultEventLog(cwd) {
|
|
6673
6916
|
if (process.env.TAMPERWARD_FSEVENTS) return process.env.TAMPERWARD_FSEVENTS;
|
|
6674
|
-
return
|
|
6917
|
+
return join10(gitDir(cwd) ?? join10(cwd, ".git"), "tamperward", "fsevents.jsonl");
|
|
6675
6918
|
}
|
|
6676
6919
|
function watchTree(dir, cb) {
|
|
6677
6920
|
if (process.env.TAMPERWARD_WATCH_NO_RECURSIVE !== "1") {
|
|
@@ -6688,12 +6931,12 @@ function watchTree(dir, cb) {
|
|
|
6688
6931
|
if (watchers.has(rel) || SKIP2.test(rel + "/")) return;
|
|
6689
6932
|
let w;
|
|
6690
6933
|
try {
|
|
6691
|
-
w = watch(rel ?
|
|
6934
|
+
w = watch(rel ? join10(dir, rel) : dir, (kind, fname) => {
|
|
6692
6935
|
if (!fname) return;
|
|
6693
6936
|
const child = rel ? `${rel}/${String(fname)}` : String(fname);
|
|
6694
6937
|
cb(kind, child);
|
|
6695
6938
|
try {
|
|
6696
|
-
if (statSync(
|
|
6939
|
+
if (statSync(join10(dir, child)).isDirectory()) addDir(child);
|
|
6697
6940
|
} catch {
|
|
6698
6941
|
}
|
|
6699
6942
|
});
|
|
@@ -6706,7 +6949,7 @@ function watchTree(dir, cb) {
|
|
|
6706
6949
|
addDir(rel);
|
|
6707
6950
|
let names = [];
|
|
6708
6951
|
try {
|
|
6709
|
-
names = readdirSync2(rel ?
|
|
6952
|
+
names = readdirSync2(rel ? join10(dir, rel) : dir);
|
|
6710
6953
|
} catch {
|
|
6711
6954
|
return;
|
|
6712
6955
|
}
|
|
@@ -6714,7 +6957,7 @@ function watchTree(dir, cb) {
|
|
|
6714
6957
|
const child = rel ? `${rel}/${name}` : name;
|
|
6715
6958
|
if (SKIP2.test(child + "/")) continue;
|
|
6716
6959
|
try {
|
|
6717
|
-
if (statSync(
|
|
6960
|
+
if (statSync(join10(dir, child)).isDirectory()) walk2(child);
|
|
6718
6961
|
} catch {
|
|
6719
6962
|
}
|
|
6720
6963
|
}
|
|
@@ -6726,13 +6969,13 @@ function watchTree(dir, cb) {
|
|
|
6726
6969
|
}
|
|
6727
6970
|
function startWatcher(dir, log, policy) {
|
|
6728
6971
|
try {
|
|
6729
|
-
mkdirSync4(
|
|
6972
|
+
mkdirSync4(join10(log, ".."), { recursive: true });
|
|
6730
6973
|
} catch {
|
|
6731
6974
|
}
|
|
6732
6975
|
const last = /* @__PURE__ */ new Map();
|
|
6733
6976
|
return watchTree(dir, (kind, rel) => {
|
|
6734
6977
|
if (SKIP2.test(rel) || !isProtected(rel, policy)) return;
|
|
6735
|
-
const s = snap(
|
|
6978
|
+
const s = snap(join10(dir, rel));
|
|
6736
6979
|
const key2 = `${s.hash}:${s.mode}`;
|
|
6737
6980
|
if (last.get(rel) === key2) return;
|
|
6738
6981
|
last.set(rel, key2);
|
|
@@ -6759,7 +7002,7 @@ function runWatch(args) {
|
|
|
6759
7002
|
}
|
|
6760
7003
|
|
|
6761
7004
|
// src/detectors/fs-events.ts
|
|
6762
|
-
import { readFileSync as
|
|
7005
|
+
import { readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
|
|
6763
7006
|
var RULE11 = "transient-protected-mutation";
|
|
6764
7007
|
function readEvents(log, offset) {
|
|
6765
7008
|
let size = 0;
|
|
@@ -6769,7 +7012,7 @@ function readEvents(log, offset) {
|
|
|
6769
7012
|
return { events: [], newOffset: offset };
|
|
6770
7013
|
}
|
|
6771
7014
|
if (size <= offset) return { events: [], newOffset: offset >= 0 ? Math.min(offset, size) : 0 };
|
|
6772
|
-
const raw =
|
|
7015
|
+
const raw = readFileSync9(log, "utf8").slice(offset);
|
|
6773
7016
|
const events = [];
|
|
6774
7017
|
for (const line of raw.split("\n")) {
|
|
6775
7018
|
if (!line.trim()) continue;
|
|
@@ -6822,7 +7065,7 @@ var HookInputError = class extends Error {
|
|
|
6822
7065
|
};
|
|
6823
7066
|
function readStdin() {
|
|
6824
7067
|
try {
|
|
6825
|
-
return
|
|
7068
|
+
return readFileSync10(0, "utf8");
|
|
6826
7069
|
} catch (e) {
|
|
6827
7070
|
throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
|
|
6828
7071
|
}
|
|
@@ -7078,7 +7321,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
7078
7321
|
if (!existsSync7(log)) return none;
|
|
7079
7322
|
let offset = 0;
|
|
7080
7323
|
if (cp && existsSync7(cp)) {
|
|
7081
|
-
const n = Number(
|
|
7324
|
+
const n = Number(readFileSync10(cp, "utf8"));
|
|
7082
7325
|
offset = Number.isFinite(n) ? n : 0;
|
|
7083
7326
|
}
|
|
7084
7327
|
const { events, newOffset } = readEvents(log, offset);
|
|
@@ -7231,8 +7474,8 @@ Honored at LOCAL pre-commit only. The agent-layer hook ignores this file; CI req
|
|
|
7231
7474
|
// src/cli/init.ts
|
|
7232
7475
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
7233
7476
|
import { createHash as createHash5 } from "node:crypto";
|
|
7234
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as
|
|
7235
|
-
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute7, join as
|
|
7477
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, chmodSync, statSync as statSync3 } from "node:fs";
|
|
7478
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute7, join as join11, relative as relative3, resolve as resolve3 } from "node:path";
|
|
7236
7479
|
var errText3 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
|
|
7237
7480
|
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7238
7481
|
function gitConfig(cwd, key2) {
|
|
@@ -7408,7 +7651,7 @@ function hooksShapeError(hooks) {
|
|
|
7408
7651
|
return null;
|
|
7409
7652
|
}
|
|
7410
7653
|
function planPolicy(cwd) {
|
|
7411
|
-
const path =
|
|
7654
|
+
const path = join11(cwd, POLICY_FILE);
|
|
7412
7655
|
if (existsSync8(path)) {
|
|
7413
7656
|
try {
|
|
7414
7657
|
loadPolicy(cwd);
|
|
@@ -7427,12 +7670,12 @@ function planPolicy(cwd) {
|
|
|
7427
7670
|
}
|
|
7428
7671
|
function planClaudeHooks(cwd) {
|
|
7429
7672
|
const rel = ".claude/settings.json";
|
|
7430
|
-
const path =
|
|
7673
|
+
const path = join11(cwd, rel);
|
|
7431
7674
|
let settings = {};
|
|
7432
7675
|
if (existsSync8(path)) {
|
|
7433
7676
|
let parsed;
|
|
7434
7677
|
try {
|
|
7435
|
-
parsed = JSON.parse(
|
|
7678
|
+
parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
7436
7679
|
} catch {
|
|
7437
7680
|
return {
|
|
7438
7681
|
item: "agent",
|
|
@@ -7528,7 +7771,7 @@ function hooksDir(cwd) {
|
|
|
7528
7771
|
if (out2) return resolve3(cwd, out2);
|
|
7529
7772
|
} catch {
|
|
7530
7773
|
}
|
|
7531
|
-
return existsSync8(
|
|
7774
|
+
return existsSync8(join11(cwd, ".git")) ? join11(cwd, ".git", "hooks") : null;
|
|
7532
7775
|
}
|
|
7533
7776
|
function display(cwd, path) {
|
|
7534
7777
|
const rel = relative3(cwd, path);
|
|
@@ -7555,30 +7798,30 @@ function planPreCommit(cwd) {
|
|
|
7555
7798
|
const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
|
|
7556
7799
|
let path;
|
|
7557
7800
|
let note;
|
|
7558
|
-
if (existsSync8(
|
|
7559
|
-
path =
|
|
7801
|
+
if (existsSync8(join11(cwd, ".husky"))) {
|
|
7802
|
+
path = join11(cwd, ".husky", "pre-commit");
|
|
7560
7803
|
note = "husky";
|
|
7561
7804
|
} else {
|
|
7562
|
-
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(
|
|
7805
|
+
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(join11(cwd, c))));
|
|
7563
7806
|
if (managed) {
|
|
7564
7807
|
return skip(`${managed.name} manages this repository's hooks and regenerates them on install, so a line in the hooks directory would not last \u2014 ${managed.hint}`);
|
|
7565
7808
|
}
|
|
7566
7809
|
const dir = hooksDir(cwd);
|
|
7567
7810
|
if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
|
|
7568
7811
|
if (basename2(dir) === "_" && basename2(dirname4(dir)) === ".husky") {
|
|
7569
|
-
path =
|
|
7812
|
+
path = join11(dirname4(dir), "pre-commit");
|
|
7570
7813
|
note = "husky";
|
|
7571
7814
|
} else {
|
|
7572
|
-
path =
|
|
7815
|
+
path = join11(dir, "pre-commit");
|
|
7573
7816
|
const rel2 = display(cwd, dir);
|
|
7574
|
-
note = rel2 ===
|
|
7817
|
+
note = rel2 === join11(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
|
|
7575
7818
|
}
|
|
7576
7819
|
}
|
|
7577
7820
|
const rel = display(cwd, path);
|
|
7578
7821
|
if (existsSync8(path) && !statSync3(path).isFile()) {
|
|
7579
7822
|
return { item: "pre-commit", path: rel, status: "error", detail: "exists but is not a regular file \u2014 refusing to write" };
|
|
7580
7823
|
}
|
|
7581
|
-
const existing = existsSync8(path) ?
|
|
7824
|
+
const existing = existsSync8(path) ? readFileSync11(path, "utf8") : null;
|
|
7582
7825
|
if (existing === null) {
|
|
7583
7826
|
return {
|
|
7584
7827
|
item: "pre-commit",
|
|
@@ -7667,10 +7910,10 @@ ${block.join("\n")}
|
|
|
7667
7910
|
function planCodeowners(cwd) {
|
|
7668
7911
|
const rel = ".github/CODEOWNERS";
|
|
7669
7912
|
const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
|
|
7670
|
-
(r) => existsSync8(
|
|
7913
|
+
(r) => existsSync8(join11(cwd, r))
|
|
7671
7914
|
);
|
|
7672
|
-
const path =
|
|
7673
|
-
const existing = existingRel ?
|
|
7915
|
+
const path = join11(cwd, existingRel ?? rel);
|
|
7916
|
+
const existing = existingRel ? readFileSync11(path, "utf8") : null;
|
|
7674
7917
|
const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
|
|
7675
7918
|
if (missing.length === 0) {
|
|
7676
7919
|
return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
|
|
@@ -7703,7 +7946,7 @@ ${CODEOWNERS_MARK}
|
|
|
7703
7946
|
}
|
|
7704
7947
|
function planWorkflow(cwd, force) {
|
|
7705
7948
|
const rel = ".github/workflows/tamperward.yml";
|
|
7706
|
-
const path =
|
|
7949
|
+
const path = join11(cwd, rel);
|
|
7707
7950
|
const write = () => {
|
|
7708
7951
|
mkdirSync5(dirname4(path), { recursive: true });
|
|
7709
7952
|
writeFileSync5(path, workflowFile(WORKFLOW_CONTENT));
|
|
@@ -7719,7 +7962,7 @@ function planWorkflow(cwd, force) {
|
|
|
7719
7962
|
}
|
|
7720
7963
|
let src;
|
|
7721
7964
|
try {
|
|
7722
|
-
src =
|
|
7965
|
+
src = readFileSync11(path, "utf8");
|
|
7723
7966
|
} catch {
|
|
7724
7967
|
return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
|
|
7725
7968
|
}
|
|
@@ -7818,15 +8061,15 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
|
|
|
7818
8061
|
// src/cli/verify.ts
|
|
7819
8062
|
import { execFileSync as execFileSync8, spawnSync } from "node:child_process";
|
|
7820
8063
|
import { createHash as createHash8 } from "node:crypto";
|
|
7821
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as
|
|
8064
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, readlinkSync as readlinkSync3, rmSync as rmSync3, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
|
|
7822
8065
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
7823
|
-
import { dirname as dirname5, join as
|
|
8066
|
+
import { dirname as dirname5, join as join14, resolve as resolve4 } from "node:path";
|
|
7824
8067
|
|
|
7825
8068
|
// src/git/trusted.ts
|
|
7826
8069
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
7827
8070
|
import { createHash as createHash6 } from "node:crypto";
|
|
7828
|
-
import { existsSync as existsSync9, readFileSync as
|
|
7829
|
-
import { isAbsolute as isAbsolute8, join as
|
|
8071
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "node:fs";
|
|
8072
|
+
import { isAbsolute as isAbsolute8, join as join12 } from "node:path";
|
|
7830
8073
|
function trustedGitEnv() {
|
|
7831
8074
|
return { ...process.env, GIT_NO_REPLACE_OBJECTS: "1" };
|
|
7832
8075
|
}
|
|
@@ -7839,7 +8082,7 @@ function gitDirOf(cwd) {
|
|
|
7839
8082
|
env: trustedGitEnv()
|
|
7840
8083
|
}).trim();
|
|
7841
8084
|
if (!d) return null;
|
|
7842
|
-
return isAbsolute8(d) ? d :
|
|
8085
|
+
return isAbsolute8(d) ? d : join12(cwd, d);
|
|
7843
8086
|
} catch {
|
|
7844
8087
|
return null;
|
|
7845
8088
|
}
|
|
@@ -7864,9 +8107,9 @@ function objectRewriteState(cwd) {
|
|
|
7864
8107
|
parts.push(`${rel}:<no-git-dir>`);
|
|
7865
8108
|
continue;
|
|
7866
8109
|
}
|
|
7867
|
-
const p =
|
|
8110
|
+
const p = join12(dir, rel);
|
|
7868
8111
|
try {
|
|
7869
|
-
parts.push(`${rel}:` + (existsSync9(p) ?
|
|
8112
|
+
parts.push(`${rel}:` + (existsSync9(p) ? readFileSync12(p, "utf8").trim() : ""));
|
|
7870
8113
|
} catch {
|
|
7871
8114
|
parts.push(`${rel}:<unreadable>`);
|
|
7872
8115
|
}
|
|
@@ -7877,8 +8120,8 @@ function objectRewriteState(cwd) {
|
|
|
7877
8120
|
// src/fingerprint.ts
|
|
7878
8121
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
7879
8122
|
import { createHash as createHash7 } from "node:crypto";
|
|
7880
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
7881
|
-
import { join as
|
|
8123
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync13, readlinkSync as readlinkSync2, statSync as statSync4 } from "node:fs";
|
|
8124
|
+
import { join as join13 } from "node:path";
|
|
7882
8125
|
function treeFingerprint(cwd) {
|
|
7883
8126
|
const files = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
7884
8127
|
cwd,
|
|
@@ -7896,7 +8139,7 @@ function treeFingerprint(cwd) {
|
|
|
7896
8139
|
return h.digest("hex");
|
|
7897
8140
|
}
|
|
7898
8141
|
function depsFingerprint(cwd) {
|
|
7899
|
-
const root =
|
|
8142
|
+
const root = join13(cwd, "node_modules");
|
|
7900
8143
|
const h = createHash7("sha256");
|
|
7901
8144
|
const walk2 = (dir) => {
|
|
7902
8145
|
let entries;
|
|
@@ -7906,12 +8149,12 @@ function depsFingerprint(cwd) {
|
|
|
7906
8149
|
return;
|
|
7907
8150
|
}
|
|
7908
8151
|
for (const e of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
7909
|
-
const p =
|
|
8152
|
+
const p = join13(dir, e.name);
|
|
7910
8153
|
if (e.isDirectory()) walk2(p);
|
|
7911
8154
|
else if (e.isFile()) {
|
|
7912
8155
|
h.update(p);
|
|
7913
8156
|
try {
|
|
7914
|
-
h.update(
|
|
8157
|
+
h.update(readFileSync13(p));
|
|
7915
8158
|
h.update(String(statSync4(p).mode));
|
|
7916
8159
|
} catch {
|
|
7917
8160
|
h.update("<unreadable>");
|
|
@@ -8026,7 +8269,7 @@ function baseIsAncestorOfHead(base, cwd) {
|
|
|
8026
8269
|
function materialize(cwd, dest) {
|
|
8027
8270
|
const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
8028
8271
|
for (const rel of listed) {
|
|
8029
|
-
const src =
|
|
8272
|
+
const src = join14(cwd, rel);
|
|
8030
8273
|
let st;
|
|
8031
8274
|
try {
|
|
8032
8275
|
st = statSync5(src);
|
|
@@ -8034,18 +8277,18 @@ function materialize(cwd, dest) {
|
|
|
8034
8277
|
continue;
|
|
8035
8278
|
}
|
|
8036
8279
|
if (!st.isFile()) continue;
|
|
8037
|
-
const out2 =
|
|
8280
|
+
const out2 = join14(dest, rel);
|
|
8038
8281
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8039
8282
|
dropSymlink(out2);
|
|
8040
8283
|
if (lstatSync3(src).isSymbolicLink()) {
|
|
8041
|
-
writeFileSync6(out2,
|
|
8284
|
+
writeFileSync6(out2, readFileSync14(src));
|
|
8042
8285
|
} else {
|
|
8043
8286
|
cpSync(src, out2, { dereference: true });
|
|
8044
8287
|
}
|
|
8045
8288
|
chmodSync2(out2, st.mode);
|
|
8046
8289
|
}
|
|
8047
8290
|
const nm = resolve4(cwd, "node_modules");
|
|
8048
|
-
if (existsSync10(nm)) symlinkSync(nm,
|
|
8291
|
+
if (existsSync10(nm)) symlinkSync(nm, join14(dest, "node_modules"), "dir");
|
|
8049
8292
|
}
|
|
8050
8293
|
function dropSymlink(p) {
|
|
8051
8294
|
try {
|
|
@@ -8078,11 +8321,11 @@ function overlayDigest(dest, paths) {
|
|
|
8078
8321
|
for (const rel of [...paths].sort()) {
|
|
8079
8322
|
h.update(rel);
|
|
8080
8323
|
h.update("\0");
|
|
8081
|
-
const p =
|
|
8324
|
+
const p = join14(dest, rel);
|
|
8082
8325
|
try {
|
|
8083
8326
|
const st = lstatSync3(p);
|
|
8084
8327
|
h.update(String(st.mode));
|
|
8085
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
8328
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync14(p));
|
|
8086
8329
|
} catch {
|
|
8087
8330
|
h.update("<gone>");
|
|
8088
8331
|
}
|
|
@@ -8105,7 +8348,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8105
8348
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
8106
8349
|
}
|
|
8107
8350
|
const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
8108
|
-
const out2 =
|
|
8351
|
+
const out2 = join14(dest, e.path);
|
|
8109
8352
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8110
8353
|
rmSync3(out2, { force: true });
|
|
8111
8354
|
if (e.mode === "120000") {
|
|
@@ -8120,7 +8363,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8120
8363
|
const inCopy = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
8121
8364
|
for (const rel of inCopy) {
|
|
8122
8365
|
if (!isOverlay(rel) || baseProtected.has(rel)) continue;
|
|
8123
|
-
rmSync3(
|
|
8366
|
+
rmSync3(join14(dest, rel), { force: true });
|
|
8124
8367
|
removed++;
|
|
8125
8368
|
}
|
|
8126
8369
|
return { restored, removed };
|
|
@@ -8162,8 +8405,8 @@ function suiteEnv(scratch) {
|
|
|
8162
8405
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
8163
8406
|
env[k] = v;
|
|
8164
8407
|
}
|
|
8165
|
-
const userRc =
|
|
8166
|
-
const globalRc =
|
|
8408
|
+
const userRc = join14(scratch, "npmrc-user");
|
|
8409
|
+
const globalRc = join14(scratch, "npmrc-global");
|
|
8167
8410
|
for (const f of [userRc, globalRc]) writeFileSync6(f, "", { mode: 292 });
|
|
8168
8411
|
env.npm_config_userconfig = userRc;
|
|
8169
8412
|
env.npm_config_globalconfig = globalRc;
|
|
@@ -8173,8 +8416,8 @@ function suiteEnv(scratch) {
|
|
|
8173
8416
|
}
|
|
8174
8417
|
function runSuite(dir, cmd, budgetSecs) {
|
|
8175
8418
|
const t0 = Date.now();
|
|
8176
|
-
const outDir = mkdtempSync2(
|
|
8177
|
-
const outFile =
|
|
8419
|
+
const outDir = mkdtempSync2(join14(tmpdir2(), "tw-verify-run-"));
|
|
8420
|
+
const outFile = join14(outDir, "result.json");
|
|
8178
8421
|
try {
|
|
8179
8422
|
spawnSync(process.execPath, ["-e", SUPERVISOR, cmd, String(budgetSecs * 1e3), outFile], {
|
|
8180
8423
|
cwd: dir,
|
|
@@ -8187,7 +8430,7 @@ function runSuite(dir, cmd, budgetSecs) {
|
|
|
8187
8430
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
8188
8431
|
let r;
|
|
8189
8432
|
try {
|
|
8190
|
-
r = JSON.parse(
|
|
8433
|
+
r = JSON.parse(readFileSync14(outFile, "utf8"));
|
|
8191
8434
|
} catch {
|
|
8192
8435
|
return { exit: null, secs };
|
|
8193
8436
|
}
|
|
@@ -8238,8 +8481,8 @@ function runVerify(opts) {
|
|
|
8238
8481
|
out2(`verify: cannot resolve base rev "${opts.base ?? "HEAD"}" \u2014 failing closed`);
|
|
8239
8482
|
return 2;
|
|
8240
8483
|
}
|
|
8241
|
-
const visRoot = mkdtempSync2(
|
|
8242
|
-
const visDir =
|
|
8484
|
+
const visRoot = mkdtempSync2(join14(tmpdir2(), "tw-verify-vis-"));
|
|
8485
|
+
const visDir = join14(visRoot, "t");
|
|
8243
8486
|
const cleanup = (dirs) => {
|
|
8244
8487
|
if (opts.keep) return;
|
|
8245
8488
|
for (const d of dirs) rmSync3(d, { recursive: true, force: true });
|
|
@@ -8267,8 +8510,8 @@ function runVerify(opts) {
|
|
|
8267
8510
|
out2("pristine run executes through it too. Failing closed, not open.");
|
|
8268
8511
|
return 2;
|
|
8269
8512
|
}
|
|
8270
|
-
const priRoot = mkdtempSync2(
|
|
8271
|
-
const priDir =
|
|
8513
|
+
const priRoot = mkdtempSync2(join14(tmpdir2(), "tw-verify-pri-"));
|
|
8514
|
+
const priDir = join14(priRoot, "t");
|
|
8272
8515
|
let restored = [];
|
|
8273
8516
|
let removedAdded = 0;
|
|
8274
8517
|
try {
|
|
@@ -8380,7 +8623,7 @@ function parseVerify(args) {
|
|
|
8380
8623
|
|
|
8381
8624
|
// src/cli/run.ts
|
|
8382
8625
|
import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
|
|
8383
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
8626
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync15, realpathSync as realpathSync3 } from "node:fs";
|
|
8384
8627
|
import { resolve as resolve5 } from "node:path";
|
|
8385
8628
|
var out = (s) => process.stdout.write(s + "\n");
|
|
8386
8629
|
var err = (s) => process.stderr.write(s + "\n");
|
|
@@ -8389,7 +8632,7 @@ function git3(args, cwd) {
|
|
|
8389
8632
|
}
|
|
8390
8633
|
function nowTicks() {
|
|
8391
8634
|
try {
|
|
8392
|
-
return Math.floor(parseFloat(
|
|
8635
|
+
return Math.floor(parseFloat(readFileSync15("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
8393
8636
|
} catch {
|
|
8394
8637
|
return Number.POSITIVE_INFINITY;
|
|
8395
8638
|
}
|
|
@@ -8419,7 +8662,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
8419
8662
|
}
|
|
8420
8663
|
if (held !== real && !held.startsWith(real + "/")) continue;
|
|
8421
8664
|
try {
|
|
8422
|
-
const stat =
|
|
8665
|
+
const stat = readFileSync15(`/proc/${pid}/stat`, "utf8");
|
|
8423
8666
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
8424
8667
|
if (Number.isFinite(startTicks) && startTicks >= spawnedAfterTicks) out2.push(n);
|
|
8425
8668
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.2",
|
|
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",
|