tamperward 2.10.1 → 2.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +295 -105
  2. 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 readFileSync2 } from "node:fs";
2664
- import { isAbsolute as isAbsolute3, join as join3, relative } from "node:path";
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 join2, posix } from "node:path";
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(readFileSync(join2(here, rel), "utf8"));
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 = join3(ctx.cwd, n);
4555
- if (existsSync2(p)) return parseDoc(readFileSync2(p, "utf8"));
4585
+ const p = join4(ctx.cwd, n);
4586
+ if (existsSync2(p)) return parseDoc(readFileSync3(p, "utf8"));
4556
4587
  } catch {
4557
4588
  }
4558
4589
  }
@@ -4673,7 +4704,7 @@ function huskyRuntimeWrite(path, content, ctx) {
4673
4704
  if (!ok) return false;
4674
4705
  if (!ctx?.cwd) return false;
4675
4706
  const root = path.slice(0, path.length - m[0].length + (m[0].startsWith("/") ? 1 : 0));
4676
- const entry = inspectRel(join3(ctx.cwd, root), ".husky/pre-commit");
4707
+ const entry = inspectRel(join4(ctx.cwd, root), ".husky/pre-commit");
4677
4708
  if (entry.kind !== "file") return false;
4678
4709
  const gate = textOf(entry);
4679
4710
  if (gate == null) return false;
@@ -4953,6 +4984,8 @@ import ts3 from "typescript";
4953
4984
  var JEST_DEFAULT_MATCH = ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"];
4954
4985
  var JEST_DEFAULT_IGNORE = ["/node_modules/"];
4955
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/**", "**/.*/**"];
4956
4989
  var VITEST_DEFAULT_EXCLUDE = [
4957
4990
  "**/node_modules/**",
4958
4991
  "**/dist/**",
@@ -4970,6 +5003,13 @@ var CANONICAL_SAMPLES = [
4970
5003
  "a.spec.js",
4971
5004
  "test/a.test.mjs"
4972
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
+ ];
4973
5013
  function keyName2(name) {
4974
5014
  if (ts3.isIdentifier(name) || ts3.isStringLiteral(name)) return name.text;
4975
5015
  return null;
@@ -5004,8 +5044,107 @@ function runnerOf(path, content) {
5004
5044
  if (/^jest\.config\./.test(base) || base === "package.json") return "jest";
5005
5045
  if (/^vitest\.(?:config|workspace)\./.test(base)) return "vitest";
5006
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
+ }
5007
5066
  return null;
5008
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 === "-p" || o.startsWith("-p=") || o.length > 2 && o.startsWith("-p")) {
5141
+ const v = o === "-p" ? opts[++j] ?? "" : o.startsWith("-p=") ? o.slice(3) : o.slice(2);
5142
+ if (v.startsWith("no:")) ignore.push({ pattern: "\0-p " + v, key: "-p" });
5143
+ } else if (o === "--collect-only" || o === "--co") ignore.push({ pattern: "**", key: o });
5144
+ }
5145
+ sel.ignore = ignore;
5146
+ return sel;
5147
+ }
5009
5148
  var isWorkspaceFile = (path) => /^vitest\.workspace\./.test(path.split("/").pop() ?? path);
5010
5149
  var empty = () => ({ include: null, regex: null, ignore: null, roots: null, base: null, projects: null, opaque: false, present: false });
5011
5150
  var stripRootDir = (g) => g.replace(/^<rootDir>\/?/, "");
@@ -5154,6 +5293,7 @@ function workspaceArray(sf) {
5154
5293
  }
5155
5294
  function parseSelection(src, runner, path = "") {
5156
5295
  let sel = empty();
5296
+ if (runner === "pytest") return pytestSelection(src);
5157
5297
  try {
5158
5298
  const sf = ts3.createSourceFile("cfg.ts", asExpression2(src), ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
5159
5299
  if (runner === "vitest" && isWorkspaceFile(path)) {
@@ -5202,10 +5342,14 @@ function predicate(sel, runner) {
5202
5342
  why: (p) => ps.length ? `no project selects it (${ps[0].why(p)})` : "the projects list is empty"
5203
5343
  };
5204
5344
  }
5205
- const includeList = sel.include ?? (runner === "jest" ? sel.regex ? null : JEST_DEFAULT_MATCH : VITEST_DEFAULT_INCLUDE);
5206
- const ignoreList = sel.ignore ?? (runner === "jest" ? JEST_DEFAULT_IGNORE : VITEST_DEFAULT_EXCLUDE).map((pattern) => ({ pattern, key: runner === "jest" ? "testPathIgnorePatterns" : "test.exclude" }));
5345
+ const includeList = sel.include ?? (runner === "jest" ? sel.regex ? null : JEST_DEFAULT_MATCH : runner === "pytest" ? PYTEST_DEFAULT_INCLUDE : VITEST_DEFAULT_INCLUDE);
5346
+ const ignoreList = sel.ignore ?? (runner === "jest" ? JEST_DEFAULT_IGNORE : runner === "pytest" ? PYTEST_DEFAULT_IGNORE : VITEST_DEFAULT_EXCLUDE).map((pattern) => ({
5347
+ pattern,
5348
+ key: runner === "jest" ? "testPathIgnorePatterns" : runner === "pytest" ? "norecursedirs" : "test.exclude"
5349
+ }));
5350
+ const blockWide = ignoreList.filter((e) => e.pattern.startsWith("\0"));
5207
5351
  const included = includeList ? globList2(includeList) : sel.regex ? regexes(sel.regex) : () => true;
5208
- const ignoredBy = (p) => ignoreList.find((e) => (runner === "jest" ? regexes([e.pattern]) : globs3([e.pattern]))(p));
5352
+ const ignoredBy = (p) => blockWide[0] ?? ignoreList.filter((e) => !e.pattern.startsWith("\0")).find((e) => (runner === "jest" ? regexes([e.pattern]) : globs3([e.pattern]))(p));
5209
5353
  const roots = sel.roots?.map((r) => r.replace(/^\.\//, "").replace(/\/+$/, "")).map((r) => r === "." ? "" : r) ?? null;
5210
5354
  const rebase = (p) => sel.base === null ? p : underDir(p, sel.base) ? p.slice(sel.base.length + 1) : null;
5211
5355
  const inRoots = (p) => roots === null || roots.some((r) => underDir(p, r));
@@ -5220,7 +5364,10 @@ function predicate(sel, runner) {
5220
5364
  if (r === null) return `${baseKey} no longer covers it (${JSON.stringify(sel.base)})`;
5221
5365
  if (!inRoots(r)) return `roots no longer covers it (${JSON.stringify(sel.roots)})`;
5222
5366
  const hit = ignoredBy(r);
5223
- if (hit) return `${hit.key === "exclude" ? "test.exclude" : hit.key} now matches it (${JSON.stringify(hit.pattern)})`;
5367
+ if (hit) {
5368
+ if (hit.pattern.startsWith("\0")) return `${hit.key} narrows the whole run (${JSON.stringify(hit.pattern.slice(1))})`;
5369
+ return `${hit.key === "exclude" ? "test.exclude" : hit.key} now matches it (${JSON.stringify(hit.pattern)})`;
5370
+ }
5224
5371
  const key2 = runner === "jest" ? sel.include ? "testMatch" : sel.regex ? "testRegex" : "testMatch (default)" : sel.include ? "test.include" : "test.include (default)";
5225
5372
  return `${key2} no longer matches it (${JSON.stringify(sel.include ?? sel.regex ?? includeList ?? [])})`;
5226
5373
  }
@@ -5370,12 +5517,14 @@ function significantLines(src, path) {
5370
5517
  return out2;
5371
5518
  }
5372
5519
  var OTHER_RUNNER_DIR = /(?:^|\/)(?:e2e|cypress|playwright|fixtures|__fixtures__|__mocks__)\//;
5373
- function runnerSamples(configPath, policy, ctx) {
5520
+ function runnerSamples(configPath, policy, ctx, runner) {
5521
+ const lang = runner === "pytest" ? "py" : "js";
5522
+ const fallback = runner === "pytest" ? PYTEST_CANONICAL_SAMPLES : CANONICAL_SAMPLES;
5374
5523
  const files = trackedFiles(ctx);
5375
- if (!files) return CANONICAL_SAMPLES;
5524
+ if (!files) return fallback;
5376
5525
  const root = configPath.includes("/") ? configPath.slice(0, configPath.lastIndexOf("/") + 1) : "";
5377
- const own = files.filter((f) => f.startsWith(root) && langOf(f) === "js" && isProtected(f, policy, "tests") && !OTHER_RUNNER_DIR.test(f)).map((f) => f.slice(root.length));
5378
- return own.length ? own : CANONICAL_SAMPLES;
5526
+ 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));
5527
+ return own.length ? own : fallback;
5379
5528
  }
5380
5529
  var WHOLE_SUITE_SCRIPT = /^test(?::ci)?$/;
5381
5530
  function excludesOtherRunner(flag, cmd) {
@@ -5496,7 +5645,9 @@ var testDeletion = {
5496
5645
  );
5497
5646
  }
5498
5647
  } else if (c.op !== "delete" && c.after != null && !isTest && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
5499
- for (const n of suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx))) {
5648
+ const rn = runnerOf(c.path, c.after);
5649
+ const readable = rn !== "pytest" || effectivePytestConfig(c.path, ctx);
5650
+ for (const n of readable ? suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx, rn)) : []) {
5500
5651
  out2.push(
5501
5652
  makeFinding(RULE7, policy, {
5502
5653
  file: c.path,
@@ -5606,9 +5757,48 @@ var testDeletion = {
5606
5757
  }
5607
5758
  }
5608
5759
  }
5760
+ out2.push(...pytestPrecedenceFindings(changes, policy, ctx));
5609
5761
  return out2;
5610
5762
  }
5611
5763
  };
5764
+ function pytestPrecedenceFindings(changes, policy, ctx) {
5765
+ const order = PYTEST_INI_ORDER;
5766
+ const touched = /* @__PURE__ */ new Map();
5767
+ for (const c of changes) {
5768
+ if (c.kind !== "file" || c.oldPath) continue;
5769
+ if (order.includes(c.path)) touched.set(c.path, c);
5770
+ }
5771
+ if (touched.size === 0) return [];
5772
+ const listing = trackedFiles(ctx);
5773
+ if (!listing) return [];
5774
+ const rootNames = new Set(listing.filter((f) => !f.includes("/")));
5775
+ const base = /* @__PURE__ */ new Map();
5776
+ const head = /* @__PURE__ */ new Map();
5777
+ for (const name of order) {
5778
+ const c = touched.get(name);
5779
+ if (c) {
5780
+ base.set(name, c.before);
5781
+ head.set(name, c.after);
5782
+ continue;
5783
+ }
5784
+ if (!rootNames.has(name)) continue;
5785
+ const src = trackedContent(name, ctx);
5786
+ base.set(name, src);
5787
+ head.set(name, src);
5788
+ }
5789
+ const was = effectivePytestFile(base);
5790
+ const now = effectivePytestFile(head);
5791
+ if (!was || !now || was.path === now.path) return [];
5792
+ const samples = runnerSamples(now.path, policy, ctx, "pytest");
5793
+ return suiteNarrowings(was.content, now.content, now.path, samples).map(
5794
+ (n) => makeFinding(RULE7, policy, {
5795
+ file: now.path,
5796
+ message: `The effective pytest config moved from ${was.path} to ${now.path}, and ${n.path} is no longer selected: ${n.reason}.`,
5797
+ evidence: `inifile precedence ${was.path} \u2192 ${now.path} drops ${n.path} from the suite`,
5798
+ 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."
5799
+ })
5800
+ );
5801
+ }
5612
5802
 
