tamperward 2.5.0 → 2.5.1

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 +150 -54
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -330,26 +330,52 @@ function diffWorktree(opts = {}) {
330
330
  (c) => enrich(c, (p) => blobAt("HEAD", p, opts.cwd), (p) => fromDisk(p, opts.cwd))
331
331
  );
332
332
  }
333
+ function addOf(rel, cwd) {
334
+ const after = fromDisk(rel, cwd);
335
+ return {
336
+ kind: "file",
337
+ path: rel,
338
+ oldPath: null,
339
+ op: "add",
340
+ before: null,
341
+ after,
342
+ binary: false,
343
+ hunks: after == null ? [] : addHunks(after)
344
+ };
345
+ }
333
346
  function untrackedAdds(opts = {}, keep, exclude) {
334
347
  const others = git(["ls-files", "--others", "--exclude-standard", "-z"], opts.cwd).split("\0").filter(Boolean);
335
- return others.filter((rel) => !exclude?.has(rel) && (!keep || keep(rel))).map((rel) => {
336
- const after = fromDisk(rel, opts.cwd);
337
- return {
338
- kind: "file",
339
- path: rel,
340
- oldPath: null,
341
- op: "add",
342
- before: null,
343
- after,
344
- binary: false,
345
- hunks: after == null ? [] : addHunks(after)
346
- };
347
- });
348
+ return others.filter((rel) => !exclude?.has(rel) && (!keep || keep(rel))).map((rel) => addOf(rel, opts.cwd));
349
+ }
350
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
351
+ var IGNORED_LS = ["ls-files", "--others", "--ignored", "--exclude-standard", "-z"];
352
+ function underSkippedDir(rel) {
353
+ return rel.split("/").some((seg) => SKIP_DIRS.has(seg));
348
354
  }