5613
5803
  // src/detectors/test-content-removal.ts
5614
5804
  import ts5 from "typescript";
@@ -5894,8 +6084,8 @@ function hasBlocking(findings) {
5894
6084
  }
5895
6085
 
5896
6086
  // src/policy-load.ts
5897
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
5898
- import { isAbsolute as isAbsolute4, join as join4, posix as posix2 } from "node:path";
6087
+ import { readFileSync as readFileSync4, existsSync as existsSync3 } from "node:fs";
6088
+ import { isAbsolute as isAbsolute4, join as join5, posix as posix2 } from "node:path";
5899
6089
  import { parse as parse2 } from "yaml";
5900
6090
  var PolicyError = class extends Error {
5901
6091
  };
@@ -6019,9 +6209,9 @@ function parseOrThrow(src, where) {
6019
6209
  return parsePolicy(raw, where);
6020
6210
  }
6021
6211
  function loadPolicy(cwd = process.cwd()) {
6022
- const path = join4(cwd, POLICY_FILE);
6212
+ const path = join5(cwd, POLICY_FILE);
6023
6213
  if (!existsSync3(path)) return defaultPolicy();
6024
- return parseOrThrow(readFileSync3(path, "utf8"), POLICY_FILE);
6214
+ return parseOrThrow(readFileSync4(path, "utf8"), POLICY_FILE);
6025
6215
  }
6026
6216
  function loadPolicyAt(rev, cwd) {
6027
6217
  const src = fileAt(rev, POLICY_FILE, { cwd });
@@ -6031,8 +6221,8 @@ function loadPolicyAt(rev, cwd) {
6031
6221
 
6032
6222
  // src/signoff.ts
6033
6223
  import { createHash } from "node:crypto";
6034
- import { appendFileSync, mkdirSync, readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
6035
- import { dirname as dirname2, join as join5 } from "node:path";
6224
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync5, existsSync as existsSync4 } from "node:fs";
6225
+ import { dirname as dirname2, join as join6 } from "node:path";
6036
6226
  var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
6037
6227
  function fingerprint(rule, file, evidence) {
6038
6228
  return createHash("sha256").update(`${rule}\0${file ?? ""}\0${evidence}`).digest("hex").slice(0, 16);
@@ -6043,13 +6233,13 @@ function ledgerPath(cwd, policy) {
6043
6233
  if (!ledgerInsideRepo(rel)) {
6044
6234
  throw new PolicyError(`signoff.ledger must be a relative path inside the repository, got ${JSON.stringify(rel)}`);
6045
6235
  }
6046
- return join5(cwd, rel);
6236
+ return join6(cwd, rel);
6047
6237
  }
6048
6238
  function readLedger(cwd, policy) {
6049
6239
  const p = ledgerPath(cwd, policy);
6050
6240
  if (!existsSync4(p)) return [];
6051
6241
  const out2 = [];
6052
- for (const line of readFileSync4(p, "utf8").split("\n")) {
6242
+ for (const line of readFileSync5(p, "utf8").split("\n")) {
6053
6243
  if (!line.trim()) continue;
6054
6244
  try {
6055
6245
  const e = JSON.parse(line);
@@ -6419,14 +6609,14 @@ function runCheck(opts) {
6419
6609
  }
6420
6610
 
6421
6611
  // src/cli/hook.ts
6422
- import { readFileSync as readFileSync9, appendFileSync as appendFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
6612
+ import { readFileSync as readFileSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
6423
6613
  import { createHash as createHash4 } from "node:crypto";
6424
6614
 
6425
6615
  // src/adapters/claude/changes.ts
6426
6616
  import { execFileSync as execFileSync3 } from "node:child_process";
6427
6617
  import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
6428
6618
  import { tmpdir } from "node:os";
6429
- import { isAbsolute as isAbsolute5, join as join6, relative as relative2, resolve as resolve2 } from "node:path";
6619
+ import { isAbsolute as isAbsolute5, join as join7, relative as relative2, resolve as resolve2 } from "node:path";
6430
6620
  function asStr(v) {
6431
6621
  return typeof v === "string" ? v : "";
6432
6622
  }
@@ -6451,11 +6641,11 @@ function synthFileChange(displayPath, before, after) {
6451
6641
  if (before === null) op = "add";
6452
6642
  else if (after === null) op = "delete";
6453
6643
  else op = "modify";
6454
- const dir = mkdtempSync(join6(tmpdir(), "hf-"));
6644
+ const dir = mkdtempSync(join7(tmpdir(), "hf-"));
6455
6645
  let raw = "";
6456
6646
  try {
6457
- const a = join6(dir, "a");
6458
- const b = join6(dir, "b");
6647
+ const a = join7(dir, "a");
6648
+ const b = join7(dir, "b");
6459
6649
  writeFileSync(a, before ?? "");
6460
6650
  writeFileSync(b, after ?? "");
6461
6651
  try {
@@ -6540,14 +6730,14 @@ function formatDenial(blocks) {
6540
6730
  }
6541
6731
 
6542
6732
  // src/session.ts
6543
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
6544
- import { dirname as dirname3, join as join7 } from "node:path";
6733
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
6734
+ import { dirname as dirname3, join as join8 } from "node:path";
6545
6735
  var UNSAFE = /[^A-Za-z0-9_-]/g;
6546
6736
  var SHA = /^[0-9a-f]{7,40}$/;
6547
6737
  function baselinePath(cwd, sessionId) {
6548
6738
  const gd = gitDir(cwd);
6549
6739
  if (!gd) return null;
6550
- return join7(gd, "tamperward", `session-${sessionId.replace(UNSAFE, "")}`);
6740
+ return join8(gd, "tamperward", `session-${sessionId.replace(UNSAFE, "")}`);
6551
6741
  }
6552
6742
  function turnBaseline(cwd, sessionId) {
6553
6743
  if (!sessionId) return null;
@@ -6555,7 +6745,7 @@ function turnBaseline(cwd, sessionId) {
6555
6745
  const p = baselinePath(cwd, sessionId);
6556
6746
  if (!p) return null;
6557
6747
  if (existsSync5(p)) {
6558
- const v = readFileSync5(p, "utf8").trim();
6748
+ const v = readFileSync6(p, "utf8").trim();
6559
6749
  if (SHA.test(v)) return v;
6560
6750
  }
6561
6751
  const head = headSha(cwd);
@@ -6581,8 +6771,8 @@ function advanceTurnBaseline(cwd, sessionId) {
6581
6771
 
6582
6772
  // src/effect.ts
6583
6773
  import { createHash as createHash2 } from "node:crypto";
6584
- import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
6585
- import { isAbsolute as isAbsolute6, join as join8 } from "node:path";
6774
+ import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync7, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
6775
+ import { isAbsolute as isAbsolute6, join as join9 } from "node:path";
6586
6776
  import { execFileSync as execFileSync4 } from "node:child_process";
6587
6777
  var UNSAFE2 = /[^A-Za-z0-9._-]/g;
6588
6778
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
@@ -6594,7 +6784,7 @@ function gitDir2(cwd) {
6594
6784
  stdio: ["ignore", "pipe", "pipe"]
6595
6785
  }).trim();
6596
6786
  if (!d) return null;
6597
- return isAbsolute6(d) ? d : join8(cwd, d);
6787
+ return isAbsolute6(d) ? d : join9(cwd, d);
6598
6788
  } catch {
6599
6789
  return null;
6600
6790
  }
@@ -6603,8 +6793,8 @@ function statePath(cwd, sessionId, kind) {
6603
6793
  if (!sessionId) return null;
6604
6794
  const gd = gitDir2(cwd);
6605
6795
  if (!gd) return null;
6606
- const dir = join8(gd, "tamperward");
6607
- return join8(dir, `${kind}-${sessionId.replace(UNSAFE2, "")}.json`);
6796
+ const dir = join9(gd, "tamperward");
6797
+ return join9(dir, `${kind}-${sessionId.replace(UNSAFE2, "")}.json`);
6608
6798
  }
6609
6799
  function ptreePath(cwd, sessionId) {
6610
6800
  return statePath(cwd, sessionId, "ptree");
@@ -6633,7 +6823,7 @@ function snapshotProtected(cwd, policy, prev) {
6633
6823
  } catch {
6634
6824
  }
6635
6825
  const walk2 = (rel) => {
6636
- const abs2 = rel ? join8(cwd, rel) : cwd;
6826
+ const abs2 = rel ? join9(cwd, rel) : cwd;
6637
6827
  let names;
6638
6828
  try {
6639
6829
  names = readdirSync(abs2);
@@ -6646,7 +6836,7 @@ function snapshotProtected(cwd, policy, prev) {
6646
6836
  if (pruned.has(r)) continue;
6647
6837
  let st;
6648
6838
  try {
6649
- st = lstatSync2(join8(cwd, r));
6839
+ st = lstatSync2(join9(cwd, r));
6650
6840
  } catch {
6651
6841
  continue;
6652
6842
  }
@@ -6675,7 +6865,7 @@ function driftBetween(expected, current) {
6675
6865
  function loadTree(p) {
6676
6866
  if (!p || !existsSync6(p)) return null;
6677
6867
  try {
6678
- return JSON.parse(readFileSync6(p, "utf8"));
6868
+ return JSON.parse(readFileSync7(p, "utf8"));
6679
6869
  } catch {
6680
6870
  return null;
6681
6871
  }
@@ -6683,7 +6873,7 @@ function loadTree(p) {
6683
6873
  function saveTree(p, tree) {
6684
6874
  if (!p) return;
6685
6875
  try {
6686
- mkdirSync3(join8(p, ".."), { recursive: true });
6876
+ mkdirSync3(join9(p, ".."), { recursive: true });
6687
6877
  const tmp = `${p}.${process.pid}.tmp`;
6688
6878
  writeFileSync3(tmp, JSON.stringify(tree));
6689
6879
  renameSync(tmp, p);
@@ -6715,7 +6905,7 @@ function dropTurnTree(cwd, sessionId) {
6715
6905
 
6716
6906
  // src/cli/watch.ts
6717
6907
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync, watch } from "node:fs";
6718
- import { join as join9 } from "node:path";
6908
+ import { join as join10 } from "node:path";
6719
6909
  import { createHash as createHash3 } from "node:crypto";
6720
6910
  var SKIP2 = /(^|\/)(\.git|node_modules)(\/|$)/;
6721
6911
  function snap(abs2) {
@@ -6727,7 +6917,7 @@ function snap(abs2) {
6727
6917
  }
6728
6918
  function defaultEventLog(cwd) {
6729
6919
  if (process.env.TAMPERWARD_FSEVENTS) return process.env.TAMPERWARD_FSEVENTS;
6730
- return join9(gitDir(cwd) ?? join9(cwd, ".git"), "tamperward", "fsevents.jsonl");
6920
+ return join10(gitDir(cwd) ?? join10(cwd, ".git"), "tamperward", "fsevents.jsonl");
6731
6921
  }
6732
6922
  function watchTree(dir, cb) {
6733
6923
  if (process.env.TAMPERWARD_WATCH_NO_RECURSIVE !== "1") {
@@ -6744,12 +6934,12 @@ function watchTree(dir, cb) {
6744
6934
  if (watchers.has(rel) || SKIP2.test(rel + "/")) return;
6745
6935
  let w;
6746
6936
  try {
6747
- w = watch(rel ? join9(dir, rel) : dir, (kind, fname) => {
6937
+ w = watch(rel ? join10(dir, rel) : dir, (kind, fname) => {
6748
6938
  if (!fname) return;
6749
6939
  const child = rel ? `${rel}/${String(fname)}` : String(fname);
6750
6940
  cb(kind, child);
6751
6941
  try {
6752
- if (statSync(join9(dir, child)).isDirectory()) addDir(child);
6942
+ if (statSync(join10(dir, child)).isDirectory()) addDir(child);
6753
6943
  } catch {
6754
6944
  }
6755
6945
  });
@@ -6762,7 +6952,7 @@ function watchTree(dir, cb) {
6762
6952
  addDir(rel);
6763
6953
  let names = [];
6764
6954
  try {
6765
- names = readdirSync2(rel ? join9(dir, rel) : dir);
6955
+ names = readdirSync2(rel ? join10(dir, rel) : dir);
6766
6956
  } catch {
6767
6957
  return;
6768
6958
  }
@@ -6770,7 +6960,7 @@ function watchTree(dir, cb) {
6770
6960
  const child = rel ? `${rel}/${name}` : name;
6771
6961
  if (SKIP2.test(child + "/")) continue;
6772
6962
  try {
6773
- if (statSync(join9(dir, child)).isDirectory()) walk2(child);
6963
+ if (statSync(join10(dir, child)).isDirectory()) walk2(child);
6774
6964
  } catch {
6775
6965
  }
6776
6966
  }
@@ -6782,13 +6972,13 @@ function watchTree(dir, cb) {
6782
6972
  }
6783
6973
  function startWatcher(dir, log, policy) {
6784
6974
  try {
6785
- mkdirSync4(join9(log, ".."), { recursive: true });
6975
+ mkdirSync4(join10(log, ".."), { recursive: true });
6786
6976
  } catch {
6787
6977
  }
6788
6978
  const last = /* @__PURE__ */ new Map();
6789
6979
  return watchTree(dir, (kind, rel) => {
6790
6980
  if (SKIP2.test(rel) || !isProtected(rel, policy)) return;
6791
- const s = snap(join9(dir, rel));
6981
+ const s = snap(join10(dir, rel));
6792
6982
  const key2 = `${s.hash}:${s.mode}`;
6793
6983
  if (last.get(rel) === key2) return;
6794
6984
  last.set(rel, key2);
@@ -6815,7 +7005,7 @@ function runWatch(args) {
6815
7005
  }
6816
7006
 
6817
7007
  // src/detectors/fs-events.ts
6818
- import { readFileSync as readFileSync8, statSync as statSync2 } from "node:fs";
7008
+ import { readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
6819
7009
  var RULE11 = "transient-protected-mutation";
6820
7010
  function readEvents(log, offset) {
6821
7011
  let size = 0;
@@ -6825,7 +7015,7 @@ function readEvents(log, offset) {
6825
7015
  return { events: [], newOffset: offset };
6826
7016
  }
6827
7017
  if (size <= offset) return { events: [], newOffset: offset >= 0 ? Math.min(offset, size) : 0 };
6828
- const raw = readFileSync8(log, "utf8").slice(offset);
7018
+ const raw = readFileSync9(log, "utf8").slice(offset);
6829
7019
  const events = [];
6830
7020
  for (const line of raw.split("\n")) {
6831
7021
  if (!line.trim()) continue;
@@ -6878,7 +7068,7 @@ var HookInputError = class extends Error {
6878
7068
  };
6879
7069
  function readStdin() {
6880
7070
  try {
6881
- return readFileSync9(0, "utf8");
7071
+ return readFileSync10(0, "utf8");
6882
7072
  } catch (e) {
6883
7073
  throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
6884
7074
  }
@@ -7134,7 +7324,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
7134
7324
  if (!existsSync7(log)) return none;
7135
7325
  let offset = 0;
7136
7326
  if (cp && existsSync7(cp)) {
7137
- const n = Number(readFileSync9(cp, "utf8"));
7327
+ const n = Number(readFileSync10(cp, "utf8"));
7138
7328
  offset = Number.isFinite(n) ? n : 0;
7139
7329
  }
7140
7330
  const { events, newOffset } = readEvents(log, offset);
@@ -7287,8 +7477,8 @@ Honored at LOCAL pre-commit only. The agent-layer hook ignores this file; CI req
7287
7477
  // src/cli/init.ts
7288
7478
  import { execFileSync as execFileSync5 } from "node:child_process";
7289
7479
  import { createHash as createHash5 } from "node:crypto";
7290
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5, chmodSync, statSync as statSync3 } from "node:fs";
7291
- import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute7, join as join10, relative as relative3, resolve as resolve3 } from "node:path";
7480
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, chmodSync, statSync as statSync3 } from "node:fs";
7481
+ import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute7, join as join11, relative as relative3, resolve as resolve3 } from "node:path";
7292
7482
  var errText3 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
7293
7483
  var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7294
7484
  function gitConfig(cwd, key2) {
@@ -7464,7 +7654,7 @@ function hooksShapeError(hooks) {
7464
7654
  return null;
7465
7655
  }
7466
7656
  function planPolicy(cwd) {
7467
- const path = join10(cwd, POLICY_FILE);
7657
+ const path = join11(cwd, POLICY_FILE);
7468
7658
  if (existsSync8(path)) {
7469
7659
  try {
7470
7660
  loadPolicy(cwd);
@@ -7483,12 +7673,12 @@ function planPolicy(cwd) {
7483
7673
  }
7484
7674
  function planClaudeHooks(cwd) {
7485
7675
  const rel = ".claude/settings.json";
7486
- const path = join10(cwd, rel);
7676
+ const path = join11(cwd, rel);
7487
7677
  let settings = {};
7488
7678
  if (existsSync8(path)) {
7489
7679
  let parsed;
7490
7680
  try {
7491
- parsed = JSON.parse(readFileSync10(path, "utf8"));
7681
+ parsed = JSON.parse(readFileSync11(path, "utf8"));
7492
7682
  } catch {
7493
7683
  return {
7494
7684
  item: "agent",
@@ -7584,7 +7774,7 @@ function hooksDir(cwd) {
7584
7774
  if (out2) return resolve3(cwd, out2);
7585
7775
  } catch {
7586
7776
  }
7587
- return existsSync8(join10(cwd, ".git")) ? join10(cwd, ".git", "hooks") : null;
7777
+ return existsSync8(join11(cwd, ".git")) ? join11(cwd, ".git", "hooks") : null;
7588
7778
  }
7589
7779
  function display(cwd, path) {
7590
7780
  const rel = relative3(cwd, path);
@@ -7611,30 +7801,30 @@ function planPreCommit(cwd) {
7611
7801
  const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
7612
7802
  let path;
7613
7803
  let note;
7614
- if (existsSync8(join10(cwd, ".husky"))) {
7615
- path = join10(cwd, ".husky", "pre-commit");
7804
+ if (existsSync8(join11(cwd, ".husky"))) {
7805
+ path = join11(cwd, ".husky", "pre-commit");
7616
7806
  note = "husky";
7617
7807
  } else {
7618
- const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(join10(cwd, c))));
7808
+ const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(join11(cwd, c))));
7619
7809
  if (managed) {
7620
7810
  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}`);
7621
7811
  }
7622
7812
  const dir = hooksDir(cwd);
7623
7813
  if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
7624
7814
  if (basename2(dir) === "_" && basename2(dirname4(dir)) === ".husky") {
7625
- path = join10(dirname4(dir), "pre-commit");
7815
+ path = join11(dirname4(dir), "pre-commit");
7626
7816
  note = "husky";
7627
7817
  } else {
7628
- path = join10(dir, "pre-commit");
7818
+ path = join11(dir, "pre-commit");
7629
7819
  const rel2 = display(cwd, dir);
7630
- note = rel2 === join10(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
7820
+ 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)`;
7631
7821
  }
7632
7822
  }
7633
7823
  const rel = display(cwd, path);
7634
7824
  if (existsSync8(path) && !statSync3(path).isFile()) {
7635
7825
  return { item: "pre-commit", path: rel, status: "error", detail: "exists but is not a regular file \u2014 refusing to write" };
7636
7826
  }
7637
- const existing = existsSync8(path) ? readFileSync10(path, "utf8") : null;
7827
+ const existing = existsSync8(path) ? readFileSync11(path, "utf8") : null;
7638
7828
  if (existing === null) {
7639
7829
  return {
7640
7830
  item: "pre-commit",
@@ -7723,10 +7913,10 @@ ${block.join("\n")}
7723
7913
  function planCodeowners(cwd) {
7724
7914
  const rel = ".github/CODEOWNERS";
7725
7915
  const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
7726
- (r) => existsSync8(join10(cwd, r))
7916
+ (r) => existsSync8(join11(cwd, r))
7727
7917
  );
7728
- const path = join10(cwd, existingRel ?? rel);
7729
- const existing = existingRel ? readFileSync10(path, "utf8") : null;
7918
+ const path = join11(cwd, existingRel ?? rel);
7919
+ const existing = existingRel ? readFileSync11(path, "utf8") : null;
7730
7920
  const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
7731
7921
  if (missing.length === 0) {
7732
7922
  return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
@@ -7759,7 +7949,7 @@ ${CODEOWNERS_MARK}
7759
7949
  }
7760
7950
  function planWorkflow(cwd, force) {
7761
7951
  const rel = ".github/workflows/tamperward.yml";
7762
- const path = join10(cwd, rel);
7952
+ const path = join11(cwd, rel);
7763
7953
  const write = () => {
7764
7954
  mkdirSync5(dirname4(path), { recursive: true });
7765
7955
  writeFileSync5(path, workflowFile(WORKFLOW_CONTENT));
@@ -7775,7 +7965,7 @@ function planWorkflow(cwd, force) {
7775
7965
  }
7776
7966
  let src;
7777
7967
  try {
7778
- src = readFileSync10(path, "utf8");
7968
+ src = readFileSync11(path, "utf8");
7779
7969
  } catch {
7780
7970
  return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
7781
7971
  }
@@ -7874,15 +8064,15 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
7874
8064
  // src/cli/verify.ts
7875
8065
  import { execFileSync as execFileSync8, spawnSync } from "node:child_process";
7876
8066
  import { createHash as createHash8 } from "node:crypto";
7877
- import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13, readlinkSync as readlinkSync3, rmSync as rmSync3, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
8067
+ 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";
7878
8068
  import { tmpdir as tmpdir2 } from "node:os";
7879
- import { dirname as dirname5, join as join13, resolve as resolve4 } from "node:path";
8069
+ import { dirname as dirname5, join as join14, resolve as resolve4 } from "node:path";
7880
8070
 
7881
8071
  // src/git/trusted.ts
7882
8072
  import { execFileSync as execFileSync6 } from "node:child_process";
7883
8073
  import { createHash as createHash6 } from "node:crypto";
7884
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "node:fs";
7885
- import { isAbsolute as isAbsolute8, join as join11 } from "node:path";
8074
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "node:fs";
8075
+ import { isAbsolute as isAbsolute8, join as join12 } from "node:path";
7886
8076
  function trustedGitEnv() {
7887
8077
  return { ...process.env, GIT_NO_REPLACE_OBJECTS: "1" };
7888
8078
  }
@@ -7895,7 +8085,7 @@ function gitDirOf(cwd) {
7895
8085
  env: trustedGitEnv()
7896
8086
  }).trim();
7897
8087
  if (!d) return null;
7898
- return isAbsolute8(d) ? d : join11(cwd, d);
8088
+ return isAbsolute8(d) ? d : join12(cwd, d);
7899
8089
  } catch {
7900
8090
  return null;
7901
8091
  }
@@ -7920,9 +8110,9 @@ function objectRewriteState(cwd) {
7920
8110
  parts.push(`${rel}:<no-git-dir>`);
7921
8111
  continue;
7922
8112
  }
7923
- const p = join11(dir, rel);
8113
+ const p = join12(dir, rel);
7924
8114
  try {
7925
- parts.push(`${rel}:` + (existsSync9(p) ? readFileSync11(p, "utf8").trim() : ""));
8115
+ parts.push(`${rel}:` + (existsSync9(p) ? readFileSync12(p, "utf8").trim() : ""));
7926
8116
  } catch {
7927
8117
  parts.push(`${rel}:<unreadable>`);
7928
8118
  }
@@ -7933,8 +8123,8 @@ function objectRewriteState(cwd) {
7933
8123
  // src/fingerprint.ts
7934
8124
  import { execFileSync as execFileSync7 } from "node:child_process";
7935
8125
  import { createHash as createHash7 } from "node:crypto";
7936
- import { readdirSync as readdirSync3, readFileSync as readFileSync12, readlinkSync as readlinkSync2, statSync as statSync4 } from "node:fs";
7937
- import { join as join12 } from "node:path";
8126
+ import { readdirSync as readdirSync3, readFileSync as readFileSync13, readlinkSync as readlinkSync2, statSync as statSync4 } from "node:fs";
8127
+ import { join as join13 } from "node:path";
7938
8128
  function treeFingerprint(cwd) {
7939
8129
  const files = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
7940
8130
  cwd,
@@ -7952,7 +8142,7 @@ function treeFingerprint(cwd) {
7952
8142
  return h.digest("hex");
7953
8143
  }
7954
8144
  function depsFingerprint(cwd) {
7955
- const root = join12(cwd, "node_modules");
8145
+ const root = join13(cwd, "node_modules");
7956
8146
  const h = createHash7("sha256");
7957
8147
  const walk2 = (dir) => {
7958
8148
  let entries;
@@ -7962,12 +8152,12 @@ function depsFingerprint(cwd) {
7962
8152
  return;
7963
8153
  }
7964
8154
  for (const e of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
7965
- const p = join12(dir, e.name);
8155
+ const p = join13(dir, e.name);
7966
8156
  if (e.isDirectory()) walk2(p);
7967
8157
  else if (e.isFile()) {
7968
8158
  h.update(p);
7969
8159
  try {
7970
- h.update(readFileSync12(p));
8160
+ h.update(readFileSync13(p));
7971
8161
  h.update(String(statSync4(p).mode));
7972
8162
  } catch {
7973
8163
  h.update("<unreadable>");
@@ -8082,7 +8272,7 @@ function baseIsAncestorOfHead(base, cwd) {
8082
8272
  function materialize(cwd, dest) {
8083
8273
  const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
8084
8274
  for (const rel of listed) {
8085
- const src = join13(cwd, rel);
8275
+ const src = join14(cwd, rel);
8086
8276
  let st;
8087
8277
  try {
8088
8278
  st = statSync5(src);
@@ -8090,18 +8280,18 @@ function materialize(cwd, dest) {
8090
8280
  continue;
8091
8281
  }
8092
8282
  if (!st.isFile()) continue;
8093
- const out2 = join13(dest, rel);
8283
+ const out2 = join14(dest, rel);
8094
8284
  mkdirSync6(dirname5(out2), { recursive: true });
8095
8285
  dropSymlink(out2);
8096
8286
  if (lstatSync3(src).isSymbolicLink()) {
8097
- writeFileSync6(out2, readFileSync13(src));
8287
+ writeFileSync6(out2, readFileSync14(src));
8098
8288
  } else {
8099
8289
  cpSync(src, out2, { dereference: true });
8100
8290
  }
8101
8291
  chmodSync2(out2, st.mode);
8102
8292
  }
8103
8293
  const nm = resolve4(cwd, "node_modules");
8104
- if (existsSync10(nm)) symlinkSync(nm, join13(dest, "node_modules"), "dir");
8294
+ if (existsSync10(nm)) symlinkSync(nm, join14(dest, "node_modules"), "dir");
8105
8295
  }
8106
8296
  function dropSymlink(p) {
8107
8297
  try {
@@ -8134,11 +8324,11 @@ function overlayDigest(dest, paths) {
8134
8324
  for (const rel of [...paths].sort()) {
8135
8325
  h.update(rel);
8136
8326
  h.update("\0");
8137
- const p = join13(dest, rel);
8327
+ const p = join14(dest, rel);
8138
8328
  try {
8139
8329
  const st = lstatSync3(p);
8140
8330
  h.update(String(st.mode));
8141
- h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync13(p));
8331
+ h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync14(p));
8142
8332
  } catch {
8143
8333
  h.update("<gone>");
8144
8334
  }
@@ -8161,7 +8351,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
8161
8351
  throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
8162
8352
  }
8163
8353
  const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
8164
- const out2 = join13(dest, e.path);
8354
+ const out2 = join14(dest, e.path);
8165
8355
  mkdirSync6(dirname5(out2), { recursive: true });
8166
8356
  rmSync3(out2, { force: true });
8167
8357
  if (e.mode === "120000") {
@@ -8176,7 +8366,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
8176
8366
  const inCopy = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
8177
8367
  for (const rel of inCopy) {
8178
8368
  if (!isOverlay(rel) || baseProtected.has(rel)) continue;
8179
- rmSync3(join13(dest, rel), { force: true });
8369
+ rmSync3(join14(dest, rel), { force: true });
8180
8370
  removed++;
8181
8371
  }
8182
8372
  return { restored, removed };
@@ -8218,8 +8408,8 @@ function suiteEnv(scratch) {
8218
8408
  if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
8219
8409
  env[k] = v;
8220
8410
  }
8221
- const userRc = join13(scratch, "npmrc-user");
8222
- const globalRc = join13(scratch, "npmrc-global");
8411
+ const userRc = join14(scratch, "npmrc-user");
8412
+ const globalRc = join14(scratch, "npmrc-global");
8223
8413
  for (const f of [userRc, globalRc]) writeFileSync6(f, "", { mode: 292 });
8224
8414
  env.npm_config_userconfig = userRc;
8225
8415
  env.npm_config_globalconfig = globalRc;
@@ -8229,8 +8419,8 @@ function suiteEnv(scratch) {
8229
8419
  }
8230
8420
  function runSuite(dir, cmd, budgetSecs) {
8231
8421
  const t0 = Date.now();
8232
- const outDir = mkdtempSync2(join13(tmpdir2(), "tw-verify-run-"));
8233
- const outFile = join13(outDir, "result.json");
8422
+ const outDir = mkdtempSync2(join14(tmpdir2(), "tw-verify-run-"));
8423
+ const outFile = join14(outDir, "result.json");
8234
8424
  try {
8235
8425
  spawnSync(process.execPath, ["-e", SUPERVISOR, cmd, String(budgetSecs * 1e3), outFile], {
8236
8426
  cwd: dir,
@@ -8243,7 +8433,7 @@ function runSuite(dir, cmd, budgetSecs) {
8243
8433
  const secs = Math.round((Date.now() - t0) / 1e3);
8244
8434
  let r;
8245
8435
  try {
8246
- r = JSON.parse(readFileSync13(outFile, "utf8"));
8436
+ r = JSON.parse(readFileSync14(outFile, "utf8"));
8247
8437
  } catch {
8248
8438
  return { exit: null, secs };
8249
8439
  }
@@ -8294,8 +8484,8 @@ function runVerify(opts) {
8294
8484
  out2(`verify: cannot resolve base rev "${opts.base ?? "HEAD"}" \u2014 failing closed`);
8295
8485
  return 2;
8296
8486
  }
8297
- const visRoot = mkdtempSync2(join13(tmpdir2(), "tw-verify-vis-"));
8298
- const visDir = join13(visRoot, "t");
8487
+ const visRoot = mkdtempSync2(join14(tmpdir2(), "tw-verify-vis-"));
8488
+ const visDir = join14(visRoot, "t");
8299
8489
  const cleanup = (dirs) => {
8300
8490
  if (opts.keep) return;
8301
8491
  for (const d of dirs) rmSync3(d, { recursive: true, force: true });
@@ -8323,8 +8513,8 @@ function runVerify(opts) {
8323
8513
  out2("pristine run executes through it too. Failing closed, not open.");
8324
8514
  return 2;
8325
8515
  }
8326
- const priRoot = mkdtempSync2(join13(tmpdir2(), "tw-verify-pri-"));
8327
- const priDir = join13(priRoot, "t");
8516
+ const priRoot = mkdtempSync2(join14(tmpdir2(), "tw-verify-pri-"));
8517
+ const priDir = join14(priRoot, "t");
8328
8518
  let restored = [];
8329
8519
  let removedAdded = 0;
8330
8520
  try {
@@ -8436,7 +8626,7 @@ function parseVerify(args) {
8436
8626
 
8437
8627
  // src/cli/run.ts
8438
8628
  import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
8439
- import { readdirSync as readdirSync4, readFileSync as readFileSync14, realpathSync as realpathSync3 } from "node:fs";
8629
+ import { readdirSync as readdirSync4, readFileSync as readFileSync15, realpathSync as realpathSync3 } from "node:fs";
8440
8630
  import { resolve as resolve5 } from "node:path";
8441
8631
  var out = (s) => process.stdout.write(s + "\n");
8442
8632
  var err = (s) => process.stderr.write(s + "\n");
@@ -8445,7 +8635,7 @@ function git3(args, cwd) {
8445
8635
  }
8446
8636
  function nowTicks() {
8447
8637
  try {
8448
- return Math.floor(parseFloat(readFileSync14("/proc/uptime", "utf8").split(" ")[0]) * 100);
8638
+ return Math.floor(parseFloat(readFileSync15("/proc/uptime", "utf8").split(" ")[0]) * 100);
8449
8639
  } catch {
8450
8640
  return Number.POSITIVE_INFINITY;
8451
8641
  }
@@ -8475,7 +8665,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
8475
8665
  }
8476
8666
  if (held !== real && !held.startsWith(real + "/")) continue;
8477
8667
  try {
8478
- const stat = readFileSync14(`/proc/${pid}/stat`, "utf8");
8668
+ const stat = readFileSync15(`/proc/${pid}/stat`, "utf8");
8479
8669
  const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
8480
8670
  if (Number.isFinite(startTicks) && startTicks >= spawnedAfterTicks) out2.push(n);
8481
8671
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.10.1",
3
+ "version": "2.10.3",
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",