349
- function diffWorktreeWithUntracked(opts = {}) {
355
+ function ignoredPaths(opts = {}, keep) {
356
+ const files = [];
357
+ const dirs = [];
358
+ for (const entry of git([...IGNORED_LS, "--directory"], opts.cwd).split("\0").filter(Boolean)) {
359
+ if (underSkippedDir(entry)) continue;
360
+ if (entry.endsWith("/")) dirs.push(entry);
361
+ else files.push(entry);
362
+ }
363
+ if (dirs.length > 0) {
364
+ const expanded = git([...IGNORED_LS, "--", ...dirs.map((d) => `:(literal)${d}`)], opts.cwd).split("\0").filter(Boolean);
365
+ for (const rel of expanded) if (!underSkippedDir(rel)) files.push(rel);
366
+ }
367
+ return files.filter(keep);
368
+ }
369
+ function ignoredAdds(opts = {}, keep, exclude) {
370
+ return ignoredPaths(opts, keep).filter((rel) => !exclude?.has(rel)).map((rel) => addOf(rel, opts.cwd));
371
+ }
372
+ function diffWorktreeWithUntracked(opts = {}, keepIgnored) {
350
373
  const tracked = diffWorktree(opts);
351
374
  const seen = new Set(tracked.filter((c) => c.kind === "file").map((c) => c.path));
352
- return [...tracked, ...untrackedAdds(opts, void 0, seen)];
375
+ const untracked = untrackedAdds(opts, void 0, seen);
376
+ for (const c of untracked) if (c.kind === "file") seen.add(c.path);
377
+ const ignored = keepIgnored ? ignoredAdds(opts, keepIgnored, seen) : [];
378
+ return [...tracked, ...untracked, ...ignored];
353
379
  }
354
380
  function hiddenTrackedPaths(opts = {}) {
355
381
  const out2 = [];
@@ -1985,7 +2011,7 @@ var ciTampering = {
1985
2011
 
1986
2012
  // src/detectors/hook-tampering.ts
1987
2013
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1988
- import { join as join2 } from "node:path";
2014
+ import { isAbsolute, join as join2, relative } from "node:path";
1989
2015
 
1990
2016
  // src/detectors/policy-diff.ts
1991
2017
  import { parse } from "yaml";
@@ -2495,9 +2521,22 @@ function chmodDropsExec(toks) {
2495
2521
  return false;
2496
2522
  }
2497
2523
  var WRAPPERS = /^(?:sudo|command|exec|time|nice|env|[A-Za-z_][A-Za-z0-9_]*=\S*)$/;
2524
+ var REDIRECT_TARGET = /(?:^|\s)\d*>{1,2}\|?\s*(\S+)/g;
2525
+ var hookPath = (t) => t.replace(/^["']|["']$/g, "").replace(/^\.\//, "");
2526
+ function shellHookTarget(seg, toks, policy) {
2527
+ for (const m of seg.matchAll(REDIRECT_TARGET)) {
2528
+ const t = hookPath(m[1]);
2529
+ if (isProtected(t, policy, "hooks")) return t;
2530
+ }
2531
+ for (const tok of toks) {
2532
+ const t = hookPath(tok.replace(/^of=/, ""));
2533
+ if (isProtected(t, policy, "hooks")) return t;
2534
+ }
2535
+ return null;
2536
+ }
2498
2537
  function shellWritesHook(seg, toks, policy) {
2499
2538
  const hook = (t) => isProtected(t.replace(/^\.\//, ""), policy, "hooks");
2500
- for (const m of seg.matchAll(/(?:^|\s)\d*>{1,2}\|?\s*(\S+)/g)) {
2539
+ for (const m of seg.matchAll(REDIRECT_TARGET)) {
2501
2540
  if (hook(m[1].replace(/^["']|["']$/g, ""))) return "a redirect empties or rewrites a protected hook";
2502
2541
  }
2503
2542
  let at = 0;
@@ -2708,14 +2747,24 @@ function configFindings(c, policy) {
2708
2747
  function updateIndexHiding(toks, policy) {
2709
2748
  if (!toks.some((t) => t === "git" || t.endsWith("/git")) || !toks.includes("update-index")) return null;
2710
2749
  const hide = toks.find((t) => /^--(?:skip-worktree|assume-unchanged)$/.test(t));
2711
- if (hide && toks.some((t) => isProtected(t, policy))) {
2712
- return `git update-index ${hide} hides a protected file from every git diff \u2014 edits to it no longer reach the sweep or the pre-commit gate`;
2750
+ const hidden = hide ? toks.find((t) => isProtected(t, policy)) : void 0;
2751
+ if (hide && hidden) {
2752
+ return {
2753
+ reason: `git update-index ${hide} hides a protected file from every git diff \u2014 edits to it no longer reach the sweep or the pre-commit gate`,
2754
+ file: hidden
2755
+ };
2713
2756
  }
2714
- if (toks.some((t) => /^--chmod=-x$/.test(t)) && toks.some((t) => isProtected(t, policy, "hooks"))) {
2715
- return "git update-index --chmod=-x clears the execute bit on a protected hook through the index, which disables it";
2757
+ const hook = toks.some((t) => /^--chmod=-x$/.test(t)) ? toks.find((t) => isProtected(t, policy, "hooks")) : void 0;
2758
+ if (hook) {
2759
+ return { reason: "git update-index --chmod=-x clears the execute bit on a protected hook through the index, which disables it", file: hook };
2716
2760
  }
2717
2761
  return null;
2718
2762
  }
2763
+ function repoRelative(path, ctx) {
2764
+ if (!ctx?.cwd || !isAbsolute(path)) return path;
2765
+ const rel = relative(ctx.cwd, path);
2766
+ return rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : path;
2767
+ }
2719
2768
  var hookTampering = {
2720
2769
  id: RULE6,
2721
2770
  surface: ["file", "command"],
@@ -2865,7 +2914,8 @@ var hookTampering = {
2865
2914
  if (hiding) {
2866
2915
  out2.push(
2867
2916
  makeFinding(RULE6, policy, {
2868
- message: `Hook tampering via shell: ${hiding}.`,
2917
+ file: repoRelative(hiding.file, ctx),
2918
+ message: `Hook tampering via shell: ${hiding.reason}.`,
2869
2919
  evidence: seg,
2870
2920
  remediation: "Leave protected files visible to git. Hiding one from the diff hides it from the gate, which is the tamper \u2014 use `--no-skip-worktree` / `--no-assume-unchanged` to undo it."
2871
2921
  })
@@ -2874,8 +2924,10 @@ var hookTampering = {
2874
2924
  }
2875
2925
  const why = shellWritesHook(seg, toks, policy);
2876
2926
  if (why) {
2927
+ const target = shellHookTarget(seg, toks, policy);
2877
2928
  out2.push(
2878
2929
  makeFinding(RULE6, policy, {
2930
+ ...target ? { file: repoRelative(target, ctx) } : {},
2879
2931
  message: `Hook tampering via shell: ${why}.`,
2880
2932
  evidence: seg,
2881
2933
  remediation: "Leave the hooks in place. Mutating them from the shell is still tampering."
@@ -3572,7 +3624,7 @@ function hasBlocking(findings) {
3572
3624
 
3573
3625
  // src/policy-load.ts
3574
3626
  import { readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
3575
- import { isAbsolute, join as join3, posix } from "node:path";
3627
+ import { isAbsolute as isAbsolute2, join as join3, posix } from "node:path";
3576
3628
  import { parse as parse2 } from "yaml";
3577
3629
  var PolicyError = class extends Error {
3578
3630
  };
@@ -3589,7 +3641,7 @@ var isStringList = (v) => Array.isArray(v) && v.every((s) => typeof s === "strin
3589
3641
  var isMapping = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3590
3642
  var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["version", "protected", "rules", "ignore", "signoff", "verify"]);
3591
3643
  function ledgerInsideRepo(ledger) {
3592
- if (!ledger.trim() || isAbsolute(ledger) || /^[A-Za-z]:[\\/]/.test(ledger)) return false;
3644
+ if (!ledger.trim() || isAbsolute2(ledger) || /^[A-Za-z]:[\\/]/.test(ledger)) return false;
3593
3645
  const norm2 = posix.normalize(ledger.replace(/\\/g, "/"));
3594
3646
  return norm2 !== ".." && !norm2.startsWith("../") && !norm2.startsWith("/");
3595
3647
  }
@@ -4043,7 +4095,8 @@ function check(opts) {
4043
4095
  layer = "local";
4044
4096
  view = "staged";
4045
4097
  } else if (opts.worktree) {
4046
- changes = opts.includeUntracked ? diffWorktreeWithUntracked({ cwd: opts.cwd }) : diffWorktree({ cwd: opts.cwd });
4098
+ const p = policy;
4099
+ changes = opts.includeUntracked ? diffWorktreeWithUntracked({ cwd: opts.cwd }, (rel) => isProtected(rel, p)) : diffWorktree({ cwd: opts.cwd });
4047
4100
  layer = opts.ciLayer ? "ci" : "local";
4048
4101
  view = "worktree";
4049
4102
  } else if (opts.diff) {
@@ -4102,7 +4155,7 @@ import { join as join9 } from "node:path";
4102
4155
  import { execFileSync as execFileSync3 } from "node:child_process";
4103
4156
  import { readFileSync as readFileSync5, existsSync as existsSync4, writeFileSync, mkdtempSync, rmSync } from "node:fs";
4104
4157
  import { tmpdir } from "node:os";
4105
- import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
4158
+ import { isAbsolute as isAbsolute3, join as join5, relative as relative2, resolve } from "node:path";
4106
4159
  function asStr(v) {
4107
4160
  return typeof v === "string" ? v : "";
4108
4161
  }
@@ -4117,8 +4170,8 @@ function abs(path, cwd) {
4117
4170
  return resolve(cwd, path);
4118
4171
  }
4119
4172
  function relForDisplay(path, cwd) {
4120
- const rel = relative(cwd, path);
4121
- return rel && !rel.startsWith("..") && !isAbsolute2(rel) ? rel : path;
4173
+ const rel = relative2(cwd, path);
4174
+ return rel && !rel.startsWith("..") && !isAbsolute3(rel) ? rel : path;
4122
4175
  }
4123
4176
  function applyEdit(content, oldStr, newStr) {
4124
4177
  if (content === null) return newStr;
@@ -4261,11 +4314,11 @@ function advanceTurnBaseline(cwd, sessionId) {
4261
4314
 
4262
4315
  // src/effect.ts
4263
4316
  import { createHash as createHash2 } from "node:crypto";
4264
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync3 } from "node:fs";
4265
- import { isAbsolute as isAbsolute3, join as join7 } from "node:path";
4317
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync7, renameSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
4318
+ import { isAbsolute as isAbsolute4, join as join7 } from "node:path";
4266
4319
  import { execFileSync as execFileSync4 } from "node:child_process";
4267
4320
  var UNSAFE2 = /[^A-Za-z0-9._-]/g;
4268
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
4321
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
4269
4322
  function gitDir2(cwd) {
4270
4323
  try {
4271
4324
  const d = execFileSync4("git", ["rev-parse", "--git-dir"], {
@@ -4274,7 +4327,7 @@ function gitDir2(cwd) {
4274
4327
  stdio: ["ignore", "pipe", "pipe"]
4275
4328
  }).trim();
4276
4329
  if (!d) return null;
4277
- return isAbsolute3(d) ? d : join7(cwd, d);
4330
+ return isAbsolute4(d) ? d : join7(cwd, d);
4278
4331
  } catch {
4279
4332
  return null;
4280
4333
  }
@@ -4307,7 +4360,7 @@ function snapshotProtected(cwd, policy, prev) {
4307
4360
  return;
4308
4361
  }
4309
4362
  for (const name of names) {
4310
- if (SKIP_DIRS.has(name)) continue;
4363
+ if (SKIP_DIRS2.has(name)) continue;
4311
4364
  const r = rel ? `${rel}/${name}` : name;
4312
4365
  let st;
4313
4366
  try {
@@ -4333,12 +4386,16 @@ function snapshotProtected(cwd, policy, prev) {
4333
4386
  function driftBetween(expected, current) {
4334
4387
  const changed = [];
4335
4388
  const deleted = [];
4389
+ const added = [];
4336
4390
  for (const [path, e] of Object.entries(expected)) {
4337
4391
  const c = current[path];
4338
4392
  if (!c) deleted.push(path);
4339
4393
  else if (c.hash !== e.hash || c.mode !== e.mode) changed.push(path);
4340
4394
  }
4341
- return { changed, deleted };
4395
+ for (const path of Object.keys(current)) {
4396
+ if (!expected[path]) added.push(path);
4397
+ }
4398
+ return { changed, deleted, added };
4342
4399
  }
4343
4400
  function loadTree(p) {
4344
4401
  if (!p || !existsSync6(p)) return null;
@@ -4352,7 +4409,16 @@ function saveTree(p, tree) {
4352
4409
  if (!p) return;
4353
4410
  try {
4354
4411
  mkdirSync3(join7(p, ".."), { recursive: true });
4355
- writeFileSync3(p, JSON.stringify(tree));
4412
+ const tmp = `${p}.${process.pid}.tmp`;
4413
+ writeFileSync3(tmp, JSON.stringify(tree));
4414
+ renameSync(tmp, p);
4415
+ } catch {
4416
+ }
4417
+ }
4418
+ function dropTree(p) {
4419
+ if (!p) return;
4420
+ try {
4421
+ rmSync2(p, { force: true });
4356
4422
  } catch {
4357
4423
  }
4358
4424
  }
@@ -4368,6 +4434,9 @@ function loadTurnTree(cwd, sessionId) {
4368
4434
  function saveTurnTree(cwd, sessionId, tree) {
4369
4435
  saveTree(turnTreePath(cwd, sessionId), tree);
4370
4436
  }
4437
+ function dropTurnTree(cwd, sessionId) {
4438
+ dropTree(turnTreePath(cwd, sessionId));
4439
+ }
4371
4440
 
4372
4441
  // src/cli/watch.ts
4373
4442
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync2, watch } from "node:fs";
@@ -4612,17 +4681,26 @@ function turnView(cwd, sessionId, policy, base) {
4612
4681
  if (c.oldPath) seen.add(c.oldPath);
4613
4682
  }
4614
4683
  const turnTree = loadTurnTree(cwd, sessionId);
4615
- const untracked = untrackedAdds({ cwd }, (rel) => isProtected(rel, policy), seen).filter(
4616
- (c) => c.kind !== "file" || !turnTree || turnTree[c.path]?.hash !== hashOnDisk(cwd, c.path)
4617
- );
4684
+ const ptree = loadPtree(cwd, sessionId);
4685
+ const keep = (rel) => isProtected(rel, policy);
4686
+ const untouched = (rel, disk) => disk != null && turnTree?.[rel]?.hash === disk && ptree?.[rel]?.hash === disk;
4687
+ const untracked = untrackedAdds({ cwd }, keep, seen).filter((c) => c.kind !== "file" || !untouched(c.path, hashOnDisk(cwd, c.path)));
4618
4688
  for (const c of fileChanges(untracked)) seen.add(c.path);
4689
+ const ignored = ignoredAdds({ cwd }, keep, seen).filter((c) => {
4690
+ if (c.kind !== "file") return false;
4691
+ const disk = hashOnDisk(cwd, c.path);
4692
+ if (untouched(c.path, disk)) return false;
4693
+ const sanctioned = ptree?.[c.path];
4694
+ return !(sanctioned && disk != null && sanctioned.hash !== disk);
4695
+ });
4696
+ for (const c of fileChanges(ignored)) seen.add(c.path);
4619
4697
  const trusted = base ?? "HEAD";
4620
4698
  const hidden = [];
4621
4699
  for (const rel of hiddenTrackedPaths({ cwd })) {
4622
4700
  if (seen.has(rel) || !isProtected(rel, policy)) continue;
4623
4701
  hidden.push(...synthFileChange(rel, fileAt(trusted, rel, { cwd }), fileOnDisk(rel, { cwd })));
4624
4702
  }
4625
- return [...tracked, ...untracked, ...hidden];
4703
+ return [...tracked, ...untracked, ...ignored, ...hidden];
4626
4704
  }
4627
4705
  function uncoveredDrift(cwd, policy, expected, current, covered, base, turnTree) {
4628
4706
  const d = driftBetween(expected, current);
@@ -4640,7 +4718,7 @@ function uncoveredDrift(cwd, policy, expected, current, covered, base, turnTree)
4640
4718
  const after = fileOnDisk(rel, { cwd });
4641
4719
  const trustedBlob = fileAt(trusted, rel, { cwd });
4642
4720
  const atGitState = after != null && trustedBlob != null && contentHash(after) === contentHash(trustedBlob);
4643
- const atTurnStart = current[rel] != null && turnTree?.[rel]?.hash === current[rel].hash;
4721
+ const atTurnStart = current[rel] != null && expected[rel]?.mtimeMs === -1 && turnTree?.[rel]?.hash === current[rel].hash;
4644
4722
  if ((atGitState || atTurnStart) && current[rel] && expected[rel] && current[rel].mode === expected[rel].mode) {
4645
4723
  continue;
4646
4724
  }
@@ -4683,6 +4761,23 @@ function uncoveredDrift(cwd, policy, expected, current, covered, base, turnTree)
4683
4761
  });
4684
4762
  }
4685
4763
  }
4764
+ for (const rel of d.added) {
4765
+ if (seen.has(rel)) continue;
4766
+ const after = fileOnDisk(rel, { cwd });
4767
+ if (after == null) {
4768
+ blocks.push({
4769
+ rule: "hidden-drift",
4770
+ severity: "block",
4771
+ file: rel,
4772
+ message: `Protected file ${rel} appeared outside git's view and cannot be read to judge it.`,
4773
+ evidence: rel,
4774
+ remediation: "Remove the file, or make it readable and bring it into git's view; create protected files through a tool call the gate can see.",
4775
+ signoff: { required: true, command: `tamperward allow hidden-drift --file ${rel} --reason "..."` }
4776
+ });
4777
+ continue;
4778
+ }
4779
+ changes.push(...synthFileChange(rel, null, after));
4780
+ }
4686
4781
  return { changes, blocks };
4687
4782
  }
4688
4783
  function effectDriftBlocks(cwd, sessionId, policy) {
@@ -4691,17 +4786,18 @@ function effectDriftBlocks(cwd, sessionId, policy) {
4691
4786
  const current = snapshotProtected(cwd, policy, expected ?? void 0);
4692
4787
  if (!expected) {
4693
4788
  savePtree(cwd, sessionId, current);
4694
- if (!loadTurnTree(cwd, sessionId)) saveTurnTree(cwd, sessionId, current);
4789
+ if (loadTurnTree(cwd, sessionId)) dropTurnTree(cwd, sessionId);
4790
+ else saveTurnTree(cwd, sessionId, current);
4695
4791
  return null;
4696
4792
  }
4697
4793
  const d = driftBetween(expected, current);
4698
- if (d.changed.length === 0 && d.deleted.length === 0) {
4794
+ if (d.changed.length === 0 && d.deleted.length === 0 && d.added.length === 0) {
4699
4795
  savePtree(cwd, sessionId, current);
4700
4796
  return null;
4701
4797
  }
4702
4798
  const base = turnBaseline(cwd, sessionId);
4703
4799
  const all = turnView(cwd, sessionId, policy, base);
4704
- const drifted = /* @__PURE__ */ new Set([...d.changed, ...d.deleted]);
4800
+ const drifted = /* @__PURE__ */ new Set([...d.changed, ...d.deleted, ...d.added]);
4705
4801
  const scoped = all.filter(
4706
4802
  (c) => c.kind === "file" && (drifted.has(c.path) || c.oldPath != null && drifted.has(c.oldPath))
4707
4803
  );
@@ -4916,7 +5012,7 @@ Honored at LOCAL pre-commit only. The agent-layer hook ignores this file; CI req
4916
5012
  import { execFileSync as execFileSync5 } from "node:child_process";
4917
5013
  import { createHash as createHash5 } from "node:crypto";
4918
5014
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, chmodSync, statSync as statSync5 } from "node:fs";
4919
- import { basename, dirname as dirname3, isAbsolute as isAbsolute4, join as join10, relative as relative2, resolve as resolve2 } from "node:path";
5015
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute5, join as join10, relative as relative3, resolve as resolve2 } from "node:path";
4920
5016
  import { fileURLToPath } from "node:url";
4921
5017
  var errText2 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
4922
5018
  var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -5234,8 +5330,8 @@ function hooksDir(cwd) {
5234
5330
  return existsSync8(join10(cwd, ".git")) ? join10(cwd, ".git", "hooks") : null;
5235
5331
  }
5236
5332
  function display(cwd, path) {
5237
- const rel = relative2(cwd, path);
5238
- return rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : path;
5333
+ const rel = relative3(cwd, path);
5334
+ return rel && !rel.startsWith("..") && !isAbsolute5(rel) ? rel : path;
5239
5335
  }
5240
5336
  function unconditionalExitAt(lines) {
5241
5337
  let depth = 0;
@@ -5521,7 +5617,7 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
5521
5617
  // src/cli/verify.ts
5522
5618
  import { execFileSync as execFileSync8, spawnSync } from "node:child_process";
5523
5619
  import { createHash as createHash8 } from "node:crypto";
5524
- import { chmodSync as chmodSync2, cpSync, lstatSync, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, readlinkSync as readlinkSync2, rmSync as rmSync2, statSync as statSync7, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
5620
+ import { chmodSync as chmodSync2, cpSync, lstatSync, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, readlinkSync as readlinkSync2, rmSync as rmSync3, statSync as statSync7, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
5525
5621
  import { tmpdir as tmpdir2 } from "node:os";
5526
5622
  import { dirname as dirname4, join as join13, resolve as resolve3 } from "node:path";
5527
5623
 
@@ -5529,7 +5625,7 @@ import { dirname as dirname4, join as join13, resolve as resolve3 } from "node:p
5529
5625
  import { execFileSync as execFileSync6 } from "node:child_process";
5530
5626
  import { createHash as createHash6 } from "node:crypto";
5531
5627
  import { existsSync as existsSync9, readFileSync as readFileSync12 } from "node:fs";
5532
- import { isAbsolute as isAbsolute5, join as join11 } from "node:path";
5628
+ import { isAbsolute as isAbsolute6, join as join11 } from "node:path";
5533
5629
  function trustedGitEnv() {
5534
5630
  return { ...process.env, GIT_NO_REPLACE_OBJECTS: "1" };
5535
5631
  }
@@ -5542,7 +5638,7 @@ function gitDirOf(cwd) {
5542
5638
  env: trustedGitEnv()
5543
5639
  }).trim();
5544
5640
  if (!d) return null;
5545
- return isAbsolute5(d) ? d : join11(cwd, d);
5641
+ return isAbsolute6(d) ? d : join11(cwd, d);
5546
5642
  } catch {
5547
5643
  return null;
5548
5644
  }
@@ -5756,7 +5852,7 @@ function materialize(cwd, dest) {
5756
5852
  }
5757
5853
  function dropSymlink(p) {
5758
5854
  try {
5759
- if (lstatSync(p).isSymbolicLink()) rmSync2(p, { force: true });
5855
+ if (lstatSync(p).isSymbolicLink()) rmSync3(p, { force: true });
5760
5856
  } catch {
5761
5857
  }
5762
5858
  }
@@ -5814,7 +5910,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
5814
5910
  const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
5815
5911
  const out2 = join13(dest, e.path);
5816
5912
  mkdirSync6(dirname4(out2), { recursive: true });
5817
- rmSync2(out2, { force: true });
5913
+ rmSync3(out2, { force: true });
5818
5914
  if (e.mode === "120000") {
5819
5915
  symlinkSync(content.toString("utf8"), out2);
5820
5916
  } else {
@@ -5827,7 +5923,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
5827
5923
  const inCopy = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
5828
5924
  for (const rel of inCopy) {
5829
5925
  if (!isOverlay(rel) || baseProtected.has(rel)) continue;
5830
- rmSync2(join13(dest, rel), { force: true });
5926
+ rmSync3(join13(dest, rel), { force: true });
5831
5927
  removed++;
5832
5928
  }
5833
5929
  return { restored, removed };
@@ -5902,7 +5998,7 @@ function runSuite(dir, cmd, budgetSecs) {
5902
5998
  if (r.exit === null || r.exit === void 0) return { exit: null, secs };
5903
5999
  return { exit: r.exit, secs };
5904
6000
  } finally {
5905
- rmSync2(outDir, { recursive: true, force: true });
6001
+ rmSync3(outDir, { recursive: true, force: true });
5906
6002
  }
5907
6003
  }
5908
6004
  function runVerify(opts) {
@@ -5949,7 +6045,7 @@ function runVerify(opts) {
5949
6045
  const visDir = join13(visRoot, "t");
5950
6046
  const cleanup = (dirs) => {
5951
6047
  if (opts.keep) return;
5952
- for (const d of dirs) rmSync2(d, { recursive: true, force: true });
6048
+ for (const d of dirs) rmSync3(d, { recursive: true, force: true });
5953
6049
  };
5954
6050
  const treeBefore = treeFingerprint(cwd);
5955
6051
  const depsBefore = depsFingerprint(cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
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